diff --git a/apps/desktop/e2e/providers.spec.ts b/apps/desktop/e2e/providers.spec.ts index 1cb1e0ccd3..a4820b5282 100644 --- a/apps/desktop/e2e/providers.spec.ts +++ b/apps/desktop/e2e/providers.spec.ts @@ -330,6 +330,36 @@ for (const stepfun of [ await expect(page.getByRole('textbox', { name: '模型密钥' })).toBeVisible(); }); +test('adds Volcengine Ark China with its exact snapshot model, API-key field, and shared official mark', async ({ window: page }) => { + await page.getByRole('button', { name: '展开侧边栏' }).click(); + await page.getByRole('button', { name: '设置' }).click(); + await page.locator('[aria-label="设置分组"]').getByText('模型', { exact: true }).click(); + await page.getByRole('button', { name: '添加服务商' }).click(); + + await page.getByRole('tab', { name: 'API', exact: true }).click(); + await page.getByPlaceholder('搜索服务商').fill('Volcengine'); + const catalogMark = page.locator( + '.providerCatalogRow[data-provider="volcengine-ark"] .providerLogo .providerAssetMask', + ); + await expect(catalogMark).toBeVisible(); + expect(await catalogMark.evaluate(maskRenderContract)).toEqual({ usesAssetMask: true, followsForeground: true }); + await page.getByRole('button', { name: /添加模型供应商:Volcengine Ark \(China\)/ }).click(); + + await expect(page.getByLabel('模型供应商连接标识')).toHaveValue('volcengine-ark'); + await expect(page.getByLabel('模型供应商服务地址')).toHaveValue('https://ark.cn-beijing.volces.com/api/v3'); + await expect(page.getByLabel('模型供应商默认模型')).toHaveValue('doubao-seed-2-0-pro-260215'); + await page.getByRole('button', { name: '保存供应商' }).click(); + + await expect(page.getByRole('heading', { name: 'Volcengine Ark (China)', exact: true }).first()).toBeVisible(); + const detailMark = page.locator( + '.providerSubpageHeader .providerLogo[data-provider="volcengine-ark"] .providerAssetMask', + ); + await expect(detailMark).toBeVisible(); + expect(await detailMark.evaluate(maskRenderContract)).toEqual({ usesAssetMask: true, followsForeground: true }); + await expect(page.getByText('doubao-seed-2-0-pro-260215', { exact: true }).first()).toBeVisible(); + await expect(page.getByRole('textbox', { name: '模型密钥' })).toBeVisible(); +}); + test('restores keyboard focus across provider child pages', async ({ window: page }) => { await page.getByRole('button', { name: '展开侧边栏' }).click(); await page.getByRole('button', { name: '设置' }).click(); diff --git a/apps/desktop/src/main/__tests__/icon-governance-contract.test.ts b/apps/desktop/src/main/__tests__/icon-governance-contract.test.ts index 74dab236f8..5b7746859c 100644 --- a/apps/desktop/src/main/__tests__/icon-governance-contract.test.ts +++ b/apps/desktop/src/main/__tests__/icon-governance-contract.test.ts @@ -46,6 +46,7 @@ const NVIDIA_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/ass const TENCENT_HUNYUAN_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/assets/provider-brands/hunyuan.svg'); const TENCENT_CLOUD_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/assets/provider-brands/tencentcloud.svg'); const STEPFUN_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/assets/provider-brands/stepfun.svg'); +const VOLCENGINE_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/assets/provider-brands/volcengine.svg'); const DESKTOP_PACKAGE_FILE = resolve(REPO_ROOT, 'apps/desktop/package.json'); const THIRD_PARTY_NOTICES_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt'); const ONBOARDING_HERO_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/OnboardingHero.tsx'); @@ -430,6 +431,31 @@ describe('icon + typography governance contract', () => { assert.match(componentSrc, /case 'stepfun':\s*return /); }); + it('vendors and routes the byte-exact upstream Volcengine mark through the shared asset-mask seam', async () => { + const [volcengineMark, componentSrc, notices] = await Promise.all([ + readFile(VOLCENGINE_BRAND_MARK_FILE), + readFile(PROVIDER_BRAND_MARKS_FILE, 'utf8'), + readFile(THIRD_PARTY_NOTICES_FILE, 'utf8'), + ]); + + assert.equal( + createHash('sha256').update(volcengineMark).digest('hex'), + 'f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33', + 'the vendored Volcengine mark must remain byte-identical to @lobehub/icons-static-svg@1.91.0 volcengine.svg', + ); + assert.match( + notices, + /apps\/desktop\/src\/renderer\/assets\/provider-brands\/volcengine\.svg[\s\S]*e4302041fbb3039608d25f9f618bd462783b875e[\s\S]*packages\/static-svg\/icons\/volcengine\.svg[\s\S]*f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33/, + 'Volcengine must append byte-exact provenance to the existing Lobe Icons notice entry', + ); + assert.match(componentSrc, /import volcengineBrandMark from '\.\.\/assets\/provider-brands\/volcengine\.svg';/); + assert.match( + componentSrc, + /Volcengine mark vendored byte-for-byte from Lobe Icons:[\s\S]*@lobehub\/icons-static-svg@1\.91\.0[\s\S]*e4302041fbb3039608d25f9f618bd462783b875e[\s\S]*packages\/static-svg\/icons\/volcengine\.svg[\s\S]*MIT[\s\S]*f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33/, + ); + assert.match(componentSrc, /case 'volcengine-ark':\s*return /); + }); + it('vendors and routes the byte-exact Tencent Cloud mark for Tencent Coding Plan', async () => { const [tencentCloudMark, componentSrc, notices] = await Promise.all([ readFile(TENCENT_CLOUD_BRAND_MARK_FILE), diff --git a/apps/desktop/src/renderer/assets/provider-brands/volcengine.svg b/apps/desktop/src/renderer/assets/provider-brands/volcengine.svg new file mode 100644 index 0000000000..28556c3b79 --- /dev/null +++ b/apps/desktop/src/renderer/assets/provider-brands/volcengine.svg @@ -0,0 +1 @@ +Volcengine \ No newline at end of file diff --git a/apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt b/apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt index bfd2ca8935..5500ade055 100644 --- a/apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt +++ b/apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt @@ -53,6 +53,10 @@ This file records notices for third-party source assets vendored into the Maka d - Upstream commit: `e4302041fbb3039608d25f9f618bd462783b875e` - Upstream path: `packages/static-svg/icons/stepfun.svg` - SHA-256: `f46fbd1eee00a3dc7874395484bcc3e25a803e9eb4b79f07b7eec377a1e2f25c` + - `apps/desktop/src/renderer/assets/provider-brands/volcengine.svg` + - Upstream commit: `e4302041fbb3039608d25f9f618bd462783b875e` + - Upstream path: `packages/static-svg/icons/volcengine.svg` + - SHA-256: `f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33` MIT License diff --git a/apps/desktop/src/renderer/settings/provider-brand-marks.tsx b/apps/desktop/src/renderer/settings/provider-brand-marks.tsx index 56f29d07b6..4a463f3ef6 100644 --- a/apps/desktop/src/renderer/settings/provider-brand-marks.tsx +++ b/apps/desktop/src/renderer/settings/provider-brand-marks.tsx @@ -25,6 +25,7 @@ import mistralBrandMark from '../assets/provider-brands/mistral.svg'; import togetherBrandMark from '../assets/provider-brands/together.svg'; import nvidiaMarkUrl from '../assets/provider-brands/nvidia.svg'; import stepfunBrandMark from '../assets/provider-brands/stepfun.svg'; +import volcengineBrandMark from '../assets/provider-brands/volcengine.svg'; import xaiMarkUrl from '../assets/provider-brands/xai.svg'; // Real xAI/Grok mark vendored byte-for-byte from Lobe Icons: @@ -92,6 +93,14 @@ function XAI(): ReactElement { // - license: MIT (repository LICENSE) // - SHA-256: f46fbd1eee00a3dc7874395484bcc3e25a803e9eb4b79f07b7eec377a1e2f25c +// Volcengine mark vendored byte-for-byte from Lobe Icons: +// - repository: https://github.com/lobehub/lobe-icons +// - package/version: @lobehub/icons-static-svg@1.91.0 +// - commit: e4302041fbb3039608d25f9f618bd462783b875e +// - path: packages/static-svg/icons/volcengine.svg +// - license: MIT (repository LICENSE) +// - SHA-256: f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33 + function ProviderAssetMask({ src }: { src: string }): ReactElement { const mask = `url("${src}")`; return