✨ feat(vscode): 重写注释排成一队,整个扩展一次只跑一段
OpenPencil 同一时间只能开一个画板,两段同时画会互相掐断。所有预览、所有文件的重写(第一轮和接着说)排成一队,前一段写成、出错、被叫停后按先后自动开始下一段;问 AI 照旧最多同时 3 个。 排着的那一轮记在扩展端(rewriteCurrent 的 queued),块下面写「排队中」、能取消排队,切标签页、页面重新加载后照样显示。
This commit is contained in:
1 parent
b7c29c60e1
commit
aecc4d1ab5
15 files changed
+329
-114
No files matched your search
@@ -3,12 +3,12 @@ import type { ClipboardEvent, DragEvent, KeyboardEvent, ReactElement } from 'rea
|
||||
import type { Ask } from './qa'
|
||||
import type { PendingImage } from './qaImages'
|
||||
import type { ProfileChoice, QaEntryView } from './types'
|
||||
import { ChevronDown, ChevronRight, CircleStop, LoaderCircle, MessageSquareText, SendHorizontal, Sparkles, Trash2, TriangleAlert, X } from 'lucide-react'
|
||||
import { ChevronDown, ChevronRight, CircleStop, Hourglass, LoaderCircle, MessageSquareText, SendHorizontal, Sparkles, Trash2, TriangleAlert, X } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { SelectMenu } from '@/components/ui/select-menu'
|
||||
import { renderMarkdown, renderMarkdownOnce } from './markdown'
|
||||
import { answerPreview, relativeTime } from './qa'
|
||||
import { answerPreview, relativeTime, unfinished } from './qa'
|
||||
import { MAX_IMAGES, pickImages, readImage, transferFiles } from './qaImages'
|
||||
|
||||
/** 一种对话在界面上怎么称呼:区域的名字、图标、「问过 N 次」、删一条、输入框的提示和名字、回答长出来时的状态 */
|
||||
@@ -140,7 +140,7 @@ function HistoryItem({ entry, wording, now, onDelete, onOpenImage }: { entry: Qa
|
||||
)
|
||||
}
|
||||
|
||||
/** 当前这一轮说的一次,照常颜色:说的话,回答逐字长出来(按 Markdown 排),在做的事、出错(重写注释没写进文件也算)、停止写在下面 */
|
||||
/** 当前这一轮说的一次,照常颜色:说的话,回答逐字长出来(按 Markdown 排),排着队、在做的事、出错(重写注释没写进文件也算)、停止写在下面 */
|
||||
function CurrentItem({ ask, wording, onOpenImage }: { ask: Ask, wording: ThreadWording, onOpenImage: (url: string) => void }): ReactElement {
|
||||
const running = ask.status === 'running'
|
||||
const label = ask.activity ?? (ask.text === '' ? '正在想…' : wording.answering)
|
||||
@@ -165,7 +165,13 @@ function CurrentItem({ ask, wording, onOpenImage }: { ask: Ask, wording: ThreadW
|
||||
{label}
|
||||
</div>
|
||||
)}
|
||||
{ask.error !== undefined && ask.status !== 'running' && (
|
||||
{ask.status === 'queued' && (
|
||||
<div className="cr-qa-status" role="status">
|
||||
<Hourglass size={12} strokeWidth={1.8} aria-hidden />
|
||||
排队中,前面的写完就开始
|
||||
</div>
|
||||
)}
|
||||
{ask.error !== undefined && !unfinished(ask) && (
|
||||
<div className="cr-qa-error" role="alert">
|
||||
<TriangleAlert size={13} strokeWidth={1.8} className="mt-[3px] shrink-0" aria-hidden />
|
||||
<span>{ask.error}</span>
|
||||
@@ -176,9 +182,10 @@ function CurrentItem({ ask, wording, onOpenImage }: { ask: Ask, wording: ThreadW
|
||||
)
|
||||
}
|
||||
|
||||
/** 底下的输入框:Enter 发送、Shift+Enter 换行、Esc 收起;正在答时发送换成停止。能贴图、拖图,贴的图在输入框上面一排,随下一个问题发 */
|
||||
function AskInput({ running, placeholder, label, profiles, onSelectProfile, onAsk, onStop, onClose, closable, onOpenImage, autoFocus, focusToken }: {
|
||||
running: Ask | undefined
|
||||
/** 底下的输入框:Enter 发送、Shift+Enter 换行、Esc 收起;正在答时发送换成停止,排着队时换成取消排队。能贴图、拖图,贴的图在输入框上面一排,随下一个问题发 */
|
||||
function AskInput({ active, placeholder, label, profiles, onSelectProfile, onAsk, onStop, onClose, closable, onOpenImage, autoFocus, focusToken }: {
|
||||
/** 正在答或排着队的那一轮 */
|
||||
active: Ask | undefined
|
||||
/** 输入框里的提示(后面接着怎么发送、收起) */
|
||||
placeholder: string
|
||||
/** 输入框读屏用的名字 */
|
||||
@@ -197,6 +204,7 @@ function AskInput({ running, placeholder, label, profiles, onSelectProfile, onAs
|
||||
focusToken: number
|
||||
}): ReactElement {
|
||||
const [value, setValue] = useState('')
|
||||
const stopLabel = active?.status === 'queued' ? '取消排队' : '停止'
|
||||
const [images, setImages] = useState<PendingImage[]>([])
|
||||
/** 贴的、拖的有没加上的:给人看的原因,下次贴图或发送时清掉 */
|
||||
const [notice, setNotice] = useState<string>()
|
||||
@@ -221,7 +229,7 @@ function AskInput({ running, placeholder, label, profiles, onSelectProfile, onAs
|
||||
}
|
||||
const send = (): void => {
|
||||
const question = value.trim()
|
||||
if (question === '' || running)
|
||||
if (question === '' || active)
|
||||
return
|
||||
onAsk(question, images)
|
||||
setValue('')
|
||||
@@ -282,9 +290,9 @@ function AskInput({ running, placeholder, label, profiles, onSelectProfile, onAs
|
||||
tooltip="用哪个 Claude Code 配置(profile)回答,下一个问题起用它"
|
||||
className="cr-qa-profile"
|
||||
/>
|
||||
{running
|
||||
{active
|
||||
? (
|
||||
<button type="button" className="cr-tool" data-tooltip="停止" aria-label="停止" onClick={() => onStop(running.askId)}>
|
||||
<button type="button" className="cr-tool" data-tooltip={stopLabel} aria-label={stopLabel} onClick={() => onStop(active.askId)}>
|
||||
<CircleStop size={14} strokeWidth={1.8} aria-hidden />
|
||||
</button>
|
||||
)
|
||||
@@ -357,7 +365,7 @@ export function AskThread({ history, asks, open, profiles, hint = '这块代码'
|
||||
{history.length > 0 && asks.length > 0 && <hr className="cr-qa-divider" />}
|
||||
{asks.map(ask => <CurrentItem key={ask.askId} ask={ask} wording={wording} onOpenImage={setPreview} />)}
|
||||
<AskInput
|
||||
running={asks.find(ask => ask.status === 'running')}
|
||||
active={asks.find(unfinished)}
|
||||
placeholder={wording.placeholder(history.length === 0 && asks.length === 0, hint)}
|
||||
label={wording.inputLabel}
|
||||
profiles={profiles}
|
||||
|
||||
Reference in new issue
Block a user