fix: localize simulation description and display A-Copilot in v1.2.18

This commit is contained in:
2026-10-19 14:13:39 +09:00
parent 6512f5fa04
commit 234c9ad467
7 changed files with 53 additions and 6 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "lms", "name": "lms",
"version": "1.2.17", "version": "1.2.18",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "lms", "name": "lms",
"version": "1.2.17", "version": "1.2.18",
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "lms", "name": "lms",
"version": "1.2.17", "version": "1.2.18",
"description": "Autonomy Studio LMS Electron app", "description": "Autonomy Studio LMS Electron app",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": { "author": {
+9
View File
@@ -92,6 +92,15 @@ function renderResponse(message, now, reducedMotion = false, extra = {}) {
return renderToStaticMarkup(ProgressiveChatResponse({ message, dark: true, children: React.createElement('button', null, '승인'), ...extra })) return renderToStaticMarkup(ProgressiveChatResponse({ message, dark: true, children: React.createElement('button', null, '승인'), ...extra }))
} }
test('managed agent responses display A-Copilot without an internal provider ID or duplicate model name', () => {
const html = renderResponse({ content: '분석 결과', provider: 'A_COPILOT', model: 'A-Copilot' }, 0)
assert.ok(html.includes('A-Copilot'))
assert.ok(!html.includes('A_COPILOT'))
assert.ok(!html.includes('A-Copilot · A-Copilot'))
assert.equal((html.match(/>A-Copilot<\/p>/g) ?? []).length, 1)
assert.ok(renderResponse({ content: '결과', provider: 'QWEN', model: 'qwen3.8-max' }, 0).includes('QWEN · qwen3.8-max'))
})
test('approval controls wait for the explanation; restored history does not replay', () => { test('approval controls wait for the explanation; restored history does not replay', () => {
const message = { role: 'assistant', revealStartedAt: 1000, plan: { summary: '조향 설명', explanation: content }, provider: 'QWEN', model: 'test' } const message = { role: 'assistant', revealStartedAt: 1000, plan: { summary: '조향 설명', explanation: content }, provider: 'QWEN', model: 'test' }
const first = renderResponse(message, 1000) const first = renderResponse(message, 1000)
+33
View File
@@ -9,6 +9,39 @@ const { createInstance } = require('i18next')
const root = path.resolve(__dirname, '../src/renderer/src') const root = path.resolve(__dirname, '../src/renderer/src')
const dictionary = JSON.parse(fs.readFileSync(path.join(root, 'i18n/en.json'), 'utf8')) const dictionary = JSON.parse(fs.readFileSync(path.join(root, 'i18n/en.json'), 'utf8'))
test('only the default simulation description follows language; vehicle and custom text stay unchanged', () => {
const { api } = setup('ko')
const exports = {}
const source = fs.readFileSync(path.join(root, 'features/platforms/PlatformCards.tsx'), 'utf8')
const code = ts.transpileModule(source, { compilerOptions: {
module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022,
jsx: ts.JsxEmit.ReactJSX, esModuleInterop: true
} }).outputText
vm.runInNewContext(code, { exports, require: name => name === '../../i18n'
? { ...api, useLocaleRender() {} } : require(name) })
const { createElement } = require('react')
const { renderToStaticMarkup } = require('react-dom/server')
const description = '내 PC에서 직접 개발 환경을 실행합니다.'
const platform = { id: 'local', platformKey: 'futuredrive-local-pc', title: '로컬 PC', description, state: 'ready' }
const render = value => renderToStaticMarkup(createElement(exports.PlatformOptionCard, {
platform: value, Icon: () => null, dotClassName: '', unavailable: false,
statusPending: false, displayStatus: '사용 가능', primaryAction: '연결',
actionDisabled: false, running: false, onAction() {}, onSettings() {}
}))
assert.ok(render(platform).includes(description))
api.setLanguage('en')
assert.ok(render(platform).includes('Run the development environment directly on this PC.'))
assert.ok(!render(platform).includes(description))
const vehicleDescription = '기초 SLAM 및 자율주행 실습 장비입니다.'
assert.ok(render({ ...platform, platformKey: 'mk-mini', description: vehicleDescription }).includes(vehicleDescription))
assert.ok(render({ ...platform, platformKey: 'mk-mini' }).includes(description))
assert.ok(render({ ...platform, description: '강사가 작성한 시뮬레이션 안내' }).includes('강사가 작성한 시뮬레이션 안내'))
assert.ok(render({ ...platform, description: '설정' }).includes('>설정</p>'))
api.setLanguage('ko')
assert.ok(render(platform).includes(description))
assert.equal(platform.description, description)
})
test('close studio modal translates the built-in Local PC title but preserves vehicle names', () => { test('close studio modal translates the built-in Local PC title but preserves vehicle names', () => {
const source = fs.readFileSync(path.join(root, 'App.tsx'), 'utf8') const source = fs.readFileSync(path.join(root, 'App.tsx'), 'utf8')
const expression = source.match(/platformTitle=\{(runningPlatformCloseTarget\.platformKey[^\n]+)\}/)?.[1] const expression = source.match(/platformTitle=\{(runningPlatformCloseTarget\.platformKey[^\n]+)\}/)?.[1]
@@ -21,7 +21,7 @@ export function ProgressiveChatResponse({ message, dark, children, footerAction,
defaultCollapsed?: boolean defaultCollapsed?: boolean
}): React.JSX.Element { }): React.JSX.Element {
const content = message.plan?.explanation ?? message.content const content = message.plan?.explanation ?? message.content
const modelLabel = [message.provider, message.model].filter(Boolean).join(' · ') const modelLabel = message.provider === 'A_COPILOT' ? 'A-Copilot' : [message.provider, message.model].filter(Boolean).join(' · ')
const document = useMemo(() => createAgentRevealDocument(content), [content]) const document = useMemo(() => createAgentRevealDocument(content), [content])
const [now, setNow] = useState(Date.now) const [now, setNow] = useState(Date.now)
const [reducedMotion, setReducedMotion] = useState(() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches) const [reducedMotion, setReducedMotion] = useState(() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches)
@@ -105,7 +105,11 @@ export function PlatformOptionCard({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<h3 className="truncate text-[15px] font-bold text-[#111520]">{platform.platformKey === 'futuredrive-local-pc' ? t('시뮬레이션') : platform.title}</h3> <h3 className="truncate text-[15px] font-bold text-[#111520]">{platform.platformKey === 'futuredrive-local-pc' ? t('시뮬레이션') : platform.title}</h3>
</div> </div>
<p className="mt-1 text-[12px] font-medium text-[#5f6878]">{platform.description}</p> <p className="mt-1 text-[12px] font-medium text-[#5f6878]">
{platform.platformKey === 'futuredrive-local-pc' && platform.description === '내 PC에서 직접 개발 환경을 실행합니다.'
? t('내 PC에서 직접 개발 환경을 실행합니다.')
: platform.description}
</p>
<div className="mt-2 flex items-center gap-1.5 text-[11px] font-bold text-[#5f6878]"> <div className="mt-2 flex items-center gap-1.5 text-[11px] font-bold text-[#5f6878]">
{statusPending {statusPending
? <LoaderCircle size={13} className="animate-spin text-[#7a879a]" /> ? <LoaderCircle size={13} className="animate-spin text-[#7a879a]" />
+2 -1
View File
@@ -718,5 +718,6 @@
"조회 실패": "Retrieval failed", "조회 실패": "Retrieval failed",
"일부 결과": "Partial results", "일부 결과": "Partial results",
"SLAM 실습 준비": "Prepare SLAM practice", "SLAM 실습 준비": "Prepare SLAM practice",
"승인하고 준비": "Approve and prepare" "승인하고 준비": "Approve and prepare",
"내 PC에서 직접 개발 환경을 실행합니다.": "Run the development environment directly on this PC."
} }