@agent-native/core 0.84.56 → 0.84.58
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/docs/content/locales/ar-SA/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ar-SA/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/hi-IN/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/hi-IN/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/template-clips.mdx +2 -0
- package/corpus/core/docs/content/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/template-clips.mdx +22 -10
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action.ts +11 -0
- package/corpus/core/src/agent/production-agent.ts +24 -46
- package/corpus/core/src/client/AssistantChat.tsx +69 -12
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
- package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +45 -2
- package/corpus/core/src/coding-tools/run-code.ts +1 -0
- package/corpus/core/src/integrations/webhook-handler.ts +10 -9
- package/corpus/core/src/server/action-discovery.ts +3 -0
- package/corpus/core/src/server/credential-provider.ts +11 -6
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +8 -0
- package/corpus/templates/analytics/AGENTS.md +5 -0
- package/corpus/templates/analytics/actions/create-session-replay-agent-link.ts +31 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +65 -3
- package/corpus/templates/analytics/changelog/2026-07-02-session-replays-can-be-copied-as-temporary-private-links-for-agents.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +78 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +29 -0
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +313 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +198 -3
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +9 -1
- package/corpus/templates/analytics/server/routes/[...page].get.ts +104 -1
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-context.json.get.ts +48 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-events.json.get.ts +63 -0
- package/corpus/templates/analytics/shared/session-replay-agent-access.ts +10 -0
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +14 -8
- package/corpus/templates/clips/AGENTS.md +3 -2
- package/corpus/templates/clips/actions/create-recording-agent-link.ts +90 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +43 -46
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -1
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -1
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -1
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -1
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -1
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -1
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -1
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -1
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +100 -46
- package/corpus/templates/clips/changelog/2026-07-02-private-recordings-can-be-shared-with-agents-through-temporary-links-without-making-them-public.md +6 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +26 -13
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/agent-frame.jpg.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +7 -2
- package/corpus/templates/clips/shared/agent-context.ts +5 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +2 -2
- package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +11 -89
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +314 -181
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +92 -134
- package/corpus/templates/design/app/components/design/EditPanel.tsx +93 -19
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -26
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +220 -192
- package/corpus/templates/design/app/components/design/code-workbench-theme.ts +150 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +5 -7
- package/corpus/templates/design/app/i18n-data.ts +61 -77
- package/corpus/templates/design/app/lib/design-import.ts +4 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +97 -48
- package/corpus/templates/design/changelog/2026-07-02-design-inspector-and-canvas-controls-stay-consistent-when-se.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-fixed-a-crash-when-opening-the-code-editor.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-fixed-motion-drawer-open-transition.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-improved-design-assets-panel.md +6 -0
- package/dist/action.d.ts +8 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +3 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +10 -11
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +21 -44
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +61 -11
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.js +43 -2
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +1 -0
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/integrations/webhook-handler.d.ts +1 -0
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +9 -9
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +3 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +4 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +13 -7
- package/dist/server/credential-provider.js.map +1 -1
- package/docs/content/locales/ar-SA/template-analytics.mdx +2 -0
- package/docs/content/locales/ar-SA/template-clips.mdx +2 -0
- package/docs/content/locales/de-DE/template-analytics.mdx +2 -0
- package/docs/content/locales/de-DE/template-clips.mdx +2 -0
- package/docs/content/locales/es-ES/template-analytics.mdx +2 -0
- package/docs/content/locales/es-ES/template-clips.mdx +2 -0
- package/docs/content/locales/fr-FR/template-analytics.mdx +2 -0
- package/docs/content/locales/fr-FR/template-clips.mdx +2 -0
- package/docs/content/locales/hi-IN/template-analytics.mdx +2 -0
- package/docs/content/locales/hi-IN/template-clips.mdx +2 -0
- package/docs/content/locales/ja-JP/template-analytics.mdx +2 -0
- package/docs/content/locales/ja-JP/template-clips.mdx +2 -0
- package/docs/content/locales/ko-KR/template-analytics.mdx +2 -0
- package/docs/content/locales/ko-KR/template-clips.mdx +2 -0
- package/docs/content/locales/pt-BR/template-analytics.mdx +2 -0
- package/docs/content/locales/pt-BR/template-clips.mdx +2 -0
- package/docs/content/locales/zh-CN/template-analytics.mdx +2 -0
- package/docs/content/locales/zh-CN/template-clips.mdx +2 -0
- package/docs/content/locales/zh-TW/template-analytics.mdx +2 -0
- package/docs/content/locales/zh-TW/template-clips.mdx +2 -0
- package/docs/content/template-analytics.mdx +2 -0
- package/docs/content/template-clips.mdx +22 -10
- package/docs/content/tracking.mdx +2 -0
- package/package.json +1 -1
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
IconChevronDown,
|
|
25
25
|
IconChevronRight,
|
|
26
26
|
IconCircle,
|
|
27
|
-
IconCode,
|
|
28
27
|
IconFileText,
|
|
29
28
|
IconFolder,
|
|
30
29
|
IconLetterCase,
|
|
@@ -335,109 +334,10 @@ function TokenGroupSection({
|
|
|
335
334
|
// New Token popover
|
|
336
335
|
// ---------------------------------------------------------------------------
|
|
337
336
|
|
|
337
|
+
type TokenCreateMode = "menu" | "add" | "text";
|
|
338
|
+
|
|
338
339
|
interface NewTokenPopoverProps {
|
|
339
340
|
onAdd: (cssVar: string, value: string) => void;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function NewTokenPopover({ onAdd }: NewTokenPopoverProps) {
|
|
343
|
-
const [open, setOpen] = useState(false);
|
|
344
|
-
const [cssVar, setCssVar] = useState("--my-token");
|
|
345
|
-
const [value, setValue] = useState("#000000");
|
|
346
|
-
const t = useT();
|
|
347
|
-
|
|
348
|
-
const handleAdd = () => {
|
|
349
|
-
const cleanVar = cssVar.startsWith("--") ? cssVar : `--${cssVar}`;
|
|
350
|
-
onAdd(cleanVar, value);
|
|
351
|
-
setOpen(false);
|
|
352
|
-
setCssVar("--my-token");
|
|
353
|
-
setValue("#000000");
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
return (
|
|
357
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
358
|
-
<PopoverTrigger asChild>
|
|
359
|
-
<Button
|
|
360
|
-
type="button"
|
|
361
|
-
variant="ghost"
|
|
362
|
-
size="sm"
|
|
363
|
-
className="h-6 cursor-pointer gap-1 px-2 !text-[11px] text-muted-foreground hover:text-foreground"
|
|
364
|
-
>
|
|
365
|
-
<IconPlus className="size-3" />
|
|
366
|
-
{t("designEditor.tokens.newToken")}
|
|
367
|
-
</Button>
|
|
368
|
-
</PopoverTrigger>
|
|
369
|
-
<PopoverContent align="end" className="w-56 p-3 text-[12px]">
|
|
370
|
-
<div className="space-y-2">
|
|
371
|
-
<div className="space-y-1">
|
|
372
|
-
<label className="text-[10px] font-medium text-muted-foreground">
|
|
373
|
-
{t("designEditor.tokens.cssVar")}
|
|
374
|
-
</label>
|
|
375
|
-
<Input
|
|
376
|
-
value={cssVar}
|
|
377
|
-
onChange={(e) => setCssVar(e.target.value)}
|
|
378
|
-
className="h-6 font-mono !text-[11px] md:!text-[11px]"
|
|
379
|
-
placeholder="--my-token"
|
|
380
|
-
/>
|
|
381
|
-
</div>
|
|
382
|
-
<div className="space-y-1">
|
|
383
|
-
<label className="text-[10px] font-medium text-muted-foreground">
|
|
384
|
-
{t("designEditor.tokens.value")}
|
|
385
|
-
</label>
|
|
386
|
-
<Input
|
|
387
|
-
value={value}
|
|
388
|
-
onChange={(e) => setValue(e.target.value)}
|
|
389
|
-
className="h-6 font-mono !text-[11px] md:!text-[11px]"
|
|
390
|
-
placeholder="#3B82F6"
|
|
391
|
-
/>
|
|
392
|
-
</div>
|
|
393
|
-
<Button
|
|
394
|
-
type="button"
|
|
395
|
-
className="h-7 w-full cursor-pointer !text-[11px]"
|
|
396
|
-
onClick={handleAdd}
|
|
397
|
-
disabled={!cssVar || !value}
|
|
398
|
-
>
|
|
399
|
-
{t("designEditor.tokens.add")}
|
|
400
|
-
</Button>
|
|
401
|
-
</div>
|
|
402
|
-
</PopoverContent>
|
|
403
|
-
</Popover>
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// ---------------------------------------------------------------------------
|
|
408
|
-
// Import popover
|
|
409
|
-
// ---------------------------------------------------------------------------
|
|
410
|
-
|
|
411
|
-
const TOKEN_IMPORT_ACCEPT = [
|
|
412
|
-
".css",
|
|
413
|
-
".scss",
|
|
414
|
-
".sass",
|
|
415
|
-
".less",
|
|
416
|
-
".json",
|
|
417
|
-
".ts",
|
|
418
|
-
".tsx",
|
|
419
|
-
".js",
|
|
420
|
-
".jsx",
|
|
421
|
-
".md",
|
|
422
|
-
".mdx",
|
|
423
|
-
".txt",
|
|
424
|
-
].join(",");
|
|
425
|
-
|
|
426
|
-
async function readImportFiles(fileList: FileList): Promise<TokenImportFile[]> {
|
|
427
|
-
const selected = [...fileList].slice(0, 20);
|
|
428
|
-
const files = await Promise.all(
|
|
429
|
-
selected.map(async (file) => ({
|
|
430
|
-
filename:
|
|
431
|
-
(file as File & { webkitRelativePath?: string }).webkitRelativePath ||
|
|
432
|
-
file.name,
|
|
433
|
-
content: await file.text(),
|
|
434
|
-
})),
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
return files.filter((file) => file.content.trim().length > 0);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
interface ImportTokensPopoverProps {
|
|
441
341
|
onImportFiles: (
|
|
442
342
|
files: TokenImportFile[],
|
|
443
343
|
) => Promise<ImportDesignTokensResult>;
|
|
@@ -446,18 +346,31 @@ interface ImportTokensPopoverProps {
|
|
|
446
346
|
isPending: boolean;
|
|
447
347
|
}
|
|
448
348
|
|
|
449
|
-
function
|
|
349
|
+
function NewTokenPopover({
|
|
350
|
+
onAdd,
|
|
450
351
|
onImportFiles,
|
|
451
352
|
onImportText,
|
|
452
353
|
onImportCurrentDesign,
|
|
453
354
|
isPending,
|
|
454
|
-
}:
|
|
455
|
-
const t = useT();
|
|
456
|
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
457
|
-
const folderInputRef = useRef<HTMLInputElement>(null);
|
|
355
|
+
}: NewTokenPopoverProps) {
|
|
458
356
|
const [open, setOpen] = useState(false);
|
|
357
|
+
const [mode, setMode] = useState<TokenCreateMode>("menu");
|
|
358
|
+
const [cssVar, setCssVar] = useState("--my-token");
|
|
359
|
+
const [value, setValue] = useState("#000000");
|
|
459
360
|
const [text, setText] = useState("");
|
|
460
361
|
const [status, setStatus] = useState<string | null>(null);
|
|
362
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
363
|
+
const folderInputRef = useRef<HTMLInputElement>(null);
|
|
364
|
+
const t = useT();
|
|
365
|
+
|
|
366
|
+
const handleAdd = () => {
|
|
367
|
+
const cleanVar = cssVar.startsWith("--") ? cssVar : `--${cssVar}`;
|
|
368
|
+
onAdd(cleanVar, value);
|
|
369
|
+
setOpen(false);
|
|
370
|
+
setMode("menu");
|
|
371
|
+
setCssVar("--my-token");
|
|
372
|
+
setValue("#000000");
|
|
373
|
+
};
|
|
461
374
|
|
|
462
375
|
const runImport = async (
|
|
463
376
|
importer: () => Promise<ImportDesignTokensResult>,
|
|
@@ -478,6 +391,14 @@ function ImportTokensPopover({
|
|
|
478
391
|
}
|
|
479
392
|
};
|
|
480
393
|
|
|
394
|
+
const closeOrReset = (nextOpen: boolean) => {
|
|
395
|
+
setOpen(nextOpen);
|
|
396
|
+
if (!nextOpen) {
|
|
397
|
+
setMode("menu");
|
|
398
|
+
setStatus(null);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
481
402
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
482
403
|
const files = event.currentTarget.files;
|
|
483
404
|
event.currentTarget.value = "";
|
|
@@ -486,91 +407,98 @@ function ImportTokensPopover({
|
|
|
486
407
|
};
|
|
487
408
|
|
|
488
409
|
return (
|
|
489
|
-
<Popover open={open} onOpenChange={
|
|
490
|
-
<
|
|
491
|
-
<
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
<p className="!text-[11px] font-medium text-foreground">
|
|
510
|
-
{t("designEditor.tokens.importTitle")}
|
|
511
|
-
</p>
|
|
512
|
-
<p className="text-[10px] leading-snug text-muted-foreground">
|
|
513
|
-
{t("designEditor.tokens.importHint")}
|
|
514
|
-
</p>
|
|
515
|
-
</div>
|
|
516
|
-
|
|
517
|
-
<div className="grid grid-cols-3 gap-1.5">
|
|
518
|
-
<Button
|
|
519
|
-
type="button"
|
|
520
|
-
variant="outline"
|
|
521
|
-
size="sm"
|
|
522
|
-
className="h-7 cursor-pointer gap-1 px-2 text-[10px]"
|
|
410
|
+
<Popover open={open} onOpenChange={closeOrReset}>
|
|
411
|
+
<PopoverTrigger asChild>
|
|
412
|
+
<Button
|
|
413
|
+
type="button"
|
|
414
|
+
variant="ghost"
|
|
415
|
+
size="sm"
|
|
416
|
+
className="h-6 cursor-pointer gap-1 px-2 !text-[11px] text-muted-foreground hover:text-foreground"
|
|
417
|
+
>
|
|
418
|
+
<IconPlus className="size-3" />
|
|
419
|
+
{t("designEditor.tokens.newToken")}
|
|
420
|
+
</Button>
|
|
421
|
+
</PopoverTrigger>
|
|
422
|
+
<PopoverContent align="end" className="w-72 p-2 text-[12px]">
|
|
423
|
+
{mode === "menu" ? (
|
|
424
|
+
<div className="space-y-0.5">
|
|
425
|
+
<TokenCreateOption
|
|
426
|
+
icon={<IconPlus className="size-3.5" />}
|
|
427
|
+
title="Add one token"
|
|
428
|
+
description="Create a single CSS variable by hand."
|
|
429
|
+
onClick={() => setMode("add")}
|
|
523
430
|
disabled={isPending}
|
|
431
|
+
/>
|
|
432
|
+
<TokenCreateOption
|
|
433
|
+
icon={<IconUpload className="size-3.5" />}
|
|
434
|
+
title="Import a set from text"
|
|
435
|
+
description="Paste CSS variables, theme notes, or token JSON."
|
|
436
|
+
onClick={() => setMode("text")}
|
|
437
|
+
disabled={isPending}
|
|
438
|
+
/>
|
|
439
|
+
<TokenCreateOption
|
|
440
|
+
icon={<IconFileText className="size-3.5" />}
|
|
441
|
+
title="Import from a file"
|
|
442
|
+
description="Read colors, spacing, and type from selected files."
|
|
524
443
|
onClick={() => fileInputRef.current?.click()}
|
|
525
|
-
>
|
|
526
|
-
<IconFileText className="size-3" />
|
|
527
|
-
{t("designEditor.tokens.files")}
|
|
528
|
-
</Button>
|
|
529
|
-
<Button
|
|
530
|
-
type="button"
|
|
531
|
-
variant="outline"
|
|
532
|
-
size="sm"
|
|
533
|
-
className="h-7 cursor-pointer gap-1 px-2 text-[10px]"
|
|
534
444
|
disabled={isPending}
|
|
445
|
+
/>
|
|
446
|
+
<TokenCreateOption
|
|
447
|
+
icon={<IconFolder className="size-3.5" />}
|
|
448
|
+
title="Import from a folder"
|
|
449
|
+
description="Scan a small source folder for token definitions."
|
|
535
450
|
onClick={() => folderInputRef.current?.click()}
|
|
536
|
-
>
|
|
537
|
-
<IconFolder className="size-3" />
|
|
538
|
-
{t("designEditor.tokens.folder")}
|
|
539
|
-
</Button>
|
|
540
|
-
<Button
|
|
541
|
-
type="button"
|
|
542
|
-
variant="outline"
|
|
543
|
-
size="sm"
|
|
544
|
-
className="h-7 cursor-pointer px-2 text-[10px]"
|
|
545
451
|
disabled={isPending}
|
|
452
|
+
/>
|
|
453
|
+
<TokenCreateOption
|
|
454
|
+
icon={<IconPalette className="size-3.5" />}
|
|
455
|
+
title="Import from current design"
|
|
456
|
+
description="Extract reusable tokens already used on the canvas."
|
|
546
457
|
onClick={() => void runImport(onImportCurrentDesign)}
|
|
458
|
+
disabled={isPending}
|
|
459
|
+
/>
|
|
460
|
+
</div>
|
|
461
|
+
) : mode === "add" ? (
|
|
462
|
+
<div className="space-y-2 p-1">
|
|
463
|
+
<TokenCreateBackButton onClick={() => setMode("menu")} />
|
|
464
|
+
<div className="space-y-1">
|
|
465
|
+
<label className="text-[10px] font-medium text-muted-foreground">
|
|
466
|
+
{t("designEditor.tokens.cssVar")}
|
|
467
|
+
</label>
|
|
468
|
+
<Input
|
|
469
|
+
value={cssVar}
|
|
470
|
+
onChange={(e) => setCssVar(e.target.value)}
|
|
471
|
+
className="h-6 font-mono !text-[11px] md:!text-[11px]"
|
|
472
|
+
placeholder="--my-token"
|
|
473
|
+
/>
|
|
474
|
+
</div>
|
|
475
|
+
<div className="space-y-1">
|
|
476
|
+
<label className="text-[10px] font-medium text-muted-foreground">
|
|
477
|
+
{t("designEditor.tokens.value")}
|
|
478
|
+
</label>
|
|
479
|
+
<Input
|
|
480
|
+
value={value}
|
|
481
|
+
onChange={(e) => setValue(e.target.value)}
|
|
482
|
+
className="h-6 font-mono !text-[11px] md:!text-[11px]"
|
|
483
|
+
placeholder="#3B82F6"
|
|
484
|
+
/>
|
|
485
|
+
</div>
|
|
486
|
+
<Button
|
|
487
|
+
type="button"
|
|
488
|
+
className="h-7 w-full cursor-pointer !text-[11px]"
|
|
489
|
+
onClick={handleAdd}
|
|
490
|
+
disabled={!cssVar || !value}
|
|
547
491
|
>
|
|
548
|
-
{t("designEditor.tokens.
|
|
492
|
+
{t("designEditor.tokens.add")}
|
|
549
493
|
</Button>
|
|
550
494
|
</div>
|
|
551
|
-
|
|
552
|
-
<
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
onChange={handleFileChange}
|
|
559
|
-
/>
|
|
560
|
-
<input
|
|
561
|
-
ref={folderInputRef}
|
|
562
|
-
type="file"
|
|
563
|
-
multiple
|
|
564
|
-
accept={TOKEN_IMPORT_ACCEPT}
|
|
565
|
-
className="hidden"
|
|
566
|
-
onChange={handleFileChange}
|
|
567
|
-
{...({ directory: "", webkitdirectory: "" } as Record<
|
|
568
|
-
string,
|
|
569
|
-
string
|
|
570
|
-
>)}
|
|
571
|
-
/>
|
|
572
|
-
|
|
573
|
-
<div className="space-y-1.5">
|
|
495
|
+
) : (
|
|
496
|
+
<div className="space-y-2 p-1">
|
|
497
|
+
<TokenCreateBackButton onClick={() => setMode("menu")} />
|
|
498
|
+
<p className="text-[10px] leading-snug text-muted-foreground">
|
|
499
|
+
Paste a token set from CSS, JSON, Tailwind config, or design
|
|
500
|
+
notes.
|
|
501
|
+
</p>
|
|
574
502
|
<Textarea
|
|
575
503
|
value={text}
|
|
576
504
|
onChange={(event) => setText(event.target.value)}
|
|
@@ -586,18 +514,119 @@ function ImportTokensPopover({
|
|
|
586
514
|
{t("designEditor.tokens.importPasted")}
|
|
587
515
|
</Button>
|
|
588
516
|
</div>
|
|
517
|
+
)}
|
|
589
518
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
519
|
+
<input
|
|
520
|
+
ref={fileInputRef}
|
|
521
|
+
type="file"
|
|
522
|
+
multiple
|
|
523
|
+
accept={TOKEN_IMPORT_ACCEPT}
|
|
524
|
+
className="hidden"
|
|
525
|
+
onChange={handleFileChange}
|
|
526
|
+
/>
|
|
527
|
+
<input
|
|
528
|
+
ref={folderInputRef}
|
|
529
|
+
type="file"
|
|
530
|
+
multiple
|
|
531
|
+
accept={TOKEN_IMPORT_ACCEPT}
|
|
532
|
+
className="hidden"
|
|
533
|
+
onChange={handleFileChange}
|
|
534
|
+
{...({ directory: "", webkitdirectory: "" } as Record<
|
|
535
|
+
string,
|
|
536
|
+
string
|
|
537
|
+
>)}
|
|
538
|
+
/>
|
|
539
|
+
|
|
540
|
+
{status && (
|
|
541
|
+
<p className="mt-2 px-1 text-[10px] leading-snug text-muted-foreground">
|
|
542
|
+
{status}
|
|
543
|
+
</p>
|
|
544
|
+
)}
|
|
596
545
|
</PopoverContent>
|
|
597
546
|
</Popover>
|
|
598
547
|
);
|
|
599
548
|
}
|
|
600
549
|
|
|
550
|
+
function TokenCreateBackButton({ onClick }: { onClick: () => void }) {
|
|
551
|
+
return (
|
|
552
|
+
<button
|
|
553
|
+
type="button"
|
|
554
|
+
onClick={onClick}
|
|
555
|
+
className="mb-1 text-[10px] font-medium text-muted-foreground hover:text-foreground"
|
|
556
|
+
>
|
|
557
|
+
Back
|
|
558
|
+
</button>
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function TokenCreateOption({
|
|
563
|
+
icon,
|
|
564
|
+
title,
|
|
565
|
+
description,
|
|
566
|
+
onClick,
|
|
567
|
+
disabled,
|
|
568
|
+
}: {
|
|
569
|
+
icon: React.ReactNode;
|
|
570
|
+
title: string;
|
|
571
|
+
description: string;
|
|
572
|
+
onClick: () => void;
|
|
573
|
+
disabled?: boolean;
|
|
574
|
+
}) {
|
|
575
|
+
return (
|
|
576
|
+
<button
|
|
577
|
+
type="button"
|
|
578
|
+
disabled={disabled}
|
|
579
|
+
onClick={onClick}
|
|
580
|
+
className="group flex w-full cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-accent/60 disabled:cursor-not-allowed disabled:opacity-50"
|
|
581
|
+
>
|
|
582
|
+
<span className="flex size-8 shrink-0 items-center justify-center rounded-md border border-border/70 bg-muted/70 text-muted-foreground transition-colors group-hover:border-border group-hover:bg-muted">
|
|
583
|
+
{icon}
|
|
584
|
+
</span>
|
|
585
|
+
<span className="min-w-0 flex-1">
|
|
586
|
+
<span className="block truncate text-[13px] font-medium leading-tight text-foreground">
|
|
587
|
+
{title}
|
|
588
|
+
</span>
|
|
589
|
+
<span className="mt-0.5 line-clamp-1 text-[11px] leading-snug text-muted-foreground">
|
|
590
|
+
{description}
|
|
591
|
+
</span>
|
|
592
|
+
</span>
|
|
593
|
+
</button>
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ---------------------------------------------------------------------------
|
|
598
|
+
// Import helpers
|
|
599
|
+
// ---------------------------------------------------------------------------
|
|
600
|
+
|
|
601
|
+
const TOKEN_IMPORT_ACCEPT = [
|
|
602
|
+
".css",
|
|
603
|
+
".scss",
|
|
604
|
+
".sass",
|
|
605
|
+
".less",
|
|
606
|
+
".json",
|
|
607
|
+
".ts",
|
|
608
|
+
".tsx",
|
|
609
|
+
".js",
|
|
610
|
+
".jsx",
|
|
611
|
+
".md",
|
|
612
|
+
".mdx",
|
|
613
|
+
".txt",
|
|
614
|
+
].join(",");
|
|
615
|
+
|
|
616
|
+
async function readImportFiles(fileList: FileList): Promise<TokenImportFile[]> {
|
|
617
|
+
const selected = [...fileList].slice(0, 20);
|
|
618
|
+
const files = await Promise.all(
|
|
619
|
+
selected.map(async (file) => ({
|
|
620
|
+
filename:
|
|
621
|
+
(file as File & { webkitRelativePath?: string }).webkitRelativePath ||
|
|
622
|
+
file.name,
|
|
623
|
+
content: await file.text(),
|
|
624
|
+
})),
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
return files.filter((file) => file.content.trim().length > 0);
|
|
628
|
+
}
|
|
629
|
+
|
|
601
630
|
// ---------------------------------------------------------------------------
|
|
602
631
|
// Main panel
|
|
603
632
|
// ---------------------------------------------------------------------------
|
|
@@ -719,7 +748,6 @@ export function TokensPanel({ designId, onTokensApplied }: TokensPanelProps) {
|
|
|
719
748
|
{/* Header */}
|
|
720
749
|
<div className="flex items-center justify-between border-b border-border/60 px-3 py-2">
|
|
721
750
|
<div className="flex items-center gap-1.5">
|
|
722
|
-
<IconCode className="size-3.5 text-muted-foreground/60" />
|
|
723
751
|
<span className="!text-[11px] font-semibold text-foreground">
|
|
724
752
|
{t("designEditor.tokens.title")}
|
|
725
753
|
</span>
|
|
@@ -745,13 +773,13 @@ export function TokensPanel({ designId, onTokensApplied }: TokensPanelProps) {
|
|
|
745
773
|
</TooltipTrigger>
|
|
746
774
|
<TooltipContent>{t("designEditor.tokens.refresh")}</TooltipContent>
|
|
747
775
|
</Tooltip>
|
|
748
|
-
<
|
|
776
|
+
<NewTokenPopover
|
|
777
|
+
onAdd={handleNewToken}
|
|
749
778
|
onImportFiles={importFiles}
|
|
750
779
|
onImportText={importText}
|
|
751
780
|
onImportCurrentDesign={importCurrentDesign}
|
|
752
781
|
isPending={importMutation.isPending}
|
|
753
782
|
/>
|
|
754
|
-
<NewTokenPopover onAdd={handleNewToken} />
|
|
755
783
|
</div>
|
|
756
784
|
</div>
|
|
757
785
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export interface CodeWorkbenchTheme {
|
|
2
|
+
colorScheme: "light" | "dark";
|
|
3
|
+
values: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const WORKBENCH_THEME_VARS: Record<string, string[]> = {
|
|
7
|
+
"--workbench-bg": ["--design-editor-panel-bg", "--background"],
|
|
8
|
+
"--workbench-sidebar-bg": ["--design-editor-panel-bg", "--card"],
|
|
9
|
+
"--workbench-editor-bg": ["--design-editor-panel-bg", "--background"],
|
|
10
|
+
"--workbench-surface-bg": ["--design-editor-control-bg", "--muted"],
|
|
11
|
+
"--workbench-border": ["--design-editor-control-border", "--border"],
|
|
12
|
+
"--workbench-fg": ["--foreground"],
|
|
13
|
+
"--workbench-muted-fg": ["--muted-foreground"],
|
|
14
|
+
"--workbench-hover-bg": ["--design-editor-layer-hover-color", "--accent"],
|
|
15
|
+
"--workbench-active-bg": ["--design-editor-selection-color", "--accent"],
|
|
16
|
+
"--workbench-active-fg": [
|
|
17
|
+
"--design-editor-accent-color",
|
|
18
|
+
"--accent-foreground",
|
|
19
|
+
],
|
|
20
|
+
"--workbench-accent": ["--design-editor-accent-color", "--primary"],
|
|
21
|
+
"--workbench-button-bg": ["--design-editor-control-bg", "--background"],
|
|
22
|
+
"--workbench-button-fg": ["--foreground"],
|
|
23
|
+
"--workbench-selection-bg": ["--design-editor-selection-color", "--accent"],
|
|
24
|
+
"--workbench-dirty": ["--warning", "--destructive"],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const HEX_COLOR_RE = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
28
|
+
|
|
29
|
+
function normalizeHexColor(value: string): string {
|
|
30
|
+
const hex = value.toLowerCase();
|
|
31
|
+
if (hex.length !== 4 && hex.length !== 5) return hex;
|
|
32
|
+
const [, r, g, b, a] = hex;
|
|
33
|
+
return `#${r}${r}${g}${g}${b}${b}${a ? `${a}${a}` : ""}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseRgbChannel(value: string): number | undefined {
|
|
37
|
+
const trimmed = value.trim();
|
|
38
|
+
const parsed = trimmed.endsWith("%")
|
|
39
|
+
? (Number.parseFloat(trimmed.slice(0, -1)) * 255) / 100
|
|
40
|
+
: Number.parseFloat(trimmed);
|
|
41
|
+
if (!Number.isFinite(parsed)) return undefined;
|
|
42
|
+
return Math.min(255, Math.max(0, Math.round(parsed)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseRgbAlpha(value: string | undefined): number {
|
|
46
|
+
if (!value) return 255;
|
|
47
|
+
const trimmed = value.trim();
|
|
48
|
+
const parsed = trimmed.endsWith("%")
|
|
49
|
+
? Number.parseFloat(trimmed.slice(0, -1)) / 100
|
|
50
|
+
: Number.parseFloat(trimmed);
|
|
51
|
+
if (!Number.isFinite(parsed)) return 255;
|
|
52
|
+
return Math.min(255, Math.max(0, Math.round(parsed * 255)));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toHexByte(value: number): string {
|
|
56
|
+
return value.toString(16).padStart(2, "0");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseRgbColor(value: string): string | undefined {
|
|
60
|
+
const match = /^rgba?\((.*)\)$/i.exec(value.trim());
|
|
61
|
+
if (!match) return undefined;
|
|
62
|
+
const body = match[1]?.trim();
|
|
63
|
+
if (!body) return undefined;
|
|
64
|
+
const parts = body.includes(",")
|
|
65
|
+
? body.split(",").map((part) => part.trim())
|
|
66
|
+
: body.replace(/\//g, " / ").split(/\s+/).filter(Boolean);
|
|
67
|
+
const slashIndex = parts.indexOf("/");
|
|
68
|
+
const colorParts = slashIndex === -1 ? parts : parts.slice(0, slashIndex);
|
|
69
|
+
const alphaPart =
|
|
70
|
+
slashIndex === -1 ? colorParts[3] : parts.slice(slashIndex + 1).join(" ");
|
|
71
|
+
if (colorParts.length < 3) return undefined;
|
|
72
|
+
const red = parseRgbChannel(colorParts[0] ?? "");
|
|
73
|
+
const green = parseRgbChannel(colorParts[1] ?? "");
|
|
74
|
+
const blue = parseRgbChannel(colorParts[2] ?? "");
|
|
75
|
+
if (red === undefined || green === undefined || blue === undefined) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const alpha = parseRgbAlpha(alphaPart);
|
|
79
|
+
const hex = `#${toHexByte(red)}${toHexByte(green)}${toHexByte(blue)}`;
|
|
80
|
+
return alpha < 255 ? `${hex}${toHexByte(alpha)}` : hex;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function normalizeThemeColorValue(value: string): string {
|
|
84
|
+
const trimmed = value.trim();
|
|
85
|
+
if (!trimmed) return "";
|
|
86
|
+
if (/^-?\d+(\.\d+)?\s+-?\d+(\.\d+)?%/.test(trimmed)) {
|
|
87
|
+
return `hsl(${trimmed})`;
|
|
88
|
+
}
|
|
89
|
+
return trimmed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function normalizeMonacoThemeColor(
|
|
93
|
+
value: string | undefined,
|
|
94
|
+
): string | undefined {
|
|
95
|
+
const normalized = normalizeThemeColorValue(value ?? "");
|
|
96
|
+
if (!normalized) return undefined;
|
|
97
|
+
if (HEX_COLOR_RE.test(normalized)) return normalizeHexColor(normalized);
|
|
98
|
+
return parseRgbColor(normalized);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function resolveCssColorValue(value: string): string {
|
|
102
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
const probe = document.createElement("span");
|
|
106
|
+
probe.style.color = value;
|
|
107
|
+
probe.style.position = "fixed";
|
|
108
|
+
probe.style.pointerEvents = "none";
|
|
109
|
+
probe.style.visibility = "hidden";
|
|
110
|
+
document.body.appendChild(probe);
|
|
111
|
+
const resolved = window.getComputedStyle(probe).color;
|
|
112
|
+
probe.remove();
|
|
113
|
+
return resolved || value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readThemeVar(
|
|
117
|
+
elementStyles: CSSStyleDeclaration,
|
|
118
|
+
rootStyles: CSSStyleDeclaration,
|
|
119
|
+
names: string[],
|
|
120
|
+
): string | undefined {
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
const value =
|
|
123
|
+
elementStyles.getPropertyValue(name) || rootStyles.getPropertyValue(name);
|
|
124
|
+
const normalized = normalizeThemeColorValue(value);
|
|
125
|
+
if (normalized) return resolveCssColorValue(normalized);
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function readCodeWorkbenchTheme(
|
|
131
|
+
element: HTMLElement | null,
|
|
132
|
+
): CodeWorkbenchTheme {
|
|
133
|
+
if (typeof window === "undefined" || !element) {
|
|
134
|
+
return { colorScheme: "light", values: {} };
|
|
135
|
+
}
|
|
136
|
+
const elementStyles = window.getComputedStyle(element);
|
|
137
|
+
const rootStyles = window.getComputedStyle(document.documentElement);
|
|
138
|
+
const values: Record<string, string> = {};
|
|
139
|
+
for (const [targetVar, sourceVars] of Object.entries(WORKBENCH_THEME_VARS)) {
|
|
140
|
+
const value = readThemeVar(elementStyles, rootStyles, sourceVars);
|
|
141
|
+
if (value) values[targetVar] = value;
|
|
142
|
+
}
|
|
143
|
+
const colorScheme =
|
|
144
|
+
document.documentElement.classList.contains("dark") ||
|
|
145
|
+
elementStyles.colorScheme.includes("dark") ||
|
|
146
|
+
rootStyles.colorScheme.includes("dark")
|
|
147
|
+
? "dark"
|
|
148
|
+
: "light";
|
|
149
|
+
return { colorScheme, values };
|
|
150
|
+
}
|
|
@@ -279,8 +279,8 @@ const messages = {
|
|
|
279
279
|
"將 Figma 選取內容、.fig 匯出檔或獨立 HTML 帶入為 Design 螢幕。",
|
|
280
280
|
figmaPasteTitle: "從 Figma 貼上",
|
|
281
281
|
figmaPasteDescription:
|
|
282
|
-
"在 Figma
|
|
283
|
-
figmaPasteTarget: "
|
|
282
|
+
"在 Figma 複製畫框或圖層,然後貼到 Design 畫布。可用時會直接匯入 Figma 中繼資料。",
|
|
283
|
+
figmaPasteTarget: "貼到畫布",
|
|
284
284
|
figUploadTitle: "上傳 .fig",
|
|
285
285
|
figUploadDescription:
|
|
286
286
|
"只匯出需要的畫框。含有許多嵌入圖片的大型檔案可能超過匯入限制。",
|
|
@@ -293,17 +293,15 @@ const messages = {
|
|
|
293
293
|
chooseHtmlFile: "選擇 HTML 檔案",
|
|
294
294
|
githubTitle: "GitHub",
|
|
295
295
|
githubDescription: "即將推出:直接從儲存庫匯入螢幕和元件。",
|
|
296
|
-
localTitle: "本機 app
|
|
297
|
-
localDescription: "使用 visual-edit
|
|
296
|
+
localTitle: "本機 app",
|
|
297
|
+
localDescription: "使用 /visual-edit 連接任何執行中的本機 app。",
|
|
298
298
|
visualEditGuidance:
|
|
299
|
-
"
|
|
300
|
-
useVisualEditNow: "立即使用 visual-edit",
|
|
299
|
+
"安裝 /visual-edit skill,啟動任意本機 app,然後在該 app repo 中執行橋接命令,讓代理新增 URL 螢幕。",
|
|
301
300
|
comingSoon: "即將推出",
|
|
302
301
|
warningsToast: "匯入完成但有警告",
|
|
303
302
|
figmaSuccess: "已匯入 Figma 貼上內容",
|
|
304
303
|
htmlSuccess: "已匯入 HTML",
|
|
305
304
|
uploadSuccess: "已匯入檔案",
|
|
306
|
-
visualEditSent: "已將 visual-edit 請求傳送給代理",
|
|
307
305
|
lastImport: "上次匯入",
|
|
308
306
|
errors: {
|
|
309
307
|
notHtml: "請貼上或選擇有效的 HTML 以匯入。",
|