andy-stack_vaultkeeper-ai/Components/ChatWindow.svelte
Andrew Beal 3f37870aaa Add markdown rendering support with KaTeX and highlight.js
- Updated package.json to include dependencies for rehype and remark plugins for markdown processing.
- Added default highlight.js CSS for code highlighting.
- Included KaTeX CSS for rendering mathematical expressions.
- Created markdown.css for styling markdown content in the Obsidian plugin, incorporating styles for headings, paragraphs, lists, tables, and code blocks.
- Customized highlight.js colors to match Obsidian theme.
2025-09-16 17:14:37 +01:00

160 lines
No EOL
3.9 KiB
Svelte

<script lang="ts">
import type { Part } from "@google/genai";
import type { IActioner } from "Actioner/IActioner";
import type { IAIClass } from "AIClasses/IAIClass";
import { Semaphore } from "Helpers";
import { Resolve } from "Services/DependencyService";
import { Services } from "Services/Services";
import ChatArea from "./ChatArea.svelte";
let ai: IAIClass = Resolve(Services.IAIClass);
let actioner: IActioner = Resolve(Services.IActioner);
let semaphore: Semaphore = new Semaphore(1, false);
let textareaElement: HTMLTextAreaElement;
let userRequest = "";
let isSubmitting = false;
let messages: Array<{id: string, content: string, isUser: boolean}> = [];
async function handleSubmit() {
if (userRequest.trim() === "" || isSubmitting) {
return;
}
isSubmitting = true;
const requestToSend = userRequest;
userRequest = "";
textareaElement.value = "";
autoResize();
// Add user message to chat
const userMessageId = `user-${Date.now()}`;
messages = [...messages, {
id: userMessageId,
content: requestToSend,
isUser: true
}];
if (!await semaphore.wait()) {
return;
}
try {
let response: Part[] | null = await ai.apiRequest(requestToSend, actioner);
console.log(response);
// Add AI response to chat
if (response && response.length > 0) {
const aiMessageId = `ai-${Date.now()}`;
const responseText = response.map(part => part.text || '').join(' ');
messages = [...messages, {
id: aiMessageId,
content: responseText,
isUser: false
}];
}
} finally {
semaphore.release();
isSubmitting = false;
}
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') {
if (e.shiftKey) {
return;
} else {
e.preventDefault();
handleSubmit();
}
}
}
function autoResize() {
if (textareaElement) {
textareaElement.style.height = 'auto';
textareaElement.style.height = textareaElement.scrollHeight + 'px';
}
}
</script>
<main class="container">
<div id="chat-container">
<ChatArea bind:messages />
</div>
<div id="input-container">
<textarea
id="input"
bind:this={textareaElement}
bind:value={userRequest}
on:keydown={handleKeydown}
on:input={autoResize}
placeholder="Type a message..."
disabled={isSubmitting}
rows="1">
</textarea>
<button id="submit" on:click={handleSubmit} disabled={isSubmitting || userRequest.trim() === ""}>
{isSubmitting ? 'Sending...' : 'Submit'}
</button>
</div>
</main>
<style>
.container {
display: grid;
grid-template-rows: 1fr auto;
grid-template-columns: 1fr;
height: 100%;
border-radius: var(--radius-m);
color: var(--font-interface-theme);
}
#chat-container {
height: 100%;
width: 100%;
grid-row: 1;
grid-column: 1;
overflow: hidden;
}
#input-container {
grid-row: 2;
grid-column: 1;
display: grid;
grid-template-rows: var(--size-4-3) 1fr var(--size-4-3);
grid-template-columns: var(--size-4-3) 1fr var(--size-4-3) auto var(--size-4-3);
border-radius: var(--modal-radius);
background-color: var(--modal-background);
}
#input {
grid-row: 2;
grid-column: 2;
min-height: var(--input-height);
max-height: var(--dialog-max-height);
border-radius: var(--input-radius);
font-weight: var(--input-font-weight);
border-width: var(--input-border-width);
resize: none;
overflow-y: auto;
color: var(--font-interface-theme);
}
#submit {
grid-row: 2;
grid-column: 4;
border-radius: var(--button-radius);
align-self: end;
background-color: var(--interactive-accent);
transition-duration: 0.25s;
}
#submit:hover {
background-color: var(--interactive-accent-hover);
}
</style>