From 6f0e6d0342554b5dedeea948ce18ab0b0acdac40 Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Tue, 7 Oct 2025 00:24:45 +0100 Subject: [PATCH] Enhance Gemini web search directive for clarity, improve ChatArea streaming message handling, and ensure conversation saving logic in ChatWindow. Update AIFunction enum and AIFunctionService for RequestWebSearch functionality. Modify ConversationFileSystemService to include function call response handling. --- AIClasses/Gemini/Gemini.ts | 18 +++++++++++---- Components/ChatArea.svelte | 28 +++++++++++++++-------- Components/ChatWindow.svelte | 18 ++++++++++----- Enums/AIFunction.ts | 4 +++- Services/AIFunctionService.ts | 10 ++++---- Services/ConversationFileSystemService.ts | 2 +- 6 files changed, 54 insertions(+), 26 deletions(-) diff --git a/AIClasses/Gemini/Gemini.ts b/AIClasses/Gemini/Gemini.ts index c2efc7d..c775ece 100644 --- a/AIClasses/Gemini/Gemini.ts +++ b/AIClasses/Gemini/Gemini.ts @@ -71,11 +71,19 @@ export class Gemini implements IAIClass { text: this.aiPrompt.systemInstruction() }, { - text: `IMPORTANT: When you need current information from the web (recent events, news, current prices, weather, etc.), you should: - 1. First call the 'request_web_search' function to indicate you need web access - 2. After that, you'll be given access to Google Search - 3. Once you have the information from the search, you can answer the user's question - 4. Subsequent communication will return to providing custom function calls` + text: `## IMPORTANT: Web Search Directive + **You *must* proactively use the web search tool whenever a user's query requires current, real-time, or frequently changing information.** This includes, but is not limited to: + - Current date or time. + - Current weather conditions or forecasts. + - Recent news, events, or happenings. + - Up-to-date prices, statistics, or factual data that is dynamic. + - Any information where "current," "latest," or "today's" is implied or explicitly requested. + + When you need current information from the web, you *must* follow these steps: + 1. First call the \`request_web_search\` function with a clear and concise \`reasoning\` explaining why web search is needed. + 2. After calling this, you will be given access to Google Search. + 3. Once you have obtained the necessary information from the search results, use it to formulate your complete and accurate answer. + 4. Subsequent interactions will revert to standard function calls or general assistance as appropriate.` }, { text: await this.aiPrompt.userInstruction() diff --git a/Components/ChatArea.svelte b/Components/ChatArea.svelte index bc52f46..94884cc 100644 --- a/Components/ChatArea.svelte +++ b/Components/ChatArea.svelte @@ -171,22 +171,32 @@ {:else} + {#if isStreaming && messageIndex === messages.length - 1} + + {#if message.content && message.content !== ''}
-
- - {#if isStreaming && messageIndex === messages.length - 1} +
- - - {:else} - - {@html getStaticHTML(message, messageIndex)} - {/if}
{/if} + + + {:else} + + {#if message.content && message.content !== ''} +
+
+
+ {@html getStaticHTML(message, messageIndex)} +
+
+
+ {/if} + {/if} + {/if} {/if} {/each} diff --git a/Components/ChatWindow.svelte b/Components/ChatWindow.svelte index 7d74693..a5312d0 100644 --- a/Components/ChatWindow.svelte +++ b/Components/ChatWindow.svelte @@ -97,6 +97,7 @@ : msg ); isStreaming = false; + await conversationService.saveConversation(conversation); break; } @@ -115,12 +116,17 @@ if (chunk.isComplete) { isStreaming = false; - conversation.contents = conversation.contents.map((msg, messageIndex) => - messageIndex === aiMessageIndex - ? { ...msg, content: accumulatedContent } - : msg - ); - + // Only save the message if it has content or a function call + if (accumulatedContent.trim() !== "" || capturedFunctionCall) { + conversation.contents = conversation.contents.map((msg, messageIndex) => + messageIndex === aiMessageIndex + ? { ...msg, content: accumulatedContent } + : msg + ); + } else { + // Remove the empty placeholder message + conversation.contents = conversation.contents.filter((_, messageIndex) => messageIndex !== aiMessageIndex); + } await conversationService.saveConversation(conversation); } } diff --git a/Enums/AIFunction.ts b/Enums/AIFunction.ts index 130eb6d..b32dd69 100644 --- a/Enums/AIFunction.ts +++ b/Enums/AIFunction.ts @@ -1,3 +1,5 @@ export enum AIFunction { - ListVaultFiles = "list_vault_files" + ListVaultFiles = "list_vault_files", + + RequestWebSearch = "request_web_search" } \ No newline at end of file diff --git a/Services/AIFunctionService.ts b/Services/AIFunctionService.ts index 613bf3a..3276aa0 100644 --- a/Services/AIFunctionService.ts +++ b/Services/AIFunctionService.ts @@ -13,10 +13,12 @@ export class AIFunctionService { public async performAIFunction(functionCall: AIFunctionCall): Promise { switch (functionCall.name) { case AIFunction.ListVaultFiles: - return new AIFunctionResponse( - functionCall.name, - await this.listVaultFiles() - ); + return new AIFunctionResponse(functionCall.name, await this.listVaultFiles()); + + // this is only used by gemini + case AIFunction.RequestWebSearch: + return new AIFunctionResponse(functionCall.name, {}) + default: const error = `Unknown function request ${functionCall.name}` console.error(error); diff --git a/Services/ConversationFileSystemService.ts b/Services/ConversationFileSystemService.ts index 90d11dd..0eaa520 100644 --- a/Services/ConversationFileSystemService.ts +++ b/Services/ConversationFileSystemService.ts @@ -77,7 +77,7 @@ export class ConversationFileSystemService { conversation.created = new Date(data.created); conversation.contents = data.contents.map(content => { return new ConversationContent( - content.role, content.content, new Date(content.timestamp), content.isFunctionCall); + content.role, content.content, new Date(content.timestamp), content.isFunctionCall, content.isFunctionCallResponse); }); conversations.push(conversation); }