feat: add structured agent tools and improve workspace chat UX
This commit is contained in:
@@ -25,7 +25,7 @@ function elements(root, predicate) {
|
||||
return [...(predicate(root) ? [root] : []), ...React.Children.toArray(root.props.children).flatMap(child => elements(child, predicate))]
|
||||
}
|
||||
|
||||
function setup(overrides = {}) {
|
||||
function setup(overrides = {}, browser = {}) {
|
||||
const state = []
|
||||
const effects = []
|
||||
let index = 0
|
||||
@@ -34,6 +34,7 @@ function setup(overrides = {}) {
|
||||
const panel = compile('../src/renderer/src/features/workspace/WorkspaceQaPanel.tsx', {
|
||||
'../../i18n': i18n,
|
||||
'../../components/AppStatusModals': { ConfirmStatusModal },
|
||||
'../../components/ProgressiveChatResponse': { ProgressiveChatResponse: () => null },
|
||||
'../../components/ChatWidgets': { QaInputBox },
|
||||
'react-dom': { createPortal: child => child },
|
||||
react: {
|
||||
@@ -45,7 +46,7 @@ function setup(overrides = {}) {
|
||||
return [state[slot], value => { state[slot] = value }]
|
||||
}
|
||||
}
|
||||
}, { document: { body: {} } }).default
|
||||
}, { document: { body: {} }, window: { getSelection: () => null, ...browser } }).default
|
||||
const props = {
|
||||
dark: true, workspaceActive: true, qaMode: 'ai', agentEnabled: true, visibleAiMessages: [{ id: '1', content: 'hello' }],
|
||||
visibleTeacherMessages: [], agentMessages: [], workspaceChatScrollRef: { current: null },
|
||||
@@ -82,13 +83,46 @@ test('context menu deletion also requests confirmation', () => {
|
||||
const harness = setup()
|
||||
const root = harness.render()
|
||||
const area = elements(root, node => Boolean(node.props.onContextMenu))[0]
|
||||
area.props.onContextMenu({ target: { closest: () => null }, preventDefault() {}, clientX: 10, clientY: 10 })
|
||||
area.props.onContextMenu({ currentTarget: { contains: () => true }, preventDefault() {}, stopPropagation() {}, clientX: 10, clientY: 10 })
|
||||
const clear = elements(harness.render(), node => node.type === 'button' && node.props.children === '대화내역 삭제')[0]
|
||||
clear.props.onClick()
|
||||
assert.equal(harness.cleared(), 0)
|
||||
assert.equal(elements(harness.render(), node => node.type === ConfirmStatusModal).length, 1)
|
||||
})
|
||||
|
||||
for (const [selected, inside] of [[false, true], [true, true], [true, false]]) {
|
||||
test(`native chat menu copies only a selection inside the chat (selected=${selected}, inside=${inside})`, async () => {
|
||||
let request
|
||||
const node = {}
|
||||
const harness = setup({}, {
|
||||
getSelection: () => selected ? { anchorNode: node, focusNode: node, toString: () => '선택한 답변' } : null,
|
||||
chatTextMenu: { show: async options => { request = options; return null } }
|
||||
})
|
||||
const area = elements(harness.render(), element => Boolean(element.props.onContextMenu))[0]
|
||||
area.props.onContextMenu({
|
||||
currentTarget: { contains: () => inside }, target: { closest: () => ({}) },
|
||||
preventDefault() {}, stopPropagation() {}, clientX: 10, clientY: 10
|
||||
})
|
||||
await Promise.resolve()
|
||||
assert.equal(request.kind, 'message')
|
||||
assert.equal(request.canCopy, selected && inside)
|
||||
assert.equal(request.canClear, true)
|
||||
assert.equal(harness.cleared(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
test('native menu deletion still goes through the shared confirmation dialog', async () => {
|
||||
const harness = setup({}, { chatTextMenu: { show: async () => 'clear' } })
|
||||
const area = elements(harness.render(), node => Boolean(node.props.onContextMenu))[0]
|
||||
area.props.onContextMenu({ currentTarget: { contains: () => true }, preventDefault() {}, stopPropagation() {} })
|
||||
await Promise.resolve()
|
||||
assert.equal(harness.cleared(), 0)
|
||||
const modal = elements(harness.render(), node => node.type === ConfirmStatusModal)[0]
|
||||
assert.ok(modal)
|
||||
modal.props.onConfirm()
|
||||
assert.equal(harness.cleared(), 1)
|
||||
})
|
||||
|
||||
for (const props of [{ inputBusy: true }, { qaMessagesLoading: true }, { visibleAiMessages: [] }]) {
|
||||
test(`does not open deletion confirmation when disabled: ${JSON.stringify(props)}`, () => {
|
||||
const harness = setup(props)
|
||||
|
||||
Reference in New Issue
Block a user