Skip to content

Commit 891f03f

Browse files
authored
Merge pull request #1473 from Horizonll/main
fix(web-ui): resolve relative markdown images in chat
2 parents f072467 + 90b1925 commit 891f03f

3 files changed

Lines changed: 31 additions & 4 deletions

File tree

src/web-ui/src/component-library/components/Markdown/Markdown.test.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Markdown } from './Markdown';
99
const mocks = vi.hoisted(() => ({
1010
getCurrentWorkspacePath: vi.fn(),
1111
revealInExplorer: vi.fn(),
12+
readFileContent: vi.fn(),
1213
openExternal: vi.fn(),
1314
renderMath: vi.fn(),
1415
}));
@@ -19,7 +20,7 @@ vi.mock('../../../infrastructure/api', () => ({
1920
},
2021
workspaceAPI: {
2122
revealInExplorer: (...args: unknown[]) => mocks.revealInExplorer(...args),
22-
readFileContent: vi.fn(),
23+
readFileContent: (...args: unknown[]) => mocks.readFileContent(...args),
2324
},
2425
systemAPI: {
2526
openExternal: (...args: unknown[]) => mocks.openExternal(...args),
@@ -98,9 +99,11 @@ describe('Markdown file links', () => {
9899
onFileViewRequest = vi.fn();
99100
mocks.getCurrentWorkspacePath.mockReset();
100101
mocks.revealInExplorer.mockReset();
102+
mocks.readFileContent.mockReset();
101103
mocks.openExternal.mockReset();
102104
mocks.renderMath.mockReset();
103105
mocks.getCurrentWorkspacePath.mockResolvedValue(EXAMPLE_WORKSPACE);
106+
mocks.readFileContent.mockResolvedValue('cmVsdS1wbmc=');
104107
});
105108

106109
afterEach(() => {
@@ -228,4 +231,24 @@ describe('Markdown file links', () => {
228231
expect(container.querySelector('[data-testid="markdown-math-renderer"]')).not.toBeNull();
229232
expect(mocks.renderMath).toHaveBeenCalledWith('Formula: $x + y$');
230233
});
234+
235+
it('loads relative markdown images from the provided base path', async () => {
236+
await act(async () => {
237+
root.render(
238+
<Markdown
239+
content={'![ReLU 图像](relu.png)'}
240+
basePath={EXAMPLE_WORKSPACE}
241+
onFileViewRequest={onFileViewRequest}
242+
/>,
243+
);
244+
await Promise.resolve();
245+
await Promise.resolve();
246+
});
247+
248+
const image = container.querySelector<HTMLImageElement>('img[alt="ReLU 图像"]');
249+
expect(image).not.toBeNull();
250+
expect(mocks.readFileContent).toHaveBeenCalledWith(`${EXAMPLE_WORKSPACE}/relu.png`);
251+
expect(image?.src).toBe('data:image/png;base64,cmVsdS1wbmc=');
252+
expect(mocks.getCurrentWorkspacePath).not.toHaveBeenCalled();
253+
});
231254
});

src/web-ui/src/component-library/components/Markdown/Markdown.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ export const Markdown = React.memo<MarkdownProps>(({
811811
);
812812

813813
useEffect(() => {
814-
if (!needsWorkspacePathForLinks || currentWorkspacePath) {
814+
if (!needsWorkspacePathForLinks || currentWorkspacePath || basePath) {
815815
return;
816816
}
817817

@@ -830,7 +830,7 @@ export const Markdown = React.memo<MarkdownProps>(({
830830
return () => {
831831
cancelled = true;
832832
};
833-
}, [currentWorkspacePath, needsWorkspacePathForLinks]);
833+
}, [basePath, currentWorkspacePath, needsWorkspacePathForLinks]);
834834

835835
const markdownFeatureProfile = useMemo(() => ({
836836
contentLength: markdownContent.length,
@@ -1319,7 +1319,7 @@ export const Markdown = React.memo<MarkdownProps>(({
13191319
},
13201320

13211321
img({ node: _node, ...props }: any) {
1322-
return <MarkdownImage {...props} basePath={basePath} />;
1322+
return <MarkdownImage {...props} basePath={basePath || currentWorkspacePath} />;
13231323
},
13241324

13251325
blockquote({ children }: any) {

src/web-ui/src/flow_chat/components/FlowTextBlock.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ export const FlowTextBlock = React.memo<FlowTextBlockProps>(({
7373
onTabOpen,
7474
onHttpLinkClick,
7575
onOpenVisualization,
76+
activeSessionOverride,
7677
} = useFlowChatContext();
78+
const markdownBasePath = activeSessionOverride?.workspacePath
79+
|| activeSessionOverride?.config?.workspacePath;
7780

7881
// Normalize content to a string.
7982
const content = typeof textItem.content === 'string'
@@ -147,6 +150,7 @@ export const FlowTextBlock = React.memo<FlowTextBlockProps>(({
147150
{textItem.isMarkdown ? (
148151
<MarkdownRenderer
149152
content={displayContent}
153+
basePath={markdownBasePath}
150154
// Pass the raw streaming flag (not the idle-gated
151155
// `isActivelyStreaming`) so the code-block render path inside
152156
// Markdown stays stable across bursty AI output. Otherwise

0 commit comments

Comments
 (0)