Просмотр исходного кода

发布 v0.3.13:修复 Bot 聊天文本附件预览

LSamien 2 месяцев назад
Родитель
Сommit
e2b7591367

+ 1 - 1
README.md

@@ -1,6 +1,6 @@
 # 夭夭 Web
 
-当前发布版本:**v0.3.12**,配套 iOS **1.3(174)**。详见 [发布说明](docs/releases/v0.3.12.md)。
+当前发布版本:**v0.3.13**,配套 iOS **1.3(175)**。详见 [发布说明](docs/releases/v0.3.13.md)。
 
 夭夭 Web 为网页和 iOS 提供统一的 Agent、群聊、文件库及语音配置服务。Hermes 通过标准 9119 HTTP/WebSocket 接口执行任务;无需安装 Yaoyao 插件。
 

+ 15 - 0
docs/releases/v0.3.13.md

@@ -0,0 +1,15 @@
+# v0.3.13
+
+修复 Bot 聊天中的文本文件预览,并配套 iOS 1.3(175)的聊天滚动修复。
+
+- Web 根据附件原始文件名和 MIME 类型选择文本预览器,修复“报告”等无扩展名链接无法预览正文的问题。
+- iOS Bot 聊天接入普通聊天的文件预览器,支持文本与 Markdown,继续通过已认证的 Web 文件接口读取。
+- iOS Bot 聊天增加原样式的“回到最新消息”按钮。
+- 用户手动点击到底会停止当前滑动并执行跳转;自动跟随消息仍保留浏览历史时的延后策略。
+- 同步到底后的按钮可见状态,避免已经到底仍残留按钮。
+
+## 验证
+
+- Web 浏览器通过实际附件链接打开文本正文。
+- iOS 使用长会话验证文本预览、快速滑动后的到底动作与按钮可见性;普通聊天验证相同的手动跳转。
+- Web 500 项测试、类型检查和生产构建通过;iOS 80 项单元测试与两项界面验收通过。

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "hermes-yaoyao",
-  "version": "0.3.12",
+  "version": "0.3.13",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "hermes-yaoyao",
-      "version": "0.3.12",
+      "version": "0.3.13",
       "license": "BSL-1.1",
       "dependencies": {
         "@koa/bodyparser": "^5.0.0",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "hermes-yaoyao",
-  "version": "0.3.12",
+  "version": "0.3.13",
   "private": true,
   "license": "BSL-1.1",
   "description": "夭夭 Web:直连 Hermes 9119 的聊天、团队、文件库与产物工作台",

+ 3 - 3
release.json

@@ -1,6 +1,6 @@
 {
   "schemaVersion": 1,
-  "releaseVersion": "0.3.12",
-  "webVersion": "0.3.12",
-  "gitTag": "v0.3.12"
+  "releaseVersion": "0.3.13",
+  "webVersion": "0.3.13",
+  "gitTag": "v0.3.13"
 }

+ 10 - 1
src/client/views/ConversationsView.vue

@@ -71,7 +71,16 @@ const lightboxMedia = computed(() => media.value.filter(item => item.kind === 'i
 const reference = computed<ComposerReference | null>(() => quoted.value ? { id: quoted.value.id, content: quoted.value.content, author: quoted.value.author } : null)
 const avatarProfile = computed(() => ({ name: editingId.value || 'Agent', displayName: form.name || 'Agent', agentName: form.name || 'Agent', agentAvatar: form.avatar, isDefault: false, isRunning: true }))
 function openPreview(file: {name: string; url?: string; kind?: string}) {
-  const item = previewItemFromUrl(file.name, file.url || '')
+  let path = file.url || ''
+  try { path = decodeURIComponent(new URL(path, window.location.origin).pathname) } catch { /* preserve the supplied URL */ }
+  const attachment = messages.value.flatMap(message => message.attachments).find(candidate =>
+    path === candidate.sourcePath || path === `/api/app/files/${candidate.id}/download`
+      || path === `/api/app/files/${candidate.id}/preview`)
+  // A Markdown label such as “报告” has no extension. The archived file's name
+  // determines its viewer; the label is not reliable file-type metadata.
+  const mime = attachment?.mimeType.toLowerCase().split(';')[0] || ''
+  const textual = mime.startsWith('text/') || ['application/json', 'application/xml', 'application/yaml', 'application/x-yaml'].includes(mime)
+  const item = previewItemFromUrl(attachment?.name || file.name, file.url || '', undefined, textual ? 'text' : undefined)
   const index = lightboxMedia.value.findIndex(m => m.url === (item.previewUrl || item.downloadUrl))
   if (index >= 0) mediaIndex.value = index
   else preview.value = item

+ 5 - 0
tests/e2e/workspace-chat.spec.ts

@@ -82,6 +82,11 @@ test('created roles and editable teams share a durable chat list without plugin
     'href',
     /\/api\/app\/files\/.+\/download/,
   )
+  await page.locator('.message--assistant a').first().click()
+  const textPreview = page.getByRole('dialog', { name: '预览 workspace-report.txt', exact: true })
+  await expect(textPreview).toBeVisible()
+  await expect(textPreview).toContainText('Fixture report')
+  await textPreview.getByRole('button', { name: '关闭预览' }).click()
   await page.getByRole('button', {name:'置顶聊天',exact:true}).click()
   await expect(page.getByRole('button', {name:'取消置顶',exact:true})).toBeVisible()
   const directURL = page.url()