@signalcore/ui 1.0.0-beta.1 → 1.0.0-beta.3
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/README.md +55 -22
- package/dist/AgentAbout.js +11 -4
- package/dist/AgentAbout.js.map +1 -1
- package/dist/AgentChat.d.ts +7 -1
- package/dist/AgentChat.js +154 -2
- package/dist/AgentChat.js.map +1 -1
- package/dist/AgentSpace.d.ts +12 -3
- package/dist/AgentSpace.js +53 -11
- package/dist/AgentSpace.js.map +1 -1
- package/dist/AgentTasks.js +2 -1
- package/dist/AgentTasks.js.map +1 -1
- package/dist/Threads.d.ts +73 -0
- package/dist/Threads.js +140 -0
- package/dist/Threads.js.map +1 -0
- package/dist/VoiceCall.js +1 -1
- package/dist/VoiceCall.js.map +1 -1
- package/dist/about.d.ts +34 -144
- package/dist/about.js +6 -8
- package/dist/about.js.map +1 -1
- package/dist/automation/cron.d.ts +1 -1
- package/dist/automation/cron.js.map +1 -1
- package/dist/automation/marks.js +13 -5
- package/dist/automation/marks.js.map +1 -1
- package/dist/automation/messages.d.ts +6 -0
- package/dist/automation/messages.js +7 -1
- package/dist/automation/messages.js.map +1 -1
- package/dist/automation/types.d.ts +25 -128
- package/dist/automation/types.js +36 -0
- package/dist/automation/types.js.map +1 -0
- package/dist/automation/when.d.ts +1 -1
- package/dist/automation/when.js +13 -1
- package/dist/automation/when.js.map +1 -1
- package/dist/blocks.d.ts +23 -1
- package/dist/browser.js +2 -2
- package/dist/browser.js.map +1 -1
- package/dist/call.d.ts +1 -1
- package/dist/call.js.map +1 -1
- package/dist/client.d.ts +14 -4
- package/dist/client.js +7 -2
- package/dist/client.js.map +1 -1
- package/dist/contract.d.ts +267 -0
- package/dist/credentials.js.map +1 -1
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/es.d.ts +2 -0
- package/dist/es.js +31 -4
- package/dist/es.js.map +1 -1
- package/dist/frames.d.ts +7 -1
- package/dist/frames.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -4
- package/dist/kit/AudioPlayer.js +1 -1
- package/dist/kit/AudioPlayer.js.map +1 -1
- package/dist/kit/Choices.js +1 -1
- package/dist/kit/Choices.js.map +1 -1
- package/dist/kit/FileField.js +1 -1
- package/dist/kit/FileField.js.map +1 -1
- package/dist/kit/Popover.js.map +1 -1
- package/dist/kit/Tile.d.ts +1 -1
- package/dist/kit/Tile.js +1 -1
- package/dist/kit/Tile.js.map +1 -1
- package/dist/kit/messages.d.ts +13 -0
- package/dist/kit/messages.js +7 -1
- package/dist/kit/messages.js.map +1 -1
- package/dist/kit/uploads.d.ts +2 -1
- package/dist/kit/uploads.js +1 -1
- package/dist/kit/uploads.js.map +1 -1
- package/dist/kit/waveform.js.map +1 -1
- package/dist/markdown.js.map +1 -1
- package/dist/marks.js +2 -3
- package/dist/marks.js.map +1 -1
- package/dist/surface/Form.js +1 -1
- package/dist/surface/Markdown.js +1 -1
- package/dist/surface/Markdown.js.map +1 -1
- package/dist/surface/Surface.js +43 -4
- package/dist/surface/Surface.js.map +1 -1
- package/dist/surface/scope.d.ts +1 -1
- package/dist/surface/scope.js.map +1 -1
- package/dist/surface/values.js +1 -1
- package/dist/surface/values.js.map +1 -1
- package/dist/tint.d.ts +1 -1
- package/dist/tint.js +1 -1
- package/dist/tint.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.js.map +1 -1
- package/dist/useConversation.d.ts +2 -0
- package/dist/useConversation.js +16 -0
- package/dist/useConversation.js.map +1 -1
- package/dist/usePolled.js +1 -1
- package/dist/usePolled.js.map +1 -1
- package/dist/voice/webgl.js +1 -1
- package/dist/voice/webgl.js.map +1 -1
- package/dist/widget.js +17 -16
- package/package.json +1 -1
- package/src/AgentAbout.tsx +14 -3
- package/src/AgentChat.tsx +203 -1
- package/src/AgentSpace.tsx +89 -13
- package/src/AgentTasks.tsx +2 -2
- package/src/Threads.tsx +218 -0
- package/src/VoiceCall.tsx +1 -1
- package/src/about.ts +42 -144
- package/src/automation/cron.ts +1 -1
- package/src/automation/marks.tsx +13 -2
- package/src/automation/messages.ts +12 -0
- package/src/automation/types.ts +58 -118
- package/src/automation/when.ts +7 -1
- package/src/blocks.ts +25 -0
- package/src/browser.ts +4 -4
- package/src/call.ts +1 -1
- package/src/client.ts +26 -10
- package/src/contract.ts +1934 -0
- package/src/credentials.ts +2 -2
- package/src/dev.ts +1 -1
- package/src/es.ts +29 -0
- package/src/frames.ts +3 -1
- package/src/index.ts +10 -1
- package/src/kit/AudioPlayer.tsx +1 -1
- package/src/kit/Choices.tsx +1 -1
- package/src/kit/FileField.tsx +1 -1
- package/src/kit/Popover.tsx +1 -1
- package/src/kit/Tile.tsx +1 -1
- package/src/kit/messages.ts +20 -0
- package/src/kit/uploads.ts +2 -2
- package/src/kit/waveform.ts +1 -1
- package/src/markdown.ts +1 -1
- package/src/marks.tsx +3 -123
- package/src/styles.css +206 -0
- package/src/surface/Markdown.tsx +1 -1
- package/src/surface/Surface.tsx +53 -4
- package/src/surface/scope.ts +3 -1
- package/src/surface/values.ts +5 -5
- package/src/tint.ts +1 -1
- package/src/types.ts +11 -0
- package/src/useConversation.ts +10 -0
- package/src/usePolled.ts +2 -2
- package/src/voice/webgl.ts +1 -1
- package/src/widget/element.tsx +13 -4
- package/src/widget/widget.css +13 -0
package/src/surface/Surface.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
Block,
|
|
8
8
|
ButtonBlock,
|
|
9
9
|
ChoiceBlock,
|
|
10
|
+
ConnectBlock,
|
|
10
11
|
FieldsBlock,
|
|
11
12
|
ImageBlock,
|
|
12
13
|
ListBlock,
|
|
@@ -75,6 +76,7 @@ const BLOCKS: ReadonlySet<string> = new Set([
|
|
|
75
76
|
"image",
|
|
76
77
|
"automation",
|
|
77
78
|
"file",
|
|
79
|
+
"connect",
|
|
78
80
|
"button",
|
|
79
81
|
"choice",
|
|
80
82
|
"form",
|
|
@@ -90,12 +92,15 @@ export function known(blocks: Block[]): boolean {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
/**
|
|
93
|
-
* Whether a screen draws its own edge: one that is a single card (or an automation,
|
|
94
|
-
* its own) needs no panel around it where it is shown.
|
|
95
|
+
* Whether a screen draws its own edge: one that is a single card (or an automation, or an app
|
|
96
|
+
* to connect, each a card of its own) needs no panel around it where it is shown.
|
|
95
97
|
*/
|
|
96
98
|
export function framesItself(surface: Shown): boolean {
|
|
97
99
|
const [only, ...more] = surface.blocks;
|
|
98
|
-
return
|
|
100
|
+
return (
|
|
101
|
+
more.length === 0 &&
|
|
102
|
+
(only?.type === "card" || only?.type === "automation" || only?.type === "connect")
|
|
103
|
+
);
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
/**
|
|
@@ -214,6 +219,8 @@ function Draw({ block }: { block: Block }) {
|
|
|
214
219
|
return <AutomationShown block={block} />;
|
|
215
220
|
case "file":
|
|
216
221
|
return <FileShown block={block} />;
|
|
222
|
+
case "connect":
|
|
223
|
+
return <Connect block={block} />;
|
|
217
224
|
case "button":
|
|
218
225
|
return <Pressable block={block} />;
|
|
219
226
|
case "choice":
|
|
@@ -268,7 +275,7 @@ function FileShown({ block }: { block: FileBlock }) {
|
|
|
268
275
|
type PlayedKind = "image" | "video" | "audio";
|
|
269
276
|
|
|
270
277
|
/** How a file plays in the page: a picture, a video, a sound; null for any other kind. */
|
|
271
|
-
|
|
278
|
+
function playedAs(mediaType: string): PlayedKind | null {
|
|
272
279
|
const family = mediaType.split("/")[0];
|
|
273
280
|
return family === "image" || family === "video" || family === "audio" ? family : null;
|
|
274
281
|
}
|
|
@@ -452,6 +459,48 @@ function AutomationShown({ block }: { block: AutomationBlock }) {
|
|
|
452
459
|
return <Automation automation={block.view} runs={block.view.runs} />;
|
|
453
460
|
}
|
|
454
461
|
|
|
462
|
+
/**
|
|
463
|
+
* An app for the person to connect, as its connection stands: its logo and name over the line
|
|
464
|
+
* the agent wrote of what it will do with it; while it waits for them, the button that opens
|
|
465
|
+
* its consent page apart; once it works, or if it failed, a quiet word in its place. Before
|
|
466
|
+
* the API has asked for it, only the app as the agent named it.
|
|
467
|
+
*/
|
|
468
|
+
function Connect({ block }: { block: ConnectBlock }) {
|
|
469
|
+
const part = useSurfaceParts();
|
|
470
|
+
const { messages } = useKit();
|
|
471
|
+
const { connection } = block;
|
|
472
|
+
const title = connection?.title ?? block.app;
|
|
473
|
+
const pending = connection?.status === "pending" ? connection.url : null;
|
|
474
|
+
const url = pending !== null && https(pending) ? pending : null;
|
|
475
|
+
const working = connection?.status === "active";
|
|
476
|
+
const failed = connection !== null && !working && connection.status !== "pending";
|
|
477
|
+
return (
|
|
478
|
+
<Card title={title} subtitle={block.purpose} image={connection?.icon ?? undefined}>
|
|
479
|
+
{url !== null ? (
|
|
480
|
+
<Button tone="primary" href={url}>
|
|
481
|
+
{messages.connect.authorize(title)}
|
|
482
|
+
</Button>
|
|
483
|
+
) : (
|
|
484
|
+
<p
|
|
485
|
+
className={part(
|
|
486
|
+
"sc-surface__connect",
|
|
487
|
+
...(working ? (["sc-surface__connect--done"] as const) : []),
|
|
488
|
+
)}
|
|
489
|
+
>
|
|
490
|
+
{working && <CheckIcon aria-hidden weight="bold" />}
|
|
491
|
+
{connectWords(messages.connect, working, failed)}
|
|
492
|
+
</p>
|
|
493
|
+
)}
|
|
494
|
+
</Card>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** The quiet word an app's card says where its button isn't: working, failed, or on its way. */
|
|
499
|
+
function connectWords(words: KitMessages["connect"], working: boolean, failed: boolean): string {
|
|
500
|
+
if (working) return words.connected;
|
|
501
|
+
return failed ? words.failed : words.preparing;
|
|
502
|
+
}
|
|
503
|
+
|
|
455
504
|
/**
|
|
456
505
|
* A button that sends its action, marked once it was the one pressed; or a link that opens its
|
|
457
506
|
* page apart (it stays usable on an older turn: it changes nothing here).
|
package/src/surface/scope.ts
CHANGED
|
@@ -28,7 +28,9 @@ export type SurfacePart =
|
|
|
28
28
|
| "sc-surface__form"
|
|
29
29
|
| "sc-surface__submit"
|
|
30
30
|
| "sc-surface__foot"
|
|
31
|
-
| "sc-surface__sent"
|
|
31
|
+
| "sc-surface__sent"
|
|
32
|
+
| "sc-surface__connect"
|
|
33
|
+
| "sc-surface__connect--done";
|
|
32
34
|
|
|
33
35
|
export type SurfaceClassNames = Partial<Record<SurfacePart | KitPart, string>> | undefined;
|
|
34
36
|
|
package/src/surface/values.ts
CHANGED
|
@@ -63,7 +63,7 @@ export const FEW_OPTIONS = 5;
|
|
|
63
63
|
/** Up to this many, a multiselect is chips; more go in a list to search. */
|
|
64
64
|
export const FEW_CHIPS = 8;
|
|
65
65
|
/** What a secret reads as, anywhere it is shown. */
|
|
66
|
-
|
|
66
|
+
const MASK = "••••••••";
|
|
67
67
|
/** The longest answer label the API takes. */
|
|
68
68
|
const LABEL_MAX = 2000;
|
|
69
69
|
|
|
@@ -139,7 +139,7 @@ export function startValues(fields: FormField[], answered?: Record<string, unkno
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/** A field with nothing given: unticked, none picked, a slider at its least, else nothing. */
|
|
142
|
-
|
|
142
|
+
function emptyOf(field: FormField): FieldValue {
|
|
143
143
|
switch (field.kind) {
|
|
144
144
|
case "checkbox":
|
|
145
145
|
return false;
|
|
@@ -154,7 +154,7 @@ export function emptyOf(field: FormField): FieldValue {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/** A value read as the field's kind holds it, or null when it does not fit. */
|
|
157
|
-
|
|
157
|
+
function fit(field: FormField, raw: unknown): FieldValue {
|
|
158
158
|
switch (field.kind) {
|
|
159
159
|
case "checkbox":
|
|
160
160
|
return raw === true || raw === "true"
|
|
@@ -310,7 +310,7 @@ export function problemOf(
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
/** Whether nothing was given: no words, no pick, an unticked box, no stars. */
|
|
313
|
-
|
|
313
|
+
function isEmpty(field: FormField, value: FieldValue): boolean {
|
|
314
314
|
if (value === null) return true;
|
|
315
315
|
if (field.kind === "checkbox") return value !== true;
|
|
316
316
|
if (typeof value === "string") return value.trim() === "";
|
|
@@ -352,7 +352,7 @@ function outOfBounds(
|
|
|
352
352
|
return null;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
|
|
355
|
+
function optionOf(field: FormField, id: string): Option | undefined {
|
|
356
356
|
return field.options?.find((option) => option.id === id);
|
|
357
357
|
}
|
|
358
358
|
|
package/src/tint.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type AgentTint = "sky" | "rose" | "sand";
|
|
|
4
4
|
const TINTS: readonly AgentTint[] = ["sky", "rose", "sand"];
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* The tint an agent keeps wherever it appears (
|
|
7
|
+
* The tint an agent keeps wherever it appears (the agents' day, its profile, its card): picked from
|
|
8
8
|
* its id, so a rename changes nothing, and spread evenly over the three.
|
|
9
9
|
*/
|
|
10
10
|
export function agentTint(id: string): AgentTint {
|
package/src/types.ts
CHANGED
|
@@ -90,6 +90,12 @@ export interface Conversation {
|
|
|
90
90
|
* host's app (`host_person`, its `contact` then) or the widget's visitor.
|
|
91
91
|
*/
|
|
92
92
|
person?: ConversationPerson | null | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* For the person it is with: how many of the business's messages came since they last read it,
|
|
95
|
+
* and whether it waits on them (a question, their yes); left out by an older API.
|
|
96
|
+
*/
|
|
97
|
+
unread?: number | undefined;
|
|
98
|
+
waiting_on_person?: boolean | undefined;
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
/** Who a person's token or a publishable key started a conversation for. */
|
|
@@ -237,6 +243,8 @@ export interface Request {
|
|
|
237
243
|
input: Record<string, unknown>;
|
|
238
244
|
/** The person's when left out (an API from before it). */
|
|
239
245
|
decides?: Decides | undefined;
|
|
246
|
+
/** The JSON Schema of its input, which an edit given with the yes must fit. */
|
|
247
|
+
schema?: Record<string, unknown> | null | undefined;
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
/** An action a reply called: what, how it went, and what it drew. */
|
|
@@ -306,6 +314,9 @@ export interface Message {
|
|
|
306
314
|
heard?: string | null | undefined;
|
|
307
315
|
/** On a channel, a person's message: what was decided of it; null otherwise. */
|
|
308
316
|
decision?: Decision | null | undefined;
|
|
317
|
+
/** The agent's words first, without a turn of the person's, and the run whose code sent them. */
|
|
318
|
+
proactive?: boolean | undefined;
|
|
319
|
+
run_id?: string | null | undefined;
|
|
309
320
|
created_at: string;
|
|
310
321
|
}
|
|
311
322
|
|
package/src/useConversation.ts
CHANGED
|
@@ -444,6 +444,8 @@ export interface LiveConversation {
|
|
|
444
444
|
say: (text: string, files?: readonly string[]) => boolean;
|
|
445
445
|
/** What the person did on a surface; false when the socket is closed. */
|
|
446
446
|
act: (action: Action) => boolean;
|
|
447
|
+
/** Several things done at once (a yes or no on each call a reply waits on), sent together. */
|
|
448
|
+
actAll: (actions: readonly Action[]) => boolean;
|
|
447
449
|
/** Stops the reply on its way; false when the socket is closed. */
|
|
448
450
|
stop: () => boolean;
|
|
449
451
|
/** The conversation's browser while it is open: where it is, who holds it. */
|
|
@@ -605,6 +607,13 @@ export function useConversation(
|
|
|
605
607
|
return true;
|
|
606
608
|
};
|
|
607
609
|
|
|
610
|
+
const actAll = (actions: readonly Action[]): boolean => {
|
|
611
|
+
const sent = actions.map((one) => ({ type: "action" as const, ...one }));
|
|
612
|
+
if (!live.send({ type: "actions", actions: sent })) return false;
|
|
613
|
+
for (const action of actions) dispatch({ type: "acted", action });
|
|
614
|
+
return true;
|
|
615
|
+
};
|
|
616
|
+
|
|
608
617
|
return {
|
|
609
618
|
messages: state.messages,
|
|
610
619
|
draft: state.draft,
|
|
@@ -615,6 +624,7 @@ export function useConversation(
|
|
|
615
624
|
error: state.error,
|
|
616
625
|
say,
|
|
617
626
|
act,
|
|
627
|
+
actAll,
|
|
618
628
|
stop: () => live.send({ type: "stop" }),
|
|
619
629
|
browser: state.browser,
|
|
620
630
|
watchScreen: screen.watch,
|
package/src/usePolled.ts
CHANGED
|
@@ -3,14 +3,14 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import { ApiError } from "./client";
|
|
4
4
|
|
|
5
5
|
/** What a read gave: the value once it came, why it failed, or that it is no longer there. */
|
|
6
|
-
|
|
6
|
+
interface Polled<T> {
|
|
7
7
|
value: T | null;
|
|
8
8
|
problem: unknown;
|
|
9
9
|
gone: boolean;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/** How often something still moving (a run under way) is read again. */
|
|
13
|
-
|
|
13
|
+
const POLL_MS = 3000;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Reads `load` when `key` changes, and again every `POLL_MS` while `moving` says what it gave is
|
package/src/voice/webgl.ts
CHANGED
|
@@ -77,7 +77,7 @@ export function inkOver(color: Rgb, surface: Rgb): Rgb {
|
|
|
77
77
|
let swatch: CanvasRenderingContext2D | null | undefined;
|
|
78
78
|
|
|
79
79
|
/** A CSS colour as red, green, blue and alpha, each 0 to 1; null where it can't be read. */
|
|
80
|
-
|
|
80
|
+
function rgbaOf(css: string): readonly [number, number, number, number] | null {
|
|
81
81
|
if (swatch === undefined) {
|
|
82
82
|
swatch = document.createElement("canvas").getContext("2d", { willReadFrequently: true });
|
|
83
83
|
}
|
package/src/widget/element.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { createRoot, type Root } from "react-dom/client";
|
|
|
5
5
|
|
|
6
6
|
import type { ChatSteps } from "../AgentChat";
|
|
7
7
|
import { AgentSpace, SPACE_MESSAGES } from "../AgentSpace";
|
|
8
|
+
import { useUnread } from "../Threads";
|
|
8
9
|
import { createClient } from "../client";
|
|
9
10
|
import type { Credentials } from "../credentials";
|
|
10
11
|
import { SPACE_MESSAGES_ES } from "../es";
|
|
@@ -61,9 +62,11 @@ export interface WidgetSettings {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
/** The launcher's words, by language. */
|
|
64
|
-
const LAUNCHER: Readonly<
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const LAUNCHER: Readonly<
|
|
66
|
+
Record<Language, { open: string; close: string; chat: string; news: string }>
|
|
67
|
+
> = {
|
|
68
|
+
en: { open: "Open the chat", close: "Close the chat", chat: "Chat", news: "something new" },
|
|
69
|
+
es: { open: "Abrir el chat", close: "Cerrar el chat", chat: "Chat", news: "hay novedades" },
|
|
67
70
|
};
|
|
68
71
|
|
|
69
72
|
/**
|
|
@@ -254,6 +257,11 @@ export function Widget({ settings, look = null }: WidgetProps) {
|
|
|
254
257
|
const [open, setOpen] = useState(false);
|
|
255
258
|
const words = LAUNCHER[language];
|
|
256
259
|
const floating = mode === "floating";
|
|
260
|
+
// While closed, a dot on the launcher says something waits (an unread message, a question), for
|
|
261
|
+
// someone who talked before: nobody new is asked after until they open it.
|
|
262
|
+
const returning = keptConversation(agent) !== undefined;
|
|
263
|
+
const news = useUnread(client, agent, floating && !open && returning && client.who === "person");
|
|
264
|
+
const waits = news.unread > 0 || news.waiting;
|
|
257
265
|
const header = settings.header || look?.header === true;
|
|
258
266
|
|
|
259
267
|
const space = (
|
|
@@ -295,7 +303,7 @@ export function Widget({ settings, look = null }: WidgetProps) {
|
|
|
295
303
|
<button
|
|
296
304
|
type="button"
|
|
297
305
|
part="launcher"
|
|
298
|
-
aria-label={open ? words.close : words.open}
|
|
306
|
+
aria-label={open ? words.close : waits ? `${words.open}: ${words.news}` : words.open}
|
|
299
307
|
aria-expanded={open}
|
|
300
308
|
onClick={() => {
|
|
301
309
|
setOpen(!open);
|
|
@@ -307,6 +315,7 @@ export function Widget({ settings, look = null }: WidgetProps) {
|
|
|
307
315
|
) : (
|
|
308
316
|
<ChatCircleDotsIcon aria-hidden weight="fill" />
|
|
309
317
|
)}
|
|
318
|
+
{!open && waits && <span aria-hidden className="sc-widget__dot" />}
|
|
310
319
|
</button>
|
|
311
320
|
</div>
|
|
312
321
|
);
|
package/src/widget/widget.css
CHANGED
|
@@ -104,6 +104,19 @@ signalcore-chat[shadow="none"][mode="inline"] {
|
|
|
104
104
|
height: 46%;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/* While the chat is closed and something waits in it (an unread message, a question): a dot at
|
|
108
|
+
the launcher's corner, ringed in the page's surface so it reads on the accent. */
|
|
109
|
+
.sc-widget__dot {
|
|
110
|
+
position: absolute;
|
|
111
|
+
top: 6%;
|
|
112
|
+
right: 6%;
|
|
113
|
+
width: 22%;
|
|
114
|
+
height: 22%;
|
|
115
|
+
border: 2px solid var(--_surface);
|
|
116
|
+
border-radius: var(--_radius-full);
|
|
117
|
+
background: var(--_danger);
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
.sc-widget--floating > .sc-widget__panel {
|
|
108
121
|
position: fixed;
|
|
109
122
|
right: var(--_offset);
|