- {message.content}
+ {#each renderContent(message.content) as segment}
+ {#if segment.type === 'bold'}
+
{segment.content}
+ {:else if segment.type === 'italic'}
+
{segment.content}
+ {:else if segment.type === 'code'}
+
{segment.content}
+ {:else if segment.type === 'codeBlock'}
+
{segment.content}
+ {:else if segment.type === 'link'}
+
{segment.content}
+ {:else if segment.type === 'list' && segment.items}
+
+ {#each segment.items as item}
+ - {item}
+ {/each}
+
+ {:else}
+ {segment.content}
+ {/if}
+ {/each}
-
-
-
-
+ {#if isThinking}
+
+
+ {#if thinkingContent}
+
+ {#if showThinking}
+
+ {#each renderContent(thinkingContent) as segment}
+ {#if segment.type === 'bold'}
+
{segment.content}
+ {:else if segment.type === 'italic'}
+
{segment.content}
+ {:else if segment.type === 'code'}
+
{segment.content}
+ {:else if segment.type === 'codeBlock'}
+
{segment.content}
+ {:else if segment.type === 'list' && segment.items}
+
+ {#each segment.items as item}
+ - {item}
+ {/each}
+
+ {:else}
+ {segment.content}
+ {/if}
+ {/each}
+
+ {/if}
+ {:else}
+
+
+
+
+
+ {/if}
{/if}
@@ -105,9 +179,9 @@
onkeydown={handleKeydown}
placeholder="Describe your trading strategy..."
rows="1"
- disabled={isSending}
+ disabled={isThinking}
>
-
@@ -213,6 +287,11 @@
border: 1px solid rgba(251, 191, 36, 0.3);
}
+ .message.thinking .message-content {
+ background: rgba(255, 255, 255, 0.05);
+ border: 1px dashed rgba(255, 255, 255, 0.2);
+ }
+
.message-time {
font-size: 0.7rem;
color: #666;
@@ -220,10 +299,92 @@
padding: 0 0.5rem;
}
+ .thinking-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.5rem;
+ }
+
+ .thinking-toggle {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ background: none;
+ border: none;
+ color: #888;
+ cursor: pointer;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ transition: background 0.2s;
+ }
+
+ .thinking-toggle:hover {
+ background: rgba(255, 255, 255, 0.1);
+ }
+
+ .thinking-icon {
+ font-size: 0.6rem;
+ }
+
+ .thinking-label {
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ }
+
+ .thinking-content {
+ color: #999;
+ font-size: 0.9rem;
+ padding-top: 0.5rem;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+ margin-top: 0.5rem;
+ }
+
+ .inline-code {
+ background: rgba(0, 0, 0, 0.3);
+ padding: 0.15rem 0.4rem;
+ border-radius: 4px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 0.85em;
+ }
+
+ .code-block {
+ background: rgba(0, 0, 0, 0.4);
+ padding: 0.75rem;
+ border-radius: 6px;
+ overflow-x: auto;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 0.85rem;
+ margin: 0.5rem 0;
+ }
+
+ .code-block code {
+ white-space: pre;
+ }
+
+ ul {
+ margin: 0.5rem 0;
+ padding-left: 1.5rem;
+ }
+
+ li {
+ margin: 0.25rem 0;
+ }
+
+ a {
+ color: #667eea;
+ text-decoration: none;
+ }
+
+ a:hover {
+ text-decoration: underline;
+ }
+
.typing {
display: flex;
gap: 4px;
- padding: 1rem 1.25rem;
+ padding: 0.5rem;
}
.dot {
@@ -297,4 +458,4 @@
opacity: 0.5;
cursor: not-allowed;
}
-
\ No newline at end of file
+
diff --git a/src/frontend/src/lib/utils/markdown.ts b/src/frontend/src/lib/utils/markdown.ts
new file mode 100644
index 0000000..0c72325
--- /dev/null
+++ b/src/frontend/src/lib/utils/markdown.ts
@@ -0,0 +1,92 @@
+/**
+ * Simple markdown parser for rendering AI responses
+ * Supports: bold, italic, code blocks, inline code, links, lists, headings
+ */
+
+interface ParsedSegment {
+ type: 'text' | 'bold' | 'italic' | 'code' | 'codeBlock' | 'link' | 'list';
+ content: string;
+ items?: string[];
+}
+
+export function parseMarkdown(text: string): ParsedSegment[] {
+ const segments: ParsedSegment[] = [];
+
+ // Split by code blocks first (they can contain other markdown)
+ const codeBlockRegex = /```[\s\S]*?```/g;
+ const parts = text.split(codeBlockRegex);
+ const codeBlocks = text.match(codeBlockRegex) || [];
+
+ let partIndex = 0;
+
+ while (partIndex < parts.length) {
+ const part = parts[partIndex];
+
+ if (part) {
+ // Process inline formatting
+ segments.push(...parseInlineMarkdown(part));
+ }
+
+ // Add code block if there's one after this part
+ if (partIndex < codeBlocks.length) {
+ const codeContent = codeBlocks[partIndex].replace(/^```\w*\n?/, '').replace(/```$/, '');
+ segments.push({ type: 'codeBlock', content: codeContent });
+ }
+
+ partIndex++;
+ }
+
+ return segments;
+}
+
+function parseInlineMarkdown(text: string): ParsedSegment[] {
+ const segments: ParsedSegment[] = [];
+
+ // Combined regex for bold, italic, inline code, links
+ const inlineRegex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\\\[.*?\]\(.*?\))/g;
+ const parts = text.split(inlineRegex);
+
+ for (const part of parts) {
+ if (!part) continue;
+
+ if (part.startsWith('**') && part.endsWith('**')) {
+ segments.push({ type: 'bold', content: part.slice(2, -2) });
+ } else if (part.startsWith('*') && part.endsWith('*')) {
+ segments.push({ type: 'italic', content: part.slice(1, -1) });
+ } else if (part.startsWith('`') && part.endsWith('`')) {
+ segments.push({ type: 'code', content: part.slice(1, -1) });
+ } else if (part.startsWith('[') && part.includes('](')) {
+ const linkMatch = part.match(/\[(.*?)\]\((.*?)\)/);
+ if (linkMatch) {
+ segments.push({ type: 'link', content: linkMatch[1] });
+ }
+ } else if (part.includes('\n')) {
+ // Handle newlines and lists
+ const lines = part.split('\n');
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ if (line.match(/^[\-\*]\s/)) {
+ // List item
+ if (segments.length > 0 && segments[segments.length - 1].type === 'list') {
+ segments[segments.length - 1].items?.push(line.slice(2));
+ } else {
+ segments.push({ type: 'list', content: '', items: [line.slice(2)] });
+ }
+ } else if (line.match(/^#{1,6}\s/)) {
+ // Heading
+ segments.push({ type: 'text', content: line });
+ } else if (line) {
+ if (i > 0) {
+ segments.push({ type: 'text', content: '\n' + line });
+ } else {
+ segments.push({ type: 'text', content: line });
+ }
+ }
+ }
+ } else {
+ segments.push({ type: 'text', content: part });
+ }
+ }
+
+ return segments;
+}
diff --git a/src/frontend/src/routes/bot/[id]/+page.svelte b/src/frontend/src/routes/bot/[id]/+page.svelte
index 0193e04..bc759ad 100644
--- a/src/frontend/src/routes/bot/[id]/+page.svelte
+++ b/src/frontend/src/routes/bot/[id]/+page.svelte
@@ -108,8 +108,8 @@