@svadmin/ui 0.66.0 → 0.67.1

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 CHANGED
@@ -573,57 +573,6 @@ main {
573
573
  /* ─── svadmin UI Components CSS ─────────────────────────────── */
574
574
  /* These rules were moved from .svelte files to eliminate <style> tags */
575
575
 
576
- /* MarkdownRenderer Styles */
577
- .prose {
578
- line-height: 1.6;
579
- }
580
- .prose p {
581
- margin-top: 0.5em;
582
- margin-bottom: 0.5em;
583
- }
584
-
585
- /* Streaming cursor effect */
586
- .streaming > *:last-child::after {
587
- content: '▋';
588
- display: inline-block;
589
- animation: svadmin-blink 1s step-start infinite;
590
- margin-left: 2px;
591
- vertical-align: baseline;
592
- color: currentColor;
593
- }
594
-
595
- @keyframes svadmin-blink {
596
- 50% { opacity: 0; }
597
- }
598
-
599
- /* Handle copied state for dynamically injected buttons.
600
- * The check icon itself is swapped in by MarkdownRenderer (createCheckIcon)
601
- * with stroke="currentColor" so it inherits this semantic token color. */
602
- .copy-btn[data-copied-id] {
603
- color: var(--success);
604
- }
605
-
606
- /* ChatDialog Styles */
607
- .chat-markdown pre.chat-code-block {
608
- background: var(--color-muted, hsl(var(--muted)));
609
- border-radius: 0.5rem;
610
- padding: 0.75rem;
611
- margin: 0.5rem 0;
612
- overflow-x: auto;
613
- font-size: 0.75rem;
614
- line-height: 1.5;
615
- }
616
- .chat-markdown code.chat-inline-code {
617
- background: var(--color-muted, hsl(var(--muted)));
618
- padding: 0.125rem 0.375rem;
619
- border-radius: 0.25rem;
620
- font-size: 0.8125em;
621
- font-family: ui-monospace, monospace;
622
- }
623
- .chat-markdown strong {
624
- font-weight: 600;
625
- }
626
-
627
576
  /* Collapsible details normalization stays scoped to svadmin-owned hooks. */
628
577
  details[data-svadmin-filter]:not([open]) > :not(summary),
629
578
  details.svadmin-collapsible-filter:not([open]) > :not(summary),
@@ -572,57 +572,6 @@ main {
572
572
  /* ─── svadmin UI Components CSS ─────────────────────────────── */
573
573
  /* These rules were moved from .svelte files to eliminate <style> tags */
574
574
 
575
- /* MarkdownRenderer Styles */
576
- .prose {
577
- line-height: 1.6;
578
- }
579
- .prose p {
580
- margin-top: 0.5em;
581
- margin-bottom: 0.5em;
582
- }
583
-
584
- /* Streaming cursor effect */
585
- .streaming > *:last-child::after {
586
- content: '▋';
587
- display: inline-block;
588
- animation: svadmin-blink 1s step-start infinite;
589
- margin-left: 2px;
590
- vertical-align: baseline;
591
- color: currentColor;
592
- }
593
-
594
- @keyframes svadmin-blink {
595
- 50% { opacity: 0; }
596
- }
597
-
598
- /* Handle copied state for dynamically injected buttons.
599
- * The check icon itself is swapped in by MarkdownRenderer (createCheckIcon)
600
- * with stroke="currentColor" so it inherits this semantic token color. */
601
- .copy-btn[data-copied-id] {
602
- color: var(--success);
603
- }
604
-
605
- /* ChatDialog Styles */
606
- .chat-markdown pre.chat-code-block {
607
- background: var(--color-muted, hsl(var(--muted)));
608
- border-radius: 0.5rem;
609
- padding: 0.75rem;
610
- margin: 0.5rem 0;
611
- overflow-x: auto;
612
- font-size: 0.75rem;
613
- line-height: 1.5;
614
- }
615
- .chat-markdown code.chat-inline-code {
616
- background: var(--color-muted, hsl(var(--muted)));
617
- padding: 0.125rem 0.375rem;
618
- border-radius: 0.25rem;
619
- font-size: 0.8125em;
620
- font-family: ui-monospace, monospace;
621
- }
622
- .chat-markdown strong {
623
- font-weight: 600;
624
- }
625
-
626
575
  /* Collapsible details normalization stays scoped to svadmin-owned hooks. */
627
576
  details[data-svadmin-filter]:not([open]) > :not(summary),
628
577
  details.svadmin-collapsible-filter:not([open]) > :not(summary),
@@ -56,7 +56,7 @@
56
56
  import { useTranslation } from '@svadmin/core/i18n';
57
57
  import { QueryClient, QueryClientProvider, type DefaultOptions } from '@tanstack/svelte-query';
58
58
  import { setComponentRegistry, type ComponentRegistry } from '../component-registry.svelte.js';
59
- import Layout from './Layout.svelte';
59
+ import Layout, { type LayoutAIAssistantProps } from './Layout.svelte';
60
60
  import AutoTable from './AutoTable.svelte';
61
61
  import AutoForm from './AutoForm.svelte';
62
62
  import ShowPage from './ShowPage.svelte';
@@ -114,6 +114,8 @@
114
114
  /** Theme configuration for dark-first mode, CSS overrides, etc. */
115
115
  themeConfig?: ThemeConfig;
116
116
  dashboard?: Snippet;
117
+ /** Optional AI assistant rendered by Layout. Keep AI UI in @svadmin/ai-elements. */
118
+ aiAssistant?: Snippet<[LayoutAIAssistantProps]>;
117
119
  loginPage?: Snippet;
118
120
  /** Prefill and display credentials on the default login page, useful for demos/examples. */
119
121
  loginDefaults?: {
@@ -169,6 +171,7 @@
169
171
  defaultTheme,
170
172
  themeConfig: userThemeConfig,
171
173
  dashboard,
174
+ aiAssistant,
172
175
  loginPage,
173
176
  loginDefaults,
174
177
  components: userComponents,
@@ -548,7 +551,7 @@
548
551
  <ErrorComp status={standaloneErrorStatus} />
549
552
  </SystemPageShell>
550
553
  {:else if isAuthenticated || !resolvedAuthProvider}
551
- <Layout {title} {menu} {siteUrl} routeMode={resolvedRouteMode}>
554
+ <Layout {title} {menu} {siteUrl} routeMode={resolvedRouteMode} {aiAssistant}>
552
555
  <div class="relative min-h-40" aria-busy={authRechecking}>
553
556
  {#if authRechecking}
554
557
  <div class="absolute inset-0 z-10 flex min-h-40 items-center justify-center bg-background/80" role="status" aria-live="polite">
@@ -3,6 +3,7 @@ import { type Component, type Snippet } from 'svelte';
3
3
  import type { AccessControlProvider, AgentProvider, AuditLogProvider, AuthProvider, ChatProvider, CredentialProvider, DataProviderInput, IdentityGovernanceProvider, I18nProvider, LiveProvider, MenuItem, NotificationProvider, OrganizationProvider, ResourceDefinition, RouterProvider, SessionProvider, TaskProvider, TenantAdapter, TenantContext, ThemeConfig, ThemeMode } from '@svadmin/core';
4
4
  import { QueryClient, type DefaultOptions } from '@tanstack/svelte-query';
5
5
  import { type ComponentRegistry } from '../component-registry.svelte.js';
6
+ import { type LayoutAIAssistantProps } from './Layout.svelte';
6
7
  import type { AdminProviderBundle } from '../types.js';
7
8
  interface Props {
8
9
  /** Single provider, or named providers such as `{ default, analytics }`. */
@@ -37,6 +38,8 @@ interface Props {
37
38
  /** Theme configuration for dark-first mode, CSS overrides, etc. */
38
39
  themeConfig?: ThemeConfig;
39
40
  dashboard?: Snippet;
41
+ /** Optional AI assistant rendered by Layout. Keep AI UI in @svadmin/ai-elements. */
42
+ aiAssistant?: Snippet<[LayoutAIAssistantProps]>;
40
43
  loginPage?: Snippet;
41
44
  /** Prefill and display credentials on the default login page, useful for demos/examples. */
42
45
  loginDefaults?: {
@@ -20,7 +20,9 @@
20
20
  let searchValue = $state('');
21
21
 
22
22
  const resources = $derived(getResources());
23
- const hasAI = $derived(!!(adminContext.agentProvider || adminContext.chatProvider));
23
+ const hasAI = $derived(
24
+ !!(adminContext.agentProvider || adminContext.chatProvider) && typeof onAskAI === 'function',
25
+ );
24
26
 
25
27
  function close() {
26
28
  open = false;
@@ -1,3 +1,11 @@
1
+ <script module lang="ts">
2
+ export interface LayoutAIAssistantProps {
3
+ docked: boolean;
4
+ scope: string;
5
+ ownerScope: string;
6
+ }
7
+ </script>
8
+
1
9
  <script lang="ts">
2
10
  /* eslint-disable svelte/no-useless-children-snippet */
3
11
  import type { Snippet } from 'svelte';
@@ -6,7 +14,6 @@
6
14
  import Header from './Header.svelte';
7
15
  import CommandPalette from './CommandPalette.svelte';
8
16
  import KeyboardShortcuts from './KeyboardShortcuts.svelte';
9
- import ChatDialog from './ChatDialog.svelte';
10
17
  import DevTools from './DevTools.svelte';
11
18
  import { useTranslation } from '@svadmin/core/i18n';
12
19
 
@@ -25,7 +32,7 @@
25
32
  let shortcutsOpen = $state(false);
26
33
  let mobileMenuOpen = $state(false);
27
34
 
28
- let { children, title = 'Admin', menu, siteUrl, routeMode = 'auto' }: { children: Snippet; title?: string; menu?: MenuItem[]; siteUrl?: string; routeMode?: 'hash' | 'path' | 'auto' } = $props();
35
+ let { children, title = 'Admin', menu, siteUrl, routeMode = 'auto', aiAssistant }: { children: Snippet; title?: string; menu?: MenuItem[]; siteUrl?: string; routeMode?: 'hash' | 'path' | 'auto'; aiAssistant?: Snippet<[LayoutAIAssistantProps]> } = $props();
29
36
  const layoutId = $props.id();
30
37
  const layoutScope = `svadmin-layout-${layoutId}`;
31
38
  const mainContentId = `${layoutScope}-main`;
@@ -73,6 +80,13 @@
73
80
  document.getElementById(mainContentId)?.focus();
74
81
  }
75
82
 
83
+ function handleAskAI(query: string) {
84
+ if (!aiAssistant || typeof window === 'undefined') return;
85
+ window.dispatchEvent(new CustomEvent('svadmin:ask-ai', {
86
+ detail: { query, scope: chatScope },
87
+ }));
88
+ }
89
+
76
90
  let collapsed = $state(false);
77
91
 
78
92
  // Swipe gesture for mobile menu
@@ -235,19 +249,13 @@
235
249
 
236
250
  <footer class="flex min-h-14 shrink-0 items-center justify-end gap-2 border-t border-border/60 bg-background px-4 empty:hidden">
237
251
  <DevTools docked />
238
- <ChatDialog docked scope={chatScope} ownerScope={layoutScope} />
252
+ {@render aiAssistant?.({ docked: true, scope: chatScope, ownerScope: layoutScope })}
239
253
  </footer>
240
254
  </div>
241
255
  </div>
242
- <CommandPalette
243
- bind:open={commandOpen}
244
- onAskAI={(q) => {
245
- if (typeof window !== 'undefined') {
246
- window.dispatchEvent(new CustomEvent('svadmin:ask-ai', {
247
- detail: { query: q, scope: chatScope },
248
- }));
249
- }
250
- }}
256
+ <CommandPalette
257
+ bind:open={commandOpen}
258
+ onAskAI={aiAssistant ? handleAskAI : undefined}
251
259
  />
252
260
  <KeyboardShortcuts bind:open={shortcutsOpen} />
253
261
  {/if}
@@ -1,3 +1,8 @@
1
+ export interface LayoutAIAssistantProps {
2
+ docked: boolean;
3
+ scope: string;
4
+ ownerScope: string;
5
+ }
1
6
  import type { Snippet } from 'svelte';
2
7
  import type { MenuItem } from '@svadmin/core';
3
8
  type $$ComponentProps = {
@@ -6,6 +11,7 @@ type $$ComponentProps = {
6
11
  menu?: MenuItem[];
7
12
  siteUrl?: string;
8
13
  routeMode?: 'hash' | 'path' | 'auto';
14
+ aiAssistant?: Snippet<[LayoutAIAssistantProps]>;
9
15
  };
10
16
  declare const Layout: import("svelte").Component<$$ComponentProps, {}, "">;
11
17
  type Layout = ReturnType<typeof Layout>;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { default as ShowPage } from './components/ShowPage.svelte';
5
5
  export { default as ConfirmDialog } from './components/ConfirmDialog.svelte';
6
6
  export { default as Sidebar } from './components/Sidebar.svelte';
7
7
  export { default as Layout } from './components/Layout.svelte';
8
+ export type { LayoutAIAssistantProps } from './components/Layout.svelte';
8
9
  export { default as Header } from './components/Header.svelte';
9
10
  export { default as SvadminLogo } from './components/SvadminLogo.svelte';
10
11
  export { default as ErrorBoundary } from './components/ErrorBoundary.svelte';
@@ -119,14 +120,7 @@ export type { PendingMutation } from './components/OfflineSyncBanner.svelte';
119
120
  export { default as InfiniteList } from './components/InfiniteList.svelte';
120
121
  export { default as ComboboxField } from './components/ComboboxField.svelte';
121
122
  export { default as PasswordInput } from './components/PasswordInput.svelte';
122
- export { default as ChatDialog } from "./components/ChatDialog.svelte";
123
- export { default as MarkdownRenderer } from "./components/MarkdownRenderer.svelte";
124
- export { default as SmartSuggest } from "./components/SmartSuggest.svelte";
125
- export { default as AICommandBar } from "./components/AICommandBar.svelte";
126
- export { default as CopilotPanel } from "./components/CopilotPanel.svelte";
127
- export { default as InsightCard } from "./components/InsightCard.svelte";
128
123
  export { default as AnomalyBadge } from "./components/AnomalyBadge.svelte";
129
- export { default as VoiceInput } from "./components/VoiceInput.svelte";
130
124
  export { default as AnimatedCounter } from "./components/AnimatedCounter.svelte";
131
125
  export { default as KeyboardShortcuts } from "./components/KeyboardShortcuts.svelte";
132
126
  export { default as InlineEdit } from "./components/InlineEdit.svelte";
package/dist/index.js CHANGED
@@ -95,14 +95,7 @@ export { default as OfflineSyncBanner } from './components/OfflineSyncBanner.sve
95
95
  export { default as InfiniteList } from './components/InfiniteList.svelte';
96
96
  export { default as ComboboxField } from './components/ComboboxField.svelte';
97
97
  export { default as PasswordInput } from './components/PasswordInput.svelte';
98
- export { default as ChatDialog } from "./components/ChatDialog.svelte";
99
- export { default as MarkdownRenderer } from "./components/MarkdownRenderer.svelte";
100
- export { default as SmartSuggest } from "./components/SmartSuggest.svelte";
101
- export { default as AICommandBar } from "./components/AICommandBar.svelte";
102
- export { default as CopilotPanel } from "./components/CopilotPanel.svelte";
103
- export { default as InsightCard } from "./components/InsightCard.svelte";
104
98
  export { default as AnomalyBadge } from "./components/AnomalyBadge.svelte";
105
- export { default as VoiceInput } from "./components/VoiceInput.svelte";
106
99
  export { default as AnimatedCounter } from "./components/AnimatedCounter.svelte";
107
100
  export { default as KeyboardShortcuts } from "./components/KeyboardShortcuts.svelte";
108
101
  export { default as InlineEdit } from "./components/InlineEdit.svelte";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.66.0",
3
+ "version": "0.67.1",
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.47.1",
42
+ "@svadmin/core": "^0.49.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.3.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>
@@ -1,6 +0,0 @@
1
- type $$ComponentProps = {
2
- open?: boolean;
3
- };
4
- declare const AICommandBar: import("svelte").Component<$$ComponentProps, {}, "open">;
5
- type AICommandBar = ReturnType<typeof AICommandBar>;
6
- export default AICommandBar;