✨ feat(vscode): 代码阅读「了解一块代码」改成按路径列调用树:结果是几条路径(用户的一种操作一条),每条从入口一路调到后端服务、数据表,跨进程的写明怎么连过去(via),结构用扁平的 nodes 加 parent 连成树、不用递归;侧栏路径下面一层层展开节点,回答里用嵌套列表画树;回答和找的过程里的代码位置写成仓库链接,功能对话页上的链接按找代码用的工作区文件夹检查、打开、悬停;旧格式的结果当没有,栏上方提示打开对话重新找;删功能前先弹模态框确认,说清连带删掉调用树和问答记录,点「删除」才删

This commit is contained in:
cruldra committed 2026-10-14 21:28:40 +11:00
1 parent cc445e7123
commit c6de46ad37
17 files changed
+978 -253

No files matched your search

@@ -1,6 +1,9 @@
import { describe, expect, it } from 'vitest'
import { featureAnswer } from '../../../src/codeReader/features/featureResult'
import { codeLink } from '../../../src/codeReader/qa/linkRule'
import { renderMarkdown, renderMarkdownDiff, renderMarkdownOnce } from './markdown'
import { repoImageInner } from './repoImage'
import { parseRepoLink } from './repoLink'
describe('renderMarkdown', () => {
it('一行文字是一个段落;段内换行是软换行,空行分段', () => {
@@ -167,3 +170,42 @@ describe('问 AI 的回答里写成行内代码的代码位置', () => {
expect(renderMarkdown('见 `backend/app/routes.py:96-135`')).toBe('<p>见 <code>backend/app/routes.py:96-135</code></p>\n')
})
})
describe('「了解一块代码」的回答', () => {
/** 回答里各个链接的地址,按页面认仓库链接的办法拆开 */
const links = (html: string): unknown[] => [...html.matchAll(/<a href="([^"]*)">/g)].map(([, href]) => parseRepoLink(href.replaceAll('&amp;', '&')))
it('调用树画成嵌套列表,节点是仓库链接:路径带成对的括号(Next.js 的路由分组)也认成链接,via 是行内代码', () => {
const answer = featureAnswer({ paths: [{ name: '首页发出首句', nodes: [
{ id: '1', folder: 'web', path: 'src/app/(main)/chat/code/page.tsx', symbol: 'ChatCodePage', role: '会话页' },
{ id: '2', parent: '1', folder: 'server', path: 'app/routes.py', symbol: 'create_app', via: 'POST /apps', role: '建应用' },
{ id: '3', parent: '2', folder: 'server', path: 'app/models.py', role: '应用表' },
] }] }, ['web', 'server'])
const html = renderMarkdown(answer, 'answer')
expect(html).toBe([
'<p><strong>1. 首页发出首句</strong></p>',
'<ul>',
'<li><a href="web/src/app/(main)/chat/code/page.tsx#ChatCodePage">ChatCodePage</a>:会话页',
'<ul>',
'<li><code>POST /apps</code> → <a href="server/app/routes.py#create_app">create_app</a>:建应用',
'<ul>',
'<li><a href="server/app/models.py">models.py</a>:应用表</li>',
'</ul>',
'</li>',
'</ul>',
'</li>',
'</ul>',
'',
].join('\n'))
expect(links(html)).toEqual([
{ path: 'web/src/app/(main)/chat/code/page.tsx', symbol: 'ChatCodePage' },
{ path: 'server/app/routes.py', symbol: 'create_app' },
{ path: 'server/app/models.py' },
])
})
it('路径里有空白、百分号、不成对的括号:编码后照样认成链接,拆出来是原来的路径', () => {
const html = renderMarkdown([codeLink('a', 'docs/a b.md'), codeLink('b', 'docs/100%.md'), codeLink('c', 'docs/a(b.md', 'Doc')].join(' '), 'answer')
expect(links(html)).toEqual([{ path: 'docs/a b.md' }, { path: 'docs/100%.md' }, { path: 'docs/a(b.md', symbol: 'Doc' }])
})
})