80 lines
3.4 KiB
TypeScript
80 lines
3.4 KiB
TypeScript
import type { RewriteTree } from './BlockTree'
|
|
import type { ChatState } from './qa'
|
|
import { useEffect, useMemo, useReducer, useRef } from 'react'
|
|
import { chatReducer, EMPTY_CHAT, newAskId, unfinished } from './qa'
|
|
import { toAskImage } from './qaImages'
|
|
import { ANSWER_FRAME_MS } from './useQa'
|
|
import { onMessage, postMessage } from './vscode'
|
|
|
|
/**
|
|
* 按注释规范重写:收扩展端的重写记录、当前一轮的进展,给块树用的状态和动作。和问 AI 同一套:
|
|
* 排着队的、进行中的、出错被叫停留着的那一轮记在扩展端,页面重新加载后随 qa 消息的 rewriteCurrent 接上。profile 下拉是自己的,和问 AI 的互不影响。
|
|
*/
|
|
export function useRewrite(): RewriteTree {
|
|
const [state, dispatch] = useReducer(chatReducer<ChatState>, EMPTY_CHAT)
|
|
|
|
useEffect(() => {
|
|
/** 攒着没画的进展:askId → 最新的一份 */
|
|
const pending = new Map<string, { question: string, text: string, activity?: string, queued?: boolean }>()
|
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
const flush = (): void => {
|
|
timer = undefined
|
|
for (const [askId, progress] of pending)
|
|
dispatch({ type: 'answer', askId, ...progress })
|
|
pending.clear()
|
|
}
|
|
const off = onMessage((msg) => {
|
|
switch (msg.type) {
|
|
case 'qa':
|
|
dispatch({ type: 'sync', available: msg.available, threads: msg.rewrites, current: msg.rewriteCurrent })
|
|
return
|
|
case 'profiles':
|
|
if (msg.kind === 'rewrite')
|
|
dispatch({ type: 'profiles', profiles: { options: msg.options, selected: msg.selected } })
|
|
return
|
|
case 'rewriteProgress':
|
|
pending.set(msg.askId, { question: msg.question, text: msg.text, ...(msg.activity !== undefined && { activity: msg.activity }), ...(msg.queued && { queued: true }) })
|
|
timer ??= setTimeout(flush, ANSWER_FRAME_MS)
|
|
return
|
|
case 'rewriteEnd':
|
|
clearTimeout(timer)
|
|
flush()
|
|
dispatch({ type: 'answerEnd', end: msg })
|
|
}
|
|
})
|
|
return () => {
|
|
clearTimeout(timer)
|
|
off()
|
|
}
|
|
}, [])
|
|
|
|
/** 动作里读眼下的状态(这块是不是已经在写或排着队) */
|
|
const current = useRef(state)
|
|
current.current = state
|
|
|
|
return useMemo((): RewriteTree => ({
|
|
state,
|
|
onSelectProfile: id => postMessage({ type: 'selectProfile', kind: 'rewrite', id }),
|
|
onRewrite: (anchor) => {
|
|
if (current.current.asks.some(ask => ask.anchor === anchor && unfinished(ask)))
|
|
return
|
|
const askId = newAskId()
|
|
// 那句技能命令由扩展端拼好,开始后随 rewriteProgress 报上来
|
|
dispatch({ type: 'ask', askId, anchor, question: '', images: [] })
|
|
postMessage({ type: 'rewriteComment', askId, anchor, images: [] })
|
|
},
|
|
onAsk: (anchor, request, images) => {
|
|
const askId = newAskId()
|
|
dispatch({ type: 'ask', askId, anchor, question: request, images: images.map(image => image.url) })
|
|
postMessage({ type: 'rewriteComment', askId, anchor, request, images: images.map(toAskImage) })
|
|
},
|
|
onStop: askId => postMessage({ type: 'stopRewrite', askId }),
|
|
onDelete: (anchor, entryId) => postMessage({ type: 'deleteRewrite', anchor, entryId }),
|
|
onOpen: anchor => dispatch({ type: 'open', anchor }),
|
|
onClose: (anchor) => {
|
|
dispatch({ type: 'close', anchor })
|
|
postMessage({ type: 'forgetRound', kind: 'rewrite', anchor })
|
|
},
|
|
}), [state])
|
|
}
|