From 7efdac24cf71531f4bd513ef7dca9462d0ea6567 Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Fri, 26 Sep 2025 20:48:05 +0100 Subject: [PATCH] Remove deprecated CSS files and enhance markdown processing service - Deleted highlight-default.min.css, katex.min.css, and markdown.css as they are no longer needed. - Implemented StreamingMarkdownService for improved markdown processing with support for math and syntax highlighting. - Added StreamingService for handling streaming requests to the Gemini API with error handling and chunk parsing. - Introduced styles_old.css for enhanced code block styling and better readability. --- AIClasses/Gemini/Gemini.ts | 66 +- AIClasses/IAIClass.ts | 4 +- AIClasses/IPrompt.ts | 4 +- Components/ChatArea.svelte | 120 +--- Components/ChatWindow.svelte | 85 ++- Enums/ApiProvider.ts | 2 +- Helpers.ts | 12 + Services/MarkdownService.ts | 76 +-- Services/ServiceRegistration.ts | 6 +- Services/Services.ts | 3 + Services/StreamingMarkdownService.ts | 79 +++ Services/StreamingService.ts | 105 +++ Styles/highlight-default.min.css | 9 - Styles/katex.min.css | 373 ----------- Styles/markdown.css | 153 ----- esbuild.config.mjs | 31 +- main.css | 510 --------------- main.ts | 8 +- package-lock.json | 919 ++++++++++++++++++++++----- package.json | 10 + styles.css | 903 +++++++++++++++++++++++++- styles/highlight-default.min.css | 9 - styles/katex.min.css | 373 ----------- styles/markdown.css | 153 ----- styles_old.css | 179 ++++++ 25 files changed, 2221 insertions(+), 1971 deletions(-) create mode 100644 Services/StreamingMarkdownService.ts create mode 100644 Services/StreamingService.ts delete mode 100644 Styles/highlight-default.min.css delete mode 100644 Styles/katex.min.css delete mode 100644 Styles/markdown.css delete mode 100644 main.css delete mode 100644 styles/highlight-default.min.css delete mode 100644 styles/katex.min.css delete mode 100644 styles/markdown.css create mode 100644 styles_old.css diff --git a/AIClasses/Gemini/Gemini.ts b/AIClasses/Gemini/Gemini.ts index 51d0104..c749311 100644 --- a/AIClasses/Gemini/Gemini.ts +++ b/AIClasses/Gemini/Gemini.ts @@ -1,5 +1,4 @@ import { AIProviderURL } from "Enums/ApiProvider"; -import { request, type RequestUrlParam } from "obsidian"; import { Resolve } from "Services/DependencyService"; import { Services } from "Services/Services"; import type { IActioner } from "Actioner/IActioner"; @@ -7,62 +6,63 @@ import type { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefini import { create_file } from "Actioner/Actions"; import type { IAIClass } from "AIClasses/IAIClass"; import type { IPrompt } from "AIClasses/IPrompt"; -import type { GenerateContentResponse, Part } from "@google/genai"; +import type { Part } from "@google/genai"; +import { StreamingService, type StreamChunk } from "Services/StreamingService"; export class Gemini implements IAIClass { private readonly apiKey: string; private readonly aiPrompt: IPrompt; private readonly actionDefinitions: GeminiActionDefinitions; - + private readonly streamingService: StreamingService; public constructor(apiKey: string) { this.apiKey = apiKey; - this.aiPrompt = Resolve(Services.IPrompt); this.actionDefinitions = Resolve(Services.IActionDefinitions); + this.streamingService = new StreamingService(); } - public async apiRequest(userInput: string, actioner: IActioner): Promise { - let prompt: string = "The users prompt is: " + userInput; + /** + * Stream response from Gemini API + */ + public async* streamRequest( + userInput: string, + actioner: IActioner + ): AsyncGenerator { + const prompt = "The users prompt is: " + userInput; - let requestBody = JSON.stringify({ + const requestBody = { contents: [ { role: "user", parts: [ { - text: this.aiPrompt.instructions() + "\n" + - this.aiPrompt.responseFormat() + "\n" + - this.aiPrompt.getDirectories() + "\n" + - prompt + "\n" + - this.aiPrompt.instructionsReminder() + text: + this.aiPrompt.instructions() + + "\n" + + this.aiPrompt.responseFormat() + + "\n" + + this.aiPrompt.getDirectories() + + "\n" + + prompt + + "\n" + + this.aiPrompt.instructionsReminder(), }, ], }, ], - tools: [{ - functionDeclarations: [this.actionDefinitions[create_file]] - }] - }); - - let reqParam: RequestUrlParam = { - url: AIProviderURL.Gemini, - method: "POST", - headers: { - "X-goog-api-key": this.apiKey, - "Content-Type": "application/json" + tools: [ + { + functionDeclarations: [this.actionDefinitions[create_file]], + }, + ], + // Add streaming-specific parameters + generationConfig: { + temperature: 0.9, + //maxOutputTokens: 2048, }, - body: requestBody }; - let response: GenerateContentResponse = JSON.parse(await request(reqParam)); - - console.log(response); - - let ai_response: Part[] = response.candidates?.first()?.content?.parts ?? []; - - console.log(ai_response); - - return ai_response; + yield* this.streamingService.streamGeminiRequest(this.apiKey, requestBody); } } \ No newline at end of file diff --git a/AIClasses/IAIClass.ts b/AIClasses/IAIClass.ts index b9192e4..0674399 100644 --- a/AIClasses/IAIClass.ts +++ b/AIClasses/IAIClass.ts @@ -1,6 +1,6 @@ -import type { Part } from "@google/genai"; import type { IActioner } from "Actioner/IActioner"; +import type { StreamChunk } from "Services/StreamingService"; export interface IAIClass { - apiRequest(req: string, actioner: IActioner): Promise; + streamRequest(userInput: string, actioner: IActioner): AsyncGenerator; } \ No newline at end of file diff --git a/AIClasses/IPrompt.ts b/AIClasses/IPrompt.ts index 21642f9..c6b9fb2 100644 --- a/AIClasses/IPrompt.ts +++ b/AIClasses/IPrompt.ts @@ -24,9 +24,11 @@ export class AIPrompt implements IPrompt { } public readonly instructionsArr: string[] = [ + /* "You are an AI assistant for the Obsidian note taking app.", "The user has provided extra context to your responsibilities:", - "You are a DND expert and can provide detailed information about DND rules, character creation, and gameplay mechanics. Please give concise responses." + "You are a DND expert and can provide detailed information about DND rules, character creation, and gameplay mechanics." + */ ]; public instructions(): string { return this.instructionsArr.join("\n"); diff --git a/Components/ChatArea.svelte b/Components/ChatArea.svelte index ec1b3b6..447d1ed 100644 --- a/Components/ChatArea.svelte +++ b/Components/ChatArea.svelte @@ -2,18 +2,13 @@ import { Resolve } from "Services/DependencyService"; import { MarkdownService } from "Services/MarkdownService"; import { Services } from "Services/Services"; + import type { StreamingMarkdownService } from "Services/StreamingMarkdownService"; - export let messages: Array<{id: string, content: string, isUser: boolean}> = []; + export let messages: Array<{id: string, content: string, isUser: boolean, isStreaming: boolean}> = []; let chatContainer: HTMLDivElement; - let markdownService: MarkdownService; - - // Initialize service once - try { - markdownService = Resolve(Services.MarkdownService); - } catch (error) { - console.error('Failed to initialize MarkdownService:', error); - } + let streamingMarkdownService: StreamingMarkdownService = Resolve(Services.StreamingMarkdownService); + // Process each message content with markdown $: processedMessages = messages.map((message) => { @@ -25,7 +20,7 @@ } else { let htmlContent; try { - htmlContent = markdownService?.formatToHTML(message.content) || `

${message.content}

`; + htmlContent = streamingMarkdownService.formatText(message.content) || `

${message.content}

`; } catch (err) { console.error('HTML processing failed:', err); htmlContent = `

${message.content}

`; @@ -45,9 +40,12 @@ {#if message.isUser}

{message.content}

{:else} -
- {@html message.htmlContent} -
+
+ {@html message.htmlContent} + {#if message.isStreaming} + ● ● ● + {/if} +
{/if} @@ -60,7 +58,6 @@ {/if} - \ No newline at end of file diff --git a/Components/ChatWindow.svelte b/Components/ChatWindow.svelte index c133b14..74589b2 100644 --- a/Components/ChatWindow.svelte +++ b/Components/ChatWindow.svelte @@ -1,13 +1,12 @@