✨ feat(vscode): 重写注释留会话记录、能接着说,允许画原型图

每块的重写记录存进 .spx/comment-rewrites.json(和问答分开,改名、删文件跟着走),块下面同问答区:灰字历史、当前这一轮、输入框;输入框里的话接着这块最近一次重写的会话说,交回新正文照样写回,写回失败也记下原因。
重写跑在 claude --restricted 下:只放开读搜、OpenPencil 的 op、建 prototypes 目录、改草稿目录,写注释的插件单独加载;超时 15 分钟。
进行中的、出错被叫停留着的那一轮记在扩展端,切走标签页再切回来照样接上(问 AI 同一套)。
This commit is contained in:
cruldra committed 2026-10-12 13:45:37 +11:00
1 parent ae4e2160ef
commit c9f60f5b30
32 files changed
+1654 -860

No files matched your search

+53 -21
View File
@@ -2,13 +2,12 @@ import type { CSSProperties, ReactElement, ReactNode } from 'react'
import type { RewriteTool } from './BlockTools'
import type { DiffTree, FoldedRuns, PlacedRemoved } from './diffView'
import type { GridSlots } from './PropertyGrid'
import type { QaState } from './qa'
import type { ChatState, QaState } from './qa'
import type { PendingImage } from './qaImages'
import type { RewriteRun } from './rewriteRun'
import type { BlockMarks, BlockView } from './types'
import type { BlockMarks, BlockView, ProfileChoice } from './types'
import { ChevronDown, ChevronRight, ChevronsUpDown, EyeOff } from 'lucide-react'
import { Fragment, useState } from 'react'
import { AskThread, RewriteThread } from './AskThread'
import { AskThread, REWRITE_THREAD } from './AskThread'
import { BlockTools } from './BlockTools'
import { CodeEditor } from './CodeEditor'
import { DiffText } from './DiffText'
@@ -20,7 +19,7 @@ import { TextPreview } from './TextPreview'
import { tableId } from './viewState'
/**
* 块树里改代码的那部分:哪些块在编辑、点「编辑」、按注释规范重写。页面上的块树才有;定义卡片浮窗里的块树没有,
* 块树里改代码的那部分:哪些块在编辑、点「编辑」。页面上的块树才有;定义卡片浮窗里的块树没有,
* 卡片只读,不画右上角的按钮
*/
export interface EditTree {
@@ -30,11 +29,6 @@ export interface EditTree {
focusId: string | null
onFocused: () => void
onToggleEdit: (id: string) => void
/** 按注释规范重写的过程,按块的记号。只在这一页上 */
rewrites: ReadonlyMap<string, RewriteRun>
onRewriteComment: (anchor: string) => void
/** 叫停这块正在进行的重写,不写文件 */
onStopRewrite: (anchor: string) => void
}
/** 函数、类落在几棵调用树里。定义浮窗不传,跟问 AI 一样不画这排按钮。 */
@@ -64,6 +58,8 @@ export interface BlockTreeState {
onSetFolds: (folds: Record<string, boolean>) => void
/** 问 AI;文件不在工作区文件夹里(问答没地方存)时没有 */
qa?: QaTree
/** 按注释规范重写;同问 AI,文件不在工作区文件夹里时没有 */
rewrite?: RewriteTree
/** 代码差异:新增、修改的块和删掉的块;侧栏开着「未提交的更改」或审查工单、基准里有这个文件时才有 */
diff?: DiffTree
/** 「只看改动」开着时折起来的连着没变的块(见 foldUnchanged);onOpenRun 点开一段(按第一块的 id) */
@@ -71,6 +67,21 @@ export interface BlockTreeState {
onOpenRun: (id: string) => void
}
/** 重写注释的状态和动作,块按自己的记号(anchor)取;profile 下拉和问 AI 共用 */
export interface RewriteTree {
state: ChatState
profiles: ProfileChoice
/** 点了块右上角的按钮:按技能命令重写这块(开新会话),展开它的重写记录 */
onRewrite: (anchor: string) => void
/** 在输入框里说的修改意见,接着这块最近一次重写的会话说;images 是贴的图片 */
onAsk: (anchor: string, request: string, images: PendingImage[]) => void
onStop: (askId: string) => void
onDelete: (anchor: string, entryId: string) => void
onOpen: (anchor: string) => void
onClose: (anchor: string) => void
onSelectProfile: (id: string) => void
}
/** 问 AI 的状态和动作,块按自己的记号(anchor)取 */
export interface QaTree {
state: QaState
@@ -171,7 +182,7 @@ export function BlockLevel({ blocks, owner, tree, slotted = false }: { blocks: B
<BlockCard block={block} tree={tree} />
</ChangeRow>
{tree.qa && <BlockQa key={block.anchor} anchor={block.anchor} qa={tree.qa} />}
{tree.edit && <BlockRewrite anchor={block.anchor} edit={tree.edit} />}
{tree.rewrite && <BlockRewrite key={`rewrite ${block.anchor}`} anchor={block.anchor} rewrite={tree.rewrite} />}
{hasShownChildren(block, tree) && !tree.collapsed.has(block.id) && (
<BlockLevel blocks={block.children} owner={block} tree={tree} />
)}
@@ -203,11 +214,22 @@ function BlockQa({ anchor, qa }: { anchor: string, qa: QaTree }): ReactElement {
)
}
function BlockRewrite({ anchor, edit }: { anchor: string, edit: EditTree }): ReactElement | null {
const run = edit.rewrites.get(anchor)
if (!run)
return null
return <RewriteThread run={run} onStop={() => edit.onStopRewrite(anchor)} />
/** 块下面的重写记录,紧跟在问答区后面;样子和问答区一样,输入框里说的是对这段注释的修改意见 */
function BlockRewrite({ anchor, rewrite }: { anchor: string, rewrite: RewriteTree }): ReactElement | null {
return (
<AskThread
wording={REWRITE_THREAD}
history={historyOf(rewrite.state, anchor)}
asks={asksOf(rewrite.state, anchor)}
open={rewrite.state.open.includes(anchor)}
profiles={rewrite.profiles}
onSelectProfile={rewrite.onSelectProfile}
onAsk={(request, images) => rewrite.onAsk(anchor, request, images)}
onStop={rewrite.onStop}
onDelete={entryId => rewrite.onDelete(anchor, entryId)}
onClose={() => rewrite.onClose(anchor)}
/>
)
}
/**
@@ -248,13 +270,23 @@ function callTreeTool(block: BlockView, tree: BlockTreeState) {
}
}
function rewriteTool(block: BlockView, edit: EditTree | undefined): RewriteTool | undefined {
if (!edit || !block.commentTarget)
/**
* 这块的「重写注释」按钮:能写注释的块才有。重写过、记录收着时点了先展开记录(好接着说);展开着、或还没重写过时点了按技能命令重写。
* 展开着又正在写时不能再点。
*/
function rewriteTool(block: BlockView, tree: BlockTreeState): RewriteTool | undefined {
const { edit, rewrite } = tree
if (!edit || !rewrite || !block.commentTarget)
return undefined
const { anchor } = block
const open = rewrite.state.open.includes(anchor)
const asks = asksOf(rewrite.state, anchor)
const reopen = !open && (asks.length > 0 || historyOf(rewrite.state, anchor).length > 0)
return {
fresh: block.commentTarget.kind === 'insert',
rewriting: edit.rewrites.get(block.anchor)?.status === 'running',
onRewrite: () => edit.onRewriteComment(block.anchor),
rewriting: open && asks.some(ask => ask.status === 'running'),
reopen,
onRewrite: () => reopen ? rewrite.onOpen(anchor) : rewrite.onRewrite(anchor),
}
}
@@ -339,7 +371,7 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }):
onToggleEdit={() => edit.onToggleEdit(block.id)}
asking={tree.qa?.state.open.includes(block.anchor)}
onToggleAsk={tree.qa && (() => tree.qa?.onToggle(block.anchor))}
rewrite={rewriteTool(block, edit)}
rewrite={rewriteTool(block, tree)}
callTree={callTreeTool(block, tree)}
/>
)}