102 lines
3.8 KiB
TypeScript
102 lines
3.8 KiB
TypeScript
import { Assistant, AssistantExtension, fs, joinPath } from '@janhq/core'
|
||
export default class JanAssistantExtension extends AssistantExtension {
|
||
async onLoad() {
|
||
if (!(await fs.existsSync('file://assistants'))) {
|
||
await fs.mkdir('file://assistants')
|
||
}
|
||
const assistants = await this.getAssistants()
|
||
if (assistants.length === 0) {
|
||
await this.createAssistant(this.defaultAssistant)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Called when the extension is unloaded.
|
||
*/
|
||
onUnload(): void {}
|
||
|
||
async getAssistants(): Promise<Assistant[]> {
|
||
if (!(await fs.existsSync('file://assistants')))
|
||
return [this.defaultAssistant]
|
||
const assistants = await fs.readdirSync('file://assistants')
|
||
const assistantsData: Assistant[] = []
|
||
for (const assistant of assistants) {
|
||
const assistantPath = await joinPath([
|
||
'file://assistants',
|
||
assistant,
|
||
'assistant.json',
|
||
])
|
||
if (!(await fs.existsSync(assistantPath))) {
|
||
console.warn(`Assistant file not found: ${assistantPath}`)
|
||
continue
|
||
}
|
||
try {
|
||
const assistantData = JSON.parse(await fs.readFileSync(assistantPath))
|
||
assistantsData.push(assistantData as Assistant)
|
||
} catch (error) {
|
||
console.error(`Failed to read assistant ${assistant}:`, error)
|
||
}
|
||
}
|
||
return assistantsData
|
||
}
|
||
|
||
async createAssistant(assistant: Assistant): Promise<void> {
|
||
const assistantPath = await joinPath([
|
||
'file://assistants',
|
||
assistant.id,
|
||
'assistant.json',
|
||
])
|
||
const assistantFolder = await joinPath(['file://assistants', assistant.id])
|
||
if (!(await fs.existsSync(assistantFolder))) {
|
||
await fs.mkdir(assistantFolder)
|
||
}
|
||
await fs.writeFileSync(assistantPath, JSON.stringify(assistant, null, 2))
|
||
}
|
||
|
||
async deleteAssistant(assistant: Assistant): Promise<void> {
|
||
const assistantPath = await joinPath([
|
||
'file://assistants',
|
||
assistant.id,
|
||
'assistant.json',
|
||
])
|
||
if (await fs.existsSync(assistantPath)) {
|
||
await fs.rm(assistantPath)
|
||
}
|
||
}
|
||
|
||
private defaultAssistant: Assistant = {
|
||
avatar: '👋',
|
||
thread_location: undefined,
|
||
id: 'jan',
|
||
object: 'assistant',
|
||
created_at: Date.now() / 1000,
|
||
name: 'Jan',
|
||
description:
|
||
'Jan is a helpful desktop assistant that can reason through complex tasks and use tools to complete them on the user’s behalf.',
|
||
model: '*',
|
||
instructions:
|
||
'You are a helpful AI assistant. Your primary goal is to assist users with their questions and tasks to the best of your abilities.\n\nWhen responding:\n- Answer directly from your knowledge when you can\n- Be concise, clear, and helpful\n- Admit when you’re unsure rather than making things up\n\nIf tools are available to you:\n- Only use tools when they add real value to your response\n- Use tools when the user explicitly asks (e.g., "search for...", "calculate...", "run this code")\n- Use tools for information you don’t know or that needs verification\n- Never use tools just because they’re available\n\nWhen using tools:\n- Use one tool at a time and wait for results\n- Use actual values as arguments, not variable names\n- Learn from each result before deciding next steps\n- Avoid repeating the same tool call with identical parameters\n\nRemember: Most questions can be answered without tools. Think first whether you need them.\n\nCurrent date: {{current_date}}',
|
||
tools: [
|
||
{
|
||
type: 'retrieval',
|
||
enabled: false,
|
||
useTimeWeightedRetriever: false,
|
||
settings: {
|
||
top_k: 2,
|
||
chunk_size: 1024,
|
||
chunk_overlap: 64,
|
||
retrieval_template: `Use the following pieces of context to answer the question at the end.
|
||
----------------
|
||
CONTEXT: {CONTEXT}
|
||
----------------
|
||
QUESTION: {QUESTION}
|
||
----------------
|
||
Helpful Answer:`,
|
||
},
|
||
},
|
||
],
|
||
file_ids: [],
|
||
metadata: undefined,
|
||
}
|
||
}
|