diff --git a/.changeset/chat-phase-one.md b/.changeset/chat-phase-one.md new file mode 100644 index 0000000..196cfd9 --- /dev/null +++ b/.changeset/chat-phase-one.md @@ -0,0 +1,5 @@ +--- +'contextvm-site': minor +--- + +Add the phase-one chat workspace with OpenRouter auto mode, provider and model configuration, streaming chat, and persistent conversations. diff --git a/bun.lock b/bun.lock index 90831c8..616eeac 100644 --- a/bun.lock +++ b/bun.lock @@ -22,6 +22,7 @@ "marked": "^17.0.5", "mode-watcher": "^1.1.0", "nostr-tools": "^2.23.3", + "openai": "^6.38.0", "rxjs": "^7.8.2", "zod": "^4.3.6", }, @@ -751,6 +752,8 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "openai": ["openai@6.38.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-AoMplt2UalrpgUDMh3L09QWjNRlgJPipclQvA6sYAaeF6nHNBMgmikAZGmcYLn8on4d9sQY9Q8bOLfrBS7Lc8g=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="], diff --git a/package.json b/package.json index 2da256c..285a63d 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "marked": "^17.0.5", "mode-watcher": "^1.1.0", "nostr-tools": "^2.23.3", + "openai": "^6.38.0", "rxjs": "^7.8.2", "zod": "^4.3.6" } diff --git a/src/app.css b/src/app.css index e623f11..84ef932 100644 --- a/src/app.css +++ b/src/app.css @@ -132,6 +132,7 @@ pre { @apply overflow-x-auto rounded-lg bg-muted p-4; + max-width: 100%; } blockquote { @@ -435,6 +436,28 @@ animation: fade-in-up 0.8s ease-out; } +/* ============================================ + Chat Bubble Overflow Containment + ============================================ */ + +/* Force code blocks inside prose containers to respect parent width. +
 has an intrinsic min-width equal to content; this resets it. */
+.prose pre,
+.prose .code-block {
+	min-width: 0;
+	max-width: 100%;
+}
+
+.prose .code-block > div {
+	min-width: 0;
+	max-width: 100%;
+}
+
+/* Code blocks inside chat bubbles: force the scrollable container
+   to compute its width from the bubble, not from its content.
+   The key trick is width:0 + min-width:100% on the scroll wrapper,
+   applied via inline styles in ChatBubble.svelte's renderer. */
+
 /* ============================================
    Mobile Optimizations
    ============================================ */
diff --git a/src/lib/components/chat/AutoModeBanner.svelte b/src/lib/components/chat/AutoModeBanner.svelte
new file mode 100644
index 0000000..af6c45f
--- /dev/null
+++ b/src/lib/components/chat/AutoModeBanner.svelte
@@ -0,0 +1,32 @@
+
+
+
+
+
+ +
+
+

OpenRouter auto mode

+

+ Free models rotate automatically. Add your own key for steadier limits. +

+
+
+ {#if usingDefaultKey} +
+ + Bundled public key +
+ {/if} +
diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte new file mode 100644 index 0000000..b979948 --- /dev/null +++ b/src/lib/components/chat/Chat.svelte @@ -0,0 +1,434 @@ + + +
+ {#if autoModeEnabled} +
+ +
+ {/if} +
+ {#if !conversationId} +
+

+ Choose a conversation or start a new one to begin chatting. +

+
+ {:else if messages.length === 0} +
+
+
+
+ CV +
+
+
+

What can I help you build?

+

+ Orchestrate MCP servers, explore tools, and manage workflows — all from this chat. +

+
+
+ {#each starterPrompts as prompt (prompt.text)} + {@const Icon = prompt.icon} + + {/each} +
+

+ Press Enter to send · Shift+Enter for new line +

+
+ {:else} +
+ {#each messages as message (message.id)} + + {/each} +
+ {/if} +
+
+
+ {#if errorMessage} +

+ {errorMessage} +

+ {/if} + +
+
+
diff --git a/src/lib/components/chat/ChatBubble.svelte b/src/lib/components/chat/ChatBubble.svelte new file mode 100644 index 0000000..2769b02 --- /dev/null +++ b/src/lib/components/chat/ChatBubble.svelte @@ -0,0 +1,124 @@ + + + + +
+ {#if message.role !== 'user'} +
+ CV +
+ {/if} +
+ +
+ {#if message.role === 'user'} +

{message.content}

+ {:else if message.role === 'assistant' && !message.content} + + + + + + {:else} + + {@html html} + {/if} +
+ {#if timestampLabel} + {timestampLabel} + {/if} +
+
diff --git a/src/lib/components/chat/ChatInput.svelte b/src/lib/components/chat/ChatInput.svelte new file mode 100644 index 0000000..163195c --- /dev/null +++ b/src/lib/components/chat/ChatInput.svelte @@ -0,0 +1,104 @@ + + +
+
+