ishaq101 Claude Sonnet 4.6 commited on
Commit
eb625df
Β·
1 Parent(s): 72ff90e

fix: render markdown tables and persist message sources after refresh

Browse files

- Add preprocessMarkdown to fix concatenated table rows from AI responses
- Map sources field in loadRoomMessages so Sources footer survives page refresh
- Move ChatSource interface before RoomMessage and add sources field to RoomMessage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. src/app/components/Main.tsx +28 -1
  2. src/services/api.ts +7 -6
src/app/components/Main.tsx CHANGED
@@ -52,6 +52,32 @@ interface ChatRoom {
52
  messagesLoaded: boolean;
53
  }
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  // Markdown component overrides for clean rendering inside chat bubbles
56
  const markdownComponents: Components = {
57
  p: ({ children }) => (
@@ -254,6 +280,7 @@ export default function Main() {
254
  role: m.role,
255
  content: m.content,
256
  timestamp: new Date(m.created_at).getTime(),
 
257
  }));
258
  setChats((prev) =>
259
  prev.map((chat) =>
@@ -654,7 +681,7 @@ export default function Main() {
654
  rehypePlugins={[rehypeKatex]}
655
  components={markdownComponents}
656
  >
657
- {message.content}
658
  </ReactMarkdown>
659
  </div>
660
  )}
 
52
  messagesLoaded: boolean;
53
  }
54
 
55
+ // Preprocess markdown to ensure tables are properly separated from surrounding text
56
+ function preprocessMarkdown(content: string): string {
57
+ // Step 1: Split concatenated table rows β€” "| val ||" means end of row + start of next
58
+ let result = content.replace(/\|\|/g, "|\n|");
59
+
60
+ // Step 2: Per-line β€” if a line has non-pipe text before a pipe table row, split them
61
+ const lines = result.split("\n");
62
+ const processed = lines.map((line) => {
63
+ const tableStart = line.indexOf("|");
64
+ if (tableStart > 0) {
65
+ const tableContent = line.slice(tableStart);
66
+ // Confirm it looks like a table row (at least 2 pipes)
67
+ if ((tableContent.match(/\|/g) ?? []).length >= 2) {
68
+ return line.slice(0, tableStart).trimEnd() + "\n\n" + tableContent;
69
+ }
70
+ }
71
+ return line;
72
+ });
73
+ result = processed.join("\n");
74
+
75
+ // Step 3: Ensure blank line before table rows preceded by non-table text (not another row ending with |)
76
+ result = result.replace(/([^|\n])\n(\|)/g, "$1\n\n$2");
77
+
78
+ return result;
79
+ }
80
+
81
  // Markdown component overrides for clean rendering inside chat bubbles
82
  const markdownComponents: Components = {
83
  p: ({ children }) => (
 
280
  role: m.role,
281
  content: m.content,
282
  timestamp: new Date(m.created_at).getTime(),
283
+ sources: m.sources ?? [],
284
  }));
285
  setChats((prev) =>
286
  prev.map((chat) =>
 
681
  rehypePlugins={[rehypeKatex]}
682
  components={markdownComponents}
683
  >
684
+ {preprocessMarkdown(message.content)}
685
  </ReactMarkdown>
686
  </div>
687
  )}
src/services/api.ts CHANGED
@@ -30,11 +30,18 @@ export interface CreateRoomResponse {
30
  data: Room;
31
  }
32
 
 
 
 
 
 
 
33
  export interface RoomMessage {
34
  id: string;
35
  role: "user" | "assistant";
36
  content: string;
37
  created_at: string;
 
38
  }
39
 
40
  export interface RoomDetail extends Room {
@@ -58,12 +65,6 @@ export interface UploadDocumentResponse {
58
  data: { id: string; filename: string; status: DocumentStatus };
59
  }
60
 
61
- export interface ChatSource {
62
- document_id: string;
63
- filename: string;
64
- page_label: string | null;
65
- }
66
-
67
  // ─── Base Client ──────────────────────────────────────────────────────────────
68
 
69
  const BASE_URL = ((import.meta as unknown as { env: Record<string, string> }).env.VITE_API_BASE_URL) ?? "";
 
30
  data: Room;
31
  }
32
 
33
+ export interface ChatSource {
34
+ document_id: string;
35
+ filename: string;
36
+ page_label: string | null;
37
+ }
38
+
39
  export interface RoomMessage {
40
  id: string;
41
  role: "user" | "assistant";
42
  content: string;
43
  created_at: string;
44
+ sources?: ChatSource[];
45
  }
46
 
47
  export interface RoomDetail extends Room {
 
65
  data: { id: string; filename: string; status: DocumentStatus };
66
  }
67
 
 
 
 
 
 
 
68
  // ─── Base Client ──────────────────────────────────────────────────────────────
69
 
70
  const BASE_URL = ((import.meta as unknown as { env: Record<string, string> }).env.VITE_API_BASE_URL) ?? "";