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', () 
=> {

Reply via email to