feat(extension): 侧边栏接入思维导图(markmap)与 RAG 问答(P3 + P4)

任务完成(status === SUCCESS)后,侧边栏顶部出现 Markdown / 思维导图 / AI 问答 三个 tab:

- 思维导图:用 markmap-lib + markmap-view 把 markdown 转成可缩放思维导图
- AI 问答:
  · 进入 tab 自动调 /api/chat/index 触发后台索引,按 2s 间隔轮询 /api/chat/status
  · 索引完成后开放输入框;调 /api/chat/ask 时带上 settings 里的默认 provider/model + 完整 history
  · Cmd/Ctrl + Enter 发送
  · 回答用 markdown-it 渲染,user 气泡用纯文本
- 切换任务时清空对话历史并重新检查索引

logic/api.ts 补 indexChatTask / getChatStatus / askChat 三件套。

依赖新增:markmap-lib, markmap-view(生产依赖)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
huangjianwu
2026-05-07 12:02:12 +08:00
parent be5e1637fa
commit f37d2e95d1
6 changed files with 743 additions and 3 deletions

View File

@@ -70,6 +70,8 @@
"webextension-polyfill": "^0.12.0"
},
"dependencies": {
"markdown-it": "^14.1.0"
"markdown-it": "^14.1.0",
"markmap-lib": "^0.18.12",
"markmap-view": "^0.18.12"
}
}

View File

