@svadmin/core 0.16.0 → 0.17.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/package.json +24 -24
- package/src/README.md +18 -0
- package/src/chatProvider.svelte.ts +165 -1
- package/src/chatProvider.test.ts +220 -0
- package/src/i18n.svelte.ts +90 -0
- package/src/index.ts +2 -2
- package/src/toast.svelte.ts +3 -3
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
3
|
+
"version": "0.17.1",
|
|
4
|
+
"author": "zuohuadong",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/zuohuadong/svadmin.git",
|
|
8
|
+
"directory": "packages/core"
|
|
9
|
+
},
|
|
10
10
|
"main": "src/index.ts",
|
|
11
|
-
"
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/bun": "^1.3.11"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"svelte": "^5.0.0",
|
|
16
|
+
"@tanstack/svelte-query": "^6.0.0"
|
|
17
|
+
},
|
|
12
18
|
"exports": {
|
|
13
19
|
".": {
|
|
14
20
|
"types": "./src/index.ts",
|
|
@@ -43,25 +49,19 @@
|
|
|
43
49
|
"default": "./src/theme.svelte.ts"
|
|
44
50
|
}
|
|
45
51
|
},
|
|
46
|
-
"peerDependencies": {
|
|
47
|
-
"svelte": "^5.0.0",
|
|
48
|
-
"@tanstack/svelte-query": "^6.0.0"
|
|
49
|
-
},
|
|
50
|
-
"license": "MIT",
|
|
51
|
-
"author": "zuohuadong",
|
|
52
|
-
"repository": {
|
|
53
|
-
"type": "git",
|
|
54
|
-
"url": "https://github.com/zuohuadong/svadmin.git",
|
|
55
|
-
"directory": "packages/core"
|
|
56
|
-
},
|
|
57
|
-
"homepage": "https://github.com/zuohuadong/svadmin#readme",
|
|
58
52
|
"bugs": {
|
|
59
53
|
"url": "https://github.com/zuohuadong/svadmin/issues"
|
|
60
54
|
},
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
"description": "Core SDK — hooks, types, context, i18n, permissions, router",
|
|
56
|
+
"files": [
|
|
57
|
+
"src/**/*"
|
|
58
|
+
],
|
|
59
|
+
"homepage": "https://github.com/zuohuadong/svadmin#readme",
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"sideEffects": false,
|
|
62
|
+
"type": "module",
|
|
63
|
+
"types": "src/index.ts",
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"svelte
|
|
65
|
+
"sonner-svelte": "^0.3.3"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/src/README.md
ADDED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
// Chat provider — headless interface for AI
|
|
1
|
+
// Chat & Agent provider — headless interface for AI integration
|
|
2
|
+
//
|
|
3
|
+
// Two abstraction levels:
|
|
4
|
+
// 1. ChatProvider — simple message-in / text-out (backward compatible)
|
|
5
|
+
// 2. AgentProvider — tool calling, approval gates, generative UI events
|
|
6
|
+
//
|
|
7
|
+
// Inspired by Vercel AI SDK 6 agent abstractions.
|
|
2
8
|
|
|
3
9
|
/** Describes an actionable button rendered in assistant messages (tool-call UX). */
|
|
4
10
|
export interface ChatAction {
|
|
@@ -40,6 +46,117 @@ export interface ChatProvider {
|
|
|
40
46
|
): Promise<string> | AsyncGenerator<string, void, unknown>;
|
|
41
47
|
}
|
|
42
48
|
|
|
49
|
+
// ─── Agent Provider (extends ChatProvider concept) ─────────────
|
|
50
|
+
//
|
|
51
|
+
// An AgentProvider emits typed events instead of raw strings, enabling:
|
|
52
|
+
// • Tool calling — Agent invokes admin operations (CRUD, custom)
|
|
53
|
+
// • Approval gates — Dangerous tools require user confirmation before execution
|
|
54
|
+
// • Generative UI — Agent returns component name + props for dynamic rendering
|
|
55
|
+
|
|
56
|
+
/** JSON-Schema-compatible parameter definition for an admin tool. */
|
|
57
|
+
export interface AdminToolParameter {
|
|
58
|
+
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
59
|
+
description?: string;
|
|
60
|
+
enum?: string[];
|
|
61
|
+
required?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A tool that the Agent can invoke within the admin panel.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* const deletePostTool: AdminTool = {
|
|
70
|
+
* name: 'deletePosts',
|
|
71
|
+
* description: 'Delete posts matching a filter',
|
|
72
|
+
* parameters: {
|
|
73
|
+
* status: { type: 'string', description: 'Filter by status', enum: ['draft', 'archived'] },
|
|
74
|
+
* },
|
|
75
|
+
* needsApproval: true,
|
|
76
|
+
* execute: async (params) => {
|
|
77
|
+
* const result = await dataProvider.deleteMany({ resource: 'posts', ids: params.ids });
|
|
78
|
+
* return { success: true, data: result };
|
|
79
|
+
* },
|
|
80
|
+
* };
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export interface AdminTool {
|
|
84
|
+
/** Unique tool name (e.g. 'getList', 'deleteRecords', 'generateReport') */
|
|
85
|
+
name: string;
|
|
86
|
+
/** Human-readable description for LLM tool-use prompting */
|
|
87
|
+
description: string;
|
|
88
|
+
/** JSON-Schema-style parameter definitions */
|
|
89
|
+
parameters: Record<string, AdminToolParameter>;
|
|
90
|
+
/**
|
|
91
|
+
* When `true`, execution is paused until the user explicitly approves.
|
|
92
|
+
* The agent emits an `approval_request` event and waits for `approveToolCall()`.
|
|
93
|
+
*/
|
|
94
|
+
needsApproval?: boolean;
|
|
95
|
+
/** Execute the tool with the given arguments. */
|
|
96
|
+
execute(args: Record<string, unknown>): Promise<ToolResult>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Result returned by a tool execution. */
|
|
100
|
+
export interface ToolResult {
|
|
101
|
+
success: boolean;
|
|
102
|
+
data?: unknown;
|
|
103
|
+
error?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Discriminated union of events emitted by an AgentProvider. */
|
|
107
|
+
export type AgentEvent =
|
|
108
|
+
/** Streaming text chunk */
|
|
109
|
+
| { type: 'text'; content: string }
|
|
110
|
+
/** Agent wants to call a tool (informational — execution is handled internally) */
|
|
111
|
+
| { type: 'tool_call'; tool: string; args: Record<string, unknown> }
|
|
112
|
+
/** Agent wants to call a tool that requires user approval before execution */
|
|
113
|
+
| { type: 'approval_request'; id: string; tool: string; args: Record<string, unknown>; description: string }
|
|
114
|
+
/** Tool execution completed */
|
|
115
|
+
| { type: 'tool_result'; tool: string; result: ToolResult }
|
|
116
|
+
/** Agent wants to render a named component (Generative UI) */
|
|
117
|
+
| { type: 'component'; name: string; props: Record<string, unknown> }
|
|
118
|
+
/** Stream finished */
|
|
119
|
+
| { type: 'done' };
|
|
120
|
+
|
|
121
|
+
/** Options passed to AgentProvider.chat() */
|
|
122
|
+
export interface AgentOptions {
|
|
123
|
+
signal?: AbortSignal;
|
|
124
|
+
/** Admin context is injected automatically by the framework */
|
|
125
|
+
context?: ChatContext;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* AgentProvider — advanced AI integration with tool calling and approval gates.
|
|
130
|
+
*
|
|
131
|
+
* Implement this interface to connect to agentic AI backends that support
|
|
132
|
+
* function calling (OpenAI, Anthropic, Google Gemini, etc.).
|
|
133
|
+
*
|
|
134
|
+
* For simpler chat-only use cases, use `ChatProvider` instead.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const agentProvider: AgentProvider = {
|
|
139
|
+
* tools: [deletePostTool, getStatsTool],
|
|
140
|
+
* async *chat(messages, options) {
|
|
141
|
+
* // Stream events from your AI backend
|
|
142
|
+
* yield { type: 'text', content: 'Looking up draft posts...' };
|
|
143
|
+
* yield { type: 'tool_call', tool: 'getList', args: { resource: 'posts', filter: { status: 'draft' } } };
|
|
144
|
+
* yield { type: 'approval_request', id: 'req-1', tool: 'deletePosts', args: { ids: ['1', '2'] }, description: 'Delete 2 draft posts' };
|
|
145
|
+
* yield { type: 'done' };
|
|
146
|
+
* },
|
|
147
|
+
* };
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export interface AgentProvider {
|
|
151
|
+
/** Available tools the agent can invoke */
|
|
152
|
+
tools?: AdminTool[];
|
|
153
|
+
/** Send messages and receive a stream of typed agent events */
|
|
154
|
+
chat(
|
|
155
|
+
messages: ChatMessage[],
|
|
156
|
+
options?: AgentOptions,
|
|
157
|
+
): AsyncGenerator<AgentEvent, void, unknown>;
|
|
158
|
+
}
|
|
159
|
+
|
|
43
160
|
// ─── Chat Provider singleton ───────────────────────────────────
|
|
44
161
|
|
|
45
162
|
let chatProvider: ChatProvider | null = $state(null);
|
|
@@ -52,6 +169,53 @@ export function getChatProvider(): ChatProvider | null {
|
|
|
52
169
|
return chatProvider;
|
|
53
170
|
}
|
|
54
171
|
|
|
172
|
+
// ─── Agent Provider singleton ──────────────────────────────────
|
|
173
|
+
|
|
174
|
+
let agentProvider: AgentProvider | null = $state(null);
|
|
175
|
+
|
|
176
|
+
export function setAgentProvider(provider: AgentProvider): void {
|
|
177
|
+
agentProvider = provider;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function getAgentProvider(): AgentProvider | null {
|
|
181
|
+
return agentProvider;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── Tool Approval ─────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
type ApprovalCallback = (approved: boolean) => void;
|
|
187
|
+
const pendingApprovals = new Map<string, ApprovalCallback>();
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Register a pending approval request. Called internally by the framework
|
|
191
|
+
* when an `approval_request` event is received from the agent.
|
|
192
|
+
*/
|
|
193
|
+
export function registerApproval(id: string, callback: ApprovalCallback): void {
|
|
194
|
+
pendingApprovals.set(id, callback);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Approve or reject a pending tool call.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```svelte
|
|
202
|
+
* <button onclick={() => resolveApproval('req-1', true)}>Approve</button>
|
|
203
|
+
* <button onclick={() => resolveApproval('req-1', false)}>Reject</button>
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export function resolveApproval(id: string, approved: boolean): boolean {
|
|
207
|
+
const cb = pendingApprovals.get(id);
|
|
208
|
+
if (!cb) return false;
|
|
209
|
+
cb(approved);
|
|
210
|
+
pendingApprovals.delete(id);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Check if there are any pending approval requests. */
|
|
215
|
+
export function hasPendingApprovals(): boolean {
|
|
216
|
+
return pendingApprovals.size > 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
55
219
|
// ─── Chat Context singleton ────────────────────────────────────
|
|
56
220
|
|
|
57
221
|
let chatContext = $state<ChatContext>({});
|
package/src/chatProvider.test.ts
CHANGED
|
@@ -166,3 +166,223 @@ describe('localStorage persistence simulation', () => {
|
|
|
166
166
|
expect(restored[1].actions![0].label).toBe('Create Post');
|
|
167
167
|
});
|
|
168
168
|
});
|
|
169
|
+
|
|
170
|
+
// ─── AgentProvider Types ───────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/** Mirror of AdminToolParameter from chatProvider.svelte.ts */
|
|
173
|
+
interface AdminToolParameter {
|
|
174
|
+
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
175
|
+
description?: string;
|
|
176
|
+
enum?: string[];
|
|
177
|
+
required?: boolean;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Mirror of AdminTool from chatProvider.svelte.ts */
|
|
181
|
+
interface AdminTool {
|
|
182
|
+
name: string;
|
|
183
|
+
description: string;
|
|
184
|
+
parameters: Record<string, AdminToolParameter>;
|
|
185
|
+
needsApproval?: boolean;
|
|
186
|
+
execute(args: Record<string, unknown>): Promise<{ success: boolean; data?: unknown; error?: string }>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Mirror of AgentEvent from chatProvider.svelte.ts */
|
|
190
|
+
type AgentEvent =
|
|
191
|
+
| { type: 'text'; content: string }
|
|
192
|
+
| { type: 'tool_call'; tool: string; args: Record<string, unknown> }
|
|
193
|
+
| { type: 'approval_request'; id: string; tool: string; args: Record<string, unknown>; description: string }
|
|
194
|
+
| { type: 'tool_result'; tool: string; result: { success: boolean; data?: unknown; error?: string } }
|
|
195
|
+
| { type: 'component'; name: string; props: Record<string, unknown> }
|
|
196
|
+
| { type: 'done' };
|
|
197
|
+
|
|
198
|
+
describe('AdminTool shape', () => {
|
|
199
|
+
test('tool with needsApproval serializes correctly', () => {
|
|
200
|
+
const tool: AdminTool = {
|
|
201
|
+
name: 'deletePosts',
|
|
202
|
+
description: 'Delete posts matching a filter',
|
|
203
|
+
parameters: {
|
|
204
|
+
status: { type: 'string', description: 'Filter by status', enum: ['draft', 'archived'] },
|
|
205
|
+
},
|
|
206
|
+
needsApproval: true,
|
|
207
|
+
execute: async (args) => ({ success: true, data: { deleted: args.ids } }),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
expect(tool.name).toBe('deletePosts');
|
|
211
|
+
expect(tool.needsApproval).toBe(true);
|
|
212
|
+
expect(tool.parameters.status.enum).toEqual(['draft', 'archived']);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('tool without approval defaults to undefined', () => {
|
|
216
|
+
const tool: AdminTool = {
|
|
217
|
+
name: 'getStats',
|
|
218
|
+
description: 'Fetch dashboard statistics',
|
|
219
|
+
parameters: {},
|
|
220
|
+
execute: async () => ({ success: true, data: { users: 100 } }),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
expect(tool.needsApproval).toBeUndefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('tool execute returns ToolResult', async () => {
|
|
227
|
+
const tool: AdminTool = {
|
|
228
|
+
name: 'createPost',
|
|
229
|
+
description: 'Create a new post',
|
|
230
|
+
parameters: {
|
|
231
|
+
title: { type: 'string', required: true },
|
|
232
|
+
},
|
|
233
|
+
execute: async (args) => ({
|
|
234
|
+
success: true,
|
|
235
|
+
data: { id: '1', title: args.title },
|
|
236
|
+
}),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const result = await tool.execute({ title: 'Hello World' });
|
|
240
|
+
expect(result.success).toBe(true);
|
|
241
|
+
expect(result.data).toEqual({ id: '1', title: 'Hello World' });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('tool execute can return error', async () => {
|
|
245
|
+
const tool: AdminTool = {
|
|
246
|
+
name: 'failingTool',
|
|
247
|
+
description: 'A tool that fails',
|
|
248
|
+
parameters: {},
|
|
249
|
+
execute: async () => ({
|
|
250
|
+
success: false,
|
|
251
|
+
error: 'Permission denied',
|
|
252
|
+
}),
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const result = await tool.execute({});
|
|
256
|
+
expect(result.success).toBe(false);
|
|
257
|
+
expect(result.error).toBe('Permission denied');
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('AgentEvent stream', () => {
|
|
262
|
+
test('events can be collected from async generator', async () => {
|
|
263
|
+
async function* mockAgent(): AsyncGenerator<AgentEvent, void, unknown> {
|
|
264
|
+
yield { type: 'text', content: 'Searching...' };
|
|
265
|
+
yield { type: 'tool_call', tool: 'getList', args: { resource: 'posts' } };
|
|
266
|
+
yield { type: 'tool_result', tool: 'getList', result: { success: true, data: [{ id: 1 }] } };
|
|
267
|
+
yield { type: 'text', content: 'Found 1 post.' };
|
|
268
|
+
yield { type: 'done' };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const events: AgentEvent[] = [];
|
|
272
|
+
for await (const event of mockAgent()) {
|
|
273
|
+
events.push(event);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
expect(events).toHaveLength(5);
|
|
277
|
+
expect(events[0].type).toBe('text');
|
|
278
|
+
expect(events[1].type).toBe('tool_call');
|
|
279
|
+
expect(events[2].type).toBe('tool_result');
|
|
280
|
+
expect(events[4].type).toBe('done');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('approval_request event serializes correctly', () => {
|
|
284
|
+
const event: AgentEvent = {
|
|
285
|
+
type: 'approval_request',
|
|
286
|
+
id: 'req-42',
|
|
287
|
+
tool: 'deletePosts',
|
|
288
|
+
args: { ids: ['1', '2', '3'] },
|
|
289
|
+
description: 'Delete 3 draft posts',
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const serialized = JSON.stringify(event);
|
|
293
|
+
const parsed = JSON.parse(serialized) as AgentEvent;
|
|
294
|
+
|
|
295
|
+
expect(parsed.type).toBe('approval_request');
|
|
296
|
+
if (parsed.type === 'approval_request') {
|
|
297
|
+
expect(parsed.id).toBe('req-42');
|
|
298
|
+
expect(parsed.tool).toBe('deletePosts');
|
|
299
|
+
expect(parsed.description).toBe('Delete 3 draft posts');
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('component event carries name and props', () => {
|
|
304
|
+
const event: AgentEvent = {
|
|
305
|
+
type: 'component',
|
|
306
|
+
name: 'SalesChart',
|
|
307
|
+
props: { period: 'monthly', data: [100, 200, 150] },
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
expect(event.type).toBe('component');
|
|
311
|
+
if (event.type === 'component') {
|
|
312
|
+
expect(event.name).toBe('SalesChart');
|
|
313
|
+
expect(event.props.period).toBe('monthly');
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('Approval flow', () => {
|
|
319
|
+
test('registerApproval and resolveApproval work together', () => {
|
|
320
|
+
// Simulate the approval flow without importing rune-based module
|
|
321
|
+
const pending = new Map<string, (approved: boolean) => void>();
|
|
322
|
+
|
|
323
|
+
function register(id: string, cb: (approved: boolean) => void) {
|
|
324
|
+
pending.set(id, cb);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolve(id: string, approved: boolean): boolean {
|
|
328
|
+
const cb = pending.get(id);
|
|
329
|
+
if (!cb) return false;
|
|
330
|
+
cb(approved);
|
|
331
|
+
pending.delete(id);
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let approvalResult = false;
|
|
336
|
+
|
|
337
|
+
register('req-1', (approved) => {
|
|
338
|
+
approvalResult = approved;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
expect(pending.size).toBe(1);
|
|
342
|
+
|
|
343
|
+
// Resolve with approval
|
|
344
|
+
const resolved = resolve('req-1', true);
|
|
345
|
+
expect(resolved).toBe(true);
|
|
346
|
+
expect(approvalResult).toEqual(true);
|
|
347
|
+
expect(pending.size).toBe(0);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('resolveApproval returns false for unknown id', () => {
|
|
351
|
+
const pending = new Map<string, (approved: boolean) => void>();
|
|
352
|
+
|
|
353
|
+
function resolve(id: string, approved: boolean): boolean {
|
|
354
|
+
const cb = pending.get(id);
|
|
355
|
+
if (!cb) return false;
|
|
356
|
+
cb(approved);
|
|
357
|
+
pending.delete(id);
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
expect(resolve('nonexistent', true)).toBe(false);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test('rejection flow works', () => {
|
|
365
|
+
const pending = new Map<string, (approved: boolean) => void>();
|
|
366
|
+
|
|
367
|
+
function register(id: string, cb: (approved: boolean) => void) {
|
|
368
|
+
pending.set(id, cb);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function resolve(id: string, approved: boolean): boolean {
|
|
372
|
+
const cb = pending.get(id);
|
|
373
|
+
if (!cb) return false;
|
|
374
|
+
cb(approved);
|
|
375
|
+
pending.delete(id);
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let wasApproved = true;
|
|
380
|
+
|
|
381
|
+
register('req-2', (approved) => {
|
|
382
|
+
wasApproved = approved;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
resolve('req-2', false);
|
|
386
|
+
expect(wasApproved).toEqual(false);
|
|
387
|
+
});
|
|
388
|
+
});
|
package/src/i18n.svelte.ts
CHANGED
|
@@ -184,6 +184,51 @@ const locales: Record<string, Locale> = {
|
|
|
184
184
|
'settings.auditIp': 'IP 地址',
|
|
185
185
|
'settings.auditTotal': '条记录',
|
|
186
186
|
'settings.auditSnapshot': '变更快照',
|
|
187
|
+
// Editor
|
|
188
|
+
'editor.bold': '加粗',
|
|
189
|
+
'editor.italic': '斜体',
|
|
190
|
+
'editor.underline': '下划线',
|
|
191
|
+
'editor.strikethrough': '删除线',
|
|
192
|
+
'editor.code': '行内代码',
|
|
193
|
+
'editor.codeBlock': '代码块',
|
|
194
|
+
'editor.h1': '一级标题',
|
|
195
|
+
'editor.h2': '二级标题',
|
|
196
|
+
'editor.h3': '三级标题',
|
|
197
|
+
'editor.h4': '四级标题',
|
|
198
|
+
'editor.bulletList': '无序列表',
|
|
199
|
+
'editor.orderedList': '有序列表',
|
|
200
|
+
'editor.taskList': '任务列表',
|
|
201
|
+
'editor.blockquote': '引用',
|
|
202
|
+
'editor.alignLeft': '左对齐',
|
|
203
|
+
'editor.alignCenter': '居中对齐',
|
|
204
|
+
'editor.alignRight': '右对齐',
|
|
205
|
+
'editor.alignJustify': '两端对齐',
|
|
206
|
+
'editor.link': '插入链接',
|
|
207
|
+
'editor.image': '插入图片',
|
|
208
|
+
'editor.table': '插入表格',
|
|
209
|
+
'editor.color': '文本颜色',
|
|
210
|
+
'editor.highlight': '高亮',
|
|
211
|
+
'editor.clearFormat': '清除格式',
|
|
212
|
+
'editor.undo': '撤销',
|
|
213
|
+
'editor.redo': '重做',
|
|
214
|
+
'editor.startWriting': '开始写作...',
|
|
215
|
+
'editor.typeSlash': '输入 / 唤出菜单',
|
|
216
|
+
// Editor - Popovers
|
|
217
|
+
'editor.link.url': '链接地址',
|
|
218
|
+
'editor.link.openInNewTab': '在新标签页打开',
|
|
219
|
+
'editor.link.apply': '应用',
|
|
220
|
+
'editor.link.remove': '移除链接',
|
|
221
|
+
'editor.image.url': '图片链接 (URL)',
|
|
222
|
+
'editor.image.upload': '点击或拖拽图片到此处上传',
|
|
223
|
+
'editor.image.uploading': '上传中...',
|
|
224
|
+
'editor.table.insertRowAbove': '在上方插入行',
|
|
225
|
+
'editor.table.insertRowBelow': '在下方插入行',
|
|
226
|
+
'editor.table.deleteRow': '删除行',
|
|
227
|
+
'editor.table.insertColumnLeft': '在左侧插入列',
|
|
228
|
+
'editor.table.insertColumnRight': '在右侧插入列',
|
|
229
|
+
'editor.table.deleteColumn': '删除列',
|
|
230
|
+
'editor.table.deleteTable': '删除表格',
|
|
231
|
+
'editor.table.mergeCells': '合并/拆分单元格',
|
|
187
232
|
// Profile
|
|
188
233
|
'profile.title': '个人资料',
|
|
189
234
|
'profile.name': '姓名',
|
|
@@ -374,6 +419,51 @@ const locales: Record<string, Locale> = {
|
|
|
374
419
|
'settings.auditIp': 'IP Address',
|
|
375
420
|
'settings.auditTotal': 'total records',
|
|
376
421
|
'settings.auditSnapshot': 'Change Snapshot',
|
|
422
|
+
// Editor
|
|
423
|
+
'editor.bold': 'Bold',
|
|
424
|
+
'editor.italic': 'Italic',
|
|
425
|
+
'editor.underline': 'Underline',
|
|
426
|
+
'editor.strikethrough': 'Strikethrough',
|
|
427
|
+
'editor.code': 'Inline Code',
|
|
428
|
+
'editor.codeBlock': 'Code Block',
|
|
429
|
+
'editor.h1': 'Heading 1',
|
|
430
|
+
'editor.h2': 'Heading 2',
|
|
431
|
+
'editor.h3': 'Heading 3',
|
|
432
|
+
'editor.h4': 'Heading 4',
|
|
433
|
+
'editor.bulletList': 'Bullet List',
|
|
434
|
+
'editor.orderedList': 'Ordered List',
|
|
435
|
+
'editor.taskList': 'Task List',
|
|
436
|
+
'editor.blockquote': 'Blockquote',
|
|
437
|
+
'editor.alignLeft': 'Align Left',
|
|
438
|
+
'editor.alignCenter': 'Align Center',
|
|
439
|
+
'editor.alignRight': 'Align Right',
|
|
440
|
+
'editor.alignJustify': 'Justify',
|
|
441
|
+
'editor.link': 'Insert Link',
|
|
442
|
+
'editor.image': 'Insert Image',
|
|
443
|
+
'editor.table': 'Insert Table',
|
|
444
|
+
'editor.color': 'Text Color',
|
|
445
|
+
'editor.highlight': 'Highlight',
|
|
446
|
+
'editor.clearFormat': 'Clear Formatting',
|
|
447
|
+
'editor.undo': 'Undo',
|
|
448
|
+
'editor.redo': 'Redo',
|
|
449
|
+
'editor.startWriting': 'Start writing...',
|
|
450
|
+
'editor.typeSlash': 'Type / for commands',
|
|
451
|
+
// Editor - Popovers
|
|
452
|
+
'editor.link.url': 'Link URL',
|
|
453
|
+
'editor.link.openInNewTab': 'Open in new tab',
|
|
454
|
+
'editor.link.apply': 'Apply',
|
|
455
|
+
'editor.link.remove': 'Remove Link',
|
|
456
|
+
'editor.image.url': 'Image URL',
|
|
457
|
+
'editor.image.upload': 'Click or drag image here to upload',
|
|
458
|
+
'editor.image.uploading': 'Uploading...',
|
|
459
|
+
'editor.table.insertRowAbove': 'Insert row above',
|
|
460
|
+
'editor.table.insertRowBelow': 'Insert row below',
|
|
461
|
+
'editor.table.deleteRow': 'Delete row',
|
|
462
|
+
'editor.table.insertColumnLeft': 'Insert column left',
|
|
463
|
+
'editor.table.insertColumnRight': 'Insert column right',
|
|
464
|
+
'editor.table.deleteColumn': 'Delete column',
|
|
465
|
+
'editor.table.deleteTable': 'Delete table',
|
|
466
|
+
'editor.table.mergeCells': 'Toggle merge cells',
|
|
377
467
|
// Profile
|
|
378
468
|
'profile.title': 'Profile',
|
|
379
469
|
'profile.name': 'Name',
|
package/src/index.ts
CHANGED
|
@@ -34,8 +34,8 @@ export { notify, closeNotification, setNotificationProvider, getNotificationProv
|
|
|
34
34
|
export { t, setLocale, getLocale, getAvailableLocales, addTranslations, useTranslation } from './i18n.svelte';
|
|
35
35
|
export { audit, setAuditHandler, setAuditLogProvider, getAuditLogProvider } from './audit';
|
|
36
36
|
export type { AuditLogProvider } from './audit';
|
|
37
|
-
export { setChatProvider, getChatProvider, setChatContext, getChatContext } from './chatProvider.svelte';
|
|
38
|
-
export type { ChatProvider, ChatMessage, ChatContext, ChatAction } from './chatProvider.svelte';
|
|
37
|
+
export { setChatProvider, getChatProvider, setChatContext, getChatContext, setAgentProvider, getAgentProvider, registerApproval, resolveApproval, hasPendingApprovals } from './chatProvider.svelte';
|
|
38
|
+
export type { ChatProvider, ChatMessage, ChatContext, ChatAction, AgentProvider, AgentEvent, AgentOptions, AdminTool, AdminToolParameter, ToolResult } from './chatProvider.svelte';
|
|
39
39
|
export { getTheme, setTheme, toggleTheme, getResolvedTheme, getColorTheme, setColorTheme, colorThemes, configureTheme, getThemeConfig, clearCssOverrides, builtinPresets, registerColorPreset, getColorPresets } from './theme.svelte';
|
|
40
40
|
export type { ThemeMode, ColorTheme, ThemeStrategy, ThemeConfig, ColorPreset } from './theme.svelte';
|
|
41
41
|
export { setUnsavedChanges, getUnsavedChanges, initUnsavedChangesNotifier } from './unsaved-changes.svelte';
|
package/src/toast.svelte.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Toast notification system — wraps svelte
|
|
1
|
+
// Toast notification system — wraps sonner-svelte for consistent API
|
|
2
2
|
// Maintains backward-compatible `toast.success()` / `toast.error()` etc.
|
|
3
3
|
|
|
4
|
-
import { toast as sonner } from 'svelte
|
|
4
|
+
import { toast as sonner } from 'sonner-svelte';
|
|
5
5
|
|
|
6
6
|
export type ToastType = 'success' | 'error' | 'info' | 'warning' | 'undoable';
|
|
7
7
|
|
|
@@ -23,7 +23,7 @@ export function getToasts(): Toast[] {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function addToast(type: ToastType, message: string, duration = 3000, options?: { onUndo?: () => void, onTimeout?: () => void }): void {
|
|
26
|
-
// Use svelte
|
|
26
|
+
// Use sonner-svelte for regular toasts
|
|
27
27
|
if (type !== 'undoable') {
|
|
28
28
|
switch (type) {
|
|
29
29
|
case 'success': sonner.success(message, { duration }); break;
|