@svadmin/ui 0.65.1 → 0.67.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/dist/app.css +0 -51
- package/dist/app.theme.css +0 -51
- package/dist/components/AdminApp.svelte +5 -2
- package/dist/components/AdminApp.svelte.d.ts +3 -0
- package/dist/components/CommandPalette.svelte +3 -1
- package/dist/components/Layout.svelte +20 -12
- package/dist/components/Layout.svelte.d.ts +6 -0
- package/dist/components/MetricStrip.svelte +149 -0
- package/dist/components/MetricStrip.svelte.d.ts +32 -0
- package/dist/components/ui/badge/badge.svelte +5 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +15 -0
- package/dist/components/ui/table/table-cell.svelte +14 -2
- package/dist/components/ui/table/table-cell.svelte.d.ts +4 -1
- package/dist/components/ui/table/table-head.svelte +14 -2
- package/dist/components/ui/table/table-head.svelte.d.ts +4 -1
- package/dist/components/ui/table/table-row.svelte +1 -1
- package/dist/index.d.ts +3 -7
- package/dist/index.js +1 -7
- package/package.json +3 -14
- package/dist/components/AICommandBar.svelte +0 -228
- package/dist/components/AICommandBar.svelte.d.ts +0 -6
- package/dist/components/ChatDialog.svelte +0 -947
- package/dist/components/ChatDialog.svelte.d.ts +0 -24
- package/dist/components/CopilotPanel.svelte +0 -204
- package/dist/components/CopilotPanel.svelte.d.ts +0 -6
- package/dist/components/InsightCard.svelte +0 -139
- package/dist/components/InsightCard.svelte.d.ts +0 -11
- package/dist/components/MarkdownRenderer.svelte +0 -245
- package/dist/components/MarkdownRenderer.svelte.d.ts +0 -11
- package/dist/components/SmartSuggest.svelte +0 -207
- package/dist/components/SmartSuggest.svelte.d.ts +0 -19
- package/dist/components/VoiceInput.svelte +0 -133
- package/dist/components/VoiceInput.svelte.d.ts +0 -15
- package/dist/markdown-deps.d.ts +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.0",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -39,24 +39,12 @@
|
|
|
39
39
|
"./components/*": "./dist/components/*"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@svadmin/core": "^0.
|
|
42
|
+
"@svadmin/core": "^0.48.0",
|
|
43
43
|
"@tanstack/svelte-query": "^6.1.48",
|
|
44
|
-
"highlight.js": "^11.12.0",
|
|
45
44
|
"isomorphic-dompurify": "^3.23.0",
|
|
46
|
-
"marked": "^18.0.11",
|
|
47
|
-
"marked-highlight": "^2.2.4",
|
|
48
45
|
"svelte": "^5.56.10"
|
|
49
46
|
},
|
|
50
47
|
"peerDependenciesMeta": {
|
|
51
|
-
"marked": {
|
|
52
|
-
"optional": true
|
|
53
|
-
},
|
|
54
|
-
"marked-highlight": {
|
|
55
|
-
"optional": true
|
|
56
|
-
},
|
|
57
|
-
"highlight.js": {
|
|
58
|
-
"optional": true
|
|
59
|
-
},
|
|
60
48
|
"isomorphic-dompurify": {
|
|
61
49
|
"optional": true
|
|
62
50
|
},
|
|
@@ -101,6 +89,7 @@
|
|
|
101
89
|
},
|
|
102
90
|
"devDependencies": {
|
|
103
91
|
"@happy-dom/global-registrator": "^20.11.12",
|
|
92
|
+
"@svadmin/ai-elements": "^0.2.0",
|
|
104
93
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
105
94
|
"@testing-library/dom": "^10.4.1",
|
|
106
95
|
"@testing-library/svelte": "^5.4.2",
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { captureAdminContext, toggleTheme, useNavigation } from '@svadmin/core';
|
|
3
|
-
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
|
-
import { Command } from './ui/command/index.js';
|
|
5
|
-
import * as Dialog from './ui/dialog/index.js';
|
|
6
|
-
import { Search, LayoutDashboard, Plus, Sun, FileText, Sparkles, Loader2 } from '@lucide/svelte';
|
|
7
|
-
import MarkdownRenderer from './MarkdownRenderer.svelte';
|
|
8
|
-
import CanAccess from './CanAccess.svelte';
|
|
9
|
-
|
|
10
|
-
const i18n = useTranslation();
|
|
11
|
-
|
|
12
|
-
let { open = $bindable(false) } = $props<{ open?: boolean }>();
|
|
13
|
-
const adminContext = captureAdminContext();
|
|
14
|
-
const navigation = useNavigation();
|
|
15
|
-
let searchValue = $state('');
|
|
16
|
-
|
|
17
|
-
const resources = $derived(adminContext.resources);
|
|
18
|
-
const provider = $derived(adminContext.chatProvider);
|
|
19
|
-
|
|
20
|
-
// AI State
|
|
21
|
-
let aiMode = $state(false);
|
|
22
|
-
let aiResponse = $state('');
|
|
23
|
-
let isPredicting = $state(false);
|
|
24
|
-
let abortController: AbortController | null = null;
|
|
25
|
-
let requestEpoch = 0;
|
|
26
|
-
|
|
27
|
-
$effect(() => {
|
|
28
|
-
void provider;
|
|
29
|
-
void adminContext.tenantCacheKey?.__svadminTenant;
|
|
30
|
-
void open;
|
|
31
|
-
clearAIConversation();
|
|
32
|
-
|
|
33
|
-
return cancelAIRequest;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
function close() {
|
|
37
|
-
open = false;
|
|
38
|
-
clearAIConversation();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function cancelAIRequest() {
|
|
42
|
-
requestEpoch += 1;
|
|
43
|
-
abortController?.abort();
|
|
44
|
-
abortController = null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function clearAIConversation() {
|
|
48
|
-
cancelAIRequest();
|
|
49
|
-
searchValue = '';
|
|
50
|
-
aiMode = false;
|
|
51
|
-
aiResponse = '';
|
|
52
|
-
isPredicting = false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function act(fn: () => void) {
|
|
56
|
-
fn();
|
|
57
|
-
close();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function askAI() {
|
|
61
|
-
const scopedProvider = provider;
|
|
62
|
-
const prompt = searchValue.trim();
|
|
63
|
-
if (!scopedProvider || !prompt) return;
|
|
64
|
-
|
|
65
|
-
cancelAIRequest();
|
|
66
|
-
const epoch = requestEpoch;
|
|
67
|
-
const controller = new AbortController();
|
|
68
|
-
abortController = controller;
|
|
69
|
-
aiMode = true;
|
|
70
|
-
isPredicting = true;
|
|
71
|
-
aiResponse = '';
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const result = scopedProvider.sendMessage(
|
|
75
|
-
[{ id: 'cmd', role: 'user', content: prompt, timestamp: Date.now() }],
|
|
76
|
-
{ signal: controller.signal }
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
if (Symbol.asyncIterator in result) {
|
|
80
|
-
for await (const chunk of result as AsyncIterable<string>) {
|
|
81
|
-
if (epoch !== requestEpoch) return;
|
|
82
|
-
aiResponse += chunk;
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
const response = await result;
|
|
86
|
-
if (epoch === requestEpoch) aiResponse = response;
|
|
87
|
-
}
|
|
88
|
-
} catch (err: unknown) {
|
|
89
|
-
if (epoch === requestEpoch && err instanceof Error && err.name !== 'AbortError') {
|
|
90
|
-
aiResponse = 'Failed to get response from AI.';
|
|
91
|
-
}
|
|
92
|
-
} finally {
|
|
93
|
-
if (epoch === requestEpoch) {
|
|
94
|
-
isPredicting = false;
|
|
95
|
-
if (abortController === controller) abortController = null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function handleKeyDown(e: KeyboardEvent) {
|
|
101
|
-
if (e.key === 'Enter' && searchValue.trim() && !aiMode) {
|
|
102
|
-
if (e.ctrlKey || e.metaKey) {
|
|
103
|
-
e.preventDefault();
|
|
104
|
-
void askAI();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const itemClass = 'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[selected]:bg-accent data-[selected]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50';
|
|
110
|
-
</script>
|
|
111
|
-
|
|
112
|
-
<Dialog.Dialog bind:open onOpenChange={(v: boolean) => !v && close()}>
|
|
113
|
-
<Dialog.DialogContent class="overflow-hidden p-0 sm:max-w-[600px] border-none shadow-2xl">
|
|
114
|
-
<Dialog.DialogTitle class="sr-only">{i18n.t('common.actions')}</Dialog.DialogTitle>
|
|
115
|
-
<Command.Root
|
|
116
|
-
shouldFilter={!aiMode}
|
|
117
|
-
class="flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground [&_[data-command-group-heading]]:text-muted-foreground [&_[data-command-group-heading]]:px-2 [&_[data-command-group-heading]]:py-1.5 [&_[data-command-group-heading]]:text-xs [&_[data-command-group-heading]]:font-semibold [&_[data-command-group-heading]]:uppercase [&_[data-command-group-heading]]:tracking-wider"
|
|
118
|
-
>
|
|
119
|
-
<div class="flex items-center border-b px-3 h-12 relative" data-cmdk-input-wrapper="">
|
|
120
|
-
{#if aiMode}
|
|
121
|
-
<Sparkles class="mr-2 h-4 w-4 shrink-0 text-warning animate-pulse" />
|
|
122
|
-
{:else}
|
|
123
|
-
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
124
|
-
{/if}
|
|
125
|
-
<Command.Input
|
|
126
|
-
bind:value={searchValue}
|
|
127
|
-
onkeydown={handleKeyDown}
|
|
128
|
-
placeholder={aiMode ? "Ask AI anything..." : "Search commands or ask AI (Ctrl+Enter)..."}
|
|
129
|
-
class="flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
|
130
|
-
/>
|
|
131
|
-
{#if searchValue.trim() && !aiMode}
|
|
132
|
-
<button
|
|
133
|
-
type="button"
|
|
134
|
-
class="absolute right-3 flex items-center gap-1 text-[10px] font-medium text-warning bg-warning/10 hover:bg-warning/20 px-2 py-1 rounded transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
135
|
-
onclick={askAI}
|
|
136
|
-
>
|
|
137
|
-
<Sparkles class="h-3 w-3" />
|
|
138
|
-
<kbd class="font-sans">Ctrl+Enter</kbd> to Ask AI
|
|
139
|
-
</button>
|
|
140
|
-
{/if}
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
{#if !aiMode}
|
|
144
|
-
<Command.List class="max-h-[360px] overflow-y-auto overflow-x-hidden p-1">
|
|
145
|
-
<Command.Empty class="py-6 text-center text-sm text-muted-foreground">
|
|
146
|
-
No standard commands found. Press <kbd class="px-1 py-0.5 rounded bg-muted border font-sans text-[10px]">Ctrl+Enter</kbd> to ask AI.
|
|
147
|
-
</Command.Empty>
|
|
148
|
-
|
|
149
|
-
<!-- Navigation -->
|
|
150
|
-
<Command.Group heading={i18n.t('common.home')}>
|
|
151
|
-
<Command.Item value="home" onSelect={() => act(() => adminContext.navigate('/'))} class={itemClass}>
|
|
152
|
-
<LayoutDashboard class="h-4 w-4 text-muted-foreground" />
|
|
153
|
-
{i18n.t('common.home')}
|
|
154
|
-
</Command.Item>
|
|
155
|
-
{#each resources as r, _i (_i)}
|
|
156
|
-
{#if adminContext.accessControlProvider}
|
|
157
|
-
<CanAccess resource={r.name} action="list">
|
|
158
|
-
<Command.Item value={r.name} onSelect={() => act(() => navigation.list(r.name))} class={itemClass}>
|
|
159
|
-
<FileText class="h-4 w-4 text-muted-foreground" />
|
|
160
|
-
{r.label}
|
|
161
|
-
</Command.Item>
|
|
162
|
-
</CanAccess>
|
|
163
|
-
{:else}
|
|
164
|
-
<Command.Item value={r.name} onSelect={() => act(() => navigation.list(r.name))} class={itemClass}>
|
|
165
|
-
<FileText class="h-4 w-4 text-muted-foreground" />
|
|
166
|
-
{r.label}
|
|
167
|
-
</Command.Item>
|
|
168
|
-
{/if}
|
|
169
|
-
{/each}
|
|
170
|
-
</Command.Group>
|
|
171
|
-
|
|
172
|
-
<Command.Separator class="mx-1 my-1 h-px bg-border" />
|
|
173
|
-
|
|
174
|
-
<!-- Actions -->
|
|
175
|
-
<Command.Group heading={i18n.t('common.actions')}>
|
|
176
|
-
{#each resources as r, _i (_i)}
|
|
177
|
-
{#if r.canCreate !== false}
|
|
178
|
-
{#if adminContext.accessControlProvider}
|
|
179
|
-
<CanAccess resource={r.name} action="create">
|
|
180
|
-
<Command.Item value={`create-${r.name}`} onSelect={() => act(() => navigation.create(r.name))} class={itemClass}>
|
|
181
|
-
<Plus class="h-4 w-4 text-muted-foreground" />
|
|
182
|
-
{i18n.t('common.create')} {r.label}
|
|
183
|
-
</Command.Item>
|
|
184
|
-
</CanAccess>
|
|
185
|
-
{:else}
|
|
186
|
-
<Command.Item value={`create-${r.name}`} onSelect={() => act(() => navigation.create(r.name))} class={itemClass}>
|
|
187
|
-
<Plus class="h-4 w-4 text-muted-foreground" />
|
|
188
|
-
{i18n.t('common.create')} {r.label}
|
|
189
|
-
</Command.Item>
|
|
190
|
-
{/if}
|
|
191
|
-
{/if}
|
|
192
|
-
{/each}
|
|
193
|
-
<Command.Item value="toggle-theme" onSelect={() => act(() => toggleTheme())} class={itemClass}>
|
|
194
|
-
<Sun class="h-4 w-4 text-muted-foreground" />
|
|
195
|
-
{i18n.t('common.toggleTheme')}
|
|
196
|
-
</Command.Item>
|
|
197
|
-
</Command.Group>
|
|
198
|
-
</Command.List>
|
|
199
|
-
{:else}
|
|
200
|
-
<!-- AI Response Area -->
|
|
201
|
-
<div class="h-[360px] max-h-[360px] overflow-y-auto p-4 bg-muted/20">
|
|
202
|
-
<div class="flex items-start gap-3">
|
|
203
|
-
<div class="mt-0.5 w-8 h-8 rounded-full bg-warning/10 text-warning flex items-center justify-center shrink-0">
|
|
204
|
-
<Sparkles class="h-4 w-4" />
|
|
205
|
-
</div>
|
|
206
|
-
<div class="flex-1 w-full text-sm">
|
|
207
|
-
{#if !aiResponse && isPredicting}
|
|
208
|
-
<div class="flex items-center gap-2 text-muted-foreground mt-1.5">
|
|
209
|
-
<Loader2 class="h-4 w-4 animate-spin" /> Thinking...
|
|
210
|
-
</div>
|
|
211
|
-
{:else}
|
|
212
|
-
<MarkdownRenderer content={aiResponse} streaming={isPredicting} />
|
|
213
|
-
{/if}
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
</div>
|
|
217
|
-
<div class="p-2 border-t flex justify-end gap-2 bg-muted/10">
|
|
218
|
-
<button
|
|
219
|
-
class="text-xs px-3 py-1.5 rounded-md hover:bg-muted transition-colors text-muted-foreground"
|
|
220
|
-
onclick={clearAIConversation}
|
|
221
|
-
>
|
|
222
|
-
Clear & Return
|
|
223
|
-
</button>
|
|
224
|
-
</div>
|
|
225
|
-
{/if}
|
|
226
|
-
</Command.Root>
|
|
227
|
-
</Dialog.DialogContent>
|
|
228
|
-
</Dialog.Dialog>
|