@@ -11,6 +11,12 @@ importers:
markdown-it:
specifier: ^14.1.0
version: 14.1.1
markmap-lib:
specifier: ^0.18.12
version: 0.18.12(markmap-common@0.18.9)
markmap-view:
specifier: ^0.18.12
version: 0.18.12(markmap-common@0.18.9)
devDependencies:
'@antfu/eslint-config':
specifier: ^2.27.0
@@ -685,6 +691,9 @@ packages:
resolution: {integrity: sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==}
engines: {node: '>=14.0.0', npm: '>=7.0.0'}
'@gera2ld/jsx-dom@2.2.2':
resolution: {integrity: sha512-EOqf31IATRE6zS1W1EoWmXZhGfLAoO9FIlwTtHduSrBdud4npYBxYAkv8dZ5hudDPwJeeSjn40kbCL4wAzr8dA==}
'@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'}
@@ -1179,6 +1188,9 @@ packages:
'@vitest/utils@2.0.5':
resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==}
'@vscode/markdown-it-katex@1.1.2':
resolution: {integrity: sha512-+4IIv5PgrmhKvW/3LpkpkGg257OViEhXkOOgCyj5KMsjsOfnRXkni8XAuuF9Ui5p3B8WnUovlDXAQNb8RJ/RaQ==}
'@vue/compiler-core@3.4.38':
resolution: {integrity: sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==}
@@ -1517,6 +1529,10 @@ packages:
cheerio-select@2.1.0:
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
cheerio@1.0.0:
resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==}
engines: {node: '>=18.17'}
cheerio@1.0.0-rc.12:
resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
engines: {node: '>= 6'}
@@ -1605,6 +1621,14 @@ packages:
resolution: {integrity: sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==}
engines: {node: '>= 0.6.x'}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
commander@8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
commander@9.5.0:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
@@ -1712,6 +1736,133 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
d3-array@3.2.4:
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
engines: {node: '>=12'}
d3-axis@3.0.0:
resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
engines: {node: '>=12'}
d3-brush@3.0.0:
resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
engines: {node: '>=12'}
d3-chord@3.0.1:
resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
engines: {node: '>=12'}
d3-color@3.1.0:
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
engines: {node: '>=12'}
d3-contour@4.0.2:
resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
engines: {node: '>=12'}
d3-delaunay@6.0.4:
resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
engines: {node: '>=12'}
d3-dispatch@3.0.1:
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
engines: {node: '>=12'}
d3-drag@3.0.0:
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
engines: {node: '>=12'}
d3-dsv@3.0.1:
resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
engines: {node: '>=12'}
hasBin: true
d3-ease@3.0.1:
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
engines: {node: '>=12'}
d3-fetch@3.0.1:
resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
engines: {node: '>=12'}
d3-force@3.0.0:
resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
engines: {node: '>=12'}
d3-format@3.1.2:
resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
engines: {node: '>=12'}
d3-geo@3.1.1:
resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
engines: {node: '>=12'}
d3-hierarchy@3.1.2:
resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
engines: {node: '>=12'}
d3-interpolate@3.0.1:
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
engines: {node: '>=12'}
d3-path@3.1.0:
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
engines: {node: '>=12'}
d3-polygon@3.0.1:
resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
engines: {node: '>=12'}
d3-quadtree@3.0.1:
resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
engines: {node: '>=12'}
d3-random@3.0.1:
resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
engines: {node: '>=12'}
d3-scale-chromatic@3.1.0:
resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
engines: {node: '>=12'}
d3-scale@4.0.2:
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
engines: {node: '>=12'}
d3-selection@3.0.0:
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
engines: {node: '>=12'}
d3-shape@3.2.0:
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
engines: {node: '>=12'}
d3-time-format@4.1.0:
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
engines: {node: '>=12'}
d3-time@3.1.0:
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
engines: {node: '>=12'}
d3-timer@3.0.1:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
d3-transition@3.0.1:
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
engines: {node: '>=12'}
peerDependencies:
d3-selection: 2 - 3
d3-zoom@3.0.0:
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
engines: {node: '>=12'}
d3@7.9.0:
resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
engines: {node: '>=12'}
data-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
@@ -1816,6 +1967,9 @@ packages:
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
delaunator@5.1.0:
resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -1875,6 +2029,9 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
encoding-sniffer@0.2.1:
resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@@ -2503,6 +2660,10 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
highlight.js@11.11.1:
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
engines: {node: '>=12.0.0'}
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
@@ -2513,6 +2674,9 @@ packages:
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
htmlparser2@9.1.0:
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
http-cache-semantics@4.1.1:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
@@ -2600,6 +2764,10 @@ packages:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
engines: {node: '>= 0.4'}
internmap@2.0.3:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
invert-kv@3.0.1:
resolution: {integrity: sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==}
engines: {node: '>=8'}
@@ -2903,6 +3071,10 @@ packages:
jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
katex@0.16.45:
resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==}
hasBin: true
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -3029,10 +3201,40 @@ packages:
resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==}
engines: {node: '>=6'}
markdown-it-ins@4.0.0:
resolution: {integrity: sha512-sWbjK2DprrkINE4oYDhHdCijGT+MIDhEupjSHLXe5UXeVr5qmVxs/nTUVtgi0Oh/qtF+QKV0tNWDhQBEPxiMew==}
markdown-it-mark@4.0.0:
resolution: {integrity: sha512-YLhzaOsU9THO/cal0lUjfMjrqSMPjjyjChYM7oyj4DnyaXEzA8gnW6cVJeyCrCVeyesrY2PlEdUYJSPFYL4Nkg==}
markdown-it-sub@2.0.0:
resolution: {integrity: sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA==}
markdown-it-sup@2.0.0:
resolution: {integrity: sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA==}
markdown-it@14.1.1:
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
hasBin: true
markmap-common@0.18.9:
resolution: {integrity: sha512-MV2HQO7IGIm3jWEJXSG8vmdpqf4WIDXcEyAEN52lrWR1qD53Zg5l81JwjXoZ2l0rY5mofKYqUFlmdM2fqTGMVg==}
markmap-html-parser@0.18.11:
resolution: {integrity: sha512-+kC5C4sCGntGUhGvTa5VIb5rtM75cSy/VCy3tzZoNAcn2qZGdgYvljN0WvjsOzrEzp+V6XKgwzO0u2TdzNAiOg==}
peerDependencies:
markmap-common: '*'
markmap-lib@0.18.12:
resolution: {integrity: sha512-WCA4OT+b71jYg0e4PS/6NRKqihod5OpPsvw1jEGHQwCtqQrY/yXXCeRyuL3axOS5cMy5pV8BSl4CwKfJU1LxJg==}
peerDependencies:
markmap-common: '*'
markmap-view@0.18.12:
resolution: {integrity: sha512-D8bzT1YwIC/8rkbwm6WzigVUrpOAGv7ioEGTi1Lj+Oo8gO5sAm6hhli27jvTgUcZ9TwBeIWZ+dSUP+AupYUGlQ==}
peerDependencies:
markmap-common: '*'
marky@1.2.5:
resolution: {integrity: sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==}
@@ -3241,6 +3443,9 @@ packages:
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
npm2url@0.2.4:
resolution: {integrity: sha512-arzGp/hQz0Ey+ZGhF64XVH7Xqwd+1Q/po5uGiBbzph8ebX6T0uvt3N7c1nBHQNsQVykQgHhqoRTX7JFcHecGuw==}
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
@@ -3376,6 +3581,9 @@ packages:
parse5-htmlparser2-tree-adapter@7.0.0:
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
parse5-parser-stream@7.1.2:
resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
@@ -3503,6 +3711,10 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
@@ -3690,6 +3902,9 @@ packages:
engines: {node: 20 || >=22}
hasBin: true
robust-predicates@3.0.3:
resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
rollup@4.21.0:
resolution: {integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -3708,6 +3923,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
rw@1.3.3:
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
safe-array-concat@1.1.2:
resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==}
engines: {node: '>=0.4'}
@@ -4184,6 +4402,10 @@ packages:
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
undici@6.25.0:
resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==}
engines: {node: '>=18.17'}
unimport@3.11.0:
resolution: {integrity: sha512-mPrvWwy+li8TLUeglC7CIREFAbeEMkJ8X2Bhxg4iLdh+HraxjFyxqWv8V+4lzekoGHChx9ofv1qGOfvHBJBl0A==}
@@ -4562,6 +4784,11 @@ packages:
engines: {node: '>= 14'}
hasBin: true
yaml@2.8.4:
resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==}
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -5108,6 +5335,10 @@ snapshots:
'@fluent/syntax@0.19.0': {}
'@gera2ld/jsx-dom@2.2.2':
dependencies:
'@babel/runtime': 7.24.7
'@humanwhocodes/config-array@0.11.14':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
@@ -5705,6 +5936,10 @@ snapshots:
loupe: 3.1.1
tinyrainbow: 1.2.0
'@vscode/markdown-it-katex@1.1.2':
dependencies:
katex: 0.16.45
'@vue/compiler-core@3.4.38':
dependencies:
'@babel/parser': 7.25.3
@@ -6125,6 +6360,20 @@ snapshots:
domhandler: 5.0.3
domutils: 3.1.0
cheerio@1.0.0:
dependencies:
cheerio-select: 2.1.0
dom-serializer: 2.0.0
domhandler: 5.0.3
domutils: 3.1.0
encoding-sniffer: 0.2.1
htmlparser2: 9.1.0
parse5: 7.1.2
parse5-htmlparser2-tree-adapter: 7.0.0
parse5-parser-stream: 7.1.2
undici: 6.25.0
whatwg-mimetype: 4.0.0
cheerio@1.0.0-rc.12:
dependencies:
cheerio-select: 2.1.0
@@ -6218,6 +6467,10 @@ snapshots:
dependencies:
graceful-readlink: 1.0.1
commander@7.2.0: {}
commander@8.3.0: {}
commander@9.5.0: {}
comment-parser@1.4.1: {}
@@ -6335,6 +6588,158 @@ snapshots:
csstype@3.1.3: {}
d3-array@3.2.4:
dependencies:
internmap: 2.0.3
d3-axis@3.0.0: {}
d3-brush@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-drag: 3.0.0
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-transition: 3.0.1(d3-selection@3.0.0)
d3-chord@3.0.1:
dependencies:
d3-path: 3.1.0
d3-color@3.1.0: {}
d3-contour@4.0.2:
dependencies:
d3-array: 3.2.4
d3-delaunay@6.0.4:
dependencies:
delaunator: 5.1.0
d3-dispatch@3.0.1: {}
d3-drag@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-selection: 3.0.0
d3-dsv@3.0.1:
dependencies:
commander: 7.2.0
iconv-lite: 0.6.3
rw: 1.3.3
d3-ease@3.0.1: {}
d3-fetch@3.0.1:
dependencies:
d3-dsv: 3.0.1
d3-force@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-quadtree: 3.0.1
d3-timer: 3.0.1
d3-format@3.1.2: {}
d3-geo@3.1.1:
dependencies:
d3-array: 3.2.4
d3-hierarchy@3.1.2: {}
d3-interpolate@3.0.1:
dependencies:
d3-color: 3.1.0
d3-path@3.1.0: {}
d3-polygon@3.0.1: {}
d3-quadtree@3.0.1: {}
d3-random@3.0.1: {}
d3-scale-chromatic@3.1.0:
dependencies:
d3-color: 3.1.0
d3-interpolate: 3.0.1
d3-scale@4.0.2:
dependencies:
d3-array: 3.2.4
d3-format: 3.1.2
d3-interpolate: 3.0.1
d3-time: 3.1.0
d3-time-format: 4.1.0
d3-selection@3.0.0: {}
d3-shape@3.2.0:
dependencies:
d3-path: 3.1.0
d3-time-format@4.1.0:
dependencies:
d3-time: 3.1.0
d3-time@3.1.0:
dependencies:
d3-array: 3.2.4
d3-timer@3.0.1: {}
d3-transition@3.0.1(d3-selection@3.0.0):
dependencies:
d3-color: 3.1.0
d3-dispatch: 3.0.1
d3-ease: 3.0.1
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-timer: 3.0.1
d3-zoom@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-drag: 3.0.0
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-transition: 3.0.1(d3-selection@3.0.0)
d3@7.9.0:
dependencies:
d3-array: 3.2.4
d3-axis: 3.0.0
d3-brush: 3.0.0
d3-chord: 3.0.1
d3-color: 3.1.0
d3-contour: 4.0.2
d3-delaunay: 6.0.4
d3-dispatch: 3.0.1
d3-drag: 3.0.0
d3-dsv: 3.0.1
d3-ease: 3.0.1
d3-fetch: 3.0.1
d3-force: 3.0.0
d3-format: 3.1.2
d3-geo: 3.1.1
d3-hierarchy: 3.1.2
d3-interpolate: 3.0.1
d3-path: 3.1.0
d3-polygon: 3.0.1
d3-quadtree: 3.0.1
d3-random: 3.0.1
d3-scale: 4.0.2
d3-scale-chromatic: 3.1.0
d3-selection: 3.0.0
d3-shape: 3.2.0
d3-time: 3.1.0
d3-time-format: 4.1.0
d3-timer: 3.0.1
d3-transition: 3.0.1(d3-selection@3.0.0)
d3-zoom: 3.0.0
data-uri-to-buffer@4.0.1: {}
data-urls@5.0.0:
@@ -6424,6 +6829,10 @@ snapshots:
defu@6.1.4: {}
delaunator@5.1.0:
dependencies:
robust-predicates: 3.0.3
delayed-stream@1.0.0: {}
destr@2.0.3: {}
@@ -6482,6 +6891,11 @@ snapshots:
emoji-regex@9.2.2: {}
encoding-sniffer@0.2.1:
dependencies:
iconv-lite: 0.6.3
whatwg-encoding: 3.1.1
end-of-stream@1.4.4:
dependencies:
once: 1.4.0
@@ -7371,6 +7785,8 @@ snapshots:
dependencies:
function-bind: 1.1.2
highlight.js@11.11.1: {}
hosted-git-info@2.8.9: {}
html-encoding-sniffer@4.0.0:
@@ -7384,6 +7800,13 @@ snapshots:
domutils: 3.1.0
entities: 4.5.0
htmlparser2@9.1.0:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.1.0
entities: 4.5.0
http-cache-semantics@4.1.1: {}
http-proxy-agent@7.0.2:
@@ -7468,6 +7891,8 @@ snapshots:
hasown: 2.0.2
side-channel: 1.0.6
internmap@2.0.3: {}
invert-kv@3.0.1: {}
is-absolute@0.1.7:
@@ -7737,6 +8162,10 @@ snapshots:
readable-stream: 2.3.8
setimmediate: 1.0.5
katex@0.16.45:
dependencies:
commander: 8.3.0
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -7877,6 +8306,14 @@ snapshots:
dependencies:
p-defer: 1.0.0
markdown-it-ins@4.0.0: {}
markdown-it-mark@4.0.0: {}
markdown-it-sub@2.0.0: {}
markdown-it-sup@2.0.0: {}
markdown-it@14.1.1:
dependencies:
argparse: 2.0.1
@@ -7886,6 +8323,41 @@ snapshots:
punycode.js: 2.3.1
uc.micro: 2.1.0
markmap-common@0.18.9:
dependencies:
'@babel/runtime': 7.24.7
'@gera2ld/jsx-dom': 2.2.2
npm2url: 0.2.4
markmap-html-parser@0.18.11(markmap-common@0.18.9):
dependencies:
'@babel/runtime': 7.24.7
cheerio: 1.0.0
markmap-common: 0.18.9
markmap-lib@0.18.12(markmap-common@0.18.9):
dependencies:
'@babel/runtime': 7.24.7
'@vscode/markdown-it-katex': 1.1.2
highlight.js: 11.11.1
katex: 0.16.45
markdown-it: 14.1.1
markdown-it-ins: 4.0.0
markdown-it-mark: 4.0.0
markdown-it-sub: 2.0.0
markdown-it-sup: 2.0.0
markmap-common: 0.18.9
markmap-html-parser: 0.18.11(markmap-common@0.18.9)
markmap-view: 0.18.12(markmap-common@0.18.9)
prismjs: 1.30.0
yaml: 2.8.4
markmap-view@0.18.12(markmap-common@0.18.9):
dependencies:
'@babel/runtime': 7.24.7
d3: 7.9.0
markmap-common: 0.18.9
marky@1.2.5: {}
mdast-util-from-markdown@0.8.5:
@@ -8089,6 +8561,8 @@ snapshots:
dependencies:
path-key: 4.0.0
npm2url@0.2.4: {}
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
@@ -8238,6 +8712,10 @@ snapshots:
domhandler: 5.0.3
parse5: 7.1.2
parse5-parser-stream@7.1.2:
dependencies:
parse5: 7.1.2
parse5@7.1.2:
dependencies:
entities: 4.5.0
@@ -8347,6 +8825,8 @@ snapshots:
prelude-ls@1.2.1: {}
prismjs@1.30.0: {}
process-nextick-args@2.0.1: {}
process-warning@3.0.0: {}
@@ -8532,6 +9012,8 @@ snapshots:
glob: 11.0.0
package-json-from-dist: 1.0.0
robust-predicates@3.0.3: {}
rollup@4.21.0:
dependencies:
'@types/estree': 1.0.5
@@ -8566,6 +9048,8 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
rw@1.3.3: {}
safe-array-concat@1.1.2:
dependencies:
call-bind: 1.0.7
@@ -9025,6 +9509,8 @@ snapshots:
undici-types@6.19.8: {}
undici@6.25.0: {}
unimport@3.11.0(rollup@4.21.0):
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.21.0)
@@ -9464,6 +9950,8 @@ snapshots:
yaml@2.5.0: {}
yaml@2.8.4: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:

View File

@@ -0,0 +1,156 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import MarkdownIt from 'markdown-it'
import { askChat, getChatStatus, indexChatTask, type ChatMessage } from '~/logic/api'
import { settings } from '~/logic/storage'
const props = defineProps<{ taskId: string }>()
const md = new MarkdownIt({ html: false, linkify: true, breaks: true })
const messages = ref<ChatMessage[]>([])
const draft = ref('')
const sending = ref(false)
const indexState = ref<'idle' | 'indexing' | 'indexed' | 'failed' | 'unknown'>('unknown')
const error = ref('')
const scrollEl = ref<HTMLElement | null>(null)
let pollTimer: ReturnType<typeof setTimeout> | null = null
const ready = computed(() => indexState.value === 'indexed')
const canSend = computed(() => ready.value && draft.value.trim().length > 0 && !sending.value && !!settings.value.providerId && !!settings.value.modelName)
async function pollIndex() {
try {
const res = await getChatStatus(props.taskId)
indexState.value = res.status
if (res.status === 'indexing')
pollTimer = setTimeout(pollIndex, 2000)
}
catch (e) {
error.value = (e as Error).message
indexState.value = 'failed'
}
}
async function ensureIndexed() {
error.value = ''
indexState.value = 'unknown'
try {
const status = await getChatStatus(props.taskId)
indexState.value = status.status
if (status.indexed)
return
indexState.value = 'indexing'
await indexChatTask(props.taskId)
pollIndex()
}
catch (e) {
error.value = (e as Error).message
indexState.value = 'failed'
}
}
async function send() {
if (!canSend.value)
return
const question = draft.value.trim()
draft.value = ''
messages.value.push({ role: 'user', content: question })
await scrollDown()
sending.value = true
try {
const res = await askChat({
task_id: props.taskId,
question,
history: messages.value.slice(0, -1),
provider_id: settings.value.providerId,
model_name: settings.value.modelName,
}) as { answer?: string, content?: string, message?: string } | string
const reply = typeof res === 'string'
? res
: (res.answer ?? res.content ?? res.message ?? JSON.stringify(res))
messages.value.push({ role: 'assistant', content: reply })
await scrollDown()
}
catch (e) {
messages.value.push({ role: 'assistant', content: `❌ 调用失败:${(e as Error).message}` })
}
finally {
sending.value = false
}
}
async function scrollDown() {
await nextTick()
if (scrollEl.value)
scrollEl.value.scrollTop = scrollEl.value.scrollHeight
}
watch(() => props.taskId, () => {
messages.value = []
if (pollTimer) {
clearTimeout(pollTimer)
pollTimer = null
}
ensureIndexed()
}, { immediate: false })
onMounted(ensureIndexed)
onUnmounted(() => {
if (pollTimer)
clearTimeout(pollTimer)
})
</script>
<template>
<div class="flex flex-col h-full bg-white">
<header class="px-2 py-1 text-xs border-b flex items-center gap-2">
<span v-if="indexState === 'indexed'" class="tag bg-green-100 text-green-700">已索引</span>
<span v-else-if="indexState === 'indexing'" class="tag bg-yellow-100 text-yellow-700">索引中</span>
<span v-else-if="indexState === 'failed'" class="tag bg-red-100 text-red-700">索引失败</span>
<span v-else class="tag bg-gray-100 text-gray-500">检查中</span>
<button class="ml-auto text-xs text-gray-500 hover:text-gray-800" @click="ensureIndexed">
重新索引
</button>
</header>
<div v-if="error" class="text-xs text-red-600 px-2 py-1">{{ error }}</div>
<div ref="scrollEl" class="flex-1 overflow-auto px-2 py-2 flex flex-col gap-2">
<div v-if="messages.length === 0 && ready" class="text-xs text-gray-400 italic">
基于这条笔记的全文 + 视频元信息提问例如这个视频的核心论点是什么
</div>
<div
v-for="(m, i) in messages"
:key="i"
class="text-sm"
>
<div
class="inline-block max-w-[90%] px-3 py-2 rounded"
:class="m.role === 'user'
? 'bg-blue-600 text-white ml-auto block'
: 'bg-gray-100 text-gray-800'"
>
<div v-if="m.role === 'assistant'" v-html="md.render(m.content)" class="prose prose-sm max-w-none" />
<div v-else class="whitespace-pre-wrap break-words">{{ m.content }}</div>
</div>
</div>
<div v-if="sending" class="text-xs text-gray-500 italic">思考中</div>
</div>
<footer class="border-t p-2 flex gap-2">
<textarea
v-model="draft"
class="input flex-1 resize-none"
rows="2"
:placeholder="ready ? '问点什么…Cmd/Ctrl + Enter 发送)' : '索引完成后才能问答'"
:disabled="!ready"
@keydown.enter.exact.meta.prevent="send"
@keydown.enter.exact.ctrl.prevent="send"
/>
<button class="btn-primary" :disabled="!canSend" @click="send">
{{ sending ? '' : '发送' }}
</button>
</footer>
</div>
</template>

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { Transformer } from 'markmap-lib'
import { Markmap } from 'markmap-view'
import { absolutizeMarkdownImages } from '~/logic/api'
const props = defineProps<{ markdown: string }>()
const svgRef = ref<SVGSVGElement | null>(null)
let mm: Markmap | null = null
const transformer = new Transformer()
function render() {
if (!svgRef.value)
return
const md = absolutizeMarkdownImages(props.markdown || '')
const { root } = transformer.transform(md)
if (!mm)
mm = Markmap.create(svgRef.value, undefined, root)
else
mm.setData(root).then(() => mm?.fit())
}
onMounted(render)
watch(() => props.markdown, render)
</script>
<template>
<div class="w-full h-full bg-white rounded border overflow-hidden">
<svg ref="svgRef" class="w-full h-full" />
</div>
</template>

View File

@@ -132,6 +132,36 @@ export async function downloadTranscriberModel(modelSize: WhisperModelSize, tran
})
}
// ---- RAG Chat ----
export interface ChatMessage {
role: 'user' | 'assistant' | 'system'
content: string
}
export async function indexChatTask(taskId: string): Promise<void> {
await request('/api/chat/index', {
method: 'POST',
body: JSON.stringify({ task_id: taskId }),
})
}
export async function getChatStatus(taskId: string): Promise<{ status: 'idle' | 'indexing' | 'indexed' | 'failed', indexed: boolean }> {
return request(`/api/chat/status?task_id=${encodeURIComponent(taskId)}`)
}
export async function askChat(payload: {
task_id: string
question: string
history: ChatMessage[]
provider_id: string
model_name: string
}): Promise<unknown> {
return request('/api/chat/ask', {
method: 'POST',
body: JSON.stringify(payload),
})
}
// ---- Monitor ----
export async function getDeployStatus(): Promise<DeployStatus> {
return request<DeployStatus>('/api/deploy_status')

View File

@@ -4,9 +4,12 @@ import { getTaskStatus } from '~/logic/api'
import { settings, settingsReady, tasks, tasksReady, upsertTask } from '~/logic/storage'
import type { TaskRecord } from '~/logic/types'
type ViewMode = 'markdown' | 'mindmap' | 'chat'
const activeTaskId = ref<string>('')
const activeTask = computed<TaskRecord | undefined>(() => tasks.value?.find(t => t.taskId === activeTaskId.value))
const errorMsg = ref('')
const viewMode = ref<ViewMode>('markdown')
let pollTimer: ReturnType<typeof setTimeout> | null = null
@@ -84,12 +87,41 @@ onUnmounted(() => {
{{ activeTask.videoUrl }}
</div>
<TaskProgress :status="activeTask.status" :message="activeTask.message" />
<div class="flex-1 overflow-auto">
<div v-if="activeTask.status === 'SUCCESS' && activeTask.result?.markdown" class="flex gap-1 text-xs">
<button
class="px-2 py-1 rounded"
:class="viewMode === 'markdown' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
@click="viewMode = 'markdown'"
>Markdown</button>
<button
class="px-2 py-1 rounded"
:class="viewMode === 'mindmap' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
@click="viewMode = 'mindmap'"
>思维导图</button>
<button
class="px-2 py-1 rounded"
:class="viewMode === 'chat' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
@click="viewMode = 'chat'"
>AI 问答</button>
</div>
<div class="flex-1 overflow-auto min-h-0">
<MarkdownView
v-if="activeTask.status === 'SUCCESS' && activeTask.result?.markdown"
v-if="activeTask.status === 'SUCCESS' && activeTask.result?.markdown && viewMode === 'markdown'"
:markdown="activeTask.result.markdown"
:title="activeTask.result.audio_meta?.title"
/>
<MindMap
v-else-if="activeTask.status === 'SUCCESS' && activeTask.result?.markdown && viewMode === 'mindmap'"
:markdown="activeTask.result.markdown"
class="h-full"
/>
<ChatPanel
v-else-if="activeTask.status === 'SUCCESS' && viewMode === 'chat'"
:task-id="activeTask.taskId"
class="h-full"
/>
</div>
</section>