@pixerate/editor-svelte 0.22.0 → 0.24.0
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 +6 -2
- package/dist/MergePreview.svelte +201 -0
- package/dist/MergePreview.svelte.d.ts +25 -0
- package/dist/MergePreview.svelte.d.ts.map +1 -0
- package/dist/StylesGallery.svelte +169 -0
- package/dist/StylesGallery.svelte.d.ts +27 -0
- package/dist/StylesGallery.svelte.d.ts.map +1 -0
- package/dist/TemplateRenderer.svelte +4 -10
- package/dist/TemplateRenderer.svelte.d.ts.map +1 -1
- package/dist/canvas/TokenBindingPicker.svelte +93 -0
- package/dist/canvas/TokenBindingPicker.svelte.d.ts +20 -0
- package/dist/canvas/TokenBindingPicker.svelte.d.ts.map +1 -0
- package/dist/canvas/TokenModeSwitcher.svelte +55 -0
- package/dist/canvas/TokenModeSwitcher.svelte.d.ts +18 -0
- package/dist/canvas/TokenModeSwitcher.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasArrange.svelte.d.ts +4 -0
- package/dist/canvas/createCanvasArrange.svelte.d.ts.map +1 -1
- package/dist/canvas/createCanvasAutoLayout.svelte.d.ts +68 -0
- package/dist/canvas/createCanvasAutoLayout.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasAutoLayout.svelte.js +78 -0
- package/dist/canvas/createCanvasTokens.svelte.d.ts +31 -0
- package/dist/canvas/createCanvasTokens.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasTokens.svelte.js +50 -0
- package/dist/canvas/index.d.ts +7 -0
- package/dist/canvas/index.d.ts.map +1 -1
- package/dist/canvas/index.js +10 -0
- package/dist/fileImport.svelte.d.ts +8 -1
- package/dist/fileImport.svelte.d.ts.map +1 -1
- package/dist/fileImport.svelte.js +11 -1
- package/dist/find-replace/FindReplaceBar.svelte +203 -0
- package/dist/find-replace/FindReplaceBar.svelte.d.ts +15 -0
- package/dist/find-replace/FindReplaceBar.svelte.d.ts.map +1 -0
- package/dist/find-replace/findReplace.svelte.d.ts +27 -0
- package/dist/find-replace/findReplace.svelte.d.ts.map +1 -0
- package/dist/find-replace/findReplace.svelte.js +79 -0
- package/dist/find-replace/index.d.ts +6 -0
- package/dist/find-replace/index.d.ts.map +1 -0
- package/dist/find-replace/index.js +2 -0
- package/dist/generation/AssetMentionChips.svelte +130 -0
- package/dist/generation/AssetMentionChips.svelte.d.ts +21 -0
- package/dist/generation/AssetMentionChips.svelte.d.ts.map +1 -0
- package/dist/generation/GenerationHistory.svelte +161 -0
- package/dist/generation/GenerationHistory.svelte.d.ts +28 -0
- package/dist/generation/GenerationHistory.svelte.d.ts.map +1 -0
- package/dist/generation/GenerationSettings.svelte +174 -0
- package/dist/generation/GenerationSettings.svelte.d.ts +23 -0
- package/dist/generation/GenerationSettings.svelte.d.ts.map +1 -0
- package/dist/generation/JobProgress.svelte +53 -0
- package/dist/generation/JobProgress.svelte.d.ts +17 -0
- package/dist/generation/JobProgress.svelte.d.ts.map +1 -0
- package/dist/generation/index.d.ts +17 -0
- package/dist/generation/index.d.ts.map +1 -0
- package/dist/generation/index.js +7 -0
- package/dist/generation/plan.svelte.d.ts +16 -0
- package/dist/generation/plan.svelte.d.ts.map +1 -0
- package/dist/generation/plan.svelte.js +26 -0
- package/dist/generation/progress.svelte.d.ts +25 -0
- package/dist/generation/progress.svelte.d.ts.map +1 -0
- package/dist/generation/progress.svelte.js +36 -0
- package/dist/generation/records.svelte.d.ts +18 -0
- package/dist/generation/records.svelte.d.ts.map +1 -0
- package/dist/generation/records.svelte.js +66 -0
- package/dist/image-editor/AdjustmentLayersPanel.svelte +309 -0
- package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts +11 -0
- package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts.map +1 -0
- package/dist/image-editor/ControlSlider.svelte +39 -0
- package/dist/image-editor/ControlSlider.svelte.d.ts +13 -0
- package/dist/image-editor/ControlSlider.svelte.d.ts.map +1 -0
- package/dist/image-editor/CurvesEditor.svelte +105 -0
- package/dist/image-editor/CurvesEditor.svelte.d.ts +13 -0
- package/dist/image-editor/CurvesEditor.svelte.d.ts.map +1 -0
- package/dist/image-editor/Histogram.svelte +49 -0
- package/dist/image-editor/Histogram.svelte.d.ts +12 -0
- package/dist/image-editor/Histogram.svelte.d.ts.map +1 -0
- package/dist/image-editor/ImageEditor.svelte +104 -141
- package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
- package/dist/image-editor/RefineEdgePanel.svelte +180 -0
- package/dist/image-editor/RefineEdgePanel.svelte.d.ts +13 -0
- package/dist/image-editor/RefineEdgePanel.svelte.d.ts.map +1 -0
- package/dist/image-editor/SelectionOptionsBar.svelte +135 -0
- package/dist/image-editor/SelectionOptionsBar.svelte.d.ts +15 -0
- package/dist/image-editor/SelectionOptionsBar.svelte.d.ts.map +1 -0
- package/dist/image-editor/imageEditorState.svelte.d.ts +14 -1
- package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -1
- package/dist/image-editor/imageEditorState.svelte.js +40 -0
- package/dist/image-editor/index.d.ts +18 -0
- package/dist/image-editor/index.d.ts.map +1 -1
- package/dist/image-editor/index.js +6 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/merge.svelte.d.ts +51 -0
- package/dist/merge.svelte.d.ts.map +1 -0
- package/dist/merge.svelte.js +117 -0
- package/dist/paragraphStyles.svelte.d.ts +30 -0
- package/dist/paragraphStyles.svelte.d.ts.map +1 -0
- package/dist/paragraphStyles.svelte.js +44 -0
- package/dist/persistence/RecoveryPrompt.svelte +57 -0
- package/dist/persistence/RecoveryPrompt.svelte.d.ts +18 -0
- package/dist/persistence/RecoveryPrompt.svelte.d.ts.map +1 -0
- package/dist/persistence/autosave.svelte.d.ts +63 -0
- package/dist/persistence/autosave.svelte.d.ts.map +1 -0
- package/dist/persistence/autosave.svelte.js +110 -0
- package/dist/persistence/index.d.ts +6 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +2 -0
- package/dist/review/TrackedChangesList.svelte +67 -0
- package/dist/review/TrackedChangesList.svelte.d.ts +13 -0
- package/dist/review/TrackedChangesList.svelte.d.ts.map +1 -0
- package/dist/review/documentCompare.svelte.d.ts +21 -0
- package/dist/review/documentCompare.svelte.d.ts.map +1 -0
- package/dist/review/documentCompare.svelte.js +23 -0
- package/dist/review/index.d.ts +4 -0
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +2 -0
- package/dist/review/review.svelte.d.ts +6 -0
- package/dist/review/review.svelte.d.ts.map +1 -1
- package/dist/review/review.svelte.js +3 -0
- package/dist/spreadsheet/FormulaBar.svelte +13 -4
- package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/FormulaHints.svelte +7 -5
- package/dist/spreadsheet/FormulaHints.svelte.d.ts +3 -0
- package/dist/spreadsheet/FormulaHints.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/NameManager.svelte +165 -0
- package/dist/spreadsheet/NameManager.svelte.d.ts +11 -0
- package/dist/spreadsheet/NameManager.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SheetTabs.svelte +161 -0
- package/dist/spreadsheet/SheetTabs.svelte.d.ts +10 -0
- package/dist/spreadsheet/SheetTabs.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte +184 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte +13 -5
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts +5 -1
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte +141 -0
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetEditor.svelte +199 -11
- package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +2 -0
- package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte +92 -0
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/index.d.ts +9 -0
- package/dist/spreadsheet/index.d.ts.map +1 -1
- package/dist/spreadsheet/index.js +5 -0
- package/dist/spreadsheet/spreadsheet.svelte.d.ts +57 -2
- package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/spreadsheet.svelte.js +105 -6
- package/dist/tokens/TokenSwatch.svelte +14 -0
- package/dist/tokens/TokenSwatch.svelte.d.ts +8 -0
- package/dist/tokens/TokenSwatch.svelte.d.ts.map +1 -0
- package/dist/tokens/TokensPanel.svelte +323 -0
- package/dist/tokens/TokensPanel.svelte.d.ts +14 -0
- package/dist/tokens/TokensPanel.svelte.d.ts.map +1 -0
- package/dist/tokens/createTokens.svelte.d.ts +31 -0
- package/dist/tokens/createTokens.svelte.d.ts.map +1 -0
- package/dist/tokens/createTokens.svelte.js +43 -0
- package/dist/tokens/index.d.ts +7 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +3 -0
- package/package.json +26 -2
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Editor } from "@tiptap/core";
|
|
3
|
+
import { FIND_OPTION_TOGGLES, formatFindStatus } from "@pixerate/editor";
|
|
4
|
+
import { createFindReplace, type SvelteFindReplace } from "./findReplace.svelte.js";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
editor?: Editor | null;
|
|
8
|
+
class?: string;
|
|
9
|
+
/** Render even while the find state is closed (default: only after Mod+F / Mod+H / `openFindReplace`). */
|
|
10
|
+
alwaysOpen?: boolean;
|
|
11
|
+
/** Show the "More options" toggle. Default `true`. */
|
|
12
|
+
showMoreOptions?: boolean;
|
|
13
|
+
/** Called after the bar closes (Escape or the close button). */
|
|
14
|
+
onclose?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
editor = null,
|
|
19
|
+
class: className = "flex flex-col gap-1 rounded-md border border-border bg-background p-1 text-sm shadow-sm",
|
|
20
|
+
alwaysOpen = false,
|
|
21
|
+
showMoreOptions = true,
|
|
22
|
+
onclose,
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
const inputClass =
|
|
26
|
+
"min-w-0 flex-1 rounded border border-input bg-background px-2 py-1 text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-ring";
|
|
27
|
+
const buttonClass =
|
|
28
|
+
"rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
|
|
29
|
+
const toggleClass = (on: boolean) =>
|
|
30
|
+
`${buttonClass} font-mono ${on ? "bg-primary/10 text-primary" : "text-muted-foreground"}`;
|
|
31
|
+
const basicToggles = FIND_OPTION_TOGGLES.filter((t) => !t.advanced);
|
|
32
|
+
const advancedToggles = FIND_OPTION_TOGGLES.filter((t) => t.advanced);
|
|
33
|
+
|
|
34
|
+
let find = $state.raw<SvelteFindReplace>(createFindReplace(null));
|
|
35
|
+
let moreOpen = $state(false);
|
|
36
|
+
let findInput = $state<HTMLInputElement | null>(null);
|
|
37
|
+
|
|
38
|
+
$effect(() => {
|
|
39
|
+
const f = createFindReplace(editor);
|
|
40
|
+
find = f;
|
|
41
|
+
return () => f.destroy();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
let visible = $derived(find.available && (alwaysOpen || find.open));
|
|
45
|
+
let showReplace = $derived(find.mode === "replace");
|
|
46
|
+
let status = $derived(
|
|
47
|
+
formatFindStatus({
|
|
48
|
+
query: find.query,
|
|
49
|
+
matches: find.matches,
|
|
50
|
+
currentIndex: find.currentIndex,
|
|
51
|
+
error: find.error,
|
|
52
|
+
truncated: find.truncated,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
let hasMatches = $derived(find.matchCount > 0);
|
|
56
|
+
|
|
57
|
+
// Mod+F / Mod+H (in the editor or the bar) focus and select the find field.
|
|
58
|
+
$effect(() => {
|
|
59
|
+
if (!visible || find.focusRequest === 0 || !findInput) return;
|
|
60
|
+
findInput.focus();
|
|
61
|
+
findInput.select();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const isMod = (e: KeyboardEvent) => e.metaKey || e.ctrlKey;
|
|
65
|
+
|
|
66
|
+
function close() {
|
|
67
|
+
find.close();
|
|
68
|
+
editor?.commands.focus();
|
|
69
|
+
onclose?.();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onBarKeyDown(e: KeyboardEvent) {
|
|
73
|
+
if (e.key === "Escape") {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
close();
|
|
76
|
+
} else if (isMod(e) && (e.key === "f" || e.key === "F")) {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
find.show("find");
|
|
79
|
+
} else if (isMod(e) && (e.key === "h" || e.key === "H")) {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
find.show("replace");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function onFindKeyDown(e: KeyboardEvent) {
|
|
86
|
+
if (e.key !== "Enter") return;
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
if (e.shiftKey) find.previous();
|
|
89
|
+
else find.next();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function onReplaceKeyDown(e: KeyboardEvent) {
|
|
93
|
+
if (e.key !== "Enter") return;
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
if (isMod(e)) find.replaceAll();
|
|
96
|
+
else find.replace();
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
{#if visible}
|
|
101
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
102
|
+
<div class={className} role="search" aria-label="Find and replace" onkeydown={onBarKeyDown}>
|
|
103
|
+
<div class="flex items-center gap-1">
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
class={buttonClass}
|
|
107
|
+
aria-label="Toggle replace"
|
|
108
|
+
aria-expanded={showReplace}
|
|
109
|
+
onclick={() => find.show(showReplace ? "find" : "replace")}
|
|
110
|
+
>
|
|
111
|
+
{showReplace ? "▾" : "▸"}
|
|
112
|
+
</button>
|
|
113
|
+
<input
|
|
114
|
+
bind:this={findInput}
|
|
115
|
+
type="text"
|
|
116
|
+
class={inputClass}
|
|
117
|
+
placeholder="Find"
|
|
118
|
+
aria-label="Find"
|
|
119
|
+
aria-invalid={!!find.error}
|
|
120
|
+
value={find.query}
|
|
121
|
+
oninput={(e) => find.setQuery(e.currentTarget.value)}
|
|
122
|
+
onkeydown={onFindKeyDown}
|
|
123
|
+
/>
|
|
124
|
+
{#each basicToggles as t (t.key)}
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
class={toggleClass(!!find.options[t.key])}
|
|
128
|
+
aria-label={t.label}
|
|
129
|
+
title={t.label}
|
|
130
|
+
aria-pressed={!!find.options[t.key]}
|
|
131
|
+
onclick={() => find.toggleOption(t.key)}
|
|
132
|
+
>
|
|
133
|
+
{t.short}
|
|
134
|
+
</button>
|
|
135
|
+
{/each}
|
|
136
|
+
<span
|
|
137
|
+
class={`min-w-[5rem] px-1 text-center text-xs ${find.error ? "text-destructive" : "text-muted-foreground"}`}
|
|
138
|
+
aria-live="polite"
|
|
139
|
+
role={find.error ? "alert" : "status"}
|
|
140
|
+
data-testid="find-status"
|
|
141
|
+
>
|
|
142
|
+
{status}
|
|
143
|
+
</span>
|
|
144
|
+
<button type="button" class={buttonClass} aria-label="Previous match" disabled={!hasMatches} onclick={() => find.previous()}>
|
|
145
|
+
↑
|
|
146
|
+
</button>
|
|
147
|
+
<button type="button" class={buttonClass} aria-label="Next match" disabled={!hasMatches} onclick={() => find.next()}>
|
|
148
|
+
↓
|
|
149
|
+
</button>
|
|
150
|
+
{#if showMoreOptions}
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
class={buttonClass}
|
|
154
|
+
aria-label="More options"
|
|
155
|
+
aria-expanded={moreOpen}
|
|
156
|
+
onclick={() => (moreOpen = !moreOpen)}
|
|
157
|
+
>
|
|
158
|
+
…
|
|
159
|
+
</button>
|
|
160
|
+
{/if}
|
|
161
|
+
<button type="button" class={buttonClass} aria-label="Close find" onclick={close}>×</button>
|
|
162
|
+
</div>
|
|
163
|
+
{#if showReplace}
|
|
164
|
+
<div class="flex items-center gap-1 pl-7">
|
|
165
|
+
<input
|
|
166
|
+
type="text"
|
|
167
|
+
class={inputClass}
|
|
168
|
+
placeholder="Replace"
|
|
169
|
+
aria-label="Replace"
|
|
170
|
+
value={find.replacement}
|
|
171
|
+
oninput={(e) => find.setReplacement(e.currentTarget.value)}
|
|
172
|
+
onkeydown={onReplaceKeyDown}
|
|
173
|
+
/>
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
class={buttonClass}
|
|
177
|
+
disabled={!hasMatches || find.currentIndex < 0}
|
|
178
|
+
onclick={() => find.replace()}
|
|
179
|
+
>
|
|
180
|
+
Replace
|
|
181
|
+
</button>
|
|
182
|
+
<button type="button" class={buttonClass} disabled={!hasMatches} onclick={() => find.replaceAll()}>
|
|
183
|
+
Replace all
|
|
184
|
+
</button>
|
|
185
|
+
{#if find.lastReplaceCount !== null}
|
|
186
|
+
<span class="px-1 text-xs text-muted-foreground" aria-live="polite">
|
|
187
|
+
{find.lastReplaceCount === 1 ? "1 replaced" : `${find.lastReplaceCount} replaced`}
|
|
188
|
+
</span>
|
|
189
|
+
{/if}
|
|
190
|
+
</div>
|
|
191
|
+
{/if}
|
|
192
|
+
{#if showMoreOptions && moreOpen}
|
|
193
|
+
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 pl-7 text-xs text-muted-foreground">
|
|
194
|
+
{#each advancedToggles as t (t.key)}
|
|
195
|
+
<label class="flex items-center gap-1">
|
|
196
|
+
<input type="checkbox" checked={!!find.options[t.key]} onchange={() => find.toggleOption(t.key)} />
|
|
197
|
+
{t.label}
|
|
198
|
+
</label>
|
|
199
|
+
{/each}
|
|
200
|
+
</div>
|
|
201
|
+
{/if}
|
|
202
|
+
</div>
|
|
203
|
+
{/if}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/core";
|
|
2
|
+
interface Props {
|
|
3
|
+
editor?: Editor | null;
|
|
4
|
+
class?: string;
|
|
5
|
+
/** Render even while the find state is closed (default: only after Mod+F / Mod+H / `openFindReplace`). */
|
|
6
|
+
alwaysOpen?: boolean;
|
|
7
|
+
/** Show the "More options" toggle. Default `true`. */
|
|
8
|
+
showMoreOptions?: boolean;
|
|
9
|
+
/** Called after the bar closes (Escape or the close button). */
|
|
10
|
+
onclose?: () => void;
|
|
11
|
+
}
|
|
12
|
+
declare const FindReplaceBar: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type FindReplaceBar = ReturnType<typeof FindReplaceBar>;
|
|
14
|
+
export default FindReplaceBar;
|
|
15
|
+
//# sourceMappingURL=FindReplaceBar.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FindReplaceBar.svelte.d.ts","sourceRoot":"","sources":["../../src/find-replace/FindReplaceBar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAKzC,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0GAA0G;IAC1G,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sDAAsD;IACtD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAsJH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/core";
|
|
2
|
+
import { type FindOptions, type FindReplaceMode, type FindReplaceSnapshot } from "@pixerate/editor";
|
|
3
|
+
export interface SvelteFindReplace extends FindReplaceSnapshot {
|
|
4
|
+
/** Whether the editor has the `FindReplace` extension. */
|
|
5
|
+
readonly available: boolean;
|
|
6
|
+
readonly matchCount: number;
|
|
7
|
+
show(mode?: FindReplaceMode): void;
|
|
8
|
+
close(): void;
|
|
9
|
+
setQuery(query: string): void;
|
|
10
|
+
setReplacement(replacement: string): void;
|
|
11
|
+
setOptions(options: Partial<FindOptions>): void;
|
|
12
|
+
toggleOption(option: keyof FindOptions): void;
|
|
13
|
+
next(): void;
|
|
14
|
+
previous(): void;
|
|
15
|
+
replace(): void;
|
|
16
|
+
replaceAll(): void;
|
|
17
|
+
clear(): void;
|
|
18
|
+
subscribe(run: (value: FindReplaceSnapshot) => void): () => void;
|
|
19
|
+
destroy(): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reactive (runes) and Svelte-store-compatible view of an editor's Find and
|
|
23
|
+
* Replace state (`FindReplace` extension) with its commands. Call
|
|
24
|
+
* `destroy()` when done (or create it inside a component).
|
|
25
|
+
*/
|
|
26
|
+
export declare function createFindReplace(editor: Editor | null | undefined): SvelteFindReplace;
|
|
27
|
+
//# sourceMappingURL=findReplace.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findReplace.svelte.d.ts","sourceRoot":"","sources":["../../src/find-replace/findReplace.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;IAChD,YAAY,CAAC,MAAM,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IAC9C,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,IAAI,IAAI,CAAC;IACjB,OAAO,IAAI,IAAI,CAAC;IAChB,UAAU,IAAI,IAAI,CAAC;IACnB,KAAK,IAAI,IAAI,CAAC;IACd,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACjE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAyEtF"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { EMPTY_FIND_REPLACE_SNAPSHOT, getFindReplaceStore, } from "@pixerate/editor";
|
|
2
|
+
/**
|
|
3
|
+
* Reactive (runes) and Svelte-store-compatible view of an editor's Find and
|
|
4
|
+
* Replace state (`FindReplace` extension) with its commands. Call
|
|
5
|
+
* `destroy()` when done (or create it inside a component).
|
|
6
|
+
*/
|
|
7
|
+
export function createFindReplace(editor) {
|
|
8
|
+
const store = getFindReplaceStore(editor ?? null);
|
|
9
|
+
let snapshot = $state.raw(store?.getSnapshot() ?? EMPTY_FIND_REPLACE_SNAPSHOT);
|
|
10
|
+
const unsubscribe = store
|
|
11
|
+
? store.subscribe((value) => {
|
|
12
|
+
snapshot = value;
|
|
13
|
+
})
|
|
14
|
+
: () => { };
|
|
15
|
+
const run = (fn) => {
|
|
16
|
+
if (editor && !editor.isDestroyed && store)
|
|
17
|
+
fn(editor);
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
get available() {
|
|
21
|
+
return !!store;
|
|
22
|
+
},
|
|
23
|
+
get open() {
|
|
24
|
+
return snapshot.open;
|
|
25
|
+
},
|
|
26
|
+
get mode() {
|
|
27
|
+
return snapshot.mode;
|
|
28
|
+
},
|
|
29
|
+
get query() {
|
|
30
|
+
return snapshot.query;
|
|
31
|
+
},
|
|
32
|
+
get replacement() {
|
|
33
|
+
return snapshot.replacement;
|
|
34
|
+
},
|
|
35
|
+
get options() {
|
|
36
|
+
return snapshot.options;
|
|
37
|
+
},
|
|
38
|
+
get matches() {
|
|
39
|
+
return snapshot.matches;
|
|
40
|
+
},
|
|
41
|
+
get matchCount() {
|
|
42
|
+
return snapshot.matches.length;
|
|
43
|
+
},
|
|
44
|
+
get currentIndex() {
|
|
45
|
+
return snapshot.currentIndex;
|
|
46
|
+
},
|
|
47
|
+
get error() {
|
|
48
|
+
return snapshot.error;
|
|
49
|
+
},
|
|
50
|
+
get truncated() {
|
|
51
|
+
return snapshot.truncated;
|
|
52
|
+
},
|
|
53
|
+
get lastReplaceCount() {
|
|
54
|
+
return snapshot.lastReplaceCount;
|
|
55
|
+
},
|
|
56
|
+
get focusRequest() {
|
|
57
|
+
return snapshot.focusRequest;
|
|
58
|
+
},
|
|
59
|
+
show: (mode = "find") => run((e) => e.commands.openFindReplace({ mode })),
|
|
60
|
+
close: () => run((e) => e.commands.closeFindReplace()),
|
|
61
|
+
setQuery: (query) => run((e) => e.commands.setSearch({ query })),
|
|
62
|
+
setReplacement: (replacement) => run((e) => e.commands.setSearch({ replacement })),
|
|
63
|
+
setOptions: (options) => run((e) => e.commands.setSearch({ options })),
|
|
64
|
+
toggleOption: (option) => run((e) => e.commands.setSearch({ options: { [option]: !snapshot.options[option] } })),
|
|
65
|
+
next: () => run((e) => e.commands.findNext()),
|
|
66
|
+
previous: () => run((e) => e.commands.findPrevious()),
|
|
67
|
+
replace: () => run((e) => e.commands.replaceCurrent()),
|
|
68
|
+
replaceAll: () => run((e) => e.commands.replaceAll()),
|
|
69
|
+
clear: () => run((e) => e.commands.clearSearch()),
|
|
70
|
+
subscribe(runFn) {
|
|
71
|
+
if (!store) {
|
|
72
|
+
runFn(EMPTY_FIND_REPLACE_SNAPSHOT);
|
|
73
|
+
return () => { };
|
|
74
|
+
}
|
|
75
|
+
return store.subscribe(runFn);
|
|
76
|
+
},
|
|
77
|
+
destroy: unsubscribe,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ComponentProps } from "svelte";
|
|
2
|
+
import type FindReplaceBarComponent from "./FindReplaceBar.svelte";
|
|
3
|
+
export { default as FindReplaceBar } from "./FindReplaceBar.svelte";
|
|
4
|
+
export * from "./findReplace.svelte.js";
|
|
5
|
+
export type FindReplaceBarProps = ComponentProps<typeof FindReplaceBarComponent>;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/find-replace/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,uBAAuB,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,cAAc,yBAAyB,CAAC;AAExC,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import {
|
|
4
|
+
defaultAssetRoleOptions,
|
|
5
|
+
describeAssetMentions,
|
|
6
|
+
findTextMentions,
|
|
7
|
+
getAssetRoleOptions,
|
|
8
|
+
setMentionRoleInText,
|
|
9
|
+
type AssetInfo,
|
|
10
|
+
type AssetMentionChip,
|
|
11
|
+
type AssetRoleOption,
|
|
12
|
+
type ModelCapabilities
|
|
13
|
+
} from '@pixerate/editor/generation';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
/** Prompt text containing `@asset` / `@asset:role` mentions. */
|
|
17
|
+
content: string;
|
|
18
|
+
/** Called with the text after a role is picked (`@fox` → `@fox:style_ref`). */
|
|
19
|
+
onchange: (next: string) => void;
|
|
20
|
+
/** Mentionable assets; mentions that match none (by id or label) are not shown. */
|
|
21
|
+
assets: readonly AssetInfo[];
|
|
22
|
+
/** The selected model: its roles are offered and mentions are checked against it. */
|
|
23
|
+
capabilities?: ModelCapabilities;
|
|
24
|
+
/** Role options (default: the model's roles, else the built-in roles). */
|
|
25
|
+
roles?: readonly AssetRoleOption[];
|
|
26
|
+
/** Custom chip content (default: the asset label). */
|
|
27
|
+
asset?: Snippet<[AssetMentionChip]>;
|
|
28
|
+
class?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let { content, onchange, assets, capabilities, roles, asset, class: className = '' }: Props = $props();
|
|
32
|
+
|
|
33
|
+
let open = $state<number | null>(null);
|
|
34
|
+
let rootEl = $state<HTMLDivElement | null>(null);
|
|
35
|
+
const chips = $derived(describeAssetMentions(findTextMentions(content), assets, capabilities));
|
|
36
|
+
const options = $derived(roles ?? (capabilities ? getAssetRoleOptions(capabilities) : defaultAssetRoleOptions()));
|
|
37
|
+
|
|
38
|
+
$effect(() => {
|
|
39
|
+
if (open === null) return;
|
|
40
|
+
const onPointer = (event: MouseEvent) => {
|
|
41
|
+
if (!rootEl?.contains(event.target as Node)) open = null;
|
|
42
|
+
};
|
|
43
|
+
document.addEventListener('mousedown', onPointer);
|
|
44
|
+
return () => document.removeEventListener('mousedown', onPointer);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const pick = (chip: AssetMentionChip, role: string | null) => {
|
|
48
|
+
open = null;
|
|
49
|
+
if (chip.start === undefined) return;
|
|
50
|
+
onchange(setMentionRoleInText(content, chip.start, role));
|
|
51
|
+
};
|
|
52
|
+
const problemOf = (chip: AssetMentionChip) =>
|
|
53
|
+
chip.issues.find((issue) => issue.severity === 'error') ?? chip.issues.find((issue) => issue.severity === 'warning');
|
|
54
|
+
const onKey = (event: KeyboardEvent) => {
|
|
55
|
+
if (event.key === 'Escape') open = null;
|
|
56
|
+
};
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<!-- Asset mentions as chips with role badges and a role picker popover. -->
|
|
60
|
+
{#if chips.length > 0}
|
|
61
|
+
<div bind:this={rootEl} class="flex flex-wrap gap-1.5 {className}" data-asset-mention-chips="">
|
|
62
|
+
{#each chips as chip (`${chip.start}-${chip.index}`)}
|
|
63
|
+
{@const problem = problemOf(chip)}
|
|
64
|
+
{@const isOpen = open === chip.index}
|
|
65
|
+
<span class="relative inline-flex" data-asset-chip={chip.asset.id}>
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
aria-haspopup="listbox"
|
|
69
|
+
aria-expanded={isOpen}
|
|
70
|
+
title={problem?.message}
|
|
71
|
+
data-issue={problem?.code}
|
|
72
|
+
onclick={() => (open = isOpen ? null : chip.index)}
|
|
73
|
+
onkeydown={onKey}
|
|
74
|
+
class="inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs {problem?.severity === 'error'
|
|
75
|
+
? 'border-red-500/60'
|
|
76
|
+
: problem
|
|
77
|
+
? 'border-amber-500/60'
|
|
78
|
+
: 'border-border'} bg-muted/40 hover:bg-muted"
|
|
79
|
+
>
|
|
80
|
+
<span>{#if asset}{@render asset(chip)}{:else}@{chip.asset.label ?? chip.handle}{/if}</span>
|
|
81
|
+
<span
|
|
82
|
+
class="rounded px-1 text-[10px] uppercase tracking-wide {chip.role ? 'bg-primary/15' : 'opacity-60'}"
|
|
83
|
+
data-role-badge={chip.role ?? ''}>{chip.roleLabel ?? 'No role'}</span
|
|
84
|
+
>
|
|
85
|
+
</button>
|
|
86
|
+
{#if isOpen}
|
|
87
|
+
<div
|
|
88
|
+
role="listbox"
|
|
89
|
+
tabindex="-1"
|
|
90
|
+
aria-label="Role for {chip.asset.label ?? chip.handle}"
|
|
91
|
+
class="absolute left-0 top-full z-50 mt-1 flex min-w-[12rem] flex-col rounded-md border border-border bg-background p-1 text-xs shadow-lg"
|
|
92
|
+
data-role-picker=""
|
|
93
|
+
onkeydown={onKey}
|
|
94
|
+
>
|
|
95
|
+
{#each options as option (option.role)}
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
role="option"
|
|
99
|
+
aria-selected={chip.role === option.role}
|
|
100
|
+
data-role-option={option.role}
|
|
101
|
+
title={option.description}
|
|
102
|
+
onclick={() => pick(chip, option.role)}
|
|
103
|
+
class="flex items-center justify-between gap-2 rounded px-2 py-1 text-left hover:bg-muted"
|
|
104
|
+
>
|
|
105
|
+
<span>{option.label}</span>{#if option.accepts?.length}<span class="text-[10px] opacity-60"
|
|
106
|
+
>{option.accepts.join(', ')}</span
|
|
107
|
+
>{/if}
|
|
108
|
+
</button>
|
|
109
|
+
{/each}
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
role="option"
|
|
113
|
+
aria-selected={!chip.role}
|
|
114
|
+
data-role-option=""
|
|
115
|
+
onclick={() => pick(chip, null)}
|
|
116
|
+
class="rounded px-2 py-1 text-left opacity-70 hover:bg-muted">No role</button
|
|
117
|
+
>
|
|
118
|
+
{#if chip.issues.length > 0}
|
|
119
|
+
<ul class="mt-1 border-t border-border px-2 pt-1 text-[10px]" data-chip-issues="">
|
|
120
|
+
{#each chip.issues as issue, i (i)}
|
|
121
|
+
<li class={issue.severity === 'error' ? 'text-red-500' : 'text-amber-500'}>{issue.message}</li>
|
|
122
|
+
{/each}
|
|
123
|
+
</ul>
|
|
124
|
+
{/if}
|
|
125
|
+
</div>
|
|
126
|
+
{/if}
|
|
127
|
+
</span>
|
|
128
|
+
{/each}
|
|
129
|
+
</div>
|
|
130
|
+
{/if}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type AssetInfo, type AssetMentionChip, type AssetRoleOption, type ModelCapabilities } from '@pixerate/editor/generation';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Prompt text containing `@asset` / `@asset:role` mentions. */
|
|
5
|
+
content: string;
|
|
6
|
+
/** Called with the text after a role is picked (`@fox` → `@fox:style_ref`). */
|
|
7
|
+
onchange: (next: string) => void;
|
|
8
|
+
/** Mentionable assets; mentions that match none (by id or label) are not shown. */
|
|
9
|
+
assets: readonly AssetInfo[];
|
|
10
|
+
/** The selected model: its roles are offered and mentions are checked against it. */
|
|
11
|
+
capabilities?: ModelCapabilities;
|
|
12
|
+
/** Role options (default: the model's roles, else the built-in roles). */
|
|
13
|
+
roles?: readonly AssetRoleOption[];
|
|
14
|
+
/** Custom chip content (default: the asset label). */
|
|
15
|
+
asset?: Snippet<[AssetMentionChip]>;
|
|
16
|
+
class?: string;
|
|
17
|
+
}
|
|
18
|
+
declare const AssetMentionChips: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type AssetMentionChips = ReturnType<typeof AssetMentionChips>;
|
|
20
|
+
export default AssetMentionChips;
|
|
21
|
+
//# sourceMappingURL=AssetMentionChips.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssetMentionChips.svelte.d.ts","sourceRoot":"","sources":["../../src/generation/AssetMentionChips.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAMH,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,6BAA6B,CAAC;AAGrC,UAAU,KAAK;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,mFAAmF;IACnF,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B,qFAAqF;IACrF,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,0EAA0E;IAC1E,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyEH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
let nextId = 0;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import type { Snippet } from 'svelte';
|
|
7
|
+
import {
|
|
8
|
+
DRIFT_TYPE_LABELS,
|
|
9
|
+
recreateRequest,
|
|
10
|
+
referenceRoleLabel,
|
|
11
|
+
variationRequest,
|
|
12
|
+
type AssetLookup,
|
|
13
|
+
type GenerationRecord,
|
|
14
|
+
type ModelCapabilities,
|
|
15
|
+
type RecreateMode,
|
|
16
|
+
type RecreateOptions,
|
|
17
|
+
type RecreateResult
|
|
18
|
+
} from '@pixerate/editor/generation';
|
|
19
|
+
// Not `@pixerate/editor/grammar`: svelte-package keeps type imports in
|
|
20
|
+
// `.svelte` output, which would pull another core entry into this one.
|
|
21
|
+
type Template = NonNullable<RecreateOptions['templates']>[number];
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
records: readonly GenerationRecord[];
|
|
25
|
+
/** Current templates (drift detection, and resolution in `latest` mode). */
|
|
26
|
+
templates?: readonly Template[];
|
|
27
|
+
/** Current models (capability drift and re-planning). */
|
|
28
|
+
capabilities?: ModelCapabilities | readonly ModelCapabilities[];
|
|
29
|
+
/** Current assets (missing / changed asset drift). */
|
|
30
|
+
assets?: AssetLookup;
|
|
31
|
+
/** Recreate mode (bindable); starts at `exact`. */
|
|
32
|
+
mode?: RecreateMode;
|
|
33
|
+
onmodechange?: (mode: RecreateMode) => void;
|
|
34
|
+
/** "Recreate": receives the rebuilt request and drift report. */
|
|
35
|
+
onrecreate?: (result: RecreateResult, record: GenerationRecord) => void;
|
|
36
|
+
/** "Variation": same request with a new seed. */
|
|
37
|
+
onvariation?: (result: RecreateResult, record: GenerationRecord) => void;
|
|
38
|
+
onremove?: (record: GenerationRecord) => void;
|
|
39
|
+
/** Extra content per record (e.g. output thumbnails). */
|
|
40
|
+
preview?: Snippet<[GenerationRecord]>;
|
|
41
|
+
emptyText?: string;
|
|
42
|
+
class?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let {
|
|
46
|
+
records,
|
|
47
|
+
templates,
|
|
48
|
+
capabilities,
|
|
49
|
+
assets,
|
|
50
|
+
mode = $bindable('exact'),
|
|
51
|
+
onmodechange,
|
|
52
|
+
onrecreate,
|
|
53
|
+
onvariation,
|
|
54
|
+
onremove,
|
|
55
|
+
preview,
|
|
56
|
+
emptyText = 'No generations yet.',
|
|
57
|
+
class: className = ''
|
|
58
|
+
}: Props = $props();
|
|
59
|
+
|
|
60
|
+
const groupName = `generation-history-mode-${++nextId}`;
|
|
61
|
+
const options = $derived({ templates, capabilities, assets, mode });
|
|
62
|
+
const results = $derived(records.map((record) => recreateRequest(record, options)));
|
|
63
|
+
const models = $derived(
|
|
64
|
+
capabilities ? (Array.isArray(capabilities) ? capabilities : [capabilities as ModelCapabilities]) : []
|
|
65
|
+
);
|
|
66
|
+
const severityClass = {
|
|
67
|
+
info: 'text-sky-600 dark:text-sky-300',
|
|
68
|
+
warning: 'text-amber-600 dark:text-amber-300',
|
|
69
|
+
error: 'text-red-600 dark:text-red-400'
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
const setMode = (next: RecreateMode) => {
|
|
73
|
+
mode = next;
|
|
74
|
+
onmodechange?.(next);
|
|
75
|
+
};
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<!-- Generation history with drift reports and Recreate / Variation actions. -->
|
|
79
|
+
<div class="flex flex-col gap-2 text-sm {className}" data-generation-history="">
|
|
80
|
+
<div class="flex items-center gap-2 text-xs" role="radiogroup" aria-label="Recreate mode">
|
|
81
|
+
{#each ['exact', 'latest'] as const as value (value)}
|
|
82
|
+
<label class="inline-flex items-center gap-1">
|
|
83
|
+
<input
|
|
84
|
+
type="radio"
|
|
85
|
+
name={groupName}
|
|
86
|
+
{value}
|
|
87
|
+
checked={mode === value}
|
|
88
|
+
onchange={() => setMode(value)}
|
|
89
|
+
data-mode={value}
|
|
90
|
+
/>
|
|
91
|
+
{value === 'exact' ? 'Exact (recorded versions)' : 'Latest templates & settings'}
|
|
92
|
+
</label>
|
|
93
|
+
{/each}
|
|
94
|
+
</div>
|
|
95
|
+
{#if records.length === 0}<p class="text-xs text-muted-foreground">{emptyText}</p>{/if}
|
|
96
|
+
<ul class="flex flex-col gap-2">
|
|
97
|
+
{#each records as record, i (record.id)}
|
|
98
|
+
{@const result = results[i]}
|
|
99
|
+
{@const model = models.find((m) => m.id === record.provenance.modelId)}
|
|
100
|
+
<li class="rounded-md border border-border p-2" data-record={record.id} data-drift={result.drift.length}>
|
|
101
|
+
<div class="flex flex-wrap items-center justify-between gap-2 text-xs">
|
|
102
|
+
<span class="font-medium">{model?.name ?? record.provenance.modelId}</span>
|
|
103
|
+
<span class="text-muted-foreground">
|
|
104
|
+
{#if record.kind !== 'generate'}<span data-kind="">{record.kind} · </span>{/if}<time datetime={record.createdAt}
|
|
105
|
+
>{record.createdAt.replace('T', ' ').slice(0, 16)}</time
|
|
106
|
+
>{#if record.seed !== undefined} · seed {record.seed}{/if}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
<p class="mt-1 line-clamp-3 break-words text-xs" data-record-prompt="">{record.provenance.resolvedPrompt}</p>
|
|
110
|
+
{#if record.assets.length > 0}
|
|
111
|
+
<div class="mt-1 flex flex-wrap gap-1">
|
|
112
|
+
{#each record.assets as item (`${item.role}:${item.id}`)}
|
|
113
|
+
<span class="rounded-full bg-muted px-2 py-0.5 text-[10px]" data-asset-role={item.role}
|
|
114
|
+
>{item.id} · {referenceRoleLabel(item.role, model)}</span
|
|
115
|
+
>
|
|
116
|
+
{/each}
|
|
117
|
+
</div>
|
|
118
|
+
{/if}
|
|
119
|
+
{#if preview}{@render preview(record)}{/if}
|
|
120
|
+
{#if result.drift.length > 0}
|
|
121
|
+
<ul class="mt-1 flex flex-col gap-0.5 text-[11px]" data-drift-report="">
|
|
122
|
+
{#each result.drift as item, j (j)}
|
|
123
|
+
<li class={severityClass[item.severity]} data-drift-type={item.type}>
|
|
124
|
+
<strong>{DRIFT_TYPE_LABELS[item.type]}:</strong>
|
|
125
|
+
{item.message}
|
|
126
|
+
</li>
|
|
127
|
+
{/each}
|
|
128
|
+
</ul>
|
|
129
|
+
{/if}
|
|
130
|
+
<div class="mt-2 flex gap-2">
|
|
131
|
+
{#if onrecreate}
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
disabled={!result.ok}
|
|
135
|
+
title={!result.ok ? 'This generation cannot be recreated as recorded.' : undefined}
|
|
136
|
+
onclick={() => onrecreate(result, record)}
|
|
137
|
+
class="rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-50"
|
|
138
|
+
data-action="recreate">Recreate</button
|
|
139
|
+
>
|
|
140
|
+
{/if}
|
|
141
|
+
{#if onvariation}
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
onclick={() => onvariation(variationRequest(record, {}, options), record)}
|
|
145
|
+
class="rounded border border-border px-2 py-0.5 text-xs hover:bg-muted"
|
|
146
|
+
data-action="variation">Variation</button
|
|
147
|
+
>
|
|
148
|
+
{/if}
|
|
149
|
+
{#if onremove}
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
onclick={() => onremove(record)}
|
|
153
|
+
class="ml-auto rounded px-2 py-0.5 text-xs text-muted-foreground hover:bg-muted"
|
|
154
|
+
data-action="remove">Remove</button
|
|
155
|
+
>
|
|
156
|
+
{/if}
|
|
157
|
+
</div>
|
|
158
|
+
</li>
|
|
159
|
+
{/each}
|
|
160
|
+
</ul>
|
|
161
|
+
</div>
|