Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,128 @@
}

@layer utilities {
.chat-markdown-content {
color: var(--foreground);
font-size: 14px;
line-height: 1.55;
overflow-wrap: break-word;
}

.chat-markdown-content > :first-child {
margin-top: 0;
}

.chat-markdown-content > :last-child {
margin-bottom: 0;
}

.chat-markdown-content h1,
.chat-markdown-content h2,
.chat-markdown-content h3,
.chat-markdown-content h4,
.chat-markdown-content h5,
.chat-markdown-content h6 {
margin-top: 12px;
margin-bottom: 6px;
font-weight: 700;
line-height: 1.3;
}

.chat-markdown-content h1,
.chat-markdown-content h2 {
font-size: 1.05em;
}

.chat-markdown-content h3,
.chat-markdown-content h4,
.chat-markdown-content h5,
.chat-markdown-content h6 {
font-size: 1em;
}

.chat-markdown-content p,
.chat-markdown-content blockquote,
.chat-markdown-content ul,
.chat-markdown-content ol,
.chat-markdown-content table,
.chat-markdown-content pre {
margin-top: 0;
margin-bottom: 10px;
}

.chat-markdown-content a {
color: var(--primary);
text-decoration: underline;
text-underline-offset: 2px;
}

.chat-markdown-content strong {
font-weight: 700;
}

.chat-markdown-content ul,
.chat-markdown-content ol {
padding-left: 1.35em;
}

.chat-markdown-content li + li {
margin-top: 0.2em;
}

.chat-markdown-content blockquote {
border-left: 3px solid var(--border);
color: var(--muted-foreground);
padding-left: 0.85em;
}

.chat-markdown-content table {
border-collapse: collapse;
display: block;
font-size: 12px;
max-width: 100%;
overflow-x: auto;
width: max-content;
}

.chat-markdown-content th,
.chat-markdown-content td {
border: 1px solid var(--border);
padding: 5px 8px;
text-align: left;
vertical-align: top;
}

.chat-markdown-content th {
background-color: color-mix(in srgb, var(--muted) 60%, transparent);
font-weight: 700;
}

.chat-markdown-content code {
background-color: color-mix(in srgb, var(--muted) 72%, transparent);
border-radius: 4px;
font-family: var(--font-mono);
font-size: 85%;
padding: 0.15em 0.35em;
}

.chat-markdown-content pre {
background-color: color-mix(in srgb, var(--muted) 80%, transparent);
border: 1px solid var(--border);
border-radius: 6px;
line-height: 1.45;
max-width: 100%;
overflow-x: auto;
padding: 10px;
}

.chat-markdown-content pre code {
background: transparent;
border-radius: 0;
font-size: 100%;
padding: 0;
white-space: pre;
}

.original-markdown-preview {
color: var(--foreground);
font-size: 14px;
Expand Down
144 changes: 144 additions & 0 deletions src/components/chat-message-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,150 @@ describe("ChatMessageList", () => {
).toBeTruthy();
});

it("renders image citations as viewable image attachments", () => {
render(
React.createElement(ChatMessageList, {
messages: [
{
id: "assistant_1",
role: "assistant",
content: "Here is the launch image.",
citations: [
{
chunkType: "image",
score: 0.9,
assetUrl: "https://blob.example/images/launch.jpg",
source: {
documentId: "doc_1",
sourceFileName: "spacex-s1.pdf",
sectionPath: "Assets / images / launch.jpg",
},
},
],
},
],
}),
);

const image = screen.getByRole("img", {
name: "spacex-s1.pdf · Assets / images / launch.jpg",
});
expect(image.getAttribute("src")).toBe(
"https://blob.example/images/launch.jpg",
);
expect(
screen.queryByRole("link", {
name: "https://blob.example/images/launch.jpg",
}),
).toBeNull();
expect(
screen.queryByText("https://blob.example/images/launch.jpg"),
).toBeNull();
});

it("renders assistant markdown with GitHub-flavored tables", () => {
render(
React.createElement(ChatMessageList, {
messages: [
{
id: "assistant_1",
role: "assistant",
content:
"### Summary\n\n- **Deadline:** Monday\n\n| Item | Status |\n| --- | --- |\n| Draft | Ready |",
},
],
}),
);

expect(
screen.getByRole("heading", { name: "Summary", level: 3 }),
).toBeTruthy();
expect(screen.getByRole("listitem").textContent).toContain("Deadline:");
expect(screen.getByRole("table")).toBeTruthy();
expect(screen.getByRole("columnheader", { name: "Item" })).toBeTruthy();
expect(screen.getByRole("cell", { name: "Ready" })).toBeTruthy();
});

it("keeps user markdown-looking text literal", () => {
render(
React.createElement(ChatMessageList, {
messages: [
{
id: "user_1",
role: "user",
content: "**Do not render this as bold**",
},
],
}),
);

expect(screen.getByText("**Do not render this as bold**")).toBeTruthy();
expect(screen.queryByText("Do not render this as bold")).toBeNull();
});

it("skips assistant inline HTML while rendering markdown text", () => {
render(
React.createElement(ChatMessageList, {
messages: [
{
id: "assistant_1",
role: "assistant",
content: "Visible **text** <img src=\"x\" alt=\"hidden image\" />",
},
],
}),
);

expect(screen.getByText("text")).toBeTruthy();
expect(screen.queryByAltText("hidden image")).toBeNull();
});

it("does not hide image cards when source links dedupe the same section", () => {
render(
React.createElement(ChatMessageList, {
messages: [
{
id: "assistant_1",
role: "assistant",
content: "这里是相关身份证明图片。",
citations: [
{
chunkType: "text",
score: 0.9,
source: {
documentId: "doc_1",
sourceFileName: "商务标文件.pdf",
sectionPath: "二、法定代表人身份证明",
},
},
{
chunkType: "image",
score: 0.9,
assetUrl: "https://blob.example/images/image-6-id-front.jpg",
source: {
documentId: "doc_1",
sourceFileName: "商务标文件.pdf",
sectionPath: "二、法定代表人身份证明",
},
},
],
},
],
}),
);

expect(
screen.getByRole("img", {
name: "商务标文件.pdf · 二、法定代表人身份证明",
}),
).toBeTruthy();
expect(
screen.getAllByRole("button", {
name: "Open source 商务标文件.pdf · 二、法定代表人身份证明",
}),
).toHaveLength(1);
});

it("shows thinking progress after existing messages while sending", () => {
render(
React.createElement(ChatMessageList, {
Expand Down
Loading
Loading