diff --git a/components/Charlie.vue b/components/Charlie.vue new file mode 100644 index 0000000..5a77159 --- /dev/null +++ b/components/Charlie.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/composables/useTools.ts b/composables/useTools.ts new file mode 100644 index 0000000..1a32074 --- /dev/null +++ b/composables/useTools.ts @@ -0,0 +1,55 @@ +import { ref, type Ref } from 'vue' + +export interface Tool { + type: 'function' + name: string + description: string + parameters?: { + type: 'object' + properties: Record + } + handler: (args: any) => Promise +} + +const globalTools: Ref = ref([]) + +export function useTools() { + const registerTool = (tool: Tool) => { + console.log('Registering tool:', tool) + const existingIndex = globalTools.value.findIndex(t => t.name === tool.name) + if (existingIndex >= 0) { + globalTools.value[existingIndex] = tool + } else { + globalTools.value.push(tool) + } + } + + const unregisterTool = (toolName: string) => { + console.log('Unregistering tool:', toolName) + const index = globalTools.value.findIndex(t => t.name === toolName) + if (index >= 0) { + globalTools.value.splice(index, 1) + } + } + + const getTools = () => globalTools.value + + const executeToolHandler = async (toolName: string, args: any) => { + console.log('Executing tool handler:', toolName, args) + const tool = globalTools.value.find(t => t.name === toolName) + if (!tool) { + throw new Error(`Tool ${toolName} not found`) + } + return await tool.handler(args) + } + + return { + registerTool, + unregisterTool, + getTools, + executeToolHandler + } +} diff --git a/layouts/default.vue b/layouts/default.vue index 41b906a..406da52 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -45,5 +45,6 @@ watch(() => route.path, () => {
+ diff --git a/pages/instructions/index.vue b/pages/instructions/index.vue index 4a0d2fd..f0a5a5b 100644 --- a/pages/instructions/index.vue +++ b/pages/instructions/index.vue @@ -2,10 +2,12 @@ import type { Instruction } from '@prisma/client' import { loadOllamaInstructions } from "@/utils/settings" import InstructionForm from '~/components/InstructionForm.vue' +import { useTools } from '~/composables/useTools' const { t } = useI18n() const modal = useModal() const confirm = useDialog('confirm') +const { registerTool, unregisterTool } = useTools() const loading = ref(true) const instructions = ref([]) @@ -16,12 +18,17 @@ const tableRows = computed(() => { id: instruction.id, name: instruction.name, instruction: instruction.instruction, + class: instruction.isNew ? 'highlight-new' : '' } }) }) -const loadInstructions = async () => { +const loadInstructions = async (latestAsNew: boolean = false) => { instructions.value = await loadOllamaInstructions() + if (latestAsNew) { + const latestInstruction = instructions.value.reduce((max, current) => current.id > max.id ? current : max, { id: 0 }) + latestInstruction.isNew = true + } } onMounted(() => { @@ -29,6 +36,81 @@ onMounted(() => { .finally(() => { loading.value = false }) + + registerTool({ + type: 'function', + name: 'listInstructions', + description: 'Lists all available instructions', + handler: async () => { + return { + success: true, + instructions: instructions.value.map(i => ({ + id: i.id, + name: i.name, + instruction: i.instruction + })) + } + } + }) + + registerTool({ + type: 'function', + name: 'createInstruction', + description: 'Creates a new instruction', + parameters: { + type: 'object', + properties: { + name: { type: 'string', description: 'Name of the instruction' }, + instruction: { type: 'string', description: 'The instruction content' } + } + }, + handler: async (args) => { + try { + await $fetchWithAuth('/api/instruction', { + method: 'POST', + body: args + }) + await loadInstructions(true) + return { success: true } + } catch (error) { + return { success: false, error: error.message } + } + } + }) + + registerTool({ + type: 'function', + name: 'deleteInstruction', + description: 'Deletes an instruction by name', + parameters: { + type: 'object', + properties: { + name: { type: 'string', description: 'Name of the instruction' } + } + }, + handler: async (args) => { + try { + const instruction = instructions.value.find(i => i.name === args.name) + if (!instruction) { + return { success: false, error: 'Instruction not found' } + } + + await $fetchWithAuth(`/api/instruction/${instruction.id}`, { + method: "DELETE", + }) + await loadInstructions() + return { success: true } + } catch (error) { + return { success: false, error: error.message } + } + } + }) +}) + +onUnmounted(() => { + unregisterTool('listInstructions') + unregisterTool('createInstruction') + unregisterTool('deleteInstruction') }) const ui = { @@ -50,7 +132,7 @@ function onCreate() { title: t('instructions.createInstruction'), type: 'create', onClose: () => modal.close(), - onSuccess: () => loadInstructions(), + onSuccess: () => loadInstructions(true), }) } @@ -80,6 +162,16 @@ async function onDelete(data: Instruction) { } }).catch(noop) } + +const addInstruction = (instruction) => { + instruction.isNew = true + instructions.value.push(instruction) + + setTimeout(() => { + instruction.isNew = false + }, 2000) +} +