diff options
| author | Adam Malczewski <[email protected]> | 2026-03-24 13:44:52 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-03-24 13:44:52 +0900 |
| commit | 5a44a97111d304945bbfc3da02d29a83191d816c (patch) | |
| tree | a1e31b76db2a0b0e84c5745127a0d05ddc574ec7 /src/chat-view.ts | |
| parent | bb543c3f7840f2a3fa1b7a1fb32245fa87a30f7b (diff) | |
| download | ai-pulse-obsidian-plugin-5a44a97111d304945bbfc3da02d29a83191d816c.tar.gz ai-pulse-obsidian-plugin-5a44a97111d304945bbfc3da02d29a83191d816c.zip | |
Add initial ai tool system, and 2 tools to explore
Diffstat (limited to 'src/chat-view.ts')
| -rw-r--r-- | src/chat-view.ts | 80 |
1 files changed, 79 insertions, 1 deletions
diff --git a/src/chat-view.ts b/src/chat-view.ts index 91bff2c..16b9676 100644 --- a/src/chat-view.ts +++ b/src/chat-view.ts @@ -1,8 +1,11 @@ import { ItemView, Notice, WorkspaceLeaf, setIcon } from "obsidian"; import type AIOrganizer from "./main"; -import type { ChatMessage } from "./ollama-client"; +import type { ChatMessage, ToolCallEvent } from "./ollama-client"; import { sendChatMessage } from "./ollama-client"; import { SettingsModal } from "./settings-modal"; +import { ToolModal } from "./tool-modal"; +import { TOOL_REGISTRY } from "./tools"; +import type { OllamaToolDefinition } from "./tools"; export const VIEW_TYPE_CHAT = "ai-organizer-chat"; @@ -12,6 +15,7 @@ export class ChatView extends ItemView { private messageContainer: HTMLDivElement | null = null; private textarea: HTMLTextAreaElement | null = null; private sendButton: HTMLButtonElement | null = null; + private toolsButton: HTMLButtonElement | null = null; constructor(leaf: WorkspaceLeaf, plugin: AIOrganizer) { super(leaf); @@ -56,6 +60,21 @@ export class ChatView extends ItemView { new SettingsModal(this.plugin).open(); }); + // Tools button + this.toolsButton = buttonGroup.createEl("button", { + cls: "ai-organizer-tools-btn", + attr: { "aria-label": "Tools" }, + }); + setIcon(this.toolsButton, "wrench"); + this.updateToolsButtonState(); + this.toolsButton.addEventListener("click", () => { + const modal = new ToolModal(this.plugin); + modal.onClose = () => { + this.updateToolsButtonState(); + }; + modal.open(); + }); + // Send button this.sendButton = buttonGroup.createEl("button", { text: "Send" }); @@ -80,6 +99,28 @@ export class ChatView extends ItemView { this.messageContainer = null; this.textarea = null; this.sendButton = null; + this.toolsButton = null; + } + + private getEnabledTools(): OllamaToolDefinition[] { + const tools: OllamaToolDefinition[] = []; + for (const tool of TOOL_REGISTRY) { + if (this.plugin.settings.enabledTools[tool.id] === true) { + tools.push(tool.definition); + } + } + return tools; + } + + private hasAnyToolEnabled(): boolean { + return TOOL_REGISTRY.some( + (tool) => this.plugin.settings.enabledTools[tool.id] === true, + ); + } + + private updateToolsButtonState(): void { + if (this.toolsButton === null) return; + this.toolsButton.toggleClass("ai-organizer-tools-active", this.hasAnyToolEnabled()); } private async handleSend(): Promise<void> { @@ -109,10 +150,21 @@ export class ChatView extends ItemView { this.setInputEnabled(false); try { + const enabledTools = this.getEnabledTools(); + const hasTools = enabledTools.length > 0; + + const onToolCall = (event: ToolCallEvent): void => { + this.appendToolCall(event); + this.scrollToBottom(); + }; + const response = await sendChatMessage( this.plugin.settings.ollamaUrl, this.plugin.settings.model, this.messages, + hasTools ? enabledTools : undefined, + hasTools ? this.plugin.app : undefined, + hasTools ? onToolCall : undefined, ); this.messages.push({ role: "assistant", content: response }); @@ -143,6 +195,32 @@ export class ChatView extends ItemView { this.messageContainer.createDiv({ cls, text: content }); } + private appendToolCall(event: ToolCallEvent): void { + if (this.messageContainer === null) { + return; + } + + const container = this.messageContainer.createDiv({ cls: "ai-organizer-tool-call" }); + + const header = container.createDiv({ cls: "ai-organizer-tool-call-header" }); + setIcon(header.createSpan({ cls: "ai-organizer-tool-call-icon" }), "wrench"); + header.createSpan({ text: event.friendlyName, cls: "ai-organizer-tool-call-name" }); + + container.createDiv({ text: event.summary, cls: "ai-organizer-tool-call-summary" }); + container.createDiv({ text: event.resultSummary, cls: "ai-organizer-tool-call-result-summary" }); + + const details = container.createEl("details", { cls: "ai-organizer-tool-call-details" }); + details.createEl("summary", { text: "Details" }); + + const argsStr = JSON.stringify(event.args, null, 2); + details.createEl("pre", { text: argsStr, cls: "ai-organizer-tool-call-args" }); + + const resultPreview = event.result.length > 500 + ? event.result.substring(0, 500) + "..." + : event.result; + details.createEl("pre", { text: resultPreview, cls: "ai-organizer-tool-call-result" }); + } + private scrollToBottom(): void { if (this.messageContainer !== null) { this.messageContainer.scrollTop = this.messageContainer.scrollHeight; |
