fix: open agent model picker above chat attachment controls
This commit is contained in:
@@ -9,7 +9,7 @@ function compile(path, overrides = {}) {
|
||||
const exports = {}
|
||||
const source = fs.readFileSync(require.resolve(path), 'utf8')
|
||||
vm.runInNewContext(ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX } }).outputText,
|
||||
{ exports, require: name => overrides[name] ?? require(name) })
|
||||
{ exports, document: { body: {} }, require: name => overrides[name] ?? require(name) })
|
||||
return exports
|
||||
}
|
||||
const models = compile('../src/renderer/src/features/workspace/agentModels.ts')
|
||||
@@ -37,24 +37,60 @@ function elements(root, predicate) {
|
||||
if (!React.isValidElement(root)) return []
|
||||
return [...(predicate(root) ? [root] : []), ...React.Children.toArray(root.props.children).flatMap(child => elements(child, predicate))]
|
||||
}
|
||||
const { AgentModelSelector } = compile('../src/renderer/src/components/AgentModelSelector.tsx', {
|
||||
'../i18n': { t: value => value }, '../features/workspace/agentModels': models,
|
||||
})
|
||||
function selectorHarness(props) {
|
||||
const state = [], refs = []
|
||||
let stateIndex = 0, refIndex = 0
|
||||
const { AgentModelSelector } = compile('../src/renderer/src/components/AgentModelSelector.tsx', {
|
||||
'../i18n': { t: value => value }, '../features/workspace/agentModels': models,
|
||||
'react-dom': { createPortal: child => child },
|
||||
react: { ...React, useId: () => 'model-menu', useEffect() {}, useLayoutEffect() {},
|
||||
useState(initial) { const index = stateIndex++; if (!(index in state)) state[index] = initial; return [state[index], value => { state[index] = typeof value === 'function' ? value(state[index]) : value }] },
|
||||
useRef(initial) { return refs[refIndex++] ?? (refs[refIndex - 1] = { current: initial }) }
|
||||
}
|
||||
})
|
||||
return { render(patch = {}) { stateIndex = 0; refIndex = 0; return AgentModelSelector({ ...props, ...patch }) } }
|
||||
}
|
||||
|
||||
for (const dark of [true, false]) test(`model control lists only allowed providers and follows the theme (${dark})`, () => {
|
||||
let selected
|
||||
const tree = AgentModelSelector({ models: options, provider: 'QWEN', dark, disabled: false, onChange: value => { selected = value } })
|
||||
const select = elements(tree, node => node.type === 'select')[0]
|
||||
assert.equal(select.props.value, 'QWEN')
|
||||
assert.equal(select.props.style.colorScheme, dark ? 'dark' : 'light')
|
||||
assert.deepEqual(elements(select, node => node.type === 'option').map(node => node.props.value), ['OPENAI', 'QWEN'])
|
||||
select.props.onChange({ target: { value: 'OPENAI' } })
|
||||
const h = selectorHarness({ models: options, provider: 'QWEN', dark, disabled: false, onChange: value => { selected = value } })
|
||||
const tree = h.render()
|
||||
const trigger = elements(tree, node => node.type === 'button')[0]
|
||||
assert.equal(trigger.props['aria-haspopup'], 'menu')
|
||||
assert.equal(trigger.props['aria-expanded'], false)
|
||||
assert.equal(trigger.props.title, 'Qwen · qwen3.8-flash')
|
||||
assert.equal(elements(tree, node => node.props.role === 'menu').length, 0)
|
||||
assert.equal(elements(tree, node => node.type === 'select').length, 0)
|
||||
trigger.props.onClick()
|
||||
const menu = elements(h.render(), node => node.props.role === 'menu')[0]
|
||||
assert.match(menu.props.className, dark ? /bg-\[#252526\]/ : /bg-white/)
|
||||
assert.match(menu.props.className, /fixed/)
|
||||
const items = elements(menu, node => node.props.role === 'menuitemradio')
|
||||
assert.deepEqual(items.map(node => node.props.title), ['OpenAI · gpt-6.1-sol', 'Qwen · qwen3.8-flash'])
|
||||
assert.deepEqual(items.map(node => node.props['aria-checked']), [false, true])
|
||||
assert.ok(items.every(node => elements(node, item => item.type === 'span' && item.props.className.includes('truncate')).length === 1))
|
||||
items[0].props.onClick()
|
||||
assert.equal(selected, 'OPENAI')
|
||||
assert.match(select.props.className, /truncate/)
|
||||
assert.ok(elements(AgentModelSelector({ models: options, provider: 'QWEN', dark, disabled: true, onChange() {} }), node => node.type === 'select')[0].props.disabled)
|
||||
const empty = elements(AgentModelSelector({ models: [], provider: null, dark, disabled: false, onChange() {} }), node => node.type === 'select')[0]
|
||||
assert.equal(elements(h.render(), node => node.props.role === 'menu').length, 0)
|
||||
assert.ok(elements(h.render({ disabled: true }), node => node.type === 'button')[0].props.disabled)
|
||||
const empty = elements(h.render({ models: [], provider: null }), node => node.type === 'button')[0]
|
||||
assert.ok(empty.props.disabled)
|
||||
assert.equal(elements(empty, node => node.type === 'option')[0].props.children, '사용 가능한 모델 없음')
|
||||
assert.equal(empty.props.title, '사용 가능한 모델 없음')
|
||||
})
|
||||
|
||||
test('model menu opens from arrow keys and closes when a request blocks the selector', () => {
|
||||
const h = selectorHarness({ models: options, provider: 'QWEN', dark: true, disabled: false, onChange() {} })
|
||||
const trigger = elements(h.render(), node => node.type === 'button')[0]
|
||||
trigger.props.onKeyDown({ key: 'ArrowUp', preventDefault() {} })
|
||||
assert.equal(elements(h.render(), node => node.props.role === 'menu').length, 1)
|
||||
assert.equal(elements(h.render({ disabled: true }), node => node.props.role === 'menu').length, 0)
|
||||
})
|
||||
|
||||
test('input footer places the model action before the attachment and clear controls', () => {
|
||||
const source = fs.readFileSync(require.resolve('../src/renderer/src/components/ChatWidgets.tsx'), 'utf8')
|
||||
const row = source.slice(source.indexOf('{(attachmentsEnabled || onClearConversation || footerAction)'))
|
||||
assert.ok(row.indexOf('{footerAction}') < row.indexOf('title={t(\'파일 첨부\')}'))
|
||||
assert.ok(row.indexOf('{footerAction}') < row.indexOf('title={t(\'대화내역 삭제\')}'))
|
||||
})
|
||||
|
||||
test('A-Copilot has a single managed label', () => {
|
||||
|
||||
Reference in New Issue
Block a user