fix hermes markdown media and sync retry (#550)

This commit is contained in:
ekko
2026-05-08 19:55:55 +08:00
committed by GitHub
parent 866ae3d23d
commit bba4920fee
4 changed files with 46 additions and 6 deletions
@@ -83,7 +83,7 @@ const renderedHtml = computed(() => {
const ext = path.split('.').pop()?.toLowerCase()
// Video files: render as video player
if (ext === 'mp4' || ext === 'webm') {
if (ext === 'mp4' || ext === 'webm' || ext === 'mov') {
const downloadUrl = getDownloadUrl(path)
return `<div class="markdown-video-container">
<video class="markdown-video" controls preload="metadata" src="${downloadUrl}"></video>
+20 -3
View File
@@ -25,7 +25,7 @@ export const AI_OUTPUT_FORMAT_GUIDELINES = `
\`\`\`
## 视频格式
使用 Markdown 链接语法引用视频文件,路径必须是本地绝对路径(以 / 开头),支持的格式:mp4, webm
使用 Markdown 链接语法引用视频文件,路径必须是本地绝对路径(以 / 开头),支持的格式:mp4, webm, mov
\`\`\`
[视频名称](/tmp/recording.mp4)
\`\`\`
@@ -33,9 +33,25 @@ export const AI_OUTPUT_FORMAT_GUIDELINES = `
\`\`\`
[屏幕录制](/tmp/screen-recording.mp4)
[操作演示](/tmp/demo.webm)
[录屏2026-05-08 15.19.46](/Users/ekko/Desktop/录屏2026-05-08%2015.19.46.mov)
\`\`\`
视频会显示为可播放的视频播放器(最大 640x480),支持原生播放控件。
如果路径包含空格、中文或其他特殊字符,必须使用以下两种写法之一:
1. 对路径做 URL 编码,至少把空格写成 \`%20\`
2. 用尖括号包住链接目标
示例:
\`\`\`
[录屏2026-05-08 15.19.46](/Users/ekko/Desktop/录屏2026-05-08%2015.19.46.mov)
[录屏2026-05-08 15.19.46](</Users/ekko/Desktop/录屏2026-05-08 15.19.46.mov>)
\`\`\`
错误示例:
\`\`\`
[录屏2026-05-08 15.19.46](/Users/ekko/Desktop/录屏2026-05-08 15.19.46.mov)
\`\`\`
## 文件链接格式
使用 Markdown 链接语法,路径必须是本地绝对路径(以 / 开头):
\`\`\`
@@ -49,13 +65,15 @@ export const AI_OUTPUT_FORMAT_GUIDELINES = `
## 注意事项
1. 图片、视频、文件路径必须使用本地绝对路径(以 / 开头)
2. 确保文件确实存在且路径正确
3. 视频支持格式:.mp4, .webm
3. 视频支持格式:.mp4, .webm, .mov
4. 路径中如果有空格或特殊字符,必须编码或使用尖括号包裹链接目标
## 发送文件给用户
当用户要求"发给我"、"发送给我"、"传给我"等请求文件时,使用上述格式返回文件路径:
- 图片:\`![描述](/path/to/image.png)\`
- 视频:\`[视频名](/path/to/video.mp4)\`
- 文件:\`[文件名](/path/to/file.pdf)\`
- 如果路径中有空格,优先输出编码后的路径,例如:\`[录屏](</tmp/录屏 15.19.46.mov>)\`\`[录屏](/tmp/录屏%2015.19.46.mov)\`
`;
/**
@@ -74,4 +92,3 @@ export function getSystemPrompt(customPrompt?: string): string {
return parts.join('\n\n');
}
@@ -1304,7 +1304,10 @@ export class ChatRunSocket {
const prof = state.profile
this.hermesSessionIds.delete(sessionId)
state.profile = undefined
await this.syncFromHermes(socket, sessionId, hermesId, prof)
await this.syncFromHermes(socket, sessionId, hermesId, prof, {
maxAttempts: 4,
delayMs: 1000,
})
}
}
@@ -1341,7 +1344,7 @@ export class ChatRunSocket {
this.hermesSessionIds.delete(sessionId)
logger.info({ sessionId, hermesId, profile: profile || 'default' }, '[chat-run-socket][abort] syncing stopped run from Hermes')
synced = await this.syncFromHermes(socket, sessionId, hermesId, profile, {
maxAttempts: 10,
maxAttempts: 4,
delayMs: 1000,
})
}
+20
View File
@@ -36,6 +36,11 @@ vi.mock('naive-ui', () => ({
}),
}))
vi.mock('@/api/hermes/download', () => ({
downloadFile: vi.fn(),
getDownloadUrl: (path: string) => `http://test.local/api/hermes/download?path=${encodeURIComponent(path)}`,
}))
import MarkdownRenderer from '@/components/hermes/chat/MarkdownRenderer.vue'
describe('MarkdownRenderer', () => {
@@ -218,6 +223,21 @@ describe('MarkdownRenderer', () => {
expect(wrapper.find('.markdown-body').text()).toContain('Done outside.')
})
it('renders local mov links as inline video players', () => {
const wrapper = mount(MarkdownRenderer, {
props: {
content: '[录屏2026-05-08 15.19.46.mov](/Users/ekko/Desktop/录屏2026-05-08%2015.19.46.mov)',
},
})
const video = wrapper.find('video.markdown-video')
expect(video.exists()).toBe(true)
expect(video.attributes('src')).toContain('/api/hermes/download?path=')
const src = new URL(video.attributes('src'))
expect(decodeURIComponent(src.searchParams.get('path') || '')).toBe('/Users/ekko/Desktop/录屏2026-05-08 15.19.46.mov')
expect(wrapper.find('.markdown-video-footer .att-name').text()).toBe('录屏2026-05-08 15.19.46.mov')
})
it('keeps tilde-fenced markdown examples with nested tilde fences intact', () => {
const wrapper = mount(MarkdownRenderer, {
props: {