feat: support multiple camera previews and concurrent agent code navigation
This commit is contained in:
@@ -98,6 +98,51 @@ test('legacy cancelled references can be opened with the same read-only navigati
|
||||
assert.equal(h.message().codeReferenceStates.location.status, 'opened')
|
||||
})
|
||||
|
||||
test('code navigation does not block or abort a pending model response', async () => {
|
||||
const h = consentHarness()
|
||||
const controller = new AbortController()
|
||||
h.globals.abortControllerRef.current = controller
|
||||
let opened = 0
|
||||
await h.openCodeReference('answer', 'location', async (_reference, isCurrent) => {
|
||||
assert.equal(isCurrent(), true)
|
||||
opened++
|
||||
})
|
||||
assert.equal(opened, 1)
|
||||
assert.equal(controller.signal.aborted, false)
|
||||
assert.equal(h.globals.abortControllerRef.current, controller)
|
||||
assert.equal(h.message().codeReferenceStates.location.status, 'opened')
|
||||
})
|
||||
|
||||
test('an arriving model response does not cancel an in-flight code navigation', async () => {
|
||||
const h = consentHarness()
|
||||
h.globals.abortControllerRef.current = new AbortController()
|
||||
let finish, current
|
||||
const pending = h.openCodeReference('answer', 'location', (_reference, isCurrent) => {
|
||||
current = isCurrent
|
||||
return new Promise(resolve => { finish = resolve })
|
||||
})
|
||||
h.globals.messageStateRef.current.messages.push({ id: 'new-answer', role: 'assistant' })
|
||||
h.globals.abortControllerRef.current = null
|
||||
assert.equal(current(), true)
|
||||
finish()
|
||||
await pending
|
||||
assert.equal(h.globals.messageStateRef.current.messages[0].codeReferenceStates.location.status, 'opened')
|
||||
})
|
||||
|
||||
test('starting file application invalidates a navigation that is still reading', async () => {
|
||||
const h = consentHarness()
|
||||
let finish, current
|
||||
const pending = h.openCodeReference('answer', 'location', (_reference, isCurrent) => {
|
||||
current = isCurrent
|
||||
return new Promise((_resolve, reject) => { finish = () => reject(Error('applying')) })
|
||||
})
|
||||
h.globals.applyingRef.current = true
|
||||
assert.equal(current(), false)
|
||||
finish()
|
||||
await pending
|
||||
assert.equal(h.message().codeReferenceStates.location.status, 'error')
|
||||
})
|
||||
|
||||
test('duplicate clicks, invalid IDs and busy tasks do not open more files', async () => {
|
||||
const h = consentHarness()
|
||||
let finish, opened = 0
|
||||
|
||||
@@ -22,6 +22,7 @@ const panelSource = ts.createSourceFile('WorkspaceQaPanel.tsx', fs.readFileSync(
|
||||
const declarations = panelSource.statements.filter(node => ts.isFunctionDeclaration(node) && ['AiMessages', 'AgentMessages', 'TeacherMessages'].includes(node.name?.text))
|
||||
const ProgressiveChatResponse = props => React.createElement('div', { 'data-reveal-start': props.message.revealStartedAt, 'data-collapsed': props.defaultCollapsed }, props.message.content)
|
||||
const ChatImageAttachments = props => React.createElement('div', { 'data-chat-image-attachments': props.variant }, props.images.map(image => React.createElement('img', { key: image.url, src: image.url, alt: image.name })))
|
||||
const AgentCodeReferences = () => null
|
||||
const { AgentWorkDuration } = compile(fs.readFileSync(require.resolve('../src/renderer/src/components/AgentWorkDuration.tsx'), 'utf8'), {
|
||||
'../i18n': { t: (key, values = {}) => key.replace(/\{\{(\w+)\}\}/g, (_, name) => values[name] ?? ''), useLocaleRender() {} }
|
||||
})
|
||||
@@ -36,6 +37,7 @@ const messages = compile(declarations.map(node => `export ${node.getText(panelSo
|
||||
ChatEmptyState: widgets.ChatEmptyState,
|
||||
ProgressiveChatResponse,
|
||||
ChatImageAttachments,
|
||||
AgentCodeReferences,
|
||||
AgentProgressBubble,
|
||||
AgentWorkDuration,
|
||||
MessageCopyButton: () => null,
|
||||
@@ -43,6 +45,27 @@ const messages = compile(declarations.map(node => `export ${node.getText(panelSo
|
||||
Bot: () => React.createElement('svg'), WandSparkles: () => React.createElement('svg')
|
||||
})
|
||||
|
||||
test('existing code locations stay usable during model and command work but not file application', () => {
|
||||
for (const applying of [false, true]) {
|
||||
const onOpen = () => {}
|
||||
const tree = messages.AgentMessages({
|
||||
dark: true, enabled: true, statusLoading: false, pending: true, busy: true,
|
||||
messages: [{ id: 'answer', role: 'assistant', content: '코드 위치', applyState: applying ? 'applying' : undefined, plan: { codeReferences: [{ id: 'code-1' }], changes: [], commands: [], actionPlan: [], createModule: null } }],
|
||||
onOpenCodeReference: onOpen
|
||||
})
|
||||
let references
|
||||
function visit(element) {
|
||||
if (!React.isValidElement(element)) return
|
||||
if (element.type === AgentCodeReferences) references = element
|
||||
React.Children.toArray(element.props.children).forEach(visit)
|
||||
}
|
||||
visit(tree)
|
||||
assert.ok(references)
|
||||
assert.equal(references.props.busy, applying)
|
||||
assert.equal(typeof references.props.onOpen, 'function')
|
||||
}
|
||||
})
|
||||
|
||||
test('agent image thumbnails are siblings below the user bubble rather than bubble contents', () => {
|
||||
const tree = messages.AgentMessages({
|
||||
dark: true, enabled: true, statusLoading: false, pending: false,
|
||||
|
||||
Reference in New Issue
Block a user