feat(chat): add agent attachments and shared conversation confirmation

This commit is contained in:
2026-10-16 11:14:01 +09:00
parent ef260c8fb4
commit 4bdc5af620
13 changed files with 715 additions and 88 deletions
+67
View File
@@ -0,0 +1,67 @@
const { test } = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const vm = require('node:vm')
const ts = require('typescript')
const React = require('react')
const { renderToStaticMarkup } = require('react-dom/server')
const source = fs.readFileSync(require.resolve('../src/renderer/src/components/AppStatusModals.tsx'), 'utf8')
const compiled = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX }
}).outputText
function loadModals() {
const exports = {}
vm.runInNewContext(compiled, {
exports,
require: name => name === '../i18n' ? { t: key => key, useLocaleRender() {} } : require(name)
})
return exports
}
function buttons(element) {
if (!React.isValidElement(element)) return []
if (element.type === 'button') return [element]
return React.Children.toArray(element.props.children).flatMap(buttons)
}
for (const dark of [false, true]) {
test(`extension action stays on the right in both dialogs (${dark ? 'dark' : 'light'})`, () => {
const { StatusNoticeModal, ControlExtensionConfirmModal } = loadModals()
let confirmed = 0
let extended = 0
const notice = StatusNoticeModal({
overlayClassName: 'overlay', dark, tone: 'clock', title: '제어 시간 안내', message: '제어 시간이 곧 종료됩니다.',
actionLabel: '연장', onAction: () => { extended++ }, onConfirm: () => { confirmed++ }
})
const noticeButtons = buttons(notice)
assert.deepEqual(noticeButtons.map(button => button.props.children), ['확인', '연장'])
assert.match(noticeButtons[1].props.className, /bg-\[#6f4df6\]/)
assert.doesNotMatch(noticeButtons[0].props.className, /bg-\[#6f4df6\]/)
noticeButtons[0].props.onClick()
noticeButtons[1].props.onClick()
assert.equal(confirmed, 1)
assert.equal(extended, 1)
const confirmation = ControlExtensionConfirmModal({ overlayClassName: 'overlay', dark, platformName: 'MK-mini', onCancel() {}, onConfirm() {} })
assert.deepEqual(buttons(confirmation).map(button => button.props.children), ['취소', '연장'])
assert.match(buttons(confirmation)[1].props.className, /bg-\[#6f4df6\]/)
assert.match(renderToStaticMarkup(confirmation), /취소<\/button><button[^>]*>연장<\/button>/)
})
}
test('notices without an extension action retain their single primary confirmation button', () => {
const { StatusNoticeModal } = loadModals()
const notice = StatusNoticeModal({ overlayClassName: 'overlay', dark: true, tone: 'warning', title: '안내', message: '메시지', onConfirm() {} })
assert.equal(buttons(notice).length, 1)
assert.equal(buttons(notice)[0].props.children, '확인')
assert.match(buttons(notice)[0].props.className, /bg-\[#6f4df6\]/)
})
test('the extension confirmation keeps the right button disabled while submitting', () => {
const { ControlExtensionConfirmModal } = loadModals()
const confirmation = ControlExtensionConfirmModal({ overlayClassName: 'overlay', dark: true, platformName: 'MK-mini', submitting: true, onCancel() {}, onConfirm() {} })
assert.deepEqual(buttons(confirmation).map(button => button.props.children), ['취소', '연장 중'])
assert.ok(buttons(confirmation).every(button => button.props.disabled))
})