@braincrew-lab/langchain-canvas 0.2.0 → 0.4.9
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/dist/{ChartRenderer-AAWVGKV5.js → ChartRenderer-JGRJ23OB.js} +10 -20
- package/dist/{DocumentRenderer-NBRBPYU6.js → DocumentRenderer-ZQDQMX7X.js} +8 -4
- package/dist/FileRenderer-3ZHNORZJ.js +39 -0
- package/dist/SlidesRenderer-56R3TNWN.js +496 -0
- package/dist/{TableRenderer-VKDD3CB6.js → TableRenderer-3ESF577F.js} +49 -193
- package/dist/chunk-7T5DRR3F.js +109 -0
- package/dist/{chunk-S54GJDSJ.js → chunk-EHW446VF.js} +62 -47
- package/dist/chunk-FTNRRJ3K.js +13 -0
- package/dist/chunk-IFNRLN4Y.js +137 -0
- package/dist/{chunk-UL5F66PN.js → chunk-K2UZAYW2.js} +1 -1
- package/dist/chunk-SGOPRUQ4.js +182 -0
- package/dist/formula-27TCEZI5.js +2 -0
- package/dist/formula-cli.d.ts +2 -0
- package/dist/formula-cli.js +37 -0
- package/dist/index.d.ts +201 -289
- package/dist/index.js +393 -860
- package/dist/langgraph/index.d.ts +68 -0
- package/dist/langgraph/index.js +111 -0
- package/dist/styles.css +125 -100
- package/dist/types-BfGP9R2I.d.ts +324 -0
- package/package.json +40 -5
- package/dist/PdfRenderer-DPQT4E7O.js +0 -97
- package/dist/SlidesRenderer-6ZGHXTQX.js +0 -877
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { C as CanvasTransport, E as ElementSelection, S as StreamEvent } from '../types-BfGP9R2I.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `langgraphTransport` — speak to a LangGraph server without a translator
|
|
5
|
+
* in between.
|
|
6
|
+
*
|
|
7
|
+
* Built on the official `@langchain/langgraph-sdk` (the server's wire format
|
|
8
|
+
* belongs to LangGraph and evolves with it — we ride the official client
|
|
9
|
+
* rather than hand-parse it). Verified against `langgraph dev` (local);
|
|
10
|
+
* hosted LangGraph Platform is untested — open an issue if you need it.
|
|
11
|
+
*
|
|
12
|
+
* Per user turn it: maps the canvas thread id to the UUID LangGraph requires,
|
|
13
|
+
* makes sure the thread exists, frames any element selections into the
|
|
14
|
+
* message (targeted edits), then streams the run with
|
|
15
|
+
* `streamMode: ["messages-tuple", "custom"]` through the translation in
|
|
16
|
+
* `translate.ts`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface LangGraphTransportOptions {
|
|
20
|
+
/** LangGraph server URL, e.g. `http://127.0.0.1:2024` (`langgraph dev`). */
|
|
21
|
+
url: string;
|
|
22
|
+
/** Graph/assistant to run, e.g. `"canvas_agent"`. */
|
|
23
|
+
assistantId: string;
|
|
24
|
+
/** Extra headers (e.g. auth) passed to the SDK client. */
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
/** Frame a targeted edit so the agent changes only the selected element(s). */
|
|
28
|
+
declare function withSelections(message: string, selections: ElementSelection[]): string;
|
|
29
|
+
/**
|
|
30
|
+
* LangGraph requires UUID thread ids; the canvas allows any string. Non-UUID
|
|
31
|
+
* ids map deterministically (RFC 4122 v5 over `canvas-thread:<id>`), matching
|
|
32
|
+
* the mapping the Python bridge example uses — same id in, same UUID out.
|
|
33
|
+
*/
|
|
34
|
+
declare function threadUuid(threadId: string): Promise<string>;
|
|
35
|
+
declare function langgraphTransport(options: LangGraphTransportOptions): CanvasTransport;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Translate a LangGraph run stream into Canvas Wire Protocol events.
|
|
39
|
+
*
|
|
40
|
+
* Input: the `{event, data}` chunks the LangGraph SDK yields for a run
|
|
41
|
+
* streamed with `streamMode: ["messages-tuple", "custom"]`. Output: the
|
|
42
|
+
* `StreamEvent`s the canvas applies. The mapping:
|
|
43
|
+
*
|
|
44
|
+
* - `messages` AIMessageChunk text → `message.delta`
|
|
45
|
+
* - `messages` AIMessageChunk tool chunks → `tool.start` (once per call id)
|
|
46
|
+
* - `messages` tool result → `tool.end`
|
|
47
|
+
* - `custom` `canvas.*` → passed through untouched
|
|
48
|
+
* - `error` → `error`
|
|
49
|
+
* - stream end → `message.end` + `done`
|
|
50
|
+
*
|
|
51
|
+
* The chunk shapes are pinned by a captured fixture from a real
|
|
52
|
+
* `langgraph dev` run (`__fixtures__/langgraph-run.json`) — notably, model
|
|
53
|
+
* content arrives as block arrays (`{type: "text" | "tool_use"}`), not plain
|
|
54
|
+
* strings.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/** One chunk from `client.runs.stream(...)` — the SDK's `{event, data}` pair. */
|
|
58
|
+
interface LangGraphStreamChunk {
|
|
59
|
+
event: string;
|
|
60
|
+
data: unknown;
|
|
61
|
+
}
|
|
62
|
+
/** Text of a message chunk — models may stream content as block lists. */
|
|
63
|
+
declare function chunkText(content: unknown): string;
|
|
64
|
+
declare function translateLangGraphStream(chunks: AsyncIterable<LangGraphStreamChunk> | Iterable<LangGraphStreamChunk>, options?: {
|
|
65
|
+
messageId?: string;
|
|
66
|
+
}): AsyncGenerator<StreamEvent>;
|
|
67
|
+
|
|
68
|
+
export { type LangGraphStreamChunk, type LangGraphTransportOptions, chunkText, langgraphTransport, threadUuid, translateLangGraphStream, withSelections };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Client } from '@langchain/langgraph-sdk';
|
|
2
|
+
|
|
3
|
+
// src/langgraph/transport.ts
|
|
4
|
+
|
|
5
|
+
// src/langgraph/translate.ts
|
|
6
|
+
function chunkText(content) {
|
|
7
|
+
if (typeof content === "string") return content;
|
|
8
|
+
if (Array.isArray(content)) {
|
|
9
|
+
return content.filter(
|
|
10
|
+
(block) => typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string"
|
|
11
|
+
).map((block) => block.text).join("");
|
|
12
|
+
}
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
async function* translateLangGraphStream(chunks, options = {}) {
|
|
19
|
+
const messageId = options.messageId ?? `msg_${Math.random().toString(36).slice(2, 14)}`;
|
|
20
|
+
const startedTools = /* @__PURE__ */ new Set();
|
|
21
|
+
for await (const chunk of chunks) {
|
|
22
|
+
if (chunk.event === "error") {
|
|
23
|
+
const detail = typeof chunk.data === "string" ? chunk.data : JSON.stringify(chunk.data);
|
|
24
|
+
yield { type: "error", message: `agent run failed: ${detail}` };
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (chunk.event === "custom") {
|
|
28
|
+
if (isRecord(chunk.data) && String(chunk.data.type ?? "").startsWith("canvas.")) {
|
|
29
|
+
yield chunk.data;
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (chunk.event !== "messages" || !Array.isArray(chunk.data) || chunk.data.length === 0) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const msg = chunk.data[0];
|
|
37
|
+
if (!isRecord(msg)) continue;
|
|
38
|
+
const meta = chunk.data.length > 1 && isRecord(chunk.data[1]) ? chunk.data[1] : {};
|
|
39
|
+
const fromToolsNode = meta.langgraph_node === "tools";
|
|
40
|
+
if (msg.type === "AIMessageChunk") {
|
|
41
|
+
const calls = Array.isArray(msg.tool_call_chunks) ? msg.tool_call_chunks : [];
|
|
42
|
+
for (const call of calls) {
|
|
43
|
+
if (!isRecord(call)) continue;
|
|
44
|
+
const id = typeof call.id === "string" ? call.id : null;
|
|
45
|
+
const name = typeof call.name === "string" ? call.name : null;
|
|
46
|
+
if (id && name && !startedTools.has(id)) {
|
|
47
|
+
startedTools.add(id);
|
|
48
|
+
yield { type: "tool.start", toolCallId: id, name };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const text = chunkText(msg.content);
|
|
52
|
+
if (text && !fromToolsNode) yield { type: "message.delta", messageId, text };
|
|
53
|
+
} else if (msg.type === "tool") {
|
|
54
|
+
const id = typeof msg.tool_call_id === "string" ? msg.tool_call_id : null;
|
|
55
|
+
if (id) yield { type: "tool.end", toolCallId: id, ok: msg.status !== "error" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
yield { type: "message.end", messageId };
|
|
59
|
+
yield { type: "done" };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/langgraph/transport.ts
|
|
63
|
+
function withSelections(message, selections) {
|
|
64
|
+
if (selections.length === 0) return message;
|
|
65
|
+
const listed = selections.map((s) => `- \`${s.selector}\` (data-cid=${s.cid})`).join("\n");
|
|
66
|
+
const artifactId = selections[0].artifactId;
|
|
67
|
+
return `${message}
|
|
68
|
+
|
|
69
|
+
[Targeted edit] Apply the change to these selected element(s) in file \`${artifactId}\`:
|
|
70
|
+
${listed}
|
|
71
|
+
First call read_canvas on the file to get its current content and revision, then call edit_canvas with the element's exact current outer HTML as \`old\` and your replacement as \`new\` (keep the data-cid attribute).`;
|
|
72
|
+
}
|
|
73
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
74
|
+
var NAMESPACE_URL = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
|
|
75
|
+
async function threadUuid(threadId) {
|
|
76
|
+
if (UUID_RE.test(threadId)) return threadId.toLowerCase();
|
|
77
|
+
const name = new TextEncoder().encode(`canvas-thread:${threadId}`);
|
|
78
|
+
const namespace = NAMESPACE_URL.replace(/-/g, "");
|
|
79
|
+
const namespaceBytes = new Uint8Array(16);
|
|
80
|
+
for (let i = 0; i < 16; i++) namespaceBytes[i] = parseInt(namespace.slice(i * 2, i * 2 + 2), 16);
|
|
81
|
+
const payload = new Uint8Array(namespaceBytes.length + name.length);
|
|
82
|
+
payload.set(namespaceBytes);
|
|
83
|
+
payload.set(name, namespaceBytes.length);
|
|
84
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-1", payload)).slice(0, 16);
|
|
85
|
+
digest[6] = digest[6] & 15 | 80;
|
|
86
|
+
digest[8] = digest[8] & 63 | 128;
|
|
87
|
+
const hex = [...digest].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
88
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
89
|
+
}
|
|
90
|
+
function langgraphTransport(options) {
|
|
91
|
+
const client = new Client({ apiUrl: options.url, defaultHeaders: options.headers });
|
|
92
|
+
const knownThreads = /* @__PURE__ */ new Set();
|
|
93
|
+
return {
|
|
94
|
+
async *stream(request) {
|
|
95
|
+
const threadId = await threadUuid(request.threadId);
|
|
96
|
+
if (!knownThreads.has(threadId)) {
|
|
97
|
+
await client.threads.create({ threadId, ifExists: "do_nothing" });
|
|
98
|
+
knownThreads.add(threadId);
|
|
99
|
+
}
|
|
100
|
+
const message = withSelections(request.message, request.selections ?? []);
|
|
101
|
+
const chunks = client.runs.stream(threadId, options.assistantId, {
|
|
102
|
+
input: { messages: [{ role: "user", content: message }] },
|
|
103
|
+
streamMode: ["messages-tuple", "custom"],
|
|
104
|
+
signal: request.signal
|
|
105
|
+
});
|
|
106
|
+
yield* translateLangGraphStream(chunks);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { chunkText, langgraphTransport, threadUuid, translateLangGraphStream, withSelections };
|
package/dist/styles.css
CHANGED
|
@@ -143,8 +143,74 @@
|
|
|
143
143
|
.cv-undo button:hover:not(:disabled) { background: var(--cv-surface); }
|
|
144
144
|
.cv-undo button:disabled { opacity: 0.35; cursor: default; }
|
|
145
145
|
|
|
146
|
-
.cv-versions { display: flex; align-items: center; gap: 6px; }
|
|
147
|
-
.cv-versions__label {
|
|
146
|
+
.cv-versions { position: relative; display: flex; align-items: center; gap: 6px; }
|
|
147
|
+
.cv-versions__label {
|
|
148
|
+
font-size: 12px;
|
|
149
|
+
color: var(--cv-muted);
|
|
150
|
+
font-variant-numeric: tabular-nums;
|
|
151
|
+
border: none;
|
|
152
|
+
background: none;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
padding: 2px 4px;
|
|
155
|
+
border-radius: 6px;
|
|
156
|
+
}
|
|
157
|
+
.cv-versions__label:hover { background: var(--cv-surface); color: var(--cv-text); }
|
|
158
|
+
.cv-versions__list {
|
|
159
|
+
position: absolute;
|
|
160
|
+
top: calc(100% + 6px);
|
|
161
|
+
right: 0;
|
|
162
|
+
z-index: 30;
|
|
163
|
+
min-width: 220px;
|
|
164
|
+
max-height: 260px;
|
|
165
|
+
overflow-y: auto;
|
|
166
|
+
margin: 0;
|
|
167
|
+
padding: 4px;
|
|
168
|
+
list-style: none;
|
|
169
|
+
border: 1px solid var(--cv-border);
|
|
170
|
+
border-radius: 10px;
|
|
171
|
+
background: var(--cv-surface);
|
|
172
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
173
|
+
}
|
|
174
|
+
.cv-versions__list button {
|
|
175
|
+
display: flex;
|
|
176
|
+
gap: 8px;
|
|
177
|
+
align-items: baseline;
|
|
178
|
+
width: 100%;
|
|
179
|
+
padding: 6px 8px;
|
|
180
|
+
border: none;
|
|
181
|
+
border-radius: 6px;
|
|
182
|
+
background: none;
|
|
183
|
+
color: var(--cv-text);
|
|
184
|
+
text-align: left;
|
|
185
|
+
font-size: 12px;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
}
|
|
188
|
+
.cv-versions__list button:hover { background: var(--cv-bg); }
|
|
189
|
+
.cv-versions__list button.is-current { background: var(--cv-bg); font-weight: 600; }
|
|
190
|
+
.cv-versions__v { color: var(--cv-muted); font-variant-numeric: tabular-nums; flex: none; }
|
|
191
|
+
.cv-versions__desc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
192
|
+
|
|
193
|
+
/* Historical snapshot view: content is visible and scrollable but not editable
|
|
194
|
+
(children lose hit-testing; the scroll container itself still gets the wheel). */
|
|
195
|
+
.cv-history-banner {
|
|
196
|
+
display: flex;
|
|
197
|
+
gap: 8px;
|
|
198
|
+
align-items: center;
|
|
199
|
+
padding: 6px 14px;
|
|
200
|
+
font-size: 12px;
|
|
201
|
+
color: var(--cv-muted);
|
|
202
|
+
background: var(--cv-surface);
|
|
203
|
+
border-bottom: 1px solid var(--cv-border);
|
|
204
|
+
}
|
|
205
|
+
.cv-history-banner button {
|
|
206
|
+
border: none;
|
|
207
|
+
background: none;
|
|
208
|
+
color: var(--cv-accent, #4f46e5);
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
font-size: 12px;
|
|
211
|
+
padding: 0;
|
|
212
|
+
}
|
|
213
|
+
.cv-body--history > * { pointer-events: none; }
|
|
148
214
|
.cv-versions__nav {
|
|
149
215
|
width: 24px;
|
|
150
216
|
height: 24px;
|
|
@@ -212,16 +278,6 @@
|
|
|
212
278
|
.cv-body--flush { padding: 0; overflow: hidden; }
|
|
213
279
|
.cv-fallback { color: var(--cv-muted); font-size: 14px; }
|
|
214
280
|
|
|
215
|
-
/* Browsing an old version: a preview, not an editing surface. Interaction is
|
|
216
|
-
blocked wholesale — renderers patch by artifact id, so edits made "in the
|
|
217
|
-
past" would silently overwrite the live version. */
|
|
218
|
-
.cv-body--history > :not(.cv-history-note) { pointer-events: none; user-select: none; }
|
|
219
|
-
.cv-history-note {
|
|
220
|
-
position: sticky; top: 0; z-index: 5; margin-bottom: 12px;
|
|
221
|
-
padding: 6px 12px; border-radius: 8px; font-size: 12.5px; font-weight: 600;
|
|
222
|
-
color: var(--cv-muted); background: var(--cv-surface); border: 1px solid var(--cv-border);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
281
|
/* --- inline editing (document / table / slides) ------------------------------- */
|
|
226
282
|
|
|
227
283
|
.cv-edit-toolbar { display: flex; justify-content: flex-end; margin-bottom: 10px; }
|
|
@@ -414,23 +470,6 @@
|
|
|
414
470
|
.cv-sheet-tools__filter { min-width: 96px; max-width: 160px; }
|
|
415
471
|
.cv-sheet-tools__sort:focus, .cv-sheet-tools__filter:focus { outline: none; border-color: var(--cv-accent); }
|
|
416
472
|
.cv-sheet-tools__hint { margin-left: auto; font-size: 11px; color: var(--cv-muted); }
|
|
417
|
-
|
|
418
|
-
/* selection formatting cluster — reads as one tight group between separators */
|
|
419
|
-
.cv-sheet-tools__fmt { display: inline-flex; align-items: center; gap: 3px; }
|
|
420
|
-
.cv-sheet-tools__fmt button:disabled,
|
|
421
|
-
.cv-sheet-tools button:disabled { opacity: 0.4; cursor: default; }
|
|
422
|
-
.cv-sheet-tools button:disabled:hover { border-color: var(--cv-border); color: var(--cv-text); }
|
|
423
|
-
.cv-sheet-tools__bold, .cv-sheet-tools__align { width: 28px; padding: 4px 0 !important; text-align: center; }
|
|
424
|
-
.cv-sheet-tools__bold--on,
|
|
425
|
-
.cv-sheet-tools__freeze--on {
|
|
426
|
-
background: var(--cv-accent-weak) !important; border-color: var(--cv-accent) !important;
|
|
427
|
-
color: var(--cv-accent) !important;
|
|
428
|
-
}
|
|
429
|
-
.cv-sheet-tools__color {
|
|
430
|
-
width: 28px; height: 26px; padding: 2px; border: 1px solid var(--cv-border);
|
|
431
|
-
border-radius: 6px; background: var(--cv-bg); cursor: pointer;
|
|
432
|
-
}
|
|
433
|
-
.cv-sheet-tools__color:disabled { opacity: 0.4; cursor: default; }
|
|
434
473
|
.cv-sheet { position: relative; width: 100%; flex: 1; height: auto; min-height: 0; }
|
|
435
474
|
.cv-sheet .fortune-container { height: 100% !important; width: 100% !important; }
|
|
436
475
|
.cv-sheet--empty { display: grid; place-items: center; height: 200px; color: var(--cv-muted); font-size: 14px; }
|
|
@@ -640,31 +679,6 @@
|
|
|
640
679
|
.cv-free__fmt input[type="number"] { width: 46px; height: 24px; border: 1px solid var(--cv-border); border-radius: 5px; background: var(--cv-bg); color: var(--cv-text); font-size: 12px; padding: 0 4px; }
|
|
641
680
|
.cv-free__fmt input[type="color"] { width: 26px; height: 24px; padding: 0; border: 1px solid var(--cv-border); border-radius: 5px; background: none; cursor: pointer; }
|
|
642
681
|
|
|
643
|
-
/* multi-select: marquee, selection state, floating action bar */
|
|
644
|
-
.cv-free { outline: none; } /* the canvas is focusable for keyboard nudge/shortcuts */
|
|
645
|
-
.cv-free:focus-visible { outline: 2px solid var(--cv-accent); outline-offset: -2px; border-radius: 4px; }
|
|
646
|
-
.cv-free__marquee {
|
|
647
|
-
position: absolute; z-index: 7; pointer-events: none;
|
|
648
|
-
border: 1px solid var(--cv-accent);
|
|
649
|
-
background: color-mix(in srgb, var(--cv-accent) 10%, transparent);
|
|
650
|
-
border-radius: 2px;
|
|
651
|
-
}
|
|
652
|
-
.cv-free__el--selected { outline: 1.5px solid var(--cv-accent); outline-offset: 1px; }
|
|
653
|
-
.cv-free__multibar {
|
|
654
|
-
position: absolute; z-index: 8; transform: translate(-50%, calc(-100% - 8px));
|
|
655
|
-
display: flex; align-items: center; gap: 2px;
|
|
656
|
-
padding: 3px; background: var(--cv-bg); border: 1px solid var(--cv-border);
|
|
657
|
-
border-radius: 8px; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16); cursor: default;
|
|
658
|
-
}
|
|
659
|
-
.cv-free__multibar--below { transform: translate(-50%, 8px); }
|
|
660
|
-
.cv-free__multibar button {
|
|
661
|
-
min-width: 24px; height: 24px; padding: 0 6px; border: none; border-radius: 5px;
|
|
662
|
-
background: transparent; color: var(--cv-text); cursor: pointer; font-size: 12px; white-space: nowrap;
|
|
663
|
-
}
|
|
664
|
-
.cv-free__multibar button:hover { background: var(--cv-surface); }
|
|
665
|
-
.cv-free__multibar button:disabled { opacity: 0.4; cursor: default; background: transparent; }
|
|
666
|
-
.cv-free__multibar-del:hover { background: #fee2e2 !important; color: #dc2626; }
|
|
667
|
-
|
|
668
682
|
/* toolbar: background swatch + present button */
|
|
669
683
|
.cv-deck__bg input[type="color"] {
|
|
670
684
|
width: 30px; height: 30px; padding: 0;
|
|
@@ -708,22 +722,6 @@
|
|
|
708
722
|
background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.08);
|
|
709
723
|
border-radius: 10px; padding: 12px 16px; cursor: default; white-space: pre-wrap;
|
|
710
724
|
}
|
|
711
|
-
/* Deck progress: a hairline track pinned to the bottom edge, plus a counter chip. */
|
|
712
|
-
.cv-present__progress {
|
|
713
|
-
position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
|
|
714
|
-
background: rgba(255, 255, 255, 0.08);
|
|
715
|
-
}
|
|
716
|
-
.cv-present__progress-fill {
|
|
717
|
-
height: 100%; background: var(--cv-accent);
|
|
718
|
-
transition: width 0.28s ease;
|
|
719
|
-
}
|
|
720
|
-
@media (prefers-reduced-motion: reduce) { .cv-present__progress-fill { transition: none; } }
|
|
721
|
-
.cv-present__count {
|
|
722
|
-
position: absolute; top: 16px; right: 20px;
|
|
723
|
-
color: #9aa4b2; font-size: 13px; font-variant-numeric: tabular-nums;
|
|
724
|
-
background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.08);
|
|
725
|
-
border-radius: 999px; padding: 3px 10px; cursor: default;
|
|
726
|
-
}
|
|
727
725
|
|
|
728
726
|
/* in-place editing cues */
|
|
729
727
|
.cv-word .cv-doc { cursor: text; }
|
|
@@ -1154,36 +1152,6 @@
|
|
|
1154
1152
|
color: var(--cv-muted);
|
|
1155
1153
|
}
|
|
1156
1154
|
|
|
1157
|
-
/* --- PDF viewer ---------------------------------------------------------------- */
|
|
1158
|
-
|
|
1159
|
-
/* Fills the flush body outright — `flex: 1` alone collapsed the iframe to its
|
|
1160
|
-
content height (a thin strip of page) because `.cv-body` is a block, not a
|
|
1161
|
-
flex container. */
|
|
1162
|
-
.cv-pdf-panel { display: flex; flex-direction: column; height: 100%; min-height: 480px; }
|
|
1163
|
-
.cv-pdf-tools {
|
|
1164
|
-
display: flex; align-items: center; gap: 6px; padding: 6px 10px;
|
|
1165
|
-
border-bottom: 1px solid var(--cv-border); background: var(--cv-bg); flex: 0 0 auto;
|
|
1166
|
-
}
|
|
1167
|
-
.cv-pdf-tools button {
|
|
1168
|
-
font-size: 12px; font-weight: 600; padding: 4px 10px; border: 1px solid var(--cv-border);
|
|
1169
|
-
border-radius: 6px; background: var(--cv-bg); color: var(--cv-text); cursor: pointer;
|
|
1170
|
-
}
|
|
1171
|
-
.cv-pdf-tools button:hover { border-color: var(--cv-accent); color: var(--cv-accent); }
|
|
1172
|
-
.cv-pdf-tools__name {
|
|
1173
|
-
font-size: 12px; font-weight: 600; color: var(--cv-muted);
|
|
1174
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 55%;
|
|
1175
|
-
}
|
|
1176
|
-
.cv-pdf-tools__spacer { flex: 1; }
|
|
1177
|
-
.cv-pdf-tools__sep { width: 1px; height: 18px; background: var(--cv-border); margin: 0 2px; }
|
|
1178
|
-
.cv-pdf-tools__zoom { display: inline-flex; align-items: center; gap: 3px; }
|
|
1179
|
-
.cv-pdf-tools__zoom button { min-width: 26px; }
|
|
1180
|
-
.cv-pdf-tools__zoom-label {
|
|
1181
|
-
min-width: 52px; font-variant-numeric: tabular-nums; color: var(--cv-muted);
|
|
1182
|
-
}
|
|
1183
|
-
.cv-pdf { position: relative; flex: 1; min-height: 0; background: var(--cv-surface); }
|
|
1184
|
-
.cv-pdf__frame { display: block; width: 100%; height: 100%; border: 0; }
|
|
1185
|
-
.cv-pdf--empty { display: grid; place-items: center; min-height: 200px; color: var(--cv-muted); }
|
|
1186
|
-
|
|
1187
1155
|
/* --- inline artifact card (for the transcript) -------------------------------- */
|
|
1188
1156
|
|
|
1189
1157
|
.cv-card {
|
|
@@ -1243,6 +1211,63 @@
|
|
|
1243
1211
|
|
|
1244
1212
|
/* --- responsive --------------------------------------------------------------- */
|
|
1245
1213
|
|
|
1214
|
+
/* --- file artifact (uploads shown as themselves) ------------------------------ */
|
|
1215
|
+
|
|
1216
|
+
.cv-file {
|
|
1217
|
+
display: flex;
|
|
1218
|
+
flex-direction: column;
|
|
1219
|
+
align-items: center;
|
|
1220
|
+
gap: 14px;
|
|
1221
|
+
max-width: 640px;
|
|
1222
|
+
margin: 0 auto;
|
|
1223
|
+
}
|
|
1224
|
+
.cv-file__image,
|
|
1225
|
+
.cv-file__cover {
|
|
1226
|
+
max-width: 100%;
|
|
1227
|
+
max-height: 60vh;
|
|
1228
|
+
border: 1px solid var(--cv-border);
|
|
1229
|
+
border-radius: 12px;
|
|
1230
|
+
background: #fff;
|
|
1231
|
+
}
|
|
1232
|
+
.cv-file__excerpt {
|
|
1233
|
+
width: 100%;
|
|
1234
|
+
max-height: 40vh;
|
|
1235
|
+
overflow: auto;
|
|
1236
|
+
margin: 0;
|
|
1237
|
+
padding: 14px 16px;
|
|
1238
|
+
border: 1px solid var(--cv-border);
|
|
1239
|
+
border-radius: 12px;
|
|
1240
|
+
background: var(--cv-bg);
|
|
1241
|
+
font: 12px/1.6 ui-monospace, Menlo, Consolas, monospace;
|
|
1242
|
+
white-space: pre-wrap;
|
|
1243
|
+
word-break: break-word;
|
|
1244
|
+
color: var(--cv-text);
|
|
1245
|
+
}
|
|
1246
|
+
.cv-file__card {
|
|
1247
|
+
display: flex;
|
|
1248
|
+
align-items: center;
|
|
1249
|
+
gap: 10px;
|
|
1250
|
+
width: 100%;
|
|
1251
|
+
padding: 10px 14px;
|
|
1252
|
+
border: 1px solid var(--cv-border);
|
|
1253
|
+
border-radius: 12px;
|
|
1254
|
+
background: var(--cv-bg);
|
|
1255
|
+
}
|
|
1256
|
+
.cv-file__icon { font-size: 24px; line-height: 1; }
|
|
1257
|
+
.cv-file__meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
|
|
1258
|
+
.cv-file__meta b { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1259
|
+
.cv-file__facts { font-size: 11px; color: var(--cv-muted); }
|
|
1260
|
+
.cv-file__download {
|
|
1261
|
+
flex: 0 0 auto;
|
|
1262
|
+
padding: 6px 12px;
|
|
1263
|
+
border: 1px solid var(--cv-border);
|
|
1264
|
+
border-radius: 8px;
|
|
1265
|
+
font-size: 12px;
|
|
1266
|
+
color: var(--cv-text);
|
|
1267
|
+
text-decoration: none;
|
|
1268
|
+
}
|
|
1269
|
+
.cv-file__download:hover { border-color: var(--cv-accent); }
|
|
1270
|
+
|
|
1246
1271
|
/* Tablet / narrow desktop: tighten paddings, let toolbars wrap. */
|
|
1247
1272
|
@media (max-width: 900px) {
|
|
1248
1273
|
.cv-body { padding: 16px; }
|