@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.
- package/examples/conversation/package.json +1 -1
- package/fixtures/responsive-assistant/DeliveryFixture.vue +5 -1
- package/fixtures/responsive-assistant/main.js +2 -2
- package/package.json +1 -1
- package/src/client/conversation/AssistantTranscript.css +4 -4
- package/test/transcriptTheme.browser.test.js +87 -0
|
@@ -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: {
|
|
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
|
@@ -248,8 +248,8 @@
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
.assistant-transcript--task .assistant-transcript__message--user {
|
|
251
|
-
background:
|
|
252
|
-
color:
|
|
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:
|
|
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:
|
|
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
|
+
});
|