This is an automated email from the ASF dual-hosted git repository. imbajin pushed a commit to branch feat/oink-core-platform in repository https://gitbox.apache.org/repos/asf/hugegraph-doc.git
commit 67ff2e8d9e8a3d4afef687b438aa1a046245d111 Author: dark <[email protected]> AuthorDate: Fri Sep 4 22:34:16 2026 +0800 fix(ai): derive dark accents from theme - derive dark widget accents from the configured OINK theme color - clear stale error tooltips during retry and ready states - cover dark-mode attributes and retry recovery in Node and browser tests --- assets/js/kapa-adapter.js | 17 ++++++++++++++--- tests/e2e/ai.spec.js | 11 +++++++++++ tests/ui-ai/kapa-adapter.test.cjs | 7 +++++++ tests/ui-ai/ui-contract.test.cjs | 4 ++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/assets/js/kapa-adapter.js b/assets/js/kapa-adapter.js index 690d012e7..e964ec246 100644 --- a/assets/js/kapa-adapter.js +++ b/assets/js/kapa-adapter.js @@ -14,6 +14,16 @@ return String(value || '').trim(); } + function mixWithWhite(color, percentage) { + var match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color); + if (!match) throw new Error('Kapa theme color must be a six-digit hexadecimal color'); + var weight = percentage / 100; + return '#' + match.slice(1).map(function (channel) { + var mixed = Math.round(parseInt(channel, 16) * (1 - weight) + 255 * weight); + return mixed.toString(16).padStart(2, '0'); + }).join(''); + } + function readConfig(documentObject) { var node = documentObject.getElementById('hg-ai-config'); if (!node) return null; @@ -61,7 +71,7 @@ 'data-language': config.locale, 'data-project-name': 'Apache HugeGraph', 'data-project-color': config.themeColor, - 'data-project-color-dark': '#9f83ff', + 'data-project-color-dark': mixWithWhite(config.themeColor, 48), 'data-surface-color': '#ffffff', 'data-surface-elevated-color': '#f6f4fb', 'data-surface-hover-color': '#eeeafd', @@ -75,7 +85,7 @@ 'data-text-color-dark': '#f0edf7', 'data-text-muted-color-dark': '#b6afc2', 'data-border-color-dark': '#494254', - 'data-anchor-color-dark': '#b6a3ff', + 'data-anchor-color-dark': mixWithWhite(config.themeColor, 60), 'data-color-scheme-selector': "[data-bs-theme='dark']", 'data-font-family': '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif', @@ -111,7 +121,8 @@ button.disabled = next === 'loading'; if (next === 'loading') button.setAttribute('aria-busy', 'true'); else button.removeAttribute('aria-busy'); - if (message) button.title = message; + if (message) button.setAttribute('title', message); + else button.removeAttribute('title'); }); if (status) { status.textContent = message || ''; diff --git a/tests/e2e/ai.spec.js b/tests/e2e/ai.spec.js index 0dbf2afc0..56209630f 100644 --- a/tests/e2e/ai.spec.js +++ b/tests/e2e/ai.spec.js @@ -28,6 +28,9 @@ for (const [locale, route, source, language] of [ }); await page.goto(AI_ORIGIN + route); expect(requests).toEqual([]); + await page.evaluate(() => + document.documentElement.setAttribute("data-bs-theme", "dark") + ); const launcher = page.locator(".hg-ask-ai-launcher"); await expect(launcher).toBeVisible(); @@ -52,6 +55,12 @@ for (const [locale, route, source, language] of [ const script = page.locator("script[data-hg-kapa-widget]"); await expect(script).toHaveAttribute("data-language", language); await expect(script).toHaveAttribute("data-source-group-ids-include", source); + await expect(script).toHaveAttribute("data-project-color", "#532fc9"); + await expect(script).toHaveAttribute("data-project-color-dark", "#a693e3"); + await expect(script).toHaveAttribute("data-anchor-color-dark", "#baace9"); + await expect(script).toHaveAttribute( + "data-color-scheme-selector", "[data-bs-theme='dark']" + ); const calls = await page.evaluate(() => window.__kapaCalls); expect(calls).toContainEqual([ "open", { mode: "ai", query: "server auth", submit: true } @@ -71,9 +80,11 @@ test("AI 500 remains non-blocking and retry issues one fresh request", async ({ await launcher.dblclick(); await expect.poll(() => attempts).toBe(1); await expect(launcher).toHaveAttribute("data-hg-ai-state", "error"); + await expect(launcher).toHaveAttribute("title", /unavailable/i); await launcher.click(); await expect.poll(() => attempts).toBe(2); await expect(launcher).toHaveAttribute("data-hg-ai-state", "ready"); + await expect(launcher).not.toHaveAttribute("title", /unavailable/i); }); test("AI pending timeout discards stale state and retry waits for a fresh bundle", async ({ diff --git a/tests/ui-ai/kapa-adapter.test.cjs b/tests/ui-ai/kapa-adapter.test.cjs index 8f1127e98..c24e09e36 100644 --- a/tests/ui-ai/kapa-adapter.test.cjs +++ b/tests/ui-ai/kapa-adapter.test.cjs @@ -122,6 +122,10 @@ test('uses one fixed bundle and explicit privacy-safe widget settings', () => { assert.equal(attrs['data-source-group-ids-include'], 'source-cn'); assert.equal(attrs['data-project-color'], '#123456'); assert.equal(attrs['data-anchor-color'], '#123456'); + assert.equal(attrs['data-project-color-dark'], '#8495a7'); + assert.equal(attrs['data-anchor-color-dark'], '#a0aebb'); + assert.notEqual(attrs['data-project-color-dark'], '#9f83ff'); + assert.notEqual(attrs['data-anchor-color-dark'], '#b6a3ff'); }); test('sends only the trimmed query after explicit activation and render', () => { @@ -205,8 +209,10 @@ test('a pending timeout retries with a fresh script and ignores the late attempt h.fireTimeout(); assert.equal(controller.getState(), 'error'); assert.equal(h.scripts[0].removed, true); + assert.equal(h.trigger.attrs.title, 'unavailable'); controller.activate('second', true, h.trigger); + assert.equal(h.trigger.attrs.title, undefined); assert.equal(h.scripts.length, 2); assert.match(h.scripts[1].src, /\?hg-retry=2$/); staleRender(); @@ -220,6 +226,7 @@ test('a pending timeout retries with a fresh script and ignores the late attempt h.scripts[1].fire('load'); h.fireRender(); assert.equal(controller.getState(), 'ready'); + assert.equal(h.trigger.attrs.title, undefined); assert.deepEqual(h.calls.at(-1), [ 'open', { mode: 'ai', query: 'second', submit: true }, diff --git a/tests/ui-ai/ui-contract.test.cjs b/tests/ui-ai/ui-contract.test.cjs index 8aa35f0d7..eaa8fcbb8 100644 --- a/tests/ui-ai/ui-contract.test.cjs +++ b/tests/ui-ai/ui-contract.test.cjs @@ -39,6 +39,10 @@ test('theme color and social fallback have one configuration authority', () => { assert.match(hook, /"themeColor"\s+\$themeColor/); assert.equal(adapter.includes("'data-project-color': '#532fc9'"), false); assert.match(adapter, /'data-project-color': config\.themeColor/); + assert.equal(adapter.includes("'data-project-color-dark': '#9f83ff'"), false); + assert.equal(adapter.includes("'data-anchor-color-dark': '#b6a3ff'"), false); + assert.match(adapter, /'data-project-color-dark': mixWithWhite\(config\.themeColor, 48\)/); + assert.match(adapter, /'data-anchor-color-dark': mixWithWhite\(config\.themeColor, 60\)/); }); test('documentation menu has five groups and no duplicate version panel', () => {
