/** * 预览顶部的问答:文件、文件夹按「工作区文件夹 + 相对路径」,「了解一块代码」的功能对话页按功能的 id,记在这个窗口的 workspaceState 里。 * 不写进按块存的 `.spx/code-qa.json`。 */ import type { QaEntry } from './qaMap' import * as path from 'node:path' import { normalizeKey } from '../pathMap' /** workspaceState 里这份表的键 */ export const PATH_QA_STATE_KEY = 'codeReader.pathQa' /** * 顶部这一轮的记号。块的记号是「种类 名字」,不会是这个, * 所以页面上按记号取问答时,顶部的不会挂到某一块下面。 */ export const PATH_ASK_ANCHOR = '\0path' /** 工作区文件夹 Uri → 那条路径上的问答(按问的先后) */ export type PathQaMap = Record /** 工作区根的相对路径是空字符串,跟任何一个文件或子目录都不同。 */ export function relativeQaKey(rootPath: string, targetPath: string): string | undefined { const relative = path.posix.relative(rootPath, targetPath) if (relative === '') return '' return normalizeKey(relative) } /** 同一工作区文件夹、同一相对路径才是同一条记录。 */ export function pathQaKey(folderUri: string, relativePath: string): string { return `${folderUri}\n${relativePath}` } /** 「了解一块代码」一个功能的对话记录的键。工作区文件夹的 Uri 带协议名(`file://`),和它撞不上。 */ export function featureQaKey(id: string): string { return `feature\n${id}` } /** 给人看的相对路径;工作区根写成 `.`。 */ export function displayRelativePath(relativePath: string): string { return relativePath === '' ? '.' : relativePath } export function pathQaEntries(map: PathQaMap, key: string): QaEntry[] { return map[key] ?? [] } /** 新的一次问答接在这条路径已有的后面,别的路径不动。 */ export function withPathQa(map: PathQaMap, key: string, entry: QaEntry): PathQaMap { return { ...map, [key]: [...pathQaEntries(map, key), entry] } } /** 删掉这一次;这条路径问光了就把键拿掉。没有这一次时原样返回。 */ export function withoutPathQa(map: PathQaMap, key: string, entryId: string): PathQaMap { const entries = pathQaEntries(map, key) const kept = entries.filter(entry => entry.id !== entryId) if (kept.length === entries.length) return map if (kept.length === 0) { const next = { ...map } delete next[key] return next } return { ...map, [key]: kept } } /** 去掉这个键下的全部问答;没有这个键时原样返回。 */ export function withoutPathQaKey(map: PathQaMap, key: string): PathQaMap { if (!(key in map)) return map const next = { ...map } delete next[key] return next } /** 读 workspaceState 里存的表;认不出的丢掉,不让一条坏数据把整份弄丢。 */ export function parsePathQa(value: unknown): PathQaMap { if (!isRecord(value)) return {} const map: PathQaMap = {} for (const [key, entries] of Object.entries(value)) { if (!Array.isArray(entries)) continue const parsed = entries.flatMap((entry) => { const one = parseEntry(entry) return one ? [one] : [] }) if (parsed.length > 0) map[key] = parsed } return map } function parseEntry(value: unknown): QaEntry | undefined { if (!isRecord(value)) return undefined const { id, question, answer, createdAt, sessionId, profile, images } = value if (typeof id !== 'string' || typeof question !== 'string' || typeof answer !== 'string' || typeof createdAt !== 'string' || typeof sessionId !== 'string') return undefined const entry: QaEntry = { id, question, answer, createdAt, sessionId } if (typeof profile === 'string' && profile !== '') entry.profile = profile if (Array.isArray(images) && images.every(name => typeof name === 'string')) entry.images = images return entry } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) }