✨ feat(vscode): 新增「了解一块代码」:AI 找功能代码占一栏,对话里接着改
主栏标题栏的放大镜问要了解哪一块,占一个栏位建一栏(和收藏共用 30 个栏位,视图改名 slot1..30,按 slotKindN 分收藏 / 功能挂标题栏按钮),同时开对话页让 Claude 只读地在全部工作区文件夹里找,按 --json-schema 交回环节 → 文件 + 符号 + 一句话。 对话复用预览顶部的聊天(CodeQa.askTop:会话续接、进行中的一轮、记录存 workspaceState);结构不合格算这一轮没答成、留着能再画出来。 条目每次画、点时按眼下的代码找:有语言插件的解析成代码块按「类名.方法名」找,没有的问 VS Code 文档符号;找不到画成灰的。
This commit is contained in:
1 parent
16d27004d8
commit
880bf7fc57
38 files changed
+3386
-378
No files matched your search
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 「了解一块代码」找过的功能:问的话、栏标题、占哪个栏位、AI 最近一次交回的结果,按工作区存着。
|
||||
* 这里是记录怎么读、怎么改,栏里一行、栏上方一句话怎么写。不碰 VS Code。
|
||||
*/
|
||||
|
||||
import type { FeatureItem, FeatureResult } from './featureResult'
|
||||
import { escapeMarkdown } from '../explorer/rowDecoration'
|
||||
import { freeSlot, SLOTS } from '../slots/slots'
|
||||
import { itemLabel, parseFeatureResult } from './featureResult'
|
||||
|
||||
/** 一个功能:question 是用户问的那句话(对话页标签上的名字,开新会话时发给 AI),name 是栏标题(开始时就是问题),result 是 AI 最近一次交回的结果(还没交回过时没有)。 */
|
||||
export interface Feature {
|
||||
id: string
|
||||
question: string
|
||||
name: string
|
||||
slot: number
|
||||
result?: FeatureResult
|
||||
}
|
||||
|
||||
/** 功能栏里找得到的条目行的 contextValue 带的标记(见 entryContext):行菜单只挂文件能用的那几样(问 AI、复制、在系统文件管理器中显示)。 */
|
||||
export const FEATURE_MARKER = 'feature'
|
||||
|
||||
/** 找不到的条目行的 contextValue:什么菜单都不挂。 */
|
||||
export const MISSING_CONTEXT = `${FEATURE_MARKER} missing`
|
||||
|
||||
/** 第 slot 个功能栏标题栏上的按钮命令:openChat 打开对话、rename 重命名、remove 删除。标题栏按钮不知道点的是哪一栏,所以每个栏位各一条。 */
|
||||
export function featureCommand(action: 'openChat' | 'rename' | 'remove', slot: number): string {
|
||||
const name = { openChat: 'openFeatureChat', rename: 'renameFeature', remove: 'removeFeature' }[action]
|
||||
return `superpowers.codeReader.${name}${slot}`
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
/** 读 workspaceState 里存的功能;认不出的丢掉,同一个栏位存了两个时只认第一个,结果读不懂的当还没有结果。 */
|
||||
export function parseFeatures(value: unknown): Feature[] {
|
||||
if (!Array.isArray(value))
|
||||
return []
|
||||
const features: Feature[] = []
|
||||
for (const raw of value) {
|
||||
if (!isRecord(raw))
|
||||
continue
|
||||
const { id, question, name, slot, result } = raw
|
||||
if (typeof id !== 'string' || typeof question !== 'string' || typeof name !== 'string' || typeof slot !== 'number' || !SLOTS.includes(slot))
|
||||
continue
|
||||
if (features.some(feature => feature.slot === slot))
|
||||
continue
|
||||
const parsed = result === undefined ? undefined : parseFeatureResult(result)
|
||||
features.push({ id, question, name, slot, ...(parsed && 'result' in parsed && { result: parsed.result }) })
|
||||
}
|
||||
return features
|
||||
}
|
||||
|
||||
/** 新功能占哪个栏位:和收藏共用,taken 是两者眼下占着的全部栏位;满了没有。 */
|
||||
export function placeFeature(taken: Iterable<number>): number | undefined {
|
||||
return freeSlot(taken)
|
||||
}
|
||||
|
||||
/** 第 slot 栏的功能改叫 name,栏位不动。 */
|
||||
export function renameFeature(features: readonly Feature[], slot: number, name: string): Feature[] {
|
||||
return features.map(feature => feature.slot === slot ? { ...feature, name } : feature)
|
||||
}
|
||||
|
||||
/** id 那个功能换成这次交回的结果。 */
|
||||
export function withResult(features: readonly Feature[], id: string, result: FeatureResult): Feature[] {
|
||||
return features.map(feature => feature.id === id ? { ...feature, result } : feature)
|
||||
}
|
||||
|
||||
/** 删掉第 slot 栏的功能,空出这个栏位。 */
|
||||
export function removeFeature(features: readonly Feature[], slot: number): Feature[] {
|
||||
return features.filter(feature => feature.slot !== slot)
|
||||
}
|
||||
|
||||
/**
|
||||
* 栏里一行条目怎么画:名字(符号名,只有文件的写文件名)、后面淡色的那一句话、悬停提示(文件、符号、那句话)。
|
||||
* found 是按眼下的代码找没找到;找不到的悬停先说「找不到了」。
|
||||
*/
|
||||
export function itemRow(item: FeatureItem, found: boolean): { label: string, description: string, tooltip: string } {
|
||||
const lines = [`**${escapeMarkdown(item.path)}**`, ...(item.symbol === undefined ? [] : [escapeMarkdown(item.symbol)]), escapeMarkdown(item.role)]
|
||||
return { label: itemLabel(item), description: item.role, tooltip: [...(found ? [] : ['找不到了']), ...lines].join(' \n') }
|
||||
}
|
||||
|
||||
/** 一个功能眼下在不在找:running 还在找(activity 是正在做的事),failed 没找成(error 是原因),stopped 被叫停了;没在找时没有。 */
|
||||
export type FeatureRun
|
||||
= | { status: 'running', activity?: string }
|
||||
| { status: 'failed', error: string }
|
||||
| { status: 'stopped' }
|
||||
|
||||
/** 句子没有句末标点时补个句号,后面好接下一句。 */
|
||||
function sentence(text: string): string {
|
||||
return /[。!?.!?]$/.test(text) ? text : `${text}。`
|
||||
}
|
||||
|
||||
/**
|
||||
* 栏上方的一句话(TreeView.message):正在找、没找成(可以在对话里接着说重试)、没找到相关的代码、还没有结果;
|
||||
* 有结果、又没在找时不说话。有旧结果时再找没成,旧结果留着。
|
||||
*/
|
||||
export function featureMessage(result: FeatureResult | undefined, run: FeatureRun | undefined): string | undefined {
|
||||
if (run?.status === 'running') {
|
||||
const doing = result ? '正在按你说的改…' : '正在找…'
|
||||
return run.activity ? `${doing}(${run.activity})` : doing
|
||||
}
|
||||
if (run?.status === 'failed') {
|
||||
return result
|
||||
? `这次没改成:${sentence(run.error)}下面还是上一次的结果,可以在对话里接着说,让它再改一次。`
|
||||
: `没找成:${sentence(run.error)}可以在对话里接着说,让它再找一次。`
|
||||
}
|
||||
if (!result)
|
||||
return '还没有结果,可以在对话里接着说,让它再找一次。'
|
||||
if (result.groups.length === 0)
|
||||
return '没找到和这个功能直接相关的代码,可以在对话里换个说法接着说。'
|
||||
return undefined
|
||||
}
|
||||
Reference in new issue
Block a user