yaxa-svelte 1.10.0 → 1.12.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 +19 -12
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/springTilt.d.ts +15 -0
- package/dist/actions/springTilt.js +57 -0
- package/dist/actions/springTilt.test.d.ts +1 -0
- package/dist/actions/springTilt.test.js +28 -0
- package/dist/admin/handler.js +13 -13
- package/dist/components/ai/AiChat.svelte +263 -0
- package/dist/components/ai/AiChat.svelte.d.ts +41 -0
- package/dist/components/ai/AiThought.svelte +108 -0
- package/dist/components/ai/AiThought.svelte.d.ts +12 -0
- package/dist/components/ai/AiToolCall.svelte +133 -0
- package/dist/components/ai/AiToolCall.svelte.d.ts +13 -0
- package/dist/components/ai/PromptBar.svelte +279 -0
- package/dist/components/ai/PromptBar.svelte.d.ts +22 -0
- package/dist/components/ai/ai.test.d.ts +1 -0
- package/dist/components/ai/ai.test.js +13 -0
- package/dist/components/ai/index.d.ts +8 -0
- package/dist/components/ai/index.js +4 -0
- package/dist/components/blocks/FAQ.svelte +4 -2
- package/dist/components/blocks/FeatureGrid.svelte +50 -40
- package/dist/components/blocks/FeatureGrid.svelte.d.ts +1 -0
- package/dist/components/blocks/Testimonials.svelte +45 -31
- package/dist/components/blocks/Testimonials.svelte.d.ts +1 -0
- package/dist/components/charts/BarChart.svelte +248 -0
- package/dist/components/charts/BarChart.svelte.d.ts +22 -0
- package/dist/components/charts/DonutChart.svelte +153 -0
- package/dist/components/charts/DonutChart.svelte.d.ts +18 -0
- package/dist/components/charts/LineChart.svelte +334 -0
- package/dist/components/charts/LineChart.svelte.d.ts +28 -0
- package/dist/components/charts/charts.test.d.ts +1 -0
- package/dist/components/charts/charts.test.js +11 -0
- package/dist/components/charts/index.d.ts +6 -0
- package/dist/components/charts/index.js +3 -0
- package/dist/components/elements/CodeBlock.svelte +86 -58
- package/dist/components/elements/CodeBlock.svelte.d.ts +2 -1
- package/dist/components/elements/CodeDiff.svelte +454 -0
- package/dist/components/elements/CodeDiff.svelte.d.ts +38 -0
- package/dist/components/elements/CodeDiff.test.d.ts +1 -0
- package/dist/components/elements/CodeDiff.test.js +52 -0
- package/dist/components/elements/DataTable.dom.test.js +30 -2
- package/dist/components/elements/DataTable.svelte +418 -151
- package/dist/components/elements/DataTable.svelte.d.ts +32 -2
- package/dist/components/forms/Checkbox.svelte +30 -5
- package/dist/components/forms/Checkbox.svelte.d.ts +1 -0
- package/dist/components/forms/Combobox.svelte +31 -5
- package/dist/components/forms/Combobox.svelte.d.ts +6 -0
- package/dist/components/forms/CreditCardInput.svelte +191 -0
- package/dist/components/forms/CreditCardInput.svelte.d.ts +17 -0
- package/dist/components/forms/Form.dom.test.d.ts +1 -0
- package/dist/components/forms/Form.dom.test.js +59 -0
- package/dist/components/forms/Form.svelte +93 -3
- package/dist/components/forms/Form.svelte.d.ts +2 -0
- package/dist/components/forms/FormField.svelte +75 -11
- package/dist/components/forms/FormField.svelte.d.ts +2 -1
- package/dist/components/forms/Input.svelte +18 -1
- package/dist/components/forms/InputOTP.svelte +12 -3
- package/dist/components/forms/MultiSelect.svelte +33 -7
- package/dist/components/forms/MultiSelect.svelte.d.ts +6 -0
- package/dist/components/forms/NumberInput.svelte +23 -5
- package/dist/components/forms/NumberInput.svelte.d.ts +3 -0
- package/dist/components/forms/PhoneInput.svelte +174 -0
- package/dist/components/forms/PhoneInput.svelte.d.ts +23 -0
- package/dist/components/forms/RichTextEditor.svelte +265 -0
- package/dist/components/forms/RichTextEditor.svelte.d.ts +11 -0
- package/dist/components/forms/Select.svelte +36 -18
- package/dist/components/forms/Select.svelte.d.ts +3 -0
- package/dist/components/forms/Switch.svelte +22 -3
- package/dist/components/forms/Switch.svelte.d.ts +1 -0
- package/dist/components/forms/Textarea.svelte +23 -2
- package/dist/components/forms/advanced-forms.test.d.ts +1 -0
- package/dist/components/forms/advanced-forms.test.js +44 -0
- package/dist/components/forms/form-context.d.ts +38 -0
- package/dist/components/forms/form-context.js +15 -0
- package/dist/components/layout/DashboardShell.svelte +6 -4
- package/dist/components/layout/DashboardShell.svelte.d.ts +1 -0
- package/dist/components/layout/Footer.svelte +18 -30
- package/dist/components/overlays/Popover.svelte +16 -3
- package/dist/components/overlays/Popover.svelte.d.ts +2 -0
- package/dist/components/overlays/Tooltip.svelte +19 -3
- package/dist/components/overlays/Tooltip.svelte.d.ts +3 -0
- package/dist/components/saas/AuthCard.svelte +299 -30
- package/dist/components/saas/AuthCard.svelte.d.ts +5 -1
- package/dist/components/saas/PasskeyUI.svelte +235 -0
- package/dist/components/saas/PasskeyUI.svelte.d.ts +13 -0
- package/dist/components/saas/TwoFactorModal.svelte +253 -0
- package/dist/components/saas/TwoFactorModal.svelte.d.ts +9 -0
- package/dist/components/theme/ThemeStudio.svelte +411 -0
- package/dist/components/theme/ThemeStudio.svelte.d.ts +6 -0
- package/dist/composables/useAuth.svelte.d.ts +732 -1
- package/dist/composables/useAuth.svelte.js +240 -2
- package/dist/composables/useDataTable.svelte.d.ts +34 -0
- package/dist/composables/useDataTable.svelte.js +130 -0
- package/dist/index.d.ts +41 -4
- package/dist/index.js +26 -4
- package/dist/motion/transitions.d.ts +22 -0
- package/dist/motion/transitions.js +55 -0
- package/dist/server/auth/index.d.ts +25 -1
- package/dist/server/auth/index.js +10 -3
- package/dist/server/db/pagination.d.ts +36 -0
- package/dist/server/db/pagination.js +40 -0
- package/dist/server/db/pagination.test.d.ts +1 -0
- package/dist/server/db/pagination.test.js +41 -0
- package/dist/server/db/schema-pg.d.ts +269 -0
- package/dist/server/db/schema-pg.js +28 -2
- package/dist/server/db/schema-sqlite.d.ts +291 -0
- package/dist/server/db/schema-sqlite.js +30 -4
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -1
- package/dist/site/manifest.js +6 -5
- package/dist/styles/yaxa.css +81 -4
- package/dist/theme/theme.svelte.d.ts +6 -1
- package/dist/theme/theme.svelte.js +49 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -23,13 +23,16 @@ _Nuxt UI v4 & Nuxt UI Pro Equivalent for SvelteKit 2.7+ & Svelte 5 with Built-in
|
|
|
23
23
|
|
|
24
24
|
**Yaxa** brings the full developer experience, visual elegance, and full-stack toolkit of **Nuxt UI** and the **Nuxt SEO + SaaS ecosystem** to **SvelteKit 2.7+** and **Svelte 5**.
|
|
25
25
|
|
|
26
|
-
- 🎨 **Tailwind CSS v4 Native Tokens**: Styled with `@theme` variables;
|
|
26
|
+
- 🎨 **Tailwind CSS v4 Native Tokens**: Styled with `@theme` variables; harmonic radius scale (`--radius-xs` to `--radius-4xl`), chart palette tokens (`--color-chart-1` to `--color-chart-5`), and auto-adaptive scrollbars.
|
|
27
|
+
- 🎛️ **Visual Web Theme Studio**: Interactive runtime palette designer (`/theme` and `<ThemeStudio />`) with live sandbox preview and 1-click token export.
|
|
28
|
+
- 🤖 **AI-Native Primitives**: Drop-in `<AiChat>`, `<PromptBar>`, `<AiThought>`, and `<AiToolCall>` for conversational interfaces and agentic workflows.
|
|
29
|
+
- 📊 **Zero-Dependency SVG Charts**: Pure Svelte 5 `<LineChart>`, `<BarChart>`, and `<DonutChart>` with responsive tooltips, curved splines, and theme-palette synchronisation.
|
|
27
30
|
- ⚡ **Pure Svelte 5 Runes**: Built using `$state`, `$derived`, `$props`, and `$bindable` — zero Virtual DOM overhead.
|
|
28
31
|
- ♿ **Accessible by Default**: Headless primitives powered by **Bits UI**.
|
|
29
32
|
- 🚀 **100% DRY SEO Parity**: Automate dynamic Open Graph cards (`/api/og`), environment-aware `robots.txt`, XML sitemaps with human-readable `sitemap.xsl` stylesheets, PWA manifests, and Schema.org JSON-LD from a single `src/site.config.ts`.
|
|
30
33
|
- 🔐 **Batteries-Included SaaS Suite**: Pre-integrated **Better-Auth**, multi-dialect **Drizzle ORM** (Neon PostgreSQL & Turso LibSQL/SQLite), **Polar.sh** payments & webhook sync, and **Resend** transactional emails.
|
|
31
|
-
- 🧩 **
|
|
32
|
-
- 🛠️ **Composable Runes**: `useAuth`, `useShortcuts`, `useClipboard`, `useColorMode`, `useToast`, `useMediaQuery`, `useDebounce`.
|
|
34
|
+
- 🧩 **45+ Production Components**: Elements, AI primitives, charts, advanced forms (`<RichTextEditor>`, `<PhoneInput>`, `<CreditCardInput>`), layout, overlays, and drop-in SaaS widgets (`<AuthCard>`, `<UserMenu>`, `<PricingTable>`, `<SubscriptionCard>`).
|
|
35
|
+
- 🛠️ **Composable Runes**: `useAuth`, `useShortcuts`, `useClipboard`, `useColorMode`, `useToast`, `useMediaQuery`, `useDebounce`, `useIdle`.
|
|
33
36
|
|
|
34
37
|
---
|
|
35
38
|
|
|
@@ -228,15 +231,19 @@ export const POST = createPolarWebhookHandler();
|
|
|
228
231
|
|
|
229
232
|
## 🧩 Component Library
|
|
230
233
|
|
|
231
|
-
| Category
|
|
232
|
-
|
|
|
233
|
-
| **
|
|
234
|
-
| **
|
|
235
|
-
| **
|
|
236
|
-
| **
|
|
237
|
-
| **
|
|
238
|
-
| **
|
|
239
|
-
| **
|
|
234
|
+
| Category | Components |
|
|
235
|
+
| :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
236
|
+
| **Theming & Studio** | `<ThemeStudio>`, `theme`, `ACCENT_PALETTES`, `NEUTRAL_PALETTES`, `RADIUS_PRESETS`, Harmonic Radius Multiplier Scale, Chart Palette Series Tokens |
|
|
237
|
+
| **AI Primitives** | `<AiChat>`, `<PromptBar>`, `<AiThought>`, `<AiToolCall>` |
|
|
238
|
+
| **Charts (Pure SVG)** | `<LineChart>`, `<BarChart>`, `<DonutChart>` (0 external charting dependencies, auto-responsive, dark/light theme aware) |
|
|
239
|
+
| **SaaS & Auth Suite** | `<PasskeyUI>` (WebAuthn), `<TwoFactorModal>` (TOTP), `<AuthCard>` (Polymorphic), `<Gate>`, `<UserMenu>`, `<OrgSwitcher>`, `<PricingCard>`, `<PricingTable>`, `<SubscriptionCard>`, `useAuth`, `useGate`, `useUpload` |
|
|
240
|
+
| **Admin Suite** | `<AdminDashboard>`, `<RecordDrawer>`, `<DevSandbox>`, `<ImpersonationBanner>`, `createYaxaAdminHook`, `createDrizzleAdmin` |
|
|
241
|
+
| **Elements & Code** | `<CodeDiff>` (Interactive Diff & Changelog), `<CodeBlock>`, `<DataTable>` (Client & Server Modes), `Button`, `ButtonGroup`, `Badge`, `Avatar`, `AvatarGroup`, `Chip`, `Meter`, `MetricCard`, `Kbd`, `Icon`, `Spinner`, `Progress`, `Skeleton`, `Link`, `Logo`, `Tree`, `Timeline`, `Terminal`, `VirtualList`, `Carousel` |
|
|
242
|
+
| **Forms (Auto-Wired)** | `<Form>`, `<FormField>`, `Input`, `InputOTP`, `Textarea`, `Checkbox`, `Switch`, `Select`, `Combobox`, `MultiSelect`, `NumberInput`, `PhoneInput`, `CreditCardInput`, `RadioGroup`, `Slider`, `ColorPicker`, `ToggleGroup`, `DatePicker`, `DateRangePicker`, `Dropzone`, `<RichTextEditor>` |
|
|
243
|
+
| **Layout & Blocks** | `Container`, `Header`, `Footer`, `Section`, `Card`, `Divider`, `YaxaApp`, `DashboardShell` (with independent sidebar tokens), `ResizablePanels`, Page Blocks (`Hero`, `SaaS Metrics`, `FAQ`, `Testimonials`, `FeatureGrid`) |
|
|
244
|
+
| **Overlays & Nav** | `Tabs`, `Breadcrumb`, `Pagination`, `CommandPalette` (`⌘K`), `DropdownMenu`, `ContextMenu`, `Modal`, `Drawer`, `Slideover`, `Popover`, `Tooltip` (with `arrow={false}` & `unstyled`), `Alert`, `Accordion`, `NotificationCenter`, `Stepper` |
|
|
245
|
+
| **Motion & Actions** | `springTilt`, `smoothSlide`, `staggerFly`, `autosize`, `clickOutside`, `portal`, `sortableItem`, `infiniteScrollAction` |
|
|
246
|
+
| **Composables** | `useAuth`, `useGate`, `useUpload`, `useClipboard`, `useShortcuts`, `useColorMode`, `useToast`, `useMediaQuery`, `useDebounce`, `useIdle`, `useInfiniteScroll`, `useSortable`, `useDataTable`, `useLocale` |
|
|
240
247
|
|
|
241
248
|
---
|
|
242
249
|
|
package/dist/actions/index.d.ts
CHANGED
|
@@ -8,3 +8,5 @@ export { infiniteScroll } from './infiniteScroll';
|
|
|
8
8
|
export type { InfiniteScrollActionOptions } from './infiniteScroll';
|
|
9
9
|
export { sortableItem } from './sortable';
|
|
10
10
|
export type { SortableItemOptions } from './sortable';
|
|
11
|
+
export { springTilt } from './springTilt';
|
|
12
|
+
export type { SpringTiltOptions } from './springTilt';
|
package/dist/actions/index.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SpringTiltOptions {
|
|
2
|
+
max?: number;
|
|
3
|
+
perspective?: number;
|
|
4
|
+
scale?: number;
|
|
5
|
+
stiffness?: number;
|
|
6
|
+
damping?: number;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Svelte Action that applies physics-driven 3D spring tilt on hover.
|
|
11
|
+
*/
|
|
12
|
+
export declare function springTilt(node: HTMLElement, options?: SpringTiltOptions): {
|
|
13
|
+
update(newOptions?: SpringTiltOptions): void;
|
|
14
|
+
destroy(): void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { spring } from 'svelte/motion';
|
|
2
|
+
/**
|
|
3
|
+
* Svelte Action that applies physics-driven 3D spring tilt on hover.
|
|
4
|
+
*/
|
|
5
|
+
export function springTilt(node, options = {}) {
|
|
6
|
+
let max = options.max ?? 10;
|
|
7
|
+
let perspective = options.perspective ?? 1000;
|
|
8
|
+
let scale = options.scale ?? 1.02;
|
|
9
|
+
const stiffness = options.stiffness ?? 0.12;
|
|
10
|
+
const damping = options.damping ?? 0.28;
|
|
11
|
+
let disabled = options.disabled ?? false;
|
|
12
|
+
const coords = spring({ x: 0, y: 0, scale: 1 }, { stiffness, damping });
|
|
13
|
+
const unsub = coords.subscribe((val) => {
|
|
14
|
+
if (disabled) {
|
|
15
|
+
node.style.transform = '';
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
node.style.transform = `perspective(${perspective}px) rotateX(${val.x.toFixed(2)}deg) rotateY(${val.y.toFixed(2)}deg) scale(${val.scale.toFixed(3)})`;
|
|
19
|
+
node.style.transformStyle = 'preserve-3d';
|
|
20
|
+
});
|
|
21
|
+
function onMouseMove(e) {
|
|
22
|
+
if (disabled)
|
|
23
|
+
return;
|
|
24
|
+
const rect = node.getBoundingClientRect();
|
|
25
|
+
const x = e.clientX - rect.left;
|
|
26
|
+
const y = e.clientY - rect.top;
|
|
27
|
+
const centerX = rect.width / 2;
|
|
28
|
+
const centerY = rect.height / 2;
|
|
29
|
+
const rotateX = -((y - centerY) / (centerY || 1)) * max;
|
|
30
|
+
const rotateY = ((x - centerX) / (centerX || 1)) * max;
|
|
31
|
+
coords.set({ x: rotateX, y: rotateY, scale });
|
|
32
|
+
}
|
|
33
|
+
function onMouseLeave() {
|
|
34
|
+
if (disabled)
|
|
35
|
+
return;
|
|
36
|
+
coords.set({ x: 0, y: 0, scale: 1 });
|
|
37
|
+
}
|
|
38
|
+
node.addEventListener('mousemove', onMouseMove);
|
|
39
|
+
node.addEventListener('mouseleave', onMouseLeave);
|
|
40
|
+
return {
|
|
41
|
+
update(newOptions = {}) {
|
|
42
|
+
max = newOptions.max ?? 10;
|
|
43
|
+
perspective = newOptions.perspective ?? 1000;
|
|
44
|
+
scale = newOptions.scale ?? 1.02;
|
|
45
|
+
disabled = Boolean(newOptions.disabled);
|
|
46
|
+
if (disabled) {
|
|
47
|
+
node.style.transform = '';
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
destroy() {
|
|
51
|
+
unsub();
|
|
52
|
+
node.removeEventListener('mousemove', onMouseMove);
|
|
53
|
+
node.removeEventListener('mouseleave', onMouseLeave);
|
|
54
|
+
node.style.transform = '';
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { springTilt } from './springTilt';
|
|
3
|
+
describe('springTilt Action', () => {
|
|
4
|
+
it('attaches event listeners and updates transform on mousemove', () => {
|
|
5
|
+
const div = document.createElement('div');
|
|
6
|
+
Object.defineProperty(div, 'getBoundingClientRect', {
|
|
7
|
+
value: () => ({
|
|
8
|
+
left: 0,
|
|
9
|
+
top: 0,
|
|
10
|
+
width: 200,
|
|
11
|
+
height: 200,
|
|
12
|
+
right: 200,
|
|
13
|
+
bottom: 200
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
const action = springTilt(div, { max: 15, perspective: 800 });
|
|
17
|
+
expect(div.style.transformStyle).toBe('preserve-3d');
|
|
18
|
+
// Dispatch mousemove
|
|
19
|
+
const moveEvent = new MouseEvent('mousemove', {
|
|
20
|
+
clientX: 150,
|
|
21
|
+
clientY: 150
|
|
22
|
+
});
|
|
23
|
+
div.dispatchEvent(moveEvent);
|
|
24
|
+
// Cleanup
|
|
25
|
+
action.destroy();
|
|
26
|
+
expect(div.style.transform).toBe('');
|
|
27
|
+
});
|
|
28
|
+
});
|
package/dist/admin/handler.js
CHANGED
|
@@ -16,17 +16,17 @@ export function introspectTables(schema) {
|
|
|
16
16
|
const columns = [];
|
|
17
17
|
let primaryKey = 'id';
|
|
18
18
|
for (const [colKey, col] of Object.entries(rawColumns)) {
|
|
19
|
-
const
|
|
20
|
-
const isPk = Boolean(
|
|
19
|
+
const colTyped = col;
|
|
20
|
+
const isPk = Boolean(colTyped.primary || colTyped.isPrimaryKey);
|
|
21
21
|
if (isPk) {
|
|
22
|
-
primaryKey =
|
|
22
|
+
primaryKey = colTyped.name || colKey;
|
|
23
23
|
}
|
|
24
24
|
columns.push({
|
|
25
|
-
name:
|
|
26
|
-
dataType:
|
|
25
|
+
name: colTyped.name || colKey,
|
|
26
|
+
dataType: colTyped.dataType || colTyped.columnType || typeof colTyped,
|
|
27
27
|
primaryKey: isPk,
|
|
28
|
-
notNull: Boolean(
|
|
29
|
-
hasDefault: Boolean(
|
|
28
|
+
notNull: Boolean(colTyped.notNull),
|
|
29
|
+
hasDefault: Boolean(colTyped.hasDefault || colTyped.default !== undefined)
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
const info = {
|
|
@@ -98,13 +98,13 @@ export class DrizzleAdminService {
|
|
|
98
98
|
const searchTerm = `%${options.search.trim()}%`;
|
|
99
99
|
const textColFilters = [];
|
|
100
100
|
for (const [, col] of Object.entries(rawColumns)) {
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
const colTyped = col;
|
|
102
|
+
if (colTyped.dataType === 'string' ||
|
|
103
|
+
colTyped.dataType === 'text' ||
|
|
104
|
+
colTyped.columnType?.includes('Text') ||
|
|
105
|
+
colTyped.columnType?.includes('Varchar')) {
|
|
106
106
|
try {
|
|
107
|
-
textColFilters.push(like(
|
|
107
|
+
textColFilters.push(like(col, searchTerm));
|
|
108
108
|
}
|
|
109
109
|
catch {
|
|
110
110
|
// fallback
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
export interface AiMessageThought {
|
|
5
|
+
duration?: number;
|
|
6
|
+
running?: boolean;
|
|
7
|
+
text?: string;
|
|
8
|
+
steps?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AiMessageTool {
|
|
12
|
+
name: string;
|
|
13
|
+
status?: 'idle' | 'running' | 'success' | 'error';
|
|
14
|
+
args?: Record<string, any> | string;
|
|
15
|
+
result?: Record<string, any> | string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AiMessage {
|
|
19
|
+
id: string;
|
|
20
|
+
role: 'user' | 'assistant' | 'system';
|
|
21
|
+
content: string;
|
|
22
|
+
createdAt?: Date | string;
|
|
23
|
+
thought?: AiMessageThought;
|
|
24
|
+
tools?: AiMessageTool[];
|
|
25
|
+
isStreaming?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AiChatProps {
|
|
29
|
+
messages?: AiMessage[];
|
|
30
|
+
loading?: boolean;
|
|
31
|
+
userAvatar?: string;
|
|
32
|
+
assistantAvatar?: string;
|
|
33
|
+
assistantName?: string;
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
autoScroll?: boolean;
|
|
36
|
+
class?: string;
|
|
37
|
+
onsend?: (text: string, files?: File[]) => void;
|
|
38
|
+
onstop?: () => void;
|
|
39
|
+
onretry?: (message: AiMessage) => void;
|
|
40
|
+
header?: Snippet;
|
|
41
|
+
messageSlot?: Snippet<[AiMessage]>;
|
|
42
|
+
footer?: Snippet;
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<script lang="ts">
|
|
47
|
+
import Avatar from '../elements/Avatar.svelte';
|
|
48
|
+
import Icon from '../elements/Icon.svelte';
|
|
49
|
+
import PromptBar from './PromptBar.svelte';
|
|
50
|
+
import AiThought from './AiThought.svelte';
|
|
51
|
+
import AiToolCall from './AiToolCall.svelte';
|
|
52
|
+
import { useToast } from '../../composables/useToast';
|
|
53
|
+
|
|
54
|
+
let {
|
|
55
|
+
messages = [],
|
|
56
|
+
loading = false,
|
|
57
|
+
userAvatar = '',
|
|
58
|
+
assistantAvatar = '',
|
|
59
|
+
assistantName = 'AI Assistant',
|
|
60
|
+
placeholder = 'Ask a question or type / for commands...',
|
|
61
|
+
autoScroll = true,
|
|
62
|
+
class: className = '',
|
|
63
|
+
onsend,
|
|
64
|
+
onstop,
|
|
65
|
+
onretry,
|
|
66
|
+
header,
|
|
67
|
+
messageSlot,
|
|
68
|
+
footer
|
|
69
|
+
}: AiChatProps = $props();
|
|
70
|
+
|
|
71
|
+
let scrollContainer: HTMLDivElement | null = $state(null);
|
|
72
|
+
let promptValue = $state('');
|
|
73
|
+
const toast = useToast();
|
|
74
|
+
|
|
75
|
+
function scrollToBottom(smooth = true) {
|
|
76
|
+
if (scrollContainer && autoScroll) {
|
|
77
|
+
scrollContainer.scrollTo({
|
|
78
|
+
top: scrollContainer.scrollHeight,
|
|
79
|
+
behavior: smooth ? 'smooth' : 'auto'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$effect(() => {
|
|
85
|
+
if (messages.length > 0) {
|
|
86
|
+
setTimeout(() => scrollToBottom(true), 50);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
function copyMessage(text: string) {
|
|
91
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
92
|
+
navigator.clipboard.writeText(text);
|
|
93
|
+
toast.success('Copied to clipboard');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<div
|
|
99
|
+
class="flex h-full min-h-[500px] w-full flex-col overflow-hidden rounded-2xl border border-neutral-200 bg-white shadow-md dark:border-neutral-800 dark:bg-neutral-950 {className}"
|
|
100
|
+
>
|
|
101
|
+
<!-- Chat Header Bar -->
|
|
102
|
+
<div
|
|
103
|
+
class="flex items-center justify-between border-b border-neutral-200/80 bg-neutral-50/50 px-4 py-3 dark:border-neutral-800/80 dark:bg-neutral-900/50"
|
|
104
|
+
>
|
|
105
|
+
{#if header}
|
|
106
|
+
{@render header()}
|
|
107
|
+
{:else}
|
|
108
|
+
<div class="flex items-center gap-3">
|
|
109
|
+
<div
|
|
110
|
+
class="flex h-8 w-8 items-center justify-center rounded-xl bg-primary-100 text-primary-600 dark:bg-primary-950 dark:text-primary-400"
|
|
111
|
+
>
|
|
112
|
+
<Icon name="bot" size="sm" />
|
|
113
|
+
</div>
|
|
114
|
+
<div>
|
|
115
|
+
<div class="text-sm font-semibold text-neutral-900 dark:text-white">
|
|
116
|
+
{assistantName}
|
|
117
|
+
</div>
|
|
118
|
+
<div class="flex items-center gap-1.5 text-[11px] text-neutral-500">
|
|
119
|
+
<span
|
|
120
|
+
class="h-1.5 w-1.5 rounded-full {loading
|
|
121
|
+
? 'animate-ping bg-amber-500'
|
|
122
|
+
: 'bg-emerald-500'}"
|
|
123
|
+
></span>
|
|
124
|
+
<span>{loading ? 'Generating response...' : 'Online & Ready'}</span>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
{/if}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!-- Scrollable Messages Container -->
|
|
132
|
+
<div
|
|
133
|
+
bind:this={scrollContainer}
|
|
134
|
+
class="yaxa-scrollbar flex-1 space-y-6 overflow-y-auto p-4 sm:p-6"
|
|
135
|
+
>
|
|
136
|
+
{#if messages.length === 0}
|
|
137
|
+
<div class="flex h-full min-h-[300px] flex-col items-center justify-center text-center">
|
|
138
|
+
<div
|
|
139
|
+
class="mb-3 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary-50 text-primary-600 dark:bg-primary-950/60 dark:text-primary-400"
|
|
140
|
+
>
|
|
141
|
+
<Icon name="sparkles" size="lg" />
|
|
142
|
+
</div>
|
|
143
|
+
<h3 class="text-base font-semibold text-neutral-900 dark:text-white">
|
|
144
|
+
How can I help you today?
|
|
145
|
+
</h3>
|
|
146
|
+
<p class="mt-1 max-w-sm text-xs text-neutral-500">
|
|
147
|
+
Ask questions, analyze documents, generate code, or explore ideas with interactive AI.
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
{:else}
|
|
151
|
+
{#each messages as msg (msg.id)}
|
|
152
|
+
{#if messageSlot}
|
|
153
|
+
{@render messageSlot(msg)}
|
|
154
|
+
{:else}
|
|
155
|
+
<div class="flex gap-3.5 {msg.role === 'user' ? 'justify-end' : 'justify-start'}">
|
|
156
|
+
{#if msg.role === 'assistant'}
|
|
157
|
+
<Avatar
|
|
158
|
+
src={assistantAvatar}
|
|
159
|
+
alt={assistantName}
|
|
160
|
+
size="sm"
|
|
161
|
+
class="mt-1 flex-shrink-0"
|
|
162
|
+
/>
|
|
163
|
+
{/if}
|
|
164
|
+
|
|
165
|
+
<div
|
|
166
|
+
class="flex max-w-[85%] flex-col gap-1 sm:max-w-[75%] {msg.role === 'user'
|
|
167
|
+
? 'items-end'
|
|
168
|
+
: 'items-start'}"
|
|
169
|
+
>
|
|
170
|
+
<!-- Agent Reasoning / Thoughts -->
|
|
171
|
+
{#if msg.thought}
|
|
172
|
+
<AiThought
|
|
173
|
+
duration={msg.thought.duration}
|
|
174
|
+
running={msg.thought.running}
|
|
175
|
+
steps={msg.thought.steps}
|
|
176
|
+
class="w-full"
|
|
177
|
+
>
|
|
178
|
+
{#if msg.thought.text}
|
|
179
|
+
<p>{msg.thought.text}</p>
|
|
180
|
+
{/if}
|
|
181
|
+
</AiThought>
|
|
182
|
+
{/if}
|
|
183
|
+
|
|
184
|
+
<!-- Agent Tool Invocations -->
|
|
185
|
+
{#if msg.tools && msg.tools.length > 0}
|
|
186
|
+
<div class="w-full space-y-2">
|
|
187
|
+
{#each msg.tools as tool}
|
|
188
|
+
<AiToolCall
|
|
189
|
+
name={tool.name}
|
|
190
|
+
status={tool.status}
|
|
191
|
+
args={tool.args}
|
|
192
|
+
result={tool.result}
|
|
193
|
+
/>
|
|
194
|
+
{/each}
|
|
195
|
+
</div>
|
|
196
|
+
{/if}
|
|
197
|
+
|
|
198
|
+
<!-- Message Bubble -->
|
|
199
|
+
<div
|
|
200
|
+
class="relative rounded-2xl px-4 py-3 text-sm leading-relaxed shadow-xs {msg.role ===
|
|
201
|
+
'user'
|
|
202
|
+
? 'bg-primary-500 text-white dark:bg-primary-600'
|
|
203
|
+
: 'border border-neutral-200/80 bg-neutral-50 text-neutral-900 dark:border-neutral-800/80 dark:bg-neutral-900 dark:text-neutral-100'}"
|
|
204
|
+
>
|
|
205
|
+
<div class="whitespace-pre-wrap">{msg.content}</div>
|
|
206
|
+
|
|
207
|
+
{#if msg.isStreaming}
|
|
208
|
+
<span class="inline-block h-4 w-1.5 animate-pulse bg-current"></span>
|
|
209
|
+
{/if}
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<!-- Action Buttons -->
|
|
213
|
+
<div class="mt-1 flex items-center gap-1.5 px-1 text-[11px] text-neutral-400">
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
onclick={() => copyMessage(msg.content)}
|
|
217
|
+
class="hover:text-neutral-700 dark:hover:text-neutral-200"
|
|
218
|
+
title="Copy message"
|
|
219
|
+
>
|
|
220
|
+
<Icon name="copy" size="xs" />
|
|
221
|
+
</button>
|
|
222
|
+
|
|
223
|
+
{#if msg.role === 'assistant' && onretry}
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
onclick={() => onretry(msg)}
|
|
227
|
+
class="hover:text-neutral-700 dark:hover:text-neutral-200"
|
|
228
|
+
title="Regenerate response"
|
|
229
|
+
>
|
|
230
|
+
<Icon name="rotate-cw" size="xs" />
|
|
231
|
+
</button>
|
|
232
|
+
{/if}
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
{#if msg.role === 'user'}
|
|
237
|
+
<Avatar src={userAvatar} alt="User" size="sm" class="mt-1 flex-shrink-0" />
|
|
238
|
+
{/if}
|
|
239
|
+
</div>
|
|
240
|
+
{/if}
|
|
241
|
+
{/each}
|
|
242
|
+
{/if}
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<!-- Bottom Prompt Input Bar -->
|
|
246
|
+
<div
|
|
247
|
+
class="border-t border-neutral-200/80 bg-neutral-50/50 p-3 sm:p-4 dark:border-neutral-800/80 dark:bg-neutral-900/50"
|
|
248
|
+
>
|
|
249
|
+
{#if footer}
|
|
250
|
+
{@render footer()}
|
|
251
|
+
{:else}
|
|
252
|
+
<PromptBar
|
|
253
|
+
bind:value={promptValue}
|
|
254
|
+
{placeholder}
|
|
255
|
+
{loading}
|
|
256
|
+
onsubmit={(text, files) => {
|
|
257
|
+
if (onsend) onsend(text, files);
|
|
258
|
+
}}
|
|
259
|
+
{onstop}
|
|
260
|
+
/>
|
|
261
|
+
{/if}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export interface AiMessageThought {
|
|
3
|
+
duration?: number;
|
|
4
|
+
running?: boolean;
|
|
5
|
+
text?: string;
|
|
6
|
+
steps?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface AiMessageTool {
|
|
9
|
+
name: string;
|
|
10
|
+
status?: 'idle' | 'running' | 'success' | 'error';
|
|
11
|
+
args?: Record<string, any> | string;
|
|
12
|
+
result?: Record<string, any> | string;
|
|
13
|
+
}
|
|
14
|
+
export interface AiMessage {
|
|
15
|
+
id: string;
|
|
16
|
+
role: 'user' | 'assistant' | 'system';
|
|
17
|
+
content: string;
|
|
18
|
+
createdAt?: Date | string;
|
|
19
|
+
thought?: AiMessageThought;
|
|
20
|
+
tools?: AiMessageTool[];
|
|
21
|
+
isStreaming?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface AiChatProps {
|
|
24
|
+
messages?: AiMessage[];
|
|
25
|
+
loading?: boolean;
|
|
26
|
+
userAvatar?: string;
|
|
27
|
+
assistantAvatar?: string;
|
|
28
|
+
assistantName?: string;
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
autoScroll?: boolean;
|
|
31
|
+
class?: string;
|
|
32
|
+
onsend?: (text: string, files?: File[]) => void;
|
|
33
|
+
onstop?: () => void;
|
|
34
|
+
onretry?: (message: AiMessage) => void;
|
|
35
|
+
header?: Snippet;
|
|
36
|
+
messageSlot?: Snippet<[AiMessage]>;
|
|
37
|
+
footer?: Snippet;
|
|
38
|
+
}
|
|
39
|
+
declare const AiChat: import("svelte").Component<AiChatProps, {}, "">;
|
|
40
|
+
type AiChat = ReturnType<typeof AiChat>;
|
|
41
|
+
export default AiChat;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface AiThoughtProps {
|
|
3
|
+
duration?: number;
|
|
4
|
+
running?: boolean;
|
|
5
|
+
title?: string;
|
|
6
|
+
steps?: string[];
|
|
7
|
+
open?: boolean;
|
|
8
|
+
class?: string;
|
|
9
|
+
children?: import('svelte').Snippet;
|
|
10
|
+
}
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import Icon from '../elements/Icon.svelte';
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
duration,
|
|
18
|
+
running = false,
|
|
19
|
+
title = 'Thinking Process',
|
|
20
|
+
steps = [],
|
|
21
|
+
open = $bindable(false),
|
|
22
|
+
class: className = '',
|
|
23
|
+
children
|
|
24
|
+
}: AiThoughtProps = $props();
|
|
25
|
+
|
|
26
|
+
function toggle() {
|
|
27
|
+
open = !open;
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div
|
|
32
|
+
class="my-2 overflow-hidden rounded-xl border border-neutral-200/80 bg-neutral-50/60 transition-all dark:border-neutral-800/80 dark:bg-neutral-900/40 {className}"
|
|
33
|
+
>
|
|
34
|
+
<!-- Header Bar -->
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
onclick={toggle}
|
|
38
|
+
class="flex w-full items-center justify-between px-3.5 py-2.5 text-left text-xs font-medium text-neutral-600 transition-colors hover:bg-neutral-100/60 dark:text-neutral-300 dark:hover:bg-neutral-800/40"
|
|
39
|
+
aria-expanded={open}
|
|
40
|
+
>
|
|
41
|
+
<div class="flex items-center gap-2">
|
|
42
|
+
{#if running}
|
|
43
|
+
<span class="relative flex h-2 w-2">
|
|
44
|
+
<span
|
|
45
|
+
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary-400 opacity-75"
|
|
46
|
+
></span>
|
|
47
|
+
<span class="relative inline-flex h-2 w-2 rounded-full bg-primary-500"></span>
|
|
48
|
+
</span>
|
|
49
|
+
{:else}
|
|
50
|
+
<Icon name="sparkles" size="xs" class="text-primary-500" />
|
|
51
|
+
{/if}
|
|
52
|
+
|
|
53
|
+
<span class="font-semibold text-neutral-800 dark:text-neutral-200">
|
|
54
|
+
{title}
|
|
55
|
+
</span>
|
|
56
|
+
|
|
57
|
+
{#if running}
|
|
58
|
+
<span class="animate-pulse text-[11px] text-primary-600 dark:text-primary-400">
|
|
59
|
+
Thinking...
|
|
60
|
+
</span>
|
|
61
|
+
{:else if duration !== undefined}
|
|
62
|
+
<span
|
|
63
|
+
class="py-0.2 rounded bg-neutral-200/60 px-1.5 text-[10px] text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400"
|
|
64
|
+
>
|
|
65
|
+
{duration.toFixed(1)}s
|
|
66
|
+
</span>
|
|
67
|
+
{/if}
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="flex items-center gap-2 text-neutral-400">
|
|
71
|
+
{#if steps.length > 0}
|
|
72
|
+
<span class="text-[11px] text-neutral-500">
|
|
73
|
+
{steps.length}
|
|
74
|
+
{steps.length === 1 ? 'step' : 'steps'}
|
|
75
|
+
</span>
|
|
76
|
+
{/if}
|
|
77
|
+
<Icon
|
|
78
|
+
name={open ? 'chevron-up' : 'chevron-down'}
|
|
79
|
+
size="xs"
|
|
80
|
+
class="transition-transform duration-200"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</button>
|
|
84
|
+
|
|
85
|
+
<!-- Collapsible Content -->
|
|
86
|
+
{#if open}
|
|
87
|
+
<div
|
|
88
|
+
class="border-t border-neutral-200/60 px-3.5 py-3 text-xs leading-relaxed text-neutral-600 dark:border-neutral-800/60 dark:text-neutral-300"
|
|
89
|
+
>
|
|
90
|
+
{#if steps.length > 0}
|
|
91
|
+
<ul
|
|
92
|
+
class="mb-2.5 space-y-1.5 border-b border-neutral-200/40 pb-2.5 dark:border-neutral-800/40"
|
|
93
|
+
>
|
|
94
|
+
{#each steps as step, idx (idx)}
|
|
95
|
+
<li class="flex items-center gap-2 text-[11px] text-neutral-500 dark:text-neutral-400">
|
|
96
|
+
<Icon name="check" size="xs" class="text-emerald-500" />
|
|
97
|
+
<span>{step}</span>
|
|
98
|
+
</li>
|
|
99
|
+
{/each}
|
|
100
|
+
</ul>
|
|
101
|
+
{/if}
|
|
102
|
+
|
|
103
|
+
{#if children}
|
|
104
|
+
{@render children()}
|
|
105
|
+
{/if}
|
|
106
|
+
</div>
|
|
107
|
+
{/if}
|
|
108
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AiThoughtProps {
|
|
2
|
+
duration?: number;
|
|
3
|
+
running?: boolean;
|
|
4
|
+
title?: string;
|
|
5
|
+
steps?: string[];
|
|
6
|
+
open?: boolean;
|
|
7
|
+
class?: string;
|
|
8
|
+
children?: import('svelte').Snippet;
|
|
9
|
+
}
|
|
10
|
+
declare const AiThought: import("svelte").Component<AiThoughtProps, {}, "open">;
|
|
11
|
+
type AiThought = ReturnType<typeof AiThought>;
|
|
12
|
+
export default AiThought;
|