diff --git a/src/frontend/src/lib/components/ChatInterface.svelte b/src/frontend/src/lib/components/ChatInterface.svelte
index bf59201..b930a09 100644
--- a/src/frontend/src/lib/components/ChatInterface.svelte
+++ b/src/frontend/src/lib/components/ChatInterface.svelte
@@ -128,22 +128,50 @@
-
- {#each segment.headers as header}
- | {header} |
- {/each}
-
-
-
- {#each segment.rows as row}
- {#each row as cell}
- | {cell} |
+ {#each segment.headers as header}
+
+ {#each header as cellSeg}
+ {#if cellSeg.type === 'bold'}
+ {cellSeg.content}
+ {:else if cellSeg.type === 'italic'}
+ {cellSeg.content}
+ {:else if cellSeg.type === 'code'}
+ {cellSeg.content}
+ {:else if cellSeg.type === 'link'}
+ {cellSeg.content}
+ {:else}
+ {cellSeg.content}
+ {/if}
+ {/each}
+ |
{/each}
-
- {/each}
-
-
+
+
+
+ {#each segment.rows as row}
+
+ {#each row as cell}
+
+ {#each cell as cellSeg}
+ {#if cellSeg.type === 'bold'}
+ {cellSeg.content}
+ {:else if cellSeg.type === 'italic'}
+ {cellSeg.content}
+ {:else if cellSeg.type === 'code'}
+ {cellSeg.content}
+ {:else if cellSeg.type === 'link'}
+ {cellSeg.content}
+ {:else}
+ {cellSeg.content}
+ {/if}
+ {/each}
+ |
+ {/each}
+
+ {/each}
+
+
{:else if segment.type === 'heading'}
{segment.content}
@@ -158,7 +186,7 @@
{message.timestamp.toLocaleTimeString()}
- {/each}
+ {/each}
{#if isSending}
@@ -345,60 +373,6 @@
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;
- margin-top: 0.25rem;
- 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;
@@ -430,11 +404,6 @@
margin: 0.25rem 0;
}
- a {
- color: #667eea;
- text-decoration: none;
- }
-
.content-heading {
font-size: 1rem;
font-weight: 600;
@@ -481,10 +450,22 @@
background: rgba(255, 255, 255, 0.05);
}
+ a {
+ color: #667eea;
+ text-decoration: none;
+ }
+
a:hover {
text-decoration: underline;
}
+ .message-time {
+ font-size: 0.7rem;
+ color: #666;
+ margin-top: 0.25rem;
+ padding: 0 0.5rem;
+ }
+
.typing {
display: flex;
gap: 4px;
diff --git a/src/frontend/src/lib/utils/markdown.ts b/src/frontend/src/lib/utils/markdown.ts
index 95d07af..193490d 100644
--- a/src/frontend/src/lib/utils/markdown.ts
+++ b/src/frontend/src/lib/utils/markdown.ts
@@ -3,12 +3,18 @@
* Supports: bold, italic, code blocks, inline code, links, lists, tables, headings, line breaks
*/
+interface InlineSegment {
+ type: 'text' | 'bold' | 'italic' | 'code' | 'link';
+ content: string;
+ href?: string;
+}
+
interface ParsedSegment {
type: 'text' | 'bold' | 'italic' | 'code' | 'codeBlock' | 'link' | 'list' | 'table' | 'lineBreak' | 'heading';
content: string;
items?: string[];
- headers?: string[];
- rows?: string[][];
+ headers?: InlineSegment[][];
+ rows?: InlineSegment[][];
}
export function parseMarkdown(text: string): ParsedSegment[] {
@@ -79,7 +85,7 @@ function parseTable(tableStr: string): ParsedSegment[] {
if (lines.length < 2) return [];
// Skip separator line (|---|---|)
- const dataLines = lines.filter(line => !line.match(/^[\|\s-]+$/));
+ const dataLines = lines.filter(line => !line.match(/^[\|\s\-:]+$/));
if (dataLines.length < 2) return [];
const headers = parseTableRow(dataLines[0]);
@@ -93,8 +99,39 @@ function parseTable(tableStr: string): ParsedSegment[] {
}];
}
-function parseTableRow(row: string): string[] {
- return row.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
+function parseTableRow(row: string): InlineSegment[][] {
+ return row.split('|')
+ .map(cell => cell.trim())
+ .filter(cell => cell !== '')
+ .map(cell => parseInlineElements(cell));
+}
+
+function parseInlineElements(text: string): InlineSegment[] {
+ const segments: InlineSegment[] = [];
+
+ 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], href: linkMatch[2] });
+ }
+ } else if (part) {
+ segments.push({ type: 'text', content: part });
+ }
+ }
+
+ return segments;
}
function parseLines(text: string): ParsedSegment[] {
@@ -149,7 +186,7 @@ function parseLines(text: string): ParsedSegment[] {
}
// Process inline formatting
- const inlineSegments = parseInlineElements(line);
+ const inlineSegments = parseInlineElementsAsText(line);
segments.push(...inlineSegments);
// Add line break after non-empty lines (except last in a paragraph)
@@ -161,7 +198,7 @@ function parseLines(text: string): ParsedSegment[] {
return segments;
}
-function parseInlineElements(text: string): ParsedSegment[] {
+function parseInlineElementsAsText(text: string): ParsedSegment[] {
const segments: ParsedSegment[] = [];
const inlineRegex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[.*?\]\(.*?\))/g;