@jskit-ai/assistant-core 0.2.11 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  "build": "vite build"
9
9
  },
10
10
  "dependencies": {
11
- "@jskit-ai/assistant-core": "0.2.11",
11
+ "@jskit-ai/assistant-core": "0.2.12",
12
12
  "@jskit-ai/http-runtime": "0.2.5",
13
13
  "@jskit-ai/kernel": "0.2.5",
14
14
  "@mdi/js": "^7.4.47",
@@ -5,6 +5,7 @@ import { createAssistantMessageDelivery } from "../../src/client/conversation/me
5
5
 
6
6
  const recoveryFixture = new URLSearchParams(location.search).has("recovery");
7
7
  const acceptedFixture = new URLSearchParams(location.search).has("accepted-history");
8
+ const taskFixture = new URLSearchParams(location.search).has("task");
8
9
  const draft = ref("");
9
10
  const turns = ref([]);
10
11
  const requests = ref([]);
@@ -63,7 +64,10 @@ if (acceptedFixture) {
63
64
  }
64
65
  const adapter = reactive({
65
66
  delivery, attachments,
66
- conversation: { cancellableDelivery: recoveryFixture, turns, visible: true, scrollKey: "delivery", welcomeMessage: "Start a conversation." },
67
+ conversation: {
68
+ cancellableDelivery: recoveryFixture, turns, visible: true, scrollKey: "delivery", welcomeMessage: "Start a conversation.",
69
+ variant: taskFixture ? "task" : "main"
70
+ },
67
71
  composer: { draft, canSend: true, submitOnEnter: true, queueWhileSending },
68
72
  actions: {
69
73
  setDraft(value) { draft.value = value; },
@@ -9,6 +9,7 @@ import ConversationFixture from "./ConversationFixture.vue";
9
9
  import ProgressFixture from "./ProgressFixture.vue";
10
10
  import DeliveryFixture from "./DeliveryFixture.vue";
11
11
 
12
+ const query = new URLSearchParams(location.search);
12
13
  const vuetify = createVuetify({
13
14
  components,
14
15
  directives,
@@ -18,11 +19,10 @@ const vuetify = createVuetify({
18
19
  sets: { mdi }
19
20
  },
20
21
  theme: {
21
- defaultTheme: "light"
22
+ defaultTheme: query.has("dark") ? "dark" : "light"
22
23
  }
23
24
  });
24
25
 
25
- const query = new URLSearchParams(location.search);
26
26
  createApp(query.has("delivery") ? DeliveryFixture : query.has("progress") ? ProgressFixture : query.has("conversation") ? ConversationFixture : App)
27
27
  .use(vuetify)
28
28
  .mount("#app");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/assistant-core",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test --test-concurrency=1"
@@ -248,8 +248,8 @@
248
248
  }
249
249
 
250
250
  .assistant-transcript--task .assistant-transcript__message--user {
251
- background: #eee8f8;
252
- color: #2d2440;
251
+ background: rgba(126, 87, 194, 0.16);
252
+ color: rgb(var(--v-theme-on-surface));
253
253
  }
254
254
 
255
255
  .assistant-transcript__thinking {
@@ -325,13 +325,13 @@
325
325
  }
326
326
 
327
327
  .assistant-transcript__message-header time {
328
- color: #6d7888;
328
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
329
329
  font-weight: 650;
330
330
  }
331
331
 
332
332
  .assistant-transcript__message-footer {
333
333
  align-items: center;
334
- color: #9aa6b6;
334
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
335
335
  display: flex;
336
336
  font-size: 0.88rem;
337
337
  font-weight: 500;
@@ -0,0 +1,87 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { fileURLToPath } from "node:url";
4
+ import { chromium, expect } from "@playwright/test";
5
+ import { createChromiumLaunchOptions, startViteFixture, stopProcess } from "../../../tooling/testUtils/browserFixture.mjs";
6
+
7
+ // An element's text colour, the theme values it should come from, and its WCAG contrast over what is painted under
8
+ // it: each translucent background is laid over the next one out, down to the first opaque one (white when none is).
9
+ // Runs in the page.
10
+ function readText(element) {
11
+ const channels = (value) => {
12
+ const [red, green, blue, alpha = 1] = value.match(/[\d.]+/gu).map(Number);
13
+ return { red, green, blue, alpha };
14
+ };
15
+ const over = (top, under) => ({
16
+ red: top.red * top.alpha + under.red * (1 - top.alpha),
17
+ green: top.green * top.alpha + under.green * (1 - top.alpha),
18
+ blue: top.blue * top.alpha + under.blue * (1 - top.alpha),
19
+ alpha: 1
20
+ });
21
+ const luminance = ({ red, green, blue }) => {
22
+ const [r, g, b] = [red, green, blue].map((channel) => {
23
+ const value = channel / 255;
24
+ return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
25
+ });
26
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
27
+ };
28
+ const layers = [];
29
+ for (let node = element; node; node = node.parentElement) {
30
+ const background = channels(getComputedStyle(node).backgroundColor);
31
+ if (background.alpha > 0) layers.push(background);
32
+ if (background.alpha === 1) break;
33
+ }
34
+ const backdrop = layers.reverse().reduce((under, layer) => over(layer, under), { red: 255, green: 255, blue: 255, alpha: 1 });
35
+ const style = getComputedStyle(element);
36
+ const color = channels(style.color);
37
+ const [lighter, darker] = [luminance(over(color, backdrop)), luminance(backdrop)].sort((a, b) => b - a);
38
+ return {
39
+ color: [color.red, color.green, color.blue, color.alpha],
40
+ onSurface: style.getPropertyValue("--v-theme-on-surface").split(",").map(Number),
41
+ mediumEmphasis: Number(style.getPropertyValue("--v-medium-emphasis-opacity")),
42
+ contrast: (lighter + 0.05) / (darker + 0.05)
43
+ };
44
+ }
45
+
46
+ test("the task transcript's user bubble, header time and footer follow the light and dark themes", {
47
+ skip: process.env.JSKIT_ASSISTANT_CORE_BROWSER_INTEGRATION !== "1", timeout: 90_000
48
+ }, async () => {
49
+ const vite = await startViteFixture({ fixtureRoot: fileURLToPath(new URL("../fixtures/responsive-assistant", import.meta.url)) });
50
+ const browser = await chromium.launch(createChromiumLaunchOptions());
51
+ try {
52
+ for (const theme of ["light", "dark"]) {
53
+ const page = await browser.newPage({ viewport: { width: 800, height: 900 } });
54
+ await page.goto(`${vite.baseURL}/?delivery=1&recovery=1&task=1${theme === "dark" ? "&dark=1" : ""}`);
55
+ await expect(page.locator(".v-application")).toHaveClass(new RegExp(`v-theme--${theme}`, "u"));
56
+ const bubble = page.locator(".assistant-transcript--task .assistant-transcript__message--user").filter({ hasText: "Question one" });
57
+ await expect(bubble).toBeVisible();
58
+ assert.equal(await bubble.evaluate((element) => getComputedStyle(element).backgroundColor), "rgba(126, 87, 194, 0.16)");
59
+ // The transcript's own headers carry no time, so the check places one where an application's header would.
60
+ const header = page.locator(".assistant-transcript__turn .assistant-transcript__message-header").first();
61
+ await header.evaluate((element) => {
62
+ const time = document.createElement("time");
63
+ for (const { name } of element.attributes) {
64
+ if (name.startsWith("data-v-")) time.setAttribute(name, "");
65
+ }
66
+ time.textContent = "06:02";
67
+ element.append(time);
68
+ });
69
+ const samples = {
70
+ "user bubble": [bubble.locator(".assistant-transcript__user-content"), 1],
71
+ "user footer": [bubble.locator(".assistant-transcript__message-footer time"), "medium"],
72
+ "assistant footer": [page.locator(".assistant-transcript__message-footer--assistant time").first(), "medium"],
73
+ "header time": [header.locator("time"), "medium"]
74
+ };
75
+ for (const [name, [locator, alpha]] of Object.entries(samples)) {
76
+ const text = await locator.evaluate(readText);
77
+ assert.deepEqual(text.color.slice(0, 3), text.onSurface, `${theme} ${name}: the colour is the theme's on-surface`);
78
+ assert.equal(text.color[3], alpha === "medium" ? text.mediumEmphasis : alpha, `${theme} ${name}: the opacity`);
79
+ assert.ok(text.contrast >= 4.5, `${theme} ${name}: contrast ${text.contrast.toFixed(2)}:1 is under 4.5:1`);
80
+ }
81
+ await page.close();
82
+ }
83
+ } finally {
84
+ await browser.close();
85
+ await stopProcess(vite);
86
+ }
87
+ });