feat: complete WorkBuddy parity and Windows release workflow
This commit is contained in:
1 parent
143516b3a8
commit
d5512df549
434 files changed
+54977
-1854
No files matched your search
@@ -3,6 +3,7 @@
|
||||
//
|
||||
// 原版依据(REA ev_af920…/ev_88af07…/ev_d6e0a2…/ev_523e07…/ev_4f4f76…):
|
||||
// 面板 [232,40,638,1160];身份卡 638×100(两行 [244,48,614,34]、胶囊 172×34);
|
||||
// 身份编辑输入 [607,49,177,32]、编辑行 [607,48,251,34];图标为 REA svg_nodes 原路径。
|
||||
// 回复偏好卡 638×204(回复风格 select 120×32 @734,174;开关 34×20 @820,248/316);
|
||||
// 指令/人设/长期记忆三张长卡 638×264(y=376/656/936),预览 598×160;编辑弹窗 [130,80,640,520]。
|
||||
const fs = require('node:fs/promises')
|
||||
@@ -12,6 +13,8 @@ const assert = require('node:assert/strict')
|
||||
const TONE_LABELS = ['默认', '专业严谨', '亲和友善', '直言不讳', '天马行空', '高效务实', '毒舌吐槽', '启发引导']
|
||||
const TONE_HINTS = ['不设定特定风格。', '清晰、准确、值得信赖。', '温暖、平易近人、鼓励支持。', '简明扼要、不废话、直击要点。',
|
||||
'富有想象力、善用比喻类比。', '最少文字、最大信息量。', '犀利吐槽、但绝不伤人。', '用提问引导思考、授人以渔。']
|
||||
const CLOSE_ICON_PATH = 'M3.46967 3.46967C3.76256 3.17678 4.23744 3.17678 4.53033 3.46967L8 6.93934L11.4697 3.46967C11.7626 3.17678 12.2374 3.17678 12.5303 3.46967C12.8232 3.76256 12.8232 4.23744 12.5303 4.53033L9.06066 8L12.5303 11.4697C12.8232 11.7626 12.8232 12.2374 12.5303 12.5303C12.2374 12.8232 11.7626 12.8232 11.4697 12.5303L8 9.06066L4.53033 12.5303C4.23744 12.8232 3.76256 12.8232 3.46967 12.5303C3.17678 12.2374 3.17678 11.7626 3.46967 11.4697L6.93934 8L3.46967 4.53033C3.17678 4.23744 3.17678 3.76256 3.46967 3.46967Z'
|
||||
const CHECK_ICON_PATH = 'M5.1314 6.7172L11.4243 0.4243L10.5757 -0.4243L4.2828 5.8686L4.2732 5.8783Q4.0908 6.0607 4 6.1423Q3.9092 6.0607 3.7268 5.8783L3.7172 5.8686L0.4243 2.5757L-0.4243 3.4243L2.8686 6.7172L2.8783 6.7268Q3.1924 7.041 3.3327 7.1476Q3.6556 7.3929 4 7.3929Q4.3444 7.3929 4.6673 7.1476Q4.8076 7.041 5.1217 6.7268L5.1314 6.7172Z'
|
||||
|
||||
if (!process.versions.electron) {
|
||||
;(async () => {
|
||||
@@ -124,6 +127,9 @@ if (!process.versions.electron) {
|
||||
placeholder: !!card.querySelector('.local-memory-long__placeholder')
|
||||
})),
|
||||
toneLabel: document.querySelector('.personalization-panel__tone-trigger .wb-select__value')?.textContent?.trim(),
|
||||
toneTrigger: (() => { const node = document.querySelector('.personalization-panel__tone-trigger')
|
||||
return node ? { role: node.getAttribute('role'), valueClass: node.querySelector('.wb-select__value')?.firstElementChild?.className,
|
||||
nameClass: node.querySelector('.personalization-panel__option-name')?.className } : null })(),
|
||||
popover: rect(document.querySelector('.personalization-panel__tone-popover')),
|
||||
options: [...document.querySelectorAll('.personalization-panel__tone-popover [role=option]')].map(option => ({
|
||||
name: option.querySelector('.personalization-panel__option-name')?.textContent?.trim(),
|
||||
@@ -160,6 +166,7 @@ if (!process.versions.electron) {
|
||||
|
||||
const light = await read(layoutSource, settings)
|
||||
snapshots.light = light
|
||||
await fs.writeFile(path.join(directory, 'personalization-light.png'), (await settings.webContents.capturePage()).toPNG())
|
||||
check('panel keeps the recovered 638x1160 box at 232,40',
|
||||
[near(light.panel.x, 232), near(light.panel.y, 40), near(light.panel.width, 638), near(light.panel.height, 1160)], [true, true, true, true])
|
||||
check('identity card keeps 638x100 with two 614x34 rows',
|
||||
@@ -194,6 +201,9 @@ if (!process.versions.electron) {
|
||||
[true, '例如:回答尽量简洁,先给结论', true, true])
|
||||
|
||||
await step('personalization-tone')
|
||||
check('回复风格 trigger keeps the recovered combobox value structure',
|
||||
[light.toneTrigger?.role, light.toneTrigger?.valueClass, light.toneTrigger?.nameClass],
|
||||
['combobox', 'personalization-panel__option-text', 'personalization-panel__option-name'])
|
||||
await read(`document.querySelector('.personalization-panel__tone-trigger').click()`, settings)
|
||||
const popover = await wait(`(()=>{const node=document.querySelector('.personalization-panel__tone-popover')
|
||||
return node?{rect:(()=>{const r=node.getBoundingClientRect();return {x:r.x,width:r.width}})(),options:[...node.querySelectorAll('[role=option]')].length}:null})()`, 'tone popover', settings)
|
||||
@@ -208,13 +218,23 @@ if (!process.versions.electron) {
|
||||
check('choosing 专业严谨 persists the tone', toneStored, 'professional')
|
||||
|
||||
await step('personalization-switches')
|
||||
await read(`(()=>{window.__loadingTipsStorageEvents=0
|
||||
window.__loadingTipsStorageProbe=event=>{if(event.key==='ycode:loading-tips')window.__loadingTipsStorageEvents++}
|
||||
window.addEventListener('storage',window.__loadingTipsStorageProbe)
|
||||
return true})()`, main)
|
||||
await read(`document.querySelector('#settings-loading-tips .wb-switch__input').click()`, settings)
|
||||
await wait(`localStorage.getItem('ycode:loading-tips')==='0'`, 'loading tips mirror', settings)
|
||||
await wait(`localStorage.getItem('ycode:loading-tips')==='0'&&window.__loadingTipsStorageEvents>0`, 'loading tips cross-window mirror', main)
|
||||
check('欢迎语 switches off, persists and mirrors to the renderer',
|
||||
[await read(`window.ycode.personalization.get().then(view => view.loadingTips)`, settings), await read(`localStorage.getItem('ycode:loading-tips')`, settings)], [false, '0'])
|
||||
check('欢迎语关闭在另一个窗口触发 storage 同步', [await read(`localStorage.getItem('ycode:loading-tips')`, main),
|
||||
await read(`window.__loadingTipsStorageEvents`, main)], ['0', 1])
|
||||
await read(`document.querySelector('#settings-loading-tips .wb-switch__input').click()`, settings)
|
||||
await wait(`localStorage.getItem('ycode:loading-tips')==='1'`, 'loading tips restored', settings)
|
||||
await wait(`localStorage.getItem('ycode:loading-tips')==='1'&&window.__loadingTipsStorageEvents>1`, 'loading tips cross-window restored', main)
|
||||
check('欢迎语 switches back on', await read(`window.ycode.personalization.get().then(view => view.loadingTips)`, settings), true)
|
||||
check('欢迎语开启同样跨窗口同步', [await read(`localStorage.getItem('ycode:loading-tips')`, main),
|
||||
await read(`window.__loadingTipsStorageEvents`, main)], ['1', 2])
|
||||
await read(`document.querySelector('#settings-file-changes .wb-switch__input').click()`, settings)
|
||||
check('显示文件修改过程 stays disabled and keeps the stored default', await read(`window.ycode.personalization.get().then(view => view.showFileChanges)`, settings), false)
|
||||
|
||||
@@ -224,10 +244,34 @@ if (!process.versions.electron) {
|
||||
const input=row.querySelector('.local-memory-inline__input')
|
||||
if(!input)return null
|
||||
const r=input.getBoundingClientRect()
|
||||
return { width:r.width, height:r.height, actions:row.querySelectorAll('.local-memory-inline__icon-btn').length,
|
||||
confirmDisabled:row.querySelector('.local-memory-inline__icon-btn--confirm')?.disabled }})()`, 'identity editing', settings)
|
||||
check('clicking the pill opens the recovered 172x34 inline editor with cancel/confirm',
|
||||
[near(editing.width, 172), near(editing.height, 34), editing.actions, editing.confirmDisabled], [true, true, 2, true])
|
||||
const edit=row.querySelector('.local-memory-identity-row__edit')
|
||||
const er=edit.getBoundingClientRect()
|
||||
return { width:r.width, height:r.height, editWidth:er.width, editHeight:er.height,
|
||||
inputClass:input.className, inputRadius:getComputedStyle(input).borderRadius,
|
||||
actions:row.querySelectorAll('.local-memory-inline__icon-btn').length,
|
||||
confirmDisabled:row.querySelector('.local-memory-inline__icon-btn--confirm')?.disabled,
|
||||
buttons:[...row.querySelectorAll('.local-memory-inline__icon-btn')].map(button=>({
|
||||
className:button.className, width:button.getBoundingClientRect().width, height:button.getBoundingClientRect().height,
|
||||
iconClass:button.querySelector('.wb-button__icon')?.className,
|
||||
iconFill:button.querySelector('svg')?.getAttribute('fill'),
|
||||
path:button.querySelector('path')?.getAttribute('d'),
|
||||
transform:button.querySelector('path')?.getAttribute('transform') ?? null
|
||||
})) }})()`, 'identity editing', settings)
|
||||
check('clicking the pill opens the recovered 177x32 input inside the 251x34 inline editor',
|
||||
[near(editing.width, 177), near(editing.height, 32), near(editing.editWidth, 251), near(editing.editHeight, 34),
|
||||
editing.inputClass.split(/\s+/).includes('wb-input') && editing.inputClass.split(/\s+/).includes('wb-input--medium'),
|
||||
editing.inputRadius, editing.actions, editing.confirmDisabled], [true, true, true, true, true, '8px', 2, true])
|
||||
await fs.writeFile(path.join(directory, 'identity-edit.png'), (await settings.webContents.capturePage()).toPNG())
|
||||
check('inline editor action buttons keep the recovered 34x34 ghost geometry and exact icon wrappers',
|
||||
[editing.buttons.every(button => near(button.width, 34) && near(button.height, 34)),
|
||||
editing.buttons.every(button => button.className.includes('wb-button--ghost') && button.className.includes('wb-button--small') && button.className.includes('wb-button--icon-only')),
|
||||
editing.buttons.map(button => button.iconClass),
|
||||
editing.buttons.map(button => button.iconFill),
|
||||
editing.buttons.map(button => button.path),
|
||||
editing.buttons[1].transform],
|
||||
[true, true,
|
||||
['wb-button__icon wb-button__icon--left', 'wb-button__icon wb-button__icon--left'],
|
||||
['currentColor', 'none'], [CLOSE_ICON_PATH, CHECK_ICON_PATH], 'matrix(1 0 0 1 3 3.99854)'])
|
||||
await read(setInput('#settings-local-memory-user-call-name .local-memory-inline__input', '小明'), settings)
|
||||
await pause(120)
|
||||
await read(`document.querySelector('#settings-local-memory-user-call-name .local-memory-inline__icon-btn--confirm').click()`, settings)
|
||||
@@ -259,13 +303,25 @@ if (!process.versions.electron) {
|
||||
return { x:r.x, y:r.y, width:r.width, height:r.height,
|
||||
title:node.querySelector('.wb-modal__title')?.textContent?.trim(),
|
||||
placeholder:node.querySelector('.local-memory-editor')?.getAttribute('placeholder'),
|
||||
close:(()=>{const button=node.querySelector('.wb-modal__close'), br=button?.getBoundingClientRect()
|
||||
return button?{className:button.className,width:br.width,height:br.height,
|
||||
iconWidth:button.querySelector('svg')?.getBoundingClientRect().width,
|
||||
iconHeight:button.querySelector('svg')?.getBoundingClientRect().height,
|
||||
iconFill:button.querySelector('svg')?.getAttribute('fill'),
|
||||
pathFillRule:button.querySelector('path')?.getAttribute('fill-rule')}:null})(),
|
||||
buttons:[...node.querySelectorAll('.wb-modal__footer .wb-button')].map(button=>({text:button.textContent.trim(),width:button.getBoundingClientRect().width,height:button.getBoundingClientRect().height})) }})()`, 'prompt editor modal', settings)
|
||||
snapshots.modal = modal
|
||||
await fs.writeFile(path.join(directory, 'personalization-editor.png'), (await settings.webContents.capturePage()).toPNG())
|
||||
check('指令 editor keeps the recovered 640x520 modal with 保存/取消 68x32 buttons',
|
||||
[[near(modal.x, 130), near(modal.y, 80), near(modal.width, 640), near(modal.height, 520)].every(Boolean),
|
||||
modal.title, modal.buttons.map(button => button.text), modal.buttons.every(button => near(button.width, 68) && near(button.height, 32))],
|
||||
[true, '给 YCode 的指令', ['取消', '保存'], true])
|
||||
check('empty 指令 editor keeps the recovered placeholder', [modal.placeholder], ['当前内容为空,点击开始输入'])
|
||||
check('指令 modal close control keeps the recovered 24x24 shell and exact filled X icon',
|
||||
[near(modal.close.width, 24), near(modal.close.height, 24), modal.close.iconWidth,
|
||||
modal.close.iconHeight, modal.close.iconFill, modal.close.pathFillRule,
|
||||
modal.close.className.includes('wb-button--circle')],
|
||||
[true, true, 16, 16, 'currentColor', 'evenodd', true])
|
||||
await read(setInput('.local-memory-editor', '先给结论,再给依据。'), settings)
|
||||
await pause(120)
|
||||
await read(`[...document.querySelectorAll('.local-memory-edit-modal .wb-modal__footer .wb-button')].find(button=>button.textContent.trim()==='保存').click()`, settings)
|
||||
@@ -307,6 +363,7 @@ if (!process.versions.electron) {
|
||||
await wait(`document.documentElement.dataset.theme==='dark'&&!!document.querySelector('.personalization-panel')`, 'dark personalization panel', settings)
|
||||
const dark = await read(layoutSource, settings)
|
||||
snapshots.dark = dark
|
||||
await fs.writeFile(path.join(directory, 'personalization-dark.png'), (await settings.webContents.capturePage()).toPNG())
|
||||
check('dark theme keeps the recovered card surface and text',
|
||||
[dark.rowBackground, dark.rowColor], ['rgb(31, 31, 31)', 'rgb(242, 243, 245)'])
|
||||
check('dark theme keeps every recovered geometry',
|
||||
|
||||
Reference in new issue
Block a user