@77-zhike/app-sdk 0.2.0-d7b1.16 → 0.2.0-d7b1.17
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 +2 -2
- package/src/client/documents.d.ts +21 -0
- package/src/client/documents.js +39 -0
- package/src/client/index.d.ts +16 -0
- package/src/client/index.js +10 -0
- package/src/client/interactions.d.ts +1 -1
- package/src/client/interactions.js +2 -0
- package/src/client/points.generated.js +8 -0
- package/src/forms/index.d.ts +1 -0
- package/src/server/agents/index.d.ts +37 -0
- package/src/server/agents/index.js +12 -2
- package/src/server/index.d.ts +3 -1
- package/src/server/index.js +1 -1
- package/src/toolchain/agents/discovery.js +30 -3
- package/src/toolchain/agents/exposure.js +31 -0
- package/src/toolchain/agents/manifest.js +25 -6
- package/src/toolchain/ai-capabilities.js +69 -0
- package/src/toolchain/real-build.js +11 -6
- package/src/toolchain/source-discovery.js +22 -2
- package/src/ui/components.generated.js +3 -3
- package/src/ui/data-table-model.d.ts +14 -0
- package/src/ui/data-table-model.js +98 -0
- package/src/ui/data-table.d.ts +70 -0
- package/src/ui/data-table.js +74 -0
- package/src/ui/factory.js +2 -0
- package/src/ui/index.d.ts +5 -0
- package/src/ui/index.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@77-zhike/app-sdk",
|
|
3
|
-
"version": "0.2.0-d7b1.
|
|
3
|
+
"version": "0.2.0-d7b1.17",
|
|
4
4
|
"description": "智客 Integration App 一期 SDK 与本地 Toolchain",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"optional": true
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "df9d8e305f4b9d603b33d41840847ee3d28a654b"
|
|
71
71
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { JsonValue } from '../ui/index.js';
|
|
2
|
+
/** 受限JSON Schema;服务端校验完整白名单、数组/字符串上限与绑定。 */
|
|
3
|
+
export type RestrictedInputSchema = Readonly<Record<string, JsonValue>>;
|
|
4
|
+
export interface PreparePdfTemplateInput {
|
|
5
|
+
inputSchema: RestrictedInputSchema;
|
|
6
|
+
/** 本次制作的有界、已授权试填数据,不保存到App模板配置。 */
|
|
7
|
+
sampleData: Readonly<Record<string, JsonValue>>;
|
|
8
|
+
}
|
|
9
|
+
export interface OpenPdfDocumentInput {
|
|
10
|
+
/** opaque Job用途引用;不能传URL、附件ID或PDF字节。 */
|
|
11
|
+
documentRef: string;
|
|
12
|
+
action: 'open' | 'download';
|
|
13
|
+
}
|
|
14
|
+
export type PreparedPdfTemplate = Readonly<{templateFileRef: string}>;
|
|
15
|
+
export type OpenPdfDocumentResult = Readonly<{status: 'opened' | 'started' | 'cancelled'}>;
|
|
16
|
+
export function assertDocumentInput(operation: string, input: unknown): PreparePdfTemplateInput | OpenPdfDocumentInput;
|
|
17
|
+
export function assertDocumentResult(operation: string, value: unknown): PreparedPdfTemplate | OpenPdfDocumentResult | null;
|
|
18
|
+
export function createDocumentClient(invoke: (operation: string, input: unknown) => Promise<unknown>): {
|
|
19
|
+
preparePdfTemplate(input: PreparePdfTemplateInput): Promise<PreparedPdfTemplate | null>;
|
|
20
|
+
openPdfDocument(input: OpenPdfDocumentInput): Promise<OpenPdfDocumentResult>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const invalid = () => { throw new Error('runtime_document_input_invalid'); };
|
|
2
|
+
const object = value => value && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
const ref = value => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/.test(value);
|
|
4
|
+
function bounded(value, depth = 0) {
|
|
5
|
+
if (depth > 12) invalid();
|
|
6
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return;
|
|
7
|
+
if (typeof value === 'number' && Number.isFinite(value)) return;
|
|
8
|
+
if (Array.isArray(value)) { if (value.length > 1000) invalid(); for (const item of value) bounded(item, depth + 1); return; }
|
|
9
|
+
if (!object(value)) invalid();
|
|
10
|
+
for (const [key, item] of Object.entries(value)) {
|
|
11
|
+
if (['__proto__', 'prototype', 'constructor'].includes(key)) invalid();
|
|
12
|
+
bounded(item, depth + 1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function assertDocumentInput(operation, input) {
|
|
16
|
+
if (!object(input)) invalid();
|
|
17
|
+
if (operation === 'openPdfDocument') {
|
|
18
|
+
if (Object.keys(input).some(key => !['documentRef', 'action'].includes(key)) || !ref(input.documentRef)
|
|
19
|
+
|| !['open', 'download'].includes(input.action)) invalid();
|
|
20
|
+
} else if (operation === 'preparePdfTemplate') {
|
|
21
|
+
if (Object.keys(input).some(key => !['inputSchema', 'sampleData'].includes(key)) || !object(input.inputSchema)
|
|
22
|
+
|| input.inputSchema.type !== 'object' || input.inputSchema.additionalProperties !== false || !object(input.sampleData)) invalid();
|
|
23
|
+
bounded(input);
|
|
24
|
+
if (new TextEncoder().encode(JSON.stringify(input)).length > 512 * 1024) invalid();
|
|
25
|
+
} else invalid();
|
|
26
|
+
// 快照避免App在异步交互期间原位改动请求。
|
|
27
|
+
return JSON.parse(JSON.stringify(input));
|
|
28
|
+
}
|
|
29
|
+
export function assertDocumentResult(operation, value) {
|
|
30
|
+
if (operation === 'preparePdfTemplate' && value === null) return null;
|
|
31
|
+
if (!object(value) || Object.keys(value).length !== 1) invalid();
|
|
32
|
+
if (operation === 'preparePdfTemplate' ? !ref(value.templateFileRef) : !['opened', 'started', 'cancelled'].includes(value.status)) invalid();
|
|
33
|
+
return Object.freeze({ ...value });
|
|
34
|
+
}
|
|
35
|
+
export function createDocumentClient(invoke) {
|
|
36
|
+
return Object.freeze(Object.fromEntries(['preparePdfTemplate', 'openPdfDocument'].map(operation => [operation,
|
|
37
|
+
input => invoke(operation, assertDocumentInput(operation, input)).then(value => assertDocumentResult(operation, value)),
|
|
38
|
+
])));
|
|
39
|
+
}
|
package/src/client/index.d.ts
CHANGED
|
@@ -169,6 +169,16 @@ export interface PageDefinition {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
export type DefinedPage = Readonly<PageDefinition & { type: "page" }>;
|
|
172
|
+
/** 结果来自同次已校验 Action;身份与导航 owner 由 Host 绑定。 */
|
|
173
|
+
export interface ToolRendererProps<Result = unknown> {
|
|
174
|
+
readonly result: Result;
|
|
175
|
+
readonly actionKey: string;
|
|
176
|
+
}
|
|
177
|
+
export interface ToolRendererDefinition<Result = unknown> {
|
|
178
|
+
key: string;
|
|
179
|
+
Renderer: ComponentType<ToolRendererProps<Result>>;
|
|
180
|
+
}
|
|
181
|
+
export function defineToolRenderer<Result>(definition: ToolRendererDefinition<Result>): Readonly<ToolRendererDefinition<Result> & { type: 'tool-renderer' }>;
|
|
172
182
|
export type FrontendExtensionDefinition =
|
|
173
183
|
| SettingsExtensionDefinition
|
|
174
184
|
| RecordActionDefinition
|
|
@@ -251,3 +261,9 @@ export function queryAnalysis(request: import('./analysis.js').AnalysisRequest):
|
|
|
251
261
|
export type { OpenAssistantOptions } from './interactions.js';
|
|
252
262
|
/** 打开侧边助手;首次发言才调用模型,context 仅作为新会话初始背景。 */
|
|
253
263
|
export function openAssistant(options: import('./interactions.js').OpenAssistantOptions): Promise<void>;
|
|
264
|
+
|
|
265
|
+
export type { RestrictedInputSchema, PreparePdfTemplateInput, OpenPdfDocumentInput, PreparedPdfTemplate, OpenPdfDocumentResult } from './documents.js';
|
|
266
|
+
/** Desktop制作;取消返回null。缺能力或旧Host返回runtime_document_unsupported。 */
|
|
267
|
+
export function preparePdfTemplate(input: import('./documents.js').PreparePdfTemplateInput): Promise<import('./documents.js').PreparedPdfTemplate | null>;
|
|
268
|
+
/** Desktop/H5认证文件交付;started只表示已交浏览器,不证明磁盘保存。 */
|
|
269
|
+
export function openPdfDocument(input: import('./documents.js').OpenPdfDocumentInput): Promise<import('./documents.js').OpenPdfDocumentResult>;
|
package/src/client/index.js
CHANGED
|
@@ -5,6 +5,14 @@ const FRONTEND_EXTENSION_MARKER = Symbol.for("zhike.frontend-extension.v1");
|
|
|
5
5
|
const PAGE_VARIANTS = new Set(["full", "centered"]);
|
|
6
6
|
const PAGE_ICONS = new Set(sdkIconNames);
|
|
7
7
|
|
|
8
|
+
export function defineToolRenderer(definition) {
|
|
9
|
+
if (!definition || typeof definition !== 'object' || Array.isArray(definition)
|
|
10
|
+
|| Object.keys(definition).some(key => !['key', 'Renderer'].includes(key))
|
|
11
|
+
|| typeof definition.key !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(definition.key)
|
|
12
|
+
|| typeof definition.Renderer !== 'function') throw new TypeError('tool_renderer_definition_invalid');
|
|
13
|
+
return Object.freeze({ ...definition, type: 'tool-renderer' });
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
export function defineFrontendExtension(definition) {
|
|
9
17
|
if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
|
|
10
18
|
throw new TypeError("Frontend Extension definition 必须是对象");
|
|
@@ -76,3 +84,5 @@ export function getCurrentUser() { throw new Error('current_user_unavailable');
|
|
|
76
84
|
|
|
77
85
|
export function queryAnalysis() { return Promise.reject(new Error('runtime_analysis_unavailable')); }
|
|
78
86
|
export function openAssistant() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
|
|
87
|
+
export function preparePdfTemplate() { return Promise.reject(new Error('runtime_document_unsupported')); }
|
|
88
|
+
export function openPdfDocument() { return Promise.reject(new Error('runtime_document_unsupported')); }
|
|
@@ -11,7 +11,7 @@ export interface ToastOptions { message: string; tone?: 'info' | 'success' | 'er
|
|
|
11
11
|
export interface ConfirmOptions { title: string; description?: string; confirmLabel?: string; cancelLabel?: string }
|
|
12
12
|
export interface OpenExternalUrlOptions { url: string }
|
|
13
13
|
export interface NavigateOptions { destination: import('./destinations.js').AppDestination }
|
|
14
|
-
export type HostInteraction = {operation:'openAssistant';input:OpenAssistantOptions} | {operation:'showToast';input:ToastOptions} | {operation:'confirm';input:ConfirmOptions} | {operation:'navigate';input:NavigateOptions} | {operation:'openExternal';input:OpenExternalOptions} | {operation:'openExternalUrl';input:OpenExternalUrlOptions};
|
|
14
|
+
export type HostInteraction = {operation:'preparePdfTemplate';input:import('./documents.js').PreparePdfTemplateInput} | {operation:'openPdfDocument';input:import('./documents.js').OpenPdfDocumentInput} | {operation:'openAssistant';input:OpenAssistantOptions} | {operation:'showToast';input:ToastOptions} | {operation:'confirm';input:ConfirmOptions} | {operation:'navigate';input:NavigateOptions} | {operation:'openExternal';input:OpenExternalOptions} | {operation:'openExternalUrl';input:OpenExternalUrlOptions};
|
|
15
15
|
export function assertHostInteraction(operation: unknown, input: unknown): Readonly<ToastOptions | ConfirmOptions | NavigateOptions | OpenExternalOptions | OpenExternalUrlOptions | OpenAssistantOptions>;
|
|
16
16
|
export function createClientInteractions(invoke: (operation: string, input: ToastOptions | ConfirmOptions | NavigateOptions | OpenExternalOptions | OpenExternalUrlOptions | OpenAssistantOptions) => Promise<unknown>): {
|
|
17
17
|
openAssistant(input: OpenAssistantOptions): Promise<void>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDocumentInput } from './documents.js';
|
|
1
2
|
import { assertExternalUrl } from './external-url.js';
|
|
2
3
|
import { assertAppDestination } from './destinations.js';
|
|
3
4
|
|
|
@@ -11,6 +12,7 @@ const externalUrl = value => {
|
|
|
11
12
|
} catch { return false; }
|
|
12
13
|
};
|
|
13
14
|
export function assertHostInteraction(operation, input) {
|
|
15
|
+
if (['preparePdfTemplate', 'openPdfDocument'].includes(operation)) return assertDocumentInput(operation, input);
|
|
14
16
|
if (!input || typeof input !== 'object' || Array.isArray(input)) fail();
|
|
15
17
|
const allowed = operation === 'openAssistant' ? ['scenario','context','title'] : operation === 'showToast' ? ['message','tone'] : operation === 'confirm' ? ['title','description','confirmLabel','cancelLabel'] : operation === 'navigate' ? ['destination'] : operation === 'openExternal' ? ['href'] : operation === 'openExternalUrl' ? ['url'] : [];
|
|
16
18
|
if (!allowed.length || Object.keys(input).some(key=>!allowed.includes(key))) fail();
|
|
@@ -62,6 +62,14 @@ export const frontendPoints = Object.freeze([
|
|
|
62
62
|
"definitionName": "PageDefinition",
|
|
63
63
|
"componentName": "Page",
|
|
64
64
|
"maxPerApp": 100
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"key": "app.tool-renderers",
|
|
68
|
+
"kind": "view",
|
|
69
|
+
"legacyType": "tool-renderer",
|
|
70
|
+
"definitionName": "ToolRendererDefinition",
|
|
71
|
+
"componentName": "Renderer",
|
|
72
|
+
"maxPerApp": 64
|
|
65
73
|
}
|
|
66
74
|
].map(Object.freeze));
|
|
67
75
|
export const frontendPoint = type => frontendPoints.find(point => point.legacyType === type);
|
package/src/forms/index.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ export interface AppAgentDefinition {
|
|
|
4
4
|
name: string;
|
|
5
5
|
/** App 提供的系统提示词,最多 16KiB UTF-8,不交付 Client Worker。 */
|
|
6
6
|
systemPrompt: string;
|
|
7
|
+
/** 引用同 App 独立 Action 的 key,Schema/execute 只维护一份。 */
|
|
8
|
+
actionRefs?: readonly string[];
|
|
9
|
+
aiExposure?: AppExpertExposure;
|
|
7
10
|
}
|
|
8
11
|
export type ToolSchema = Readonly<Record<string, unknown>>;
|
|
9
12
|
export interface AppToolDefinition<Input, Output> {
|
|
@@ -15,8 +18,42 @@ export interface AppToolDefinition<Input, Output> {
|
|
|
15
18
|
outputSchema: ToolSchema;
|
|
16
19
|
/** 默认 none;output 只允许成功且通过校验的结果进入实时 callback。 */
|
|
17
20
|
clientResult?: 'none' | 'output';
|
|
21
|
+
/** 缺省私有;公开查询声明不代替执行授权。 */
|
|
22
|
+
aiExposure?: AppActionExposure;
|
|
23
|
+
/** 同 App 静态 Tool Renderer;展示不授予服务端执行或业务写入权限。 */
|
|
24
|
+
toolRenderer?: { key: string; showByDefault: boolean };
|
|
18
25
|
/** 仅在所属安装的服务端执行环境运行,凭据沿用现有 Server SDK。 */
|
|
19
26
|
execute(input: Input): Output | Promise<Output>;
|
|
20
27
|
}
|
|
21
28
|
export function defineAgent(definition: AppAgentDefinition): Readonly<AppAgentDefinition>;
|
|
22
29
|
export function defineTool<Input = Record<string, unknown>, Output = unknown>(definition: AppToolDefinition<Input, Output>): Readonly<AppToolDefinition<Input, Output>>;
|
|
30
|
+
|
|
31
|
+
export interface AppActionExposure {
|
|
32
|
+
effect: 'query';
|
|
33
|
+
/** 公开 object 标识;只用于说明适用对象,不授予权限。 */
|
|
34
|
+
subjectTypes: readonly string[];
|
|
35
|
+
usage: string;
|
|
36
|
+
limitations: string;
|
|
37
|
+
/** 已有 inputSchema 中接收 {object, recordId} 的字段;由 Host 绑定。 */
|
|
38
|
+
subjectInput?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface AppExpertExposure {
|
|
41
|
+
subjectTypes: readonly string[];
|
|
42
|
+
usage: string;
|
|
43
|
+
limitations: string;
|
|
44
|
+
outputSchema: ToolSchema;
|
|
45
|
+
/** 只允许显式开放的原私有工具子集;独立能力沿 actionRefs。 */
|
|
46
|
+
toolKeys?: readonly string[];
|
|
47
|
+
}
|
|
48
|
+
interface AppDataCapabilityBase {
|
|
49
|
+
key: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description: string;
|
|
52
|
+
subjectTypes: readonly string[];
|
|
53
|
+
usage: string;
|
|
54
|
+
}
|
|
55
|
+
export type AppDataCapabilityDefinition = AppDataCapabilityBase & (
|
|
56
|
+
{ kind: 'live'; actionRef: string; sourceBinding?: never } |
|
|
57
|
+
{ kind: 'published'; sourceBinding: string; actionRef?: never }
|
|
58
|
+
);
|
|
59
|
+
export function defineDataCapability<T extends AppDataCapabilityDefinition>(definition: T): Readonly<T>;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { assertDataCapability } from '../../toolchain/ai-capabilities.js';
|
|
2
|
+
import { assertAiExposure } from '../../toolchain/agents/exposure.js';
|
|
3
|
+
import { assertToolRendererReference } from '../../toolchain/agents/manifest.js';
|
|
1
4
|
const KEY = /^[a-z][a-z0-9-]{0,63}$/;
|
|
2
5
|
|
|
3
6
|
function fields(value, required, optional = []) {
|
|
@@ -11,7 +14,7 @@ function fields(value, required, optional = []) {
|
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export function defineAgent(definition) {
|
|
14
|
-
fields(definition, ["key", "name", "systemPrompt"]);
|
|
17
|
+
fields(definition, ["key", "name", "systemPrompt"], ["actionRefs", "aiExposure"]);
|
|
15
18
|
if (typeof definition.systemPrompt !== "string" || !definition.systemPrompt.trim() ||
|
|
16
19
|
new TextEncoder().encode(definition.systemPrompt).length > 16384) {
|
|
17
20
|
throw new TypeError("agent_definition_invalid");
|
|
@@ -20,11 +23,18 @@ export function defineAgent(definition) {
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export function defineTool(definition) {
|
|
23
|
-
fields(definition, ["key", "name", "description", "inputSchema", "outputSchema", "execute"], ["clientResult"]);
|
|
26
|
+
fields(definition, ["key", "name", "description", "inputSchema", "outputSchema", "execute"], ["clientResult", "aiExposure", "toolRenderer"]);
|
|
24
27
|
if (typeof definition.execute !== "function" || typeof definition.description !== "string" ||
|
|
25
28
|
!definition.description.trim() || definition.description.length > 1200 ||
|
|
26
29
|
![undefined, "none", "output"].includes(definition.clientResult)) {
|
|
27
30
|
throw new TypeError("agent_tool_definition_invalid");
|
|
28
31
|
}
|
|
32
|
+
if (definition.aiExposure !== undefined) assertAiExposure(definition.aiExposure, definition.inputSchema);
|
|
33
|
+
if (definition.toolRenderer !== undefined) assertToolRendererReference(definition.toolRenderer);
|
|
29
34
|
return Object.freeze({ ...definition, clientResult: definition.clientResult ?? "none" });
|
|
30
35
|
}
|
|
36
|
+
|
|
37
|
+
export function defineDataCapability(definition) {
|
|
38
|
+
assertDataCapability(definition);
|
|
39
|
+
return Object.freeze({ ...definition });
|
|
40
|
+
}
|
package/src/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { defineAgent, defineTool, type AppAgentDefinition, type AppToolDefinition, type ToolSchema } from "./agents/index.js";
|
|
1
|
+
export { defineAgent, defineTool, defineDataCapability, type AppAgentDefinition, type AppToolDefinition, type ToolSchema } from "./agents/index.js";
|
|
2
2
|
import type { JsonValue } from "../index.js";
|
|
3
3
|
export { defineWorkflowStep, defineWorkflowStepExecute } from "./steps/index.js";
|
|
4
4
|
export type { WorkflowObjectSchema, WorkflowScalarSchema, WorkflowStepDefinition, WorkflowStepOutcome, WorkflowStepResult, WorkflowSchemaValue } from "./steps/index.js";
|
|
@@ -319,3 +319,5 @@ export interface WebhookContext {
|
|
|
319
319
|
/** 从 1 开始的业务尝试序号。 */
|
|
320
320
|
readonly attemptNumber: number;
|
|
321
321
|
}
|
|
322
|
+
|
|
323
|
+
export type { AppActionExposure, AppExpertExposure, AppDataCapabilityDefinition } from "./agents/index.js";
|
package/src/server/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { defineAgent, defineTool } from "./agents/index.js";
|
|
1
|
+
export { defineAgent, defineTool, defineDataCapability } from "./agents/index.js";
|
|
2
2
|
const SERVER_FUNCTION_MARKER = Symbol.for("zhike.server-function.v1");
|
|
3
3
|
export { defineWorkflowStep, defineWorkflowStepExecute } from "./steps/index.js";
|
|
4
4
|
export { defineWorkflowTrigger, defineWorkflowTriggerActivate, defineWorkflowTriggerOnEvent, defineWorkflowTriggerDeactivate } from "./triggers/index.js";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { posix } from 'node:path';
|
|
3
3
|
import { ZhikeCliError } from '../../errors.js';
|
|
4
|
+
import { assertAiCapabilities } from '../ai-capabilities.js';
|
|
4
5
|
import { assertAgentManifest } from './manifest.js';
|
|
5
6
|
|
|
6
7
|
const AGENT_PATH = /^app\/agents\/([a-z][a-z0-9-]{0,63})\/agent\.ts$/;
|
|
8
|
+
const ACTION_PATH = /^app\/actions\/([a-z][a-z0-9-]{0,63})\.tools\.ts$/;
|
|
9
|
+
const DATA_PATH = /^app\/data-capabilities\/([a-z][a-z0-9-]{0,63})\.ts$/;
|
|
7
10
|
const TOOL_PATH = /^app\/agents\/([a-z][a-z0-9-]{0,63})\/([a-z][a-z0-9-]{0,63})\.tools\.ts$/;
|
|
8
11
|
function invalid(path, message) { throw new ZhikeCliError('zhike_agent_invalid', `${path}: ${message}`); }
|
|
9
12
|
function literal(node, path, depth = 0) {
|
|
@@ -28,7 +31,7 @@ function literal(node, path, depth = 0) {
|
|
|
28
31
|
invalid(path, '元数据不能读取变量、函数或运行时代码');
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
function definition(file, api) {
|
|
34
|
+
export function definition(file, api) {
|
|
32
35
|
const { sourceFile, relativePath } = file;
|
|
33
36
|
const imported = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) &&
|
|
34
37
|
statement.moduleSpecifier.text === '@77-zhike/app-sdk/server' &&
|
|
@@ -62,19 +65,20 @@ export function discoverAgents(files) {
|
|
|
62
65
|
const match = file.relativePath.match(AGENT_PATH);
|
|
63
66
|
if (!match) continue;
|
|
64
67
|
const value = definition(file, 'defineAgent');
|
|
65
|
-
if (value.key !== match[1] || Object.keys(value).some(key => !['key', 'name', 'systemPrompt'].includes(key))) invalid(file.relativePath, 'Agent key 必须等于目录且只接受 key/name/systemPrompt');
|
|
68
|
+
if (value.key !== match[1] || Object.keys(value).some(key => !['key', 'name', 'systemPrompt', 'actionRefs', 'aiExposure'].includes(key))) invalid(file.relativePath, 'Agent key 必须等于目录且只接受 key/name/systemPrompt');
|
|
66
69
|
agents.set(value.key, { ...value, relativePath: file.relativePath, tools: [] });
|
|
67
70
|
}
|
|
68
71
|
if (agents.size > 16) invalid("app/agents", "一个 App 最多 16 个 Agent");
|
|
69
72
|
for (const file of files) {
|
|
70
73
|
const match = file.relativePath.match(TOOL_PATH);
|
|
71
|
-
if (file.relativePath.endsWith('.tools.ts') && !match) invalid(file.relativePath, '工具必须位于 Agent 的直接子目录');
|
|
74
|
+
if (file.relativePath.endsWith('.tools.ts') && !match && !ACTION_PATH.test(file.relativePath)) invalid(file.relativePath, '工具必须位于 Agent 的直接子目录');
|
|
72
75
|
if (/^app\/agents\/.+\/agent\.(?:ts|tsx)$/.test(file.relativePath) && !AGENT_PATH.test(file.relativePath)) invalid(file.relativePath, 'Agent 入口必须是直接子目录 agent.ts');
|
|
73
76
|
for (const statement of file.sourceFile.statements) {
|
|
74
77
|
if (!(ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement)) || !statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
75
78
|
const specifier = statement.moduleSpecifier.text;
|
|
76
79
|
if (!specifier.startsWith('.')) continue;
|
|
77
80
|
const target = posix.normalize(posix.join(posix.dirname(file.relativePath), specifier)).replace(/\.js$/, '.ts');
|
|
81
|
+
if (ACTION_PATH.test(target.endsWith('.ts') ? target : `${target}.ts`)) invalid(file.relativePath, '独立 Action 只能通过 actionRefs 引用,不能导入执行声明;请抽取普通 helper');
|
|
78
82
|
const tool = (target.endsWith(".ts") ? target : `${target}.ts`).match(TOOL_PATH);
|
|
79
83
|
if (tool && (!match || match[1] !== tool[1])) invalid(file.relativePath, '不能跨 Agent 导入工具声明,请抽取普通 helper');
|
|
80
84
|
}
|
|
@@ -97,3 +101,26 @@ export function discoverAgents(files) {
|
|
|
97
101
|
export function agentManifest({ relativePath, tools, ...agent }) {
|
|
98
102
|
return { ...agent, tools: tools.map(({ relativePath: path, ...tool }) => tool) };
|
|
99
103
|
}
|
|
104
|
+
|
|
105
|
+
export function discoverAiCapabilities(files, agents) {
|
|
106
|
+
const actions = [], dataCapabilities = [];
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
const action = file.relativePath.match(ACTION_PATH);
|
|
109
|
+
const data = file.relativePath.match(DATA_PATH);
|
|
110
|
+
if (file.relativePath.startsWith('app/actions/') && file.relativePath.endsWith('.tools.ts') && !action) invalid(file.relativePath, 'Action 必须位于 actions 直接目录');
|
|
111
|
+
if (!action && !data) continue;
|
|
112
|
+
const value = definition(file, action ? 'defineTool' : 'defineDataCapability');
|
|
113
|
+
if (value.key !== (action ?? data)[1]) invalid(file.relativePath, 'key 必须与文件名一致');
|
|
114
|
+
if (action) {
|
|
115
|
+
if (value.execute !== true) invalid(file.relativePath, 'Action 缺少 execute');
|
|
116
|
+
delete value.execute;
|
|
117
|
+
actions.push({...value, clientResult:value.clientResult ?? 'none', artifact:'server', entry:`action/${value.key}`, relativePath:file.relativePath});
|
|
118
|
+
} else dataCapabilities.push({...value, relativePath:file.relativePath});
|
|
119
|
+
}
|
|
120
|
+
actions.sort((a,b)=>a.key.localeCompare(b.key));
|
|
121
|
+
dataCapabilities.sort((a,b)=>a.key.localeCompare(b.key));
|
|
122
|
+
try { assertAiCapabilities({sdkAbiVersion:'2',aiCapabilitiesVersion:'1',actions:actions.map(stripPath),dataCapabilities:dataCapabilities.map(stripPath),agents:agents.map(agentManifest)}); }
|
|
123
|
+
catch { invalid('app/actions', '独立能力声明或跨引用无效'); }
|
|
124
|
+
return {actions, dataCapabilities};
|
|
125
|
+
}
|
|
126
|
+
export function stripPath({relativePath, ...value}) { return value; }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const OBJECT = /^[a-z][a-z0-9_]{0,63}$/;
|
|
2
|
+
export function assertAiExposure(value, inputSchema, expert = false) {
|
|
3
|
+
const invalid = () => { throw new TypeError('app_manifest_ai_exposure_invalid'); };
|
|
4
|
+
const allowed = expert
|
|
5
|
+
? ['subjectTypes', 'usage', 'limitations', 'outputSchema', 'toolKeys']
|
|
6
|
+
: ['subjectTypes', 'usage', 'limitations', 'effect', 'subjectInput'];
|
|
7
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) ||
|
|
8
|
+
Object.keys(value).some(key => !allowed.includes(key)) ||
|
|
9
|
+
!Array.isArray(value.subjectTypes) || value.subjectTypes.length > 16 ||
|
|
10
|
+
new Set(value.subjectTypes).size !== value.subjectTypes.length ||
|
|
11
|
+
value.subjectTypes.some(item => typeof item !== 'string' || !OBJECT.test(item))) invalid();
|
|
12
|
+
for (const key of ['usage', 'limitations']) {
|
|
13
|
+
if (typeof value[key] !== 'string' || !value[key].trim() || value[key].length > 1200) invalid();
|
|
14
|
+
}
|
|
15
|
+
if (expert) {
|
|
16
|
+
if (!value.outputSchema || (value.toolKeys !== undefined && (!Array.isArray(value.toolKeys) ||
|
|
17
|
+
new Set(value.toolKeys).size !== value.toolKeys.length ||
|
|
18
|
+
value.toolKeys.some(key => typeof key !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(key))))) invalid();
|
|
19
|
+
} else {
|
|
20
|
+
if (value.effect !== 'query') invalid();
|
|
21
|
+
if (value.subjectInput !== undefined) {
|
|
22
|
+
const subject = inputSchema?.properties?.[value.subjectInput];
|
|
23
|
+
if (typeof value.subjectInput !== 'string' || !Object.hasOwn(inputSchema.properties, value.subjectInput) ||
|
|
24
|
+
subject?.type !== 'object' || subject.additionalProperties !== false ||
|
|
25
|
+
Object.keys(subject.properties ?? {}).sort().join(',') !== 'object,recordId' ||
|
|
26
|
+
subject.properties.object?.type !== 'string' || subject.properties.recordId?.type !== 'string' ||
|
|
27
|
+
!subject.required?.includes('object') || !subject.required?.includes('recordId')) invalid();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertAiExposure } from './exposure.js';
|
|
1
2
|
const KEY = /^[a-z][a-z0-9-]{0,63}$/;
|
|
2
3
|
const FIELD = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
3
4
|
const RESERVED = new Set(['tenant_id', 'tenantId', 'member_id', 'memberId', 'installation_id', 'installationId',
|
|
@@ -46,17 +47,35 @@ export function assertAgentSchema(value) {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export function assertAgentManifest(value) {
|
|
49
|
-
record(value); keys(value, ['key', 'name', 'systemPrompt', 'tools']);
|
|
50
|
+
record(value); keys(value, ['key', 'name', 'systemPrompt', 'tools', 'actionRefs', 'aiExposure']);
|
|
50
51
|
if (typeof value.key !== "string" || value.key.trim() !== value.key || !KEY.test(value.key)) invalid();
|
|
51
52
|
text(value.name, 80); text(value.systemPrompt, 16384);
|
|
52
53
|
if (new TextEncoder().encode(value.systemPrompt).length > 16384 || !Array.isArray(value.tools) || value.tools.length > 32) invalid();
|
|
53
54
|
const seen = new Set();
|
|
54
55
|
for (const tool of value.tools) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
seen.add(tool.key); text(tool.name, 80); text(tool.description, 1200);
|
|
59
|
-
assertAgentSchema(tool.inputSchema); assertAgentSchema(tool.outputSchema);
|
|
56
|
+
if (seen.has(tool.key)) invalid();
|
|
57
|
+
seen.add(tool.key);
|
|
58
|
+
assertToolManifest(tool, `agent-tool/${value.key}/${tool.key}`);
|
|
60
59
|
}
|
|
61
60
|
return value;
|
|
62
61
|
}
|
|
62
|
+
|
|
63
|
+
export function assertToolManifest(tool, entry) {
|
|
64
|
+
record(tool); keys(tool, ['artifact', 'key', 'entry', 'name', 'description', 'inputSchema', 'outputSchema', 'clientResult', 'aiExposure', 'toolRenderer']);
|
|
65
|
+
if (typeof tool.key !== 'string' || tool.key.trim() !== tool.key || !KEY.test(tool.key) ||
|
|
66
|
+
tool.artifact !== 'server' || tool.entry !== entry || !['none', 'output'].includes(tool.clientResult)) invalid();
|
|
67
|
+
text(tool.name, 80); text(tool.description, 1200);
|
|
68
|
+
assertAgentSchema(tool.inputSchema); assertAgentSchema(tool.outputSchema);
|
|
69
|
+
if (tool.toolRenderer !== undefined) assertToolRendererReference(tool.toolRenderer);
|
|
70
|
+
if (tool.aiExposure !== undefined) {
|
|
71
|
+
if (Object.hasOwn(tool.inputSchema.properties, 'explanation')) invalid();
|
|
72
|
+
assertAiExposure(tool.aiExposure, tool.inputSchema);
|
|
73
|
+
}
|
|
74
|
+
return tool;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function assertToolRendererReference(value) {
|
|
78
|
+
record(value); keys(value, ['key', 'showByDefault']);
|
|
79
|
+
if (typeof value.key !== 'string' || !KEY.test(value.key) || typeof value.showByDefault !== 'boolean') invalid();
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { assertToolManifest, assertAgentSchema } from './agents/manifest.js';
|
|
2
|
+
import { assertAiExposure } from './agents/exposure.js';
|
|
3
|
+
|
|
4
|
+
export function assertAiCapabilities(manifest) {
|
|
5
|
+
const invalid = () => { throw new TypeError('app_manifest_ai_capabilities_invalid'); };
|
|
6
|
+
const actions = manifest.actions === undefined ? [] : manifest.actions;
|
|
7
|
+
const data = manifest.dataCapabilities === undefined ? [] : manifest.dataCapabilities;
|
|
8
|
+
const agents = manifest.agents ?? [];
|
|
9
|
+
const hasAiFields = manifest.actions !== undefined || manifest.dataCapabilities !== undefined ||
|
|
10
|
+
agents.some(agent => agent.aiExposure !== undefined || agent.actionRefs !== undefined ||
|
|
11
|
+
agent.tools.some(tool => tool.aiExposure !== undefined));
|
|
12
|
+
if (manifest.aiCapabilitiesVersion !== undefined && manifest.aiCapabilitiesVersion !== '1') invalid();
|
|
13
|
+
if (hasAiFields && manifest.aiCapabilitiesVersion !== '1') invalid();
|
|
14
|
+
if (manifest.aiCapabilitiesVersion !== undefined && manifest.sdkAbiVersion !== '2') invalid();
|
|
15
|
+
if (!Array.isArray(actions) || actions.length > 64 || !Array.isArray(data) || data.length > 64) invalid();
|
|
16
|
+
if (manifest.frontendExtensions !== undefined) {
|
|
17
|
+
const renderers = new Set(manifest.frontendExtensions.filter(item => item.type === 'tool-renderer').map(item => item.key));
|
|
18
|
+
for (const tool of [...actions, ...agents.flatMap(agent => agent.tools)]) {
|
|
19
|
+
if (tool.toolRenderer && !renderers.has(tool.toolRenderer.key)) invalid();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const byKey = new Map();
|
|
23
|
+
for (const action of actions) {
|
|
24
|
+
assertToolManifest(action, `action/${action.key}`);
|
|
25
|
+
if (byKey.has(action.key) || action.clientResult !== 'none') invalid();
|
|
26
|
+
byKey.set(action.key, action);
|
|
27
|
+
}
|
|
28
|
+
const dataKeys = new Set();
|
|
29
|
+
for (const item of data) {
|
|
30
|
+
assertDataCapability(item);
|
|
31
|
+
if (dataKeys.has(item.key)) invalid();
|
|
32
|
+
dataKeys.add(item.key);
|
|
33
|
+
if (item.kind === 'live') {
|
|
34
|
+
const action = byKey.get(item.actionRef);
|
|
35
|
+
if (!action?.aiExposure || item.sourceBinding !== undefined ||
|
|
36
|
+
item.subjectTypes.some(type => !action.aiExposure.subjectTypes.includes(type))) invalid();
|
|
37
|
+
} else if (item.kind === 'published') {
|
|
38
|
+
if (typeof item.sourceBinding !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(item.sourceBinding) || item.actionRef !== undefined) invalid();
|
|
39
|
+
} else invalid();
|
|
40
|
+
}
|
|
41
|
+
for (const agent of agents) {
|
|
42
|
+
const refs = agent.actionRefs === undefined ? [] : agent.actionRefs;
|
|
43
|
+
if (!Array.isArray(refs) || refs.length > 32 || new Set(refs).size !== refs.length || refs.some(ref => !byKey.has(ref))) invalid();
|
|
44
|
+
if (agent.aiExposure !== undefined) {
|
|
45
|
+
assertAiExposure(agent.aiExposure, null, true);
|
|
46
|
+
assertAgentSchema(agent.aiExposure.outputSchema);
|
|
47
|
+
if (refs.some(ref => !byKey.get(ref).aiExposure) ||
|
|
48
|
+
(agent.aiExposure.toolKeys ?? []).some(key => !agent.tools.find(tool => tool.key === key)?.aiExposure)) invalid();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return manifest;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function assertDataCapability(item) {
|
|
55
|
+
const invalid = () => { throw new TypeError('app_manifest_ai_capabilities_invalid'); };
|
|
56
|
+
if (!item || typeof item !== 'object' || Array.isArray(item) ||
|
|
57
|
+
Object.keys(item).some(key => !['key', 'name', 'description', 'subjectTypes', 'kind', 'sourceBinding', 'actionRef', 'usage'].includes(key)) ||
|
|
58
|
+
typeof item.key !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(item.key)) invalid();
|
|
59
|
+
for (const [key, max] of [['name', 80], ['description', 1200], ['usage', 1200]]) {
|
|
60
|
+
if (typeof item[key] !== 'string' || !item[key].trim() || item[key].length > max) invalid();
|
|
61
|
+
}
|
|
62
|
+
assertAiExposure({subjectTypes:item.subjectTypes, usage:item.usage, limitations:item.description, effect:'query'});
|
|
63
|
+
if (item.kind === 'published') {
|
|
64
|
+
if (typeof item.sourceBinding !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(item.sourceBinding) || item.actionRef !== undefined) invalid();
|
|
65
|
+
} else if (item.kind === 'live') {
|
|
66
|
+
if (typeof item.actionRef !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(item.actionRef) || item.sourceBinding !== undefined) invalid();
|
|
67
|
+
} else invalid();
|
|
68
|
+
return item;
|
|
69
|
+
}
|
|
@@ -6,7 +6,7 @@ import { dirname, relative, resolve, sep } from "node:path";
|
|
|
6
6
|
import { build } from "esbuild";
|
|
7
7
|
import ts from "typescript";
|
|
8
8
|
|
|
9
|
-
import { agentManifest } from "./agents/discovery.js";
|
|
9
|
+
import { agentManifest, stripPath } from "./agents/discovery.js";
|
|
10
10
|
import { serializeWebhookResponse } from "./webhook-response.js";
|
|
11
11
|
|
|
12
12
|
import { TOOLING_VERSION } from "../config.js";
|
|
@@ -43,7 +43,7 @@ export async function buildAppBundle({
|
|
|
43
43
|
sources.serverFunctions.length > 0 ||
|
|
44
44
|
sources.webhookHandlers.length > 0 ||
|
|
45
45
|
sources.workflowSteps.length > 0 ||
|
|
46
|
-
sources.workflowTriggers.length > 0 || sources.agents.length > 0
|
|
46
|
+
sources.workflowTriggers.length > 0 || sources.agents.length > 0 || sources.actions.length > 0
|
|
47
47
|
? await buildServerArtifact(projectDirectory, sources) : null;
|
|
48
48
|
} catch (error) {
|
|
49
49
|
const policyError = error.errors?.find(item => item.detail instanceof ZhikeCliError)?.detail;
|
|
@@ -96,7 +96,7 @@ export async function buildAppBundle({
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
async function assertTypeScriptProgram(projectDirectory, { sourceFiles, serverFunctions, agents }) {
|
|
99
|
+
async function assertTypeScriptProgram(projectDirectory, { sourceFiles, serverFunctions, agents, actions }) {
|
|
100
100
|
const paths = {
|
|
101
101
|
"@77-zhike/app-sdk": [resolve(SDK_SOURCE_ROOT, "index.d.ts")],
|
|
102
102
|
"@77-zhike/app-sdk/client": [resolve(SDK_SOURCE_ROOT, "client", "index.d.ts")],
|
|
@@ -129,6 +129,7 @@ async function assertTypeScriptProgram(projectDirectory, { sourceFiles, serverFu
|
|
|
129
129
|
const handlers = [
|
|
130
130
|
...serverFunctions.map(item => ({ ...item, member: '' })),
|
|
131
131
|
...agents.flatMap(agent => agent.tools.map(item => ({ ...item, member: '.execute' }))),
|
|
132
|
+
...actions.map(item => ({...item, member: '.execute'})),
|
|
132
133
|
];
|
|
133
134
|
const contractSource = [
|
|
134
135
|
'import type { ServerFunction } from "@77-zhike/app-sdk/server";',
|
|
@@ -194,10 +195,10 @@ async function buildServerArtifact(projectDirectory, sources) {
|
|
|
194
195
|
const hasConnectionLifecycle = sources.lifecycleEvents.some(
|
|
195
196
|
({ event }) => event === "connection-added" || event === "connection-removed",
|
|
196
197
|
);
|
|
197
|
-
const tools = sources.agents.flatMap(agent => agent.tools);
|
|
198
|
+
const tools = [...sources.agents.flatMap(agent => agent.tools), ...sources.actions];
|
|
198
199
|
const toolImports = tools.map((tool, index) => `import agentTool${index} from "./src/${tool.relativePath}";`);
|
|
199
200
|
const agentToolDeclarations = `const agentTools = new Map([${tools.map((tool, index) => `[${JSON.stringify(tool.entry)}, agentTool${index}]`).join(",")}]);`;
|
|
200
|
-
const agentToolDispatch = ` if (request.kind === "agent_tool") { const tool = agentTools.get(request.entryKey); if (!tool) throw new Error("runtime_agent_tool_not_found"); return tool.execute(Object.freeze({ ...request.input })); }\n`;
|
|
201
|
+
const agentToolDispatch = ` if (request.kind === "agent_tool" || request.kind === "app_action") { const prefix = request.kind === "app_action" ? "action/" : "agent-tool/"; const tool = typeof request.entryKey === "string" && request.entryKey.startsWith(prefix) ? agentTools.get(request.entryKey) : null; if (!tool) throw new Error("runtime_agent_tool_not_found"); return tool.execute(Object.freeze({ ...request.input })); }\n`;
|
|
201
202
|
const imports = [
|
|
202
203
|
...toolImports,
|
|
203
204
|
...(hasConnectionLifecycle
|
|
@@ -327,7 +328,7 @@ function createSdkBuildPlugin(projectDirectory, sources, target) {
|
|
|
327
328
|
buildApi.onLoad({ filter: /.*/, namespace: "zhike-sdk-runtime" }, (args) => {
|
|
328
329
|
if (args.path.endsWith("/client")) {
|
|
329
330
|
return {
|
|
330
|
-
contents: "export const defineFrontendExtension = (definition) => definition; export const definePage = (definition) => ({ ...definition, type: 'page' });",
|
|
331
|
+
contents: "export const defineFrontendExtension = (definition) => definition; export const definePage = (definition) => ({ ...definition, type: 'page' }); export const defineToolRenderer = (definition) => ({ ...definition, type: 'tool-renderer' });",
|
|
331
332
|
loader: "js",
|
|
332
333
|
};
|
|
333
334
|
}
|
|
@@ -411,6 +412,9 @@ function createManifest({ clientBytes, serverBytes, sources }) {
|
|
|
411
412
|
key,
|
|
412
413
|
})),
|
|
413
414
|
...(sources.agents.length ? { agents: sources.agents.map(agentManifest) } : {}),
|
|
415
|
+
...((sources.actions.length || sources.dataCapabilities.length || sources.agents.some(agent => agent.aiExposure || agent.actionRefs || agent.tools.some(tool => tool.aiExposure))) ? {aiCapabilitiesVersion:'1'} : {}),
|
|
416
|
+
...(sources.actions.length ? {actions:sources.actions.map(stripPath)} : {}),
|
|
417
|
+
...(sources.dataCapabilities.length ? {dataCapabilities:sources.dataCapabilities.map(stripPath)} : {}),
|
|
414
418
|
serverFunctions: sources.serverFunctions.map(({ key }) => ({
|
|
415
419
|
artifact: "server",
|
|
416
420
|
key,
|
|
@@ -444,6 +448,7 @@ function createServerDefinitionsDigest(sources) {
|
|
|
444
448
|
...(sources.agents.length ? { agents: sources.agents.map(({ key, tools }) => ({
|
|
445
449
|
key, tools: tools.map(({ key: toolKey, entry }) => ({ key: toolKey, entry })),
|
|
446
450
|
})) } : {}),
|
|
451
|
+
...(sources.actions.length ? {actions:sources.actions.map(({key,entry})=>({key,entry}))} : {}),
|
|
447
452
|
serverFunctions: sources.serverFunctions.map(({ key }) => ({ key })),
|
|
448
453
|
webhookHandlers: sources.webhookHandlers.map(({ key, methods }) => ({ key, methods })),
|
|
449
454
|
...(sources.workflowSteps.length > 0 ? { workflowSteps: sources.workflowSteps.map(({ key, entry }) => ({ key, entry })) } : {}),
|
|
@@ -5,7 +5,7 @@ import { posix, relative, resolve, sep } from "node:path";
|
|
|
5
5
|
import ts from "typescript";
|
|
6
6
|
|
|
7
7
|
import { ZhikeCliError } from "../errors.js";
|
|
8
|
-
import { discoverAgents } from "./agents/discovery.js";
|
|
8
|
+
import { discoverAiCapabilities, discoverAgents } from "./agents/discovery.js";
|
|
9
9
|
import { discoverWorkflowBlock } from "./workflow-blocks/discovery.js";
|
|
10
10
|
import { assertAllowedPackageImport } from "./dependency-policy.js";
|
|
11
11
|
|
|
@@ -52,6 +52,7 @@ export async function discoverAppSources(projectDirectory) {
|
|
|
52
52
|
const sourceFilesByPath = new Map(parsedFiles.map(file => [file.relativePath, file.sourceFile]));
|
|
53
53
|
|
|
54
54
|
const agents = discoverAgents(parsedFiles);
|
|
55
|
+
const { actions, dataCapabilities } = discoverAiCapabilities(parsedFiles, agents);
|
|
55
56
|
const frontendExtensions = [];
|
|
56
57
|
const lifecycleEvents = [];
|
|
57
58
|
const serverFunctions = [];
|
|
@@ -69,6 +70,7 @@ export async function discoverAppSources(projectDirectory) {
|
|
|
69
70
|
const pageMatch = file.relativePath.match(
|
|
70
71
|
/^app\/pages\/([a-z][a-z0-9-]{0,63})\/page\.tsx$/,
|
|
71
72
|
);
|
|
73
|
+
const rendererMatch = file.relativePath.match(/^app\/tool-renderers\/([a-z][a-z0-9-]{0,63})\.tsx$/);
|
|
72
74
|
const lifecycleMatch = file.relativePath.match(
|
|
73
75
|
/^app\/events\/(app-installed|app-uninstalled|connection-added|connection-removed)\.event\.ts$/,
|
|
74
76
|
);
|
|
@@ -89,6 +91,15 @@ export async function discoverAppSources(projectDirectory) {
|
|
|
89
91
|
);
|
|
90
92
|
registerUniqueKey(globalKeys, definition.key, file.relativePath);
|
|
91
93
|
lifecycleEvents.push(definition);
|
|
94
|
+
} else if (rendererMatch) {
|
|
95
|
+
requireSdkNamedImport(sourceFile, file.relativePath, '@77-zhike/app-sdk/client', 'defineToolRenderer');
|
|
96
|
+
const call = requireDefaultExportCall(sourceFile, file.relativePath, 'defineToolRenderer');
|
|
97
|
+
const object = requireObjectLiteral(call.arguments[0], file.relativePath);
|
|
98
|
+
assertOnlyProperties(object, ['key', 'Renderer'], file.relativePath);
|
|
99
|
+
const key = requireStringProperty(object, 'key', file.relativePath);
|
|
100
|
+
if (key !== rendererMatch[1] || !findProperty(object, 'Renderer')) throw toolchainError('zhike_tool_renderer_invalid', file.relativePath);
|
|
101
|
+
registerUniqueKey(globalKeys, key, file.relativePath);
|
|
102
|
+
frontendExtensions.push(Object.freeze({ key, entry: key, type: 'tool-renderer', relativePath: file.relativePath }));
|
|
92
103
|
} else if (pageMatch) {
|
|
93
104
|
const definition = parseWorkspacePage(
|
|
94
105
|
sourceFile,
|
|
@@ -141,7 +152,7 @@ export async function discoverAppSources(projectDirectory) {
|
|
|
141
152
|
webhookHandlers.length === 0 &&
|
|
142
153
|
workflowSteps.length === 0 &&
|
|
143
154
|
workflowTriggers.length === 0 &&
|
|
144
|
-
agents.length === 0
|
|
155
|
+
agents.length === 0 && actions.length === 0 && dataCapabilities.length === 0
|
|
145
156
|
) {
|
|
146
157
|
throw toolchainError(
|
|
147
158
|
"zhike_source_entry_missing",
|
|
@@ -151,8 +162,16 @@ export async function discoverAppSources(projectDirectory) {
|
|
|
151
162
|
if (frontendExtensions.filter(item => item.type === "settings").length > 1) {
|
|
152
163
|
throw toolchainError("zhike_settings_duplicate", "一个 App 至多声明一个 Settings");
|
|
153
164
|
}
|
|
165
|
+
const rendererKeys = new Set(frontendExtensions.filter(item => item.type === 'tool-renderer').map(item => item.key));
|
|
166
|
+
for (const action of [...actions, ...agents.flatMap(agent => agent.tools)]) {
|
|
167
|
+
if (action.toolRenderer && !rendererKeys.has(action.toolRenderer.key)) {
|
|
168
|
+
throw toolchainError('zhike_tool_renderer_reference_invalid', 'Action 引用的 Tool Renderer 不存在');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
154
171
|
return Object.freeze({
|
|
155
172
|
agents: Object.freeze(agents),
|
|
173
|
+
actions: Object.freeze(actions),
|
|
174
|
+
dataCapabilities: Object.freeze(dataCapabilities),
|
|
156
175
|
frontendExtensions: Object.freeze(sortByKey(frontendExtensions)),
|
|
157
176
|
lifecycleEvents: Object.freeze(sortByKey(lifecycleEvents)),
|
|
158
177
|
serverFunctions: Object.freeze(sortByKey(serverFunctions)),
|
|
@@ -583,6 +602,7 @@ export function validateImports(sourceFile, relativePath, options = {}) {
|
|
|
583
602
|
options.target === "server" || (!options.target && (
|
|
584
603
|
relativePath.startsWith("server/") ||
|
|
585
604
|
relativePath.startsWith("app/blocks/") || relativePath.startsWith("app/agents/") ||
|
|
605
|
+
relativePath.startsWith("app/actions/") || relativePath.startsWith("app/data-capabilities/") ||
|
|
586
606
|
/^app\/events\/.+\.event\.ts$/.test(relativePath) ||
|
|
587
607
|
/^app\/webhooks\/.+\.webhook\.ts$/.test(relativePath)));
|
|
588
608
|
const target = allowsStandardFetch ? "server" : "client";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 此文件由 scripts/generate-components.mjs 生成,请修改产品能力声明。
|
|
2
|
-
export const componentTypes = Object.freeze(["Conversation","ConversationRoot","ConversationMessages","ConversationComposer","ConversationHistory","ConversationNewButton","ConversationStopButton","ConversationStatus","ScrollViewport","TextSelection","Popover","NativeDOM","Icon","Stack","SplitLayout","SplitLayoutMain","SplitLayoutAside","SplitLayoutActions","Section","Banner","Badge","LoadingState","Skeleton","Card","Grid","EmptyState","ZhikeLink","Button","SearchInput","FilterSelect","Slider","Switch","LookupCell","Tabs","TabsList","TabsTrigger","TabsContent","Table","TableHeader","TableBody","TableRow","TableCell","Avatar","DescriptionList","DescriptionListItem","DescriptionListMenu","DescriptionListMenuItem","Divider","Chart","DashboardSurface","DashboardLayout","DashboardPanel","MetricReadout","AnalysisView","Json","Link","StatusBadge","TextBlock","TypographyTitle","TypographyBody","TypographyCaption","TableHeaderCell","WidgetTextWidget","WidgetTitle","WidgetTextPrimary","WidgetTextSecondary","WidgetLoading","WidgetDecoration"]);
|
|
3
|
-
export const publicComponentNames = Object.freeze(["Conversation","ConversationRoot","ConversationMessages","ConversationComposer","ConversationHistory","ConversationNewButton","ConversationStopButton","ConversationStatus","ScrollViewport","TextSelection","Popover","NativeDOM","Icon","Stack","SplitLayout","SplitLayoutMain","SplitLayoutAside","SplitLayoutActions","Section","Banner","Badge","LoadingState","Skeleton","Card","Grid","EmptyState","ZhikeLink","Button","SearchInput","FilterSelect","Slider","Switch","LookupCell","Tabs","TabsList","TabsTrigger","TabsContent","Table","TableHeader","TableBody","TableRow","TableCell","Avatar","DescriptionList","DescriptionListItem","DescriptionListMenu","DescriptionListMenuItem","Divider","Chart","DashboardSurface","DashboardLayout","DashboardPanel","MetricReadout","AnalysisView","Json","Link","StatusBadge","TextBlock","TypographyTitle","TypographyBody","TypographyCaption","TableHeaderCell","WidgetTextWidget","WidgetTitle","WidgetTextPrimary","WidgetTextSecondary","WidgetLoading","WidgetDecoration"]);
|
|
4
|
-
export const legacyComponentTypes = Object.freeze(["Conversation","ConversationRoot","ConversationMessages","ConversationComposer","ConversationHistory","ConversationNewButton","ConversationStopButton","ConversationStatus","ScrollViewport","TextSelection","Popover","NativeDOM","Text","Icon","Stack","SplitLayout","SplitLayoutMain","SplitLayoutAside","SplitLayoutActions","Section","Banner","Badge","LoadingState","Skeleton","Card","Grid","EmptyState","ZhikeLink","Button","SearchInput","FilterSelect","Slider","Switch","LookupCell","Tabs","TabsList","TabsTrigger","TabsContent","Table","TableHeader","TableBody","TableRow","TableCell","Avatar","DescriptionList","DescriptionListItem","DescriptionListMenu","DescriptionListMenuItem","Divider","Chart","DashboardSurface","DashboardLayout","DashboardPanel","MetricReadout","AnalysisView","Json","Link","StatusBadge","TextBlock","TypographyTitle","TypographyBody","TypographyCaption","TableHeaderCell","WidgetTextWidget","WidgetTitle","WidgetTextPrimary","WidgetTextSecondary","WidgetLoading","WidgetDecoration"]);
|
|
2
|
+
export const componentTypes = Object.freeze(["Conversation","ConversationRoot","ConversationMessages","ConversationComposer","ConversationHistory","ConversationNewButton","ConversationStopButton","ConversationStatus","ScrollViewport","TextSelection","Popover","NativeDOM","Icon","Stack","SplitLayout","SplitLayoutMain","SplitLayoutAside","SplitLayoutActions","Section","Banner","Badge","LoadingState","Skeleton","Card","Grid","EmptyState","ZhikeLink","Button","SearchInput","FilterSelect","Slider","Switch","LookupCell","Tabs","TabsList","TabsTrigger","TabsContent","DataTable","Table","TableHeader","TableBody","TableRow","TableCell","Avatar","DescriptionList","DescriptionListItem","DescriptionListMenu","DescriptionListMenuItem","Divider","Chart","DashboardSurface","DashboardLayout","DashboardPanel","MetricReadout","AnalysisView","Json","Link","StatusBadge","TextBlock","TypographyTitle","TypographyBody","TypographyCaption","TableHeaderCell","WidgetTextWidget","WidgetTitle","WidgetTextPrimary","WidgetTextSecondary","WidgetLoading","WidgetDecoration"]);
|
|
3
|
+
export const publicComponentNames = Object.freeze(["Conversation","ConversationRoot","ConversationMessages","ConversationComposer","ConversationHistory","ConversationNewButton","ConversationStopButton","ConversationStatus","ScrollViewport","TextSelection","Popover","NativeDOM","Icon","Stack","SplitLayout","SplitLayoutMain","SplitLayoutAside","SplitLayoutActions","Section","Banner","Badge","LoadingState","Skeleton","Card","Grid","EmptyState","ZhikeLink","Button","SearchInput","FilterSelect","Slider","Switch","LookupCell","Tabs","TabsList","TabsTrigger","TabsContent","DataTable","Table","TableHeader","TableBody","TableRow","TableCell","Avatar","DescriptionList","DescriptionListItem","DescriptionListMenu","DescriptionListMenuItem","Divider","Chart","DashboardSurface","DashboardLayout","DashboardPanel","MetricReadout","AnalysisView","Json","Link","StatusBadge","TextBlock","TypographyTitle","TypographyBody","TypographyCaption","TableHeaderCell","WidgetTextWidget","WidgetTitle","WidgetTextPrimary","WidgetTextSecondary","WidgetLoading","WidgetDecoration"]);
|
|
4
|
+
export const legacyComponentTypes = Object.freeze(["Conversation","ConversationRoot","ConversationMessages","ConversationComposer","ConversationHistory","ConversationNewButton","ConversationStopButton","ConversationStatus","ScrollViewport","TextSelection","Popover","NativeDOM","Text","Icon","Stack","SplitLayout","SplitLayoutMain","SplitLayoutAside","SplitLayoutActions","Section","Banner","Badge","LoadingState","Skeleton","Card","Grid","EmptyState","ZhikeLink","Button","SearchInput","FilterSelect","Slider","Switch","LookupCell","Tabs","TabsList","TabsTrigger","TabsContent","DataTable","DataTableItem","DataTableCell","Table","TableHeader","TableBody","TableRow","TableCell","Avatar","DescriptionList","DescriptionListItem","DescriptionListMenu","DescriptionListMenuItem","Divider","Chart","DashboardSurface","DashboardLayout","DashboardPanel","MetricReadout","AnalysisView","Json","Link","StatusBadge","TextBlock","TypographyTitle","TypographyBody","TypographyCaption","TableHeaderCell","WidgetTextWidget","WidgetTitle","WidgetTextPrimary","WidgetTextSecondary","WidgetLoading","WidgetDecoration"]);
|
|
5
5
|
export const badgeColors = Object.freeze(["amber","blue","cyan","green","grey","lavender","lime","orange","pink","purple","red","yellow"]);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DataTableColumn, DataTableProps, TableState } from './data-table.js';
|
|
2
|
+
export type WireColumn = Pick<DataTableColumn<Record<string, unknown>>, 'id'|'headerName'|'width'|'flex'|'minWidth'|'maxWidth'|'align'|'sortable'|'resizable'>;
|
|
3
|
+
export interface DataTableWindow { revision: number; requestId: number; start: number; count: number; anchorIndex?: number; anchorOffset?: number; }
|
|
4
|
+
export interface DataTableModel { revision: number; count: number; columns: WireColumn[]; state: TableState; page: number; pages: number|null; total: number|null; hasNext: boolean; window: DataTableWindow; expandable: boolean; }
|
|
5
|
+
export function columnWidths(columns: readonly WireColumn[], available: number, overrides?: Record<string,number>): Record<string,number>;
|
|
6
|
+
export function sortRows<T>(rows: readonly T[], columns: readonly DataTableColumn<T>[], rules: TableState['sorting']): T[];
|
|
7
|
+
export function assertWindowRequest(value: unknown): DataTableWindow;
|
|
8
|
+
export function assertTableWire(value: unknown): DataTableModel;
|
|
9
|
+
export function initialTableState(): TableState;
|
|
10
|
+
export function validateState(value: unknown, columns: readonly WireColumn[]): TableState;
|
|
11
|
+
export function validateInput<T>(props: DataTableProps<T>): DataTableProps<T>;
|
|
12
|
+
export const DATA_TABLE_LIMITS: Readonly<{columns:number;rows:number;window:number;maxWindow:number}>;
|
|
13
|
+
|
|
14
|
+
export function resolvedTableColumns<T>(props: DataTableProps<T>): readonly DataTableColumn<T>[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export function columnWidths(columns, available, overrides = {}) {
|
|
2
|
+
let remaining = Math.max(0, available);
|
|
3
|
+
/** @type {Record<string, number>} */
|
|
4
|
+
const result = {};
|
|
5
|
+
const flexible = [];
|
|
6
|
+
for (const c of columns) {
|
|
7
|
+
const min = c.minWidth ?? 96, max = c.maxWidth ?? Infinity;
|
|
8
|
+
if (!Number.isFinite(min) || min <= 0 || max < min || (max !== Infinity && !Number.isFinite(max))) throw Error('invalid_column_bounds');
|
|
9
|
+
if (c.width !== undefined && c.flex !== undefined) throw Error('width_flex_conflict');
|
|
10
|
+
if (c.flex !== undefined && (!Number.isFinite(c.flex) || c.flex <= 0)) throw Error('invalid_flex');
|
|
11
|
+
const width = overrides[c.id] ?? c.width;
|
|
12
|
+
if (width !== undefined) {
|
|
13
|
+
if (!Number.isFinite(width) || width <= 0) throw Error('invalid_width');
|
|
14
|
+
result[c.id] = Math.min(max, Math.max(min, width)); remaining -= result[c.id];
|
|
15
|
+
} else flexible.push({ ...c, min, max, weight: c.flex ?? 1 });
|
|
16
|
+
}
|
|
17
|
+
let pending = flexible;
|
|
18
|
+
while (pending.length) {
|
|
19
|
+
const weight = pending.reduce((sum, c) => sum + c.weight, 0);
|
|
20
|
+
const proposed = new Map(pending.map(c => [c.id, remaining * c.weight / weight]));
|
|
21
|
+
const clamp = c => Math.min(c.max, Math.max(c.min, proposed.get(c.id)));
|
|
22
|
+
const clampedTotal = pending.reduce((sum, c) => sum + clamp(c), 0);
|
|
23
|
+
const constrained = Math.abs(clampedTotal - remaining) < 0.000001 ? [] : pending.filter(c =>
|
|
24
|
+
clampedTotal > remaining ? proposed.get(c.id) < c.min : proposed.get(c.id) > c.max);
|
|
25
|
+
if (!constrained.length) { for (const c of pending) result[c.id] = clamp(c); break; }
|
|
26
|
+
for (const c of constrained) { result[c.id] = clamp(c); remaining -= result[c.id]; }
|
|
27
|
+
pending = pending.filter(c => !constrained.includes(c));
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
export function sortRows(rows, columns, rules) {
|
|
32
|
+
if (!rules.length) return [...rows];
|
|
33
|
+
return rows.map((row, index) => ({ row, index })).sort((a, b) => {
|
|
34
|
+
for (const rule of rules) {
|
|
35
|
+
const c = columns.find(c => c.id === rule.columnId); if (!c) continue;
|
|
36
|
+
const av = c.valueGetter ? c.valueGetter(a.row) : a.row[c.field];
|
|
37
|
+
const bv = c.valueGetter ? c.valueGetter(b.row) : b.row[c.field];
|
|
38
|
+
if (av == null || bv == null) { if (av == null && bv == null) continue; return av == null ? 1 : -1; }
|
|
39
|
+
const cmp = c.dataType === 'number' || c.dataType === 'boolean' ? Number(av) - Number(bv) : c.dataType === 'date' ? Date.parse(av) - Date.parse(bv) : String(av).localeCompare(String(bv), 'zh-CN');
|
|
40
|
+
if (cmp) return rule.direction === 'asc' ? cmp : -cmp;
|
|
41
|
+
}
|
|
42
|
+
return a.index - b.index;
|
|
43
|
+
}).map(x => x.row);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const DATA_TABLE_LIMITS = Object.freeze({ columns: 40, rows: 50000, window: 30, maxWindow: 200 });
|
|
47
|
+
const invalid = () => { throw new Error('runtime_data_table_contract_invalid'); };
|
|
48
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value) && [Object.prototype,null].includes(Object.getPrototypeOf(value));
|
|
49
|
+
const identity = value => typeof value === 'string' && value.trim().length > 0 && value.length <= 150 && !['__proto__','constructor','prototype'].includes(value);
|
|
50
|
+
const integer = (value,min,max) => Number.isSafeInteger(value) && value >= min && value <= max;
|
|
51
|
+
export function assertWindowRequest(value) {
|
|
52
|
+
if(!object(value)||Object.keys(value).some(k=>!['revision','requestId','start','count','anchorKey','anchorOffset'].includes(k))||!integer(value.revision,1,Number.MAX_SAFE_INTEGER)||!integer(value.requestId,1,Number.MAX_SAFE_INTEGER)||!integer(value.start,0,150000)||!integer(value.count,1,DATA_TABLE_LIMITS.maxWindow))invalid();
|
|
53
|
+
if(value.anchorKey!==undefined&&(typeof value.anchorKey!=='string'||!value.anchorKey.trim()||value.anchorKey.length>160||!Number.isFinite(value.anchorOffset)||Math.abs(value.anchorOffset)>10000)||value.anchorKey===undefined&&value.anchorOffset!==undefined)invalid();
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
export const initialTableState = () => ({widthOverrides:{},pinning:{start:[],end:[]},sorting:[],pagination:{page:1,pageSize:20},expanded:{}});
|
|
57
|
+
export function validateState(value, columns) {
|
|
58
|
+
if(!object(value))invalid();
|
|
59
|
+
const known = new Set(columns.map(c=>c.id));
|
|
60
|
+
if(Object.keys(value).some(k=>!['widthOverrides','pinning','sorting','pagination','expanded'].includes(k)))invalid();
|
|
61
|
+
if(value.widthOverrides!==undefined&&(!object(value.widthOverrides)||Object.entries(value.widthOverrides).some(([id,w])=>!known.has(id)||!Number.isFinite(w)||w<=0||w>10000)))invalid();
|
|
62
|
+
if(value.pinning!==undefined){const p=value.pinning;if(!object(p)||Object.keys(p).some(k=>!['start','end'].includes(k))||!Array.isArray(p.start)||!Array.isArray(p.end)||[...p.start,...p.end].some(id=>!known.has(id))||new Set([...p.start,...p.end]).size!==p.start.length+p.end.length)invalid();}
|
|
63
|
+
if(value.sorting!==undefined&&(!Array.isArray(value.sorting)||value.sorting.length>columns.length||new Set(value.sorting.map(s=>s.columnId)).size!==value.sorting.length||value.sorting.some(s=>!object(s)||Object.keys(s).some(k=>!['columnId','direction'].includes(k))||!known.has(s.columnId)||!columns.find(c=>c.id===s.columnId).sortable||!['asc','desc'].includes(s.direction))))invalid();
|
|
64
|
+
if(value.pagination!==undefined&&(!object(value.pagination)||Object.keys(value.pagination).some(k=>!['page','pageSize'].includes(k))||!integer(value.pagination.page,1,1000000)||!integer(value.pagination.pageSize,1,1000)))invalid();
|
|
65
|
+
if(value.expanded!==undefined&&(!object(value.expanded)||Object.entries(value.expanded).length>DATA_TABLE_LIMITS.rows||Object.entries(value.expanded).some(([id,v])=>!identity(id)||v!==true)))invalid();
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
const listColumns = Object.freeze([Object.freeze({id:'item',headerName:'内容',flex:1,minWidth:1})]);
|
|
69
|
+
export const resolvedTableColumns = props => props.presentation==='list'?listColumns:props.columns;
|
|
70
|
+
export function validateInput(props) {
|
|
71
|
+
if(props.presentation!==undefined&&!['table','list'].includes(props.presentation))invalid();
|
|
72
|
+
if(props.presentation==='list'){if(props.columns!==undefined||typeof props.renderItem!=='function')invalid();for(const state of [props.state,props.defaultState])if(state&&(Object.keys(state.widthOverrides??{}).length||state.pinning?.start?.length||state.pinning?.end?.length||state.sorting?.length))invalid();}else if(props.renderItem!==undefined)invalid();
|
|
73
|
+
const columns=resolvedTableColumns(props);
|
|
74
|
+
if(!identity(props.tableId)||!identity(props.label)||!Array.isArray(columns)||!columns.length||columns.length>DATA_TABLE_LIMITS.columns||typeof props.getRowId!=='function')invalid();
|
|
75
|
+
const ids=new Set();for(const c of columns){if(!object(c)||!identity(c.id)||ids.has(c.id)||!identity(c.headerName)||c.field!==undefined&&!identity(c.field)||c.dataType!==undefined&&!['text','number','date','boolean'].includes(c.dataType)||c.align!==undefined&&!['start','center','end'].includes(c.align)||c.sortable&&!c.field&&!c.valueGetter||c.cellRenderer!==undefined&&typeof c.cellRenderer!=='function'||c.valueGetter!==undefined&&typeof c.valueGetter!=='function')invalid();ids.add(c.id);}
|
|
76
|
+
columnWidths(columns,1000);
|
|
77
|
+
const source=props.source;
|
|
78
|
+
if(!object(source)||!['complete','serverPage'].includes(source.kind)||!['ready','loading','error'].includes(source.status)||!Array.isArray(source.rows)||source.rows.length>DATA_TABLE_LIMITS.rows)invalid();
|
|
79
|
+
const rowIds=source.rows.map(row=>props.getRowId(row));if(rowIds.some(id=>!identity(id))||new Set(rowIds).size!==rowIds.length)invalid();
|
|
80
|
+
if(source.kind==='serverPage'){if(props.state?.pagination!==undefined||props.defaultState?.pagination!==undefined)invalid();if(!identity(source.queryKey)||!integer(source.page,1,1000000)||!integer(source.pageSize,1,1000)||source.rows.length>source.pageSize||typeof source.onQueryChange!=='function'||!object(source.count))invalid();const count=source.count;if(Object.keys(count).length!==1||!('total'in count?integer(count.total,0,1000000000):typeof count.hasNextPage==='boolean'))invalid();}
|
|
81
|
+
for(const state of [props.state,props.defaultState])if(state!==undefined)validateState(state,columns);
|
|
82
|
+
if(props.state&&props.defaultState&&Object.keys(props.state).some(k=>Object.hasOwn(props.defaultState,k)))invalid();
|
|
83
|
+
if(props.state&&Object.keys(props.state).length&&typeof props.onStateChange!=='function')invalid();
|
|
84
|
+
if(props.showExpandToggle!==undefined&&typeof props.showExpandToggle!=='boolean')invalid();
|
|
85
|
+
if(props.height!==undefined&&(!Number.isFinite(props.height)||props.height<160||props.height>4000))invalid();
|
|
86
|
+
if(props.virtualization!==undefined&&(!object(props.virtualization)||props.virtualization.estimateRowHeight!==undefined&&(!Number.isFinite(props.virtualization.estimateRowHeight)||props.virtualization.estimateRowHeight<20||props.virtualization.estimateRowHeight>1000)||props.virtualization.overscan!==undefined&&!integer(props.virtualization.overscan,0,30)))invalid();
|
|
87
|
+
return props;
|
|
88
|
+
}
|
|
89
|
+
/** Host与Guest两端都校验受控wire metadata,函数与原始业务记录不跨线程。 */
|
|
90
|
+
export function assertTableWire(model) {
|
|
91
|
+
if(!object(model)||Object.keys(model).some(k=>!['revision','count','columns','state','page','pages','total','hasNext','window','expandable'].includes(k))||!integer(model.revision,1,Number.MAX_SAFE_INTEGER)||!integer(model.count,0,150000)||!Array.isArray(model.columns)||!model.columns.length||model.columns.length>40)invalid();
|
|
92
|
+
const ids=new Set();for(const c of model.columns){if(!object(c)||Object.keys(c).some(k=>!['id','headerName','width','flex','minWidth','maxWidth','align','sortable','resizable'].includes(k))||!identity(c.id)||!identity(c.headerName)||ids.has(c.id)||c.sortable!==undefined&&typeof c.sortable!=='boolean'||c.resizable!==undefined&&typeof c.resizable!=='boolean'||c.align!==undefined&&!['start','center','end'].includes(c.align))invalid();ids.add(c.id);}
|
|
93
|
+
columnWidths(model.columns,1000);validateState(model.state,model.columns);
|
|
94
|
+
if(!integer(model.page,1,1000000)||model.pages!==null&&!integer(model.pages,0,1000000)||model.total!==null&&!integer(model.total,0,1000000000)||typeof model.hasNext!=='boolean'||typeof model.expandable!=='boolean')invalid();
|
|
95
|
+
if(!object(model.window))invalid();
|
|
96
|
+
const {anchorIndex,anchorOffset,...window}=model.window;assertWindowRequest(window);if(anchorIndex!==undefined&&(!integer(anchorIndex,0,Math.max(0,model.count-1))||!Number.isFinite(anchorOffset)||Math.abs(anchorOffset)>10000)||anchorIndex===undefined&&anchorOffset!==undefined)invalid();if(model.window.revision!==model.revision||model.window.start>model.count&&model.count>0)invalid();
|
|
97
|
+
return model;
|
|
98
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ReactNode, ReactElement } from 'react';
|
|
2
|
+
/** DataTable 公开合同;须随支持该组件的 Host 配套发布。 */
|
|
3
|
+
export type TableScalar = string | number | boolean | null;
|
|
4
|
+
export interface TableState {
|
|
5
|
+
widthOverrides: Record<string, number>;
|
|
6
|
+
pinning: { start: string[]; end: string[] };
|
|
7
|
+
sorting: { columnId: string; direction: 'asc' | 'desc' }[];
|
|
8
|
+
pagination: { page: number; pageSize: number };
|
|
9
|
+
expanded: Record<string, true>;
|
|
10
|
+
}
|
|
11
|
+
export interface ListItemContext<T> {
|
|
12
|
+
data: T;
|
|
13
|
+
rowId: string;
|
|
14
|
+
expanded: boolean;
|
|
15
|
+
setExpanded: (expanded: boolean) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface CellContext<T> extends ListItemContext<T> {
|
|
18
|
+
value: TableScalar | undefined;
|
|
19
|
+
columnId: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DataTableColumn<T> {
|
|
22
|
+
id: string;
|
|
23
|
+
field?: keyof T & string;
|
|
24
|
+
headerName: string;
|
|
25
|
+
valueGetter?: (data: T) => TableScalar;
|
|
26
|
+
cellRenderer?: (context: CellContext<T>) => ReactNode;
|
|
27
|
+
dataType?: 'text' | 'number' | 'date' | 'boolean';
|
|
28
|
+
width?: number;
|
|
29
|
+
/** 正数权重;不得与width同时设置。 */
|
|
30
|
+
flex?: number;
|
|
31
|
+
minWidth?: number;
|
|
32
|
+
maxWidth?: number;
|
|
33
|
+
resizable?: boolean;
|
|
34
|
+
sortable?: boolean;
|
|
35
|
+
align?: 'start' | 'center' | 'end';
|
|
36
|
+
}
|
|
37
|
+
export type TableSource<T> =
|
|
38
|
+
| { kind: 'complete'; rows: readonly T[]; status: 'loading' | 'ready' | 'error' }
|
|
39
|
+
| { kind: 'serverPage'; rows: readonly T[]; status: 'loading' | 'ready' | 'error';
|
|
40
|
+
/** 查询和权限范围变更时必须替换,不能作为权限凭据。 */
|
|
41
|
+
queryKey: string; page: number; pageSize: number;
|
|
42
|
+
count: { total: number } | { hasNextPage: boolean };
|
|
43
|
+
onQueryChange: (query: { queryKey: string; page: number; pageSize: number; sorting: TableState['sorting'] }) => void;
|
|
44
|
+
};
|
|
45
|
+
interface DataTableCommonProps<T> {
|
|
46
|
+
tableId: string;
|
|
47
|
+
label: string;
|
|
48
|
+
source: TableSource<T>;
|
|
49
|
+
getRowId: (data: T) => string;
|
|
50
|
+
density?: 'compact' | 'standard';
|
|
51
|
+
height?: number;
|
|
52
|
+
pagination?: boolean;
|
|
53
|
+
state?: Partial<TableState>;
|
|
54
|
+
defaultState?: Partial<TableState>;
|
|
55
|
+
onStateChange?: (change: { nextState: TableState; changedKeys: (keyof TableState)[];
|
|
56
|
+
reason: 'resizeEnd' | 'resetWidths' | 'pin' | 'sort' | 'page' | 'expand' }) => void;
|
|
57
|
+
/** 自定义Cell负责展开入口时可隐藏内置按钮,详情能力仍保留。 */
|
|
58
|
+
showExpandToggle?: boolean;
|
|
59
|
+
renderExpandedRow?: (context: { data: T; rowId: string }) => ReactNode;
|
|
60
|
+
getRowGroup?: (data: T) => { id: string; label: string } | null;
|
|
61
|
+
onRowActivate?: (context: { data: T; rowId: string }) => void;
|
|
62
|
+
virtualization?: { enabled?: boolean; estimateRowHeight?: number; overscan?: number };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** list复用虚拟窗口和来源合同;项内容仅在Guest执行,无表头或列布局。 */
|
|
66
|
+
export type DataTableProps<T> = DataTableCommonProps<T> & (
|
|
67
|
+
| { presentation?: 'table'; columns: readonly DataTableColumn<T>[]; renderItem?: never }
|
|
68
|
+
| { presentation: 'list'; renderItem: (context: ListItemContext<T>) => ReactNode; columns?: never }
|
|
69
|
+
);
|
|
70
|
+
export interface DataTableComponent { <T>(props: DataTableProps<T>): ReactElement | null; }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { initialTableState, validateInput, validateState, assertWindowRequest, sortRows, assertTableWire, resolvedTableColumns } from './data-table-model.js';
|
|
2
|
+
|
|
3
|
+
/** App函数只在Guest执行;Host取得列元信息和有界窗口树。 */
|
|
4
|
+
export function createDataTable(React) {
|
|
5
|
+
return function DataTable(input) {
|
|
6
|
+
React.useMemo(()=>validateInput(input),[input]);
|
|
7
|
+
const props={...input,columns:resolvedTableColumns(input)};
|
|
8
|
+
const [local,setLocal] = React.useState(()=>({...initialTableState(),...props.defaultState}));
|
|
9
|
+
const [requested,setRequested] = React.useState(null);
|
|
10
|
+
const latest=React.useRef(null), revision=React.useRef(0), lastAction=React.useRef(0);
|
|
11
|
+
const source=props.source;
|
|
12
|
+
const serverPagination=React.useMemo(()=>({page:source.page,pageSize:source.pageSize}),[source.page,source.pageSize]);
|
|
13
|
+
const state={...local,...props.state,...(source.kind==='serverPage'?{pagination:serverPagination}:{})};
|
|
14
|
+
const model=React.useMemo(()=>{
|
|
15
|
+
const getId=props.getRowId;
|
|
16
|
+
const groupOf=row=>props.getRowGroup?.(row)??null;
|
|
17
|
+
const sorted=source.kind==='complete' ? props.getRowGroup ? (()=>{const groups=[];for(const row of source.rows){const group=groupOf(row);const prev=groups.at(-1);if(prev&&prev.group?.id===group?.id)prev.rows.push(row);else groups.push({group,rows:[row]});}return groups.flatMap(g=>sortRows(g.rows,props.columns,state.sorting));})() : sortRows(source.rows,props.columns,state.sorting) : source.rows;
|
|
18
|
+
const paging=source.kind==='serverPage'||!!props.pagination;
|
|
19
|
+
const page=source.kind==='serverPage'?source.page:!paging?1:Math.min(state.pagination.page,Math.max(1,Math.ceil(sorted.length/state.pagination.pageSize)));
|
|
20
|
+
const count=source.kind==='serverPage'?source.count:{total:sorted.length};
|
|
21
|
+
const total='total'in count?count.total:null, pageSize=source.kind==='serverPage'?source.pageSize:state.pagination.pageSize;
|
|
22
|
+
const rows=source.kind==='complete'&&paging?sorted.slice((page-1)*pageSize,page*pageSize):sorted;
|
|
23
|
+
const items=[];let previousGroup=null;
|
|
24
|
+
for(const data of rows){const rowId=getId(data),group=groupOf(data);if(group&&(typeof group.id!=='string'||!group.id.trim()||group.id.length>150||!group.label||typeof group.label!=='string'||group.label.length>200))throw Error('runtime_data_table_contract_invalid');
|
|
25
|
+
if(group&&group.id!==previousGroup){items.push({key:`g:${rowId}`,rowId,kind:'group',label:group.label,data});}previousGroup=group?.id??null;
|
|
26
|
+
items.push({key:`r:${rowId}`,rowId,kind:'row',data});
|
|
27
|
+
if(state.expanded[rowId]&&props.renderExpandedRow)items.push({key:`d:${rowId}`,rowId,kind:'detail',data});
|
|
28
|
+
}
|
|
29
|
+
return {revision:++revision.current,items,page,pages:paging&&total!==null?Math.ceil(total/pageSize):null,total,paging,hasNext:paging&&(total===null?count.hasNextPage:page*pageSize<total)};
|
|
30
|
+
},[source.rows,source.kind,source.status,source.queryKey,source.page,source.pageSize,source.count?.total,source.count?.hasNextPage,props.columns,props.getRowId,props.getRowGroup,props.renderExpandedRow,props.pagination,state.sorting,state.pagination,state.expanded]);
|
|
31
|
+
const range=requested?.revision===model.revision?requested:{revision:model.revision,requestId:1,start:0,count:30};
|
|
32
|
+
const effectiveRange={...range,start:Math.min(range.start,Math.max(0,model.items.length-1))};
|
|
33
|
+
latest.current={props,state,model,range:effectiveRange};
|
|
34
|
+
const change=React.useCallback((patch,reason)=>{
|
|
35
|
+
const current=latest.current,nextState={...current.state,...patch};validateState(nextState,current.props.columns);
|
|
36
|
+
const changedKeys=Object.keys(patch),uncontrolled=Object.fromEntries(changedKeys.filter(k=>!Object.hasOwn(current.props.state??{},k)).map(k=>[k,patch[k]]));
|
|
37
|
+
if(Object.keys(uncontrolled).length)setLocal(old=>({...old,...uncontrolled}));
|
|
38
|
+
current.props.onStateChange?.({nextState,changedKeys,reason});
|
|
39
|
+
if(current.props.source.kind==='serverPage'&&changedKeys.some(k=>['sorting','pagination'].includes(k))){const s=current.props.source;s.onQueryChange({queryKey:s.queryKey,page:reason==='sort'?1:nextState.pagination.page,pageSize:nextState.pagination.pageSize,sorting:nextState.sorting});}
|
|
40
|
+
},[]);
|
|
41
|
+
const windowChange=React.useCallback(request=>{assertWindowRequest(request);const current=latest.current;if(request.revision!==current.model.revision||request.start>=current.model.items.length&¤t.model.items.length||current.range.revision===request.revision&&request.requestId<=current.range.requestId)return;const {anchorKey,anchorOffset,...range}=request;const anchorIndex=anchorKey===undefined?-1:current.model.items.findIndex(item=>item.key===anchorKey);setRequested(anchorIndex<0?range:{...range,start:Math.max(0,anchorIndex-5),anchorIndex,anchorOffset});},[]);
|
|
42
|
+
const action=React.useCallback(event=>{
|
|
43
|
+
const {props:p,state:s,model:m,range:r}=latest.current;
|
|
44
|
+
if(!event||typeof event!=='object'||Array.isArray(event)||Object.keys(event).some(k=>!['revision','sequence','type','columnId','rowId','value','multi'].includes(k))||!Number.isSafeInteger(event.sequence)||event.sequence<=0)throw Error('runtime_data_table_contract_invalid');
|
|
45
|
+
if(event.revision!==m.revision||event.sequence<=lastAction.current)return;
|
|
46
|
+
const c=p.columns.find(c=>c.id===event.columnId);
|
|
47
|
+
const item=m.items.slice(r.start,r.start+r.count).find(item=>item.kind==='row'&&item.rowId===event.rowId);
|
|
48
|
+
if(event.type==='resizeEnd'){if(!c?.resizable||!Number.isFinite(event.value)||event.value<=0||event.value>10000)throw Error('runtime_data_table_contract_invalid');lastAction.current=event.sequence;change({widthOverrides:{...s.widthOverrides,[c.id]:Math.max(c.minWidth??96,Math.min(c.maxWidth??10000,event.value))}},'resizeEnd');}
|
|
49
|
+
else if(event.type==='resetWidths'){lastAction.current=event.sequence;change({widthOverrides:{}},'resetWidths');}
|
|
50
|
+
else if(event.type==='sort'){if(!c?.sortable)throw Error('runtime_data_table_contract_invalid');const old=s.sorting.find(rule=>rule.columnId===c.id),others=event.multi?s.sorting.filter(rule=>rule.columnId!==c.id):[];lastAction.current=event.sequence;change({sorting:old?.direction==='desc'?others:[...others,{columnId:c.id,direction:old?'desc':'asc'}],pagination:{...s.pagination,page:1}},'sort');}
|
|
51
|
+
else if(event.type==='page'){if(!Number.isSafeInteger(event.value)||event.value<1||!m.paging||m.pages!==null&&event.value>Math.max(1,m.pages)||m.pages===null&&event.value>m.page+Number(m.hasNext))throw Error('runtime_data_table_contract_invalid');lastAction.current=event.sequence;change({pagination:{...s.pagination,page:event.value}},'page');}
|
|
52
|
+
else if(event.type==='expand'){if(!item||!p.renderExpandedRow)throw Error('runtime_data_table_contract_invalid');const expanded={...s.expanded};if(expanded[item.rowId])delete expanded[item.rowId];else expanded[item.rowId]=true;lastAction.current=event.sequence;change({expanded},'expand');}
|
|
53
|
+
else throw Error('runtime_data_table_contract_invalid');
|
|
54
|
+
},[change]);
|
|
55
|
+
const orderedColumns=[...state.pinning.start.map(id=>props.columns.find(c=>c.id===id)),...props.columns.filter(c=>![...state.pinning.start,...state.pinning.end].includes(c.id)),...state.pinning.end.map(id=>props.columns.find(c=>c.id===id))];
|
|
56
|
+
if(props.virtualization?.enabled===false&&model.items.length>200)throw Error('runtime_data_table_window_limit');
|
|
57
|
+
const children=model.items.slice(effectiveRange.start,effectiveRange.start+effectiveRange.count).map((item,offset)=>{
|
|
58
|
+
let content;
|
|
59
|
+
if(item.kind==='group')content=item.label;
|
|
60
|
+
else if(item.kind==='detail')content=props.renderExpandedRow({data:item.data,rowId:item.rowId});
|
|
61
|
+
else if(props.presentation==='list')content=React.createElement('DataTableCell',{key:'item',columnId:'item'},props.renderItem({data:item.data,rowId:item.rowId,expanded:!!state.expanded[item.rowId],setExpanded(value){if(typeof value!=='boolean'||!props.renderExpandedRow)throw Error('runtime_data_table_contract_invalid');const current=latest.current,expanded={...current.state.expanded};if(value)expanded[item.rowId]=true;else delete expanded[item.rowId];change({expanded},'expand');}}));
|
|
62
|
+
else content=orderedColumns.map(column=>{
|
|
63
|
+
const value=column.valueGetter?column.valueGetter(item.data):column.field?item.data[column.field]:undefined;
|
|
64
|
+
const cell=column.cellRenderer?column.cellRenderer({data:item.data,value,rowId:item.rowId,columnId:column.id,expanded:!!state.expanded[item.rowId],setExpanded(value){if(typeof value!=='boolean')throw Error('runtime_data_table_contract_invalid');const current=latest.current,expanded={...current.state.expanded};if(value)expanded[item.rowId]=true;else delete expanded[item.rowId];change({expanded},'expand');}}):value==null?'—':String(value);
|
|
65
|
+
return React.createElement('DataTableCell',{key:column.id,columnId:column.id},cell);
|
|
66
|
+
});
|
|
67
|
+
return React.createElement('DataTableItem',{key:item.key,rowId:item.rowId,itemKey:item.key,kind:item.kind,index:effectiveRange.start+offset,expanded:!!state.expanded[item.rowId],...(item.kind==='row'&&props.onRowActivate?{onActivate:()=>{const current=latest.current,row=current.model.items.find(row=>row.kind==='row'&&row.rowId===item.rowId);if(row)current.props.onRowActivate?.({data:row.data,rowId:row.rowId});}}:{})},content);
|
|
68
|
+
});
|
|
69
|
+
const columns=orderedColumns.map(c=>({...Object.fromEntries(['id','headerName','width','flex','minWidth','maxWidth','align','sortable','resizable'].flatMap(key=>c[key]===undefined?[]:[[key,c[key]]])),align:c.align??(c.dataType==='number'?'end':'start')}));
|
|
70
|
+
const wire={revision:model.revision,count:model.items.length,columns,state:{...state,expanded:{}},page:model.page,pages:model.pages,total:model.total,hasNext:model.hasNext,window:effectiveRange,expandable:!!props.renderExpandedRow&&props.showExpandToggle!==false};
|
|
71
|
+
assertTableWire(wire);
|
|
72
|
+
return React.createElement('DataTable',{tableId:props.tableId,label:props.label,...(props.presentation==='list'?{presentation:'list'}:{}),model:wire,density:props.density??'compact',...(props.height===undefined?{}:{height:props.height}),estimateRowHeight:props.virtualization?.estimateRowHeight??48,overscan:props.virtualization?.overscan??5,status:source.status,virtualized:props.virtualization?.enabled??model.items.length>100,onWindowChange:windowChange,onAction:action},children);
|
|
73
|
+
};
|
|
74
|
+
}
|
package/src/ui/factory.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createDataTable } from './data-table.js';
|
|
1
2
|
import { assertSliderConfiguration, assertSliderChange } from './slider.js';
|
|
2
3
|
import { assertTextSelection } from './text-selection.js';
|
|
3
4
|
import { assertScrollViewportRequest, assertScrollViewportSnapshot } from './scroll-viewport.js';
|
|
@@ -79,6 +80,7 @@ export function createUiComponents(React, { legacy = false, useOwner = () => nul
|
|
|
79
80
|
}
|
|
80
81
|
return React.createElement(type, props);
|
|
81
82
|
}]));
|
|
83
|
+
components.DataTable = createDataTable(React);
|
|
82
84
|
if (components.Conversation) Object.assign(components.Conversation, { Root: components.ConversationRoot,
|
|
83
85
|
Messages: components.ConversationMessages, Composer: components.ConversationComposer,
|
|
84
86
|
History: components.ConversationHistory, NewButton: components.ConversationNewButton,
|
package/src/ui/index.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export const ConversationMessages: ComponentType<ConversationMessagesProps>;
|
|
|
44
44
|
export interface ConversationComposerProps {
|
|
45
45
|
label?: string;
|
|
46
46
|
placeholder?: string;
|
|
47
|
+
appearance?: "default" | "paper";
|
|
48
|
+
minRows?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
47
49
|
}
|
|
48
50
|
export const ConversationComposer: ComponentType<ConversationComposerProps>;
|
|
49
51
|
export interface ConversationHistoryProps {
|
|
@@ -88,6 +90,7 @@ export interface PopoverProps {
|
|
|
88
90
|
icon: "a-arrow-down" | "a-arrow-up" | "a-large-small" | "accessibility" | "activity" | "activity-square" | "ad" | "air-vent" | "airplay" | "alarm-check" | "alarm-clock" | "alarm-clock-check" | "alarm-clock-minus" | "alarm-clock-off" | "alarm-clock-plus" | "alarm-minus" | "alarm-plus" | "alarm-smoke" | "album" | "alert-circle" | "alert-octagon" | "alert-triangle" | "align-center" | "align-center-horizontal" | "align-center-vertical" | "align-end-horizontal" | "align-end-vertical" | "align-horizontal-distribute-center" | "align-horizontal-distribute-end" | "align-horizontal-distribute-start" | "align-horizontal-justify-center" | "align-horizontal-justify-end" | "align-horizontal-justify-start" | "align-horizontal-space-around" | "align-horizontal-space-between" | "align-justify" | "align-left" | "align-right" | "align-start-horizontal" | "align-start-vertical" | "align-vertical-distribute-center" | "align-vertical-distribute-end" | "align-vertical-distribute-start" | "align-vertical-justify-center" | "align-vertical-justify-end" | "align-vertical-justify-start" | "align-vertical-space-around" | "align-vertical-space-between" | "ambulance" | "ampersand" | "ampersands" | "amphora" | "anchor" | "angle" | "angry" | "annoyed" | "antenna" | "anvil" | "aperture" | "app-window" | "app-window-mac" | "apple" | "archive" | "archive-restore" | "archive-x" | "area-chart" | "armchair" | "arrow-big-down" | "arrow-big-down-dash" | "arrow-big-left" | "arrow-big-left-dash" | "arrow-big-right" | "arrow-big-right-dash" | "arrow-big-up" | "arrow-big-up-dash" | "arrow-down" | "arrow-down-0-1" | "arrow-down-01" | "arrow-down-1-0" | "arrow-down-10" | "arrow-down-a-z" | "arrow-down-az" | "arrow-down-circle" | "arrow-down-from-line" | "arrow-down-left" | "arrow-down-left-from-circle" | "arrow-down-left-from-square" | "arrow-down-left-square" | "arrow-down-narrow-wide" | "arrow-down-right" | "arrow-down-right-from-circle" | "arrow-down-right-from-square" | "arrow-down-right-square" | "arrow-down-square" | "arrow-down-to-dot" | "arrow-down-to-line" | "arrow-down-up" | "arrow-down-wide-narrow" | "arrow-down-z-a" | "arrow-down-za" | "arrow-left" | "arrow-left-circle" | "arrow-left-from-line" | "arrow-left-right" | "arrow-left-square" | "arrow-left-to-line" | "arrow-right" | "arrow-right-circle" | "arrow-right-from-line" | "arrow-right-left" | "arrow-right-square" | "arrow-right-to-line" | "arrow-up" | "arrow-up-0-1" | "arrow-up-01" | "arrow-up-1-0" | "arrow-up-10" | "arrow-up-a-z" | "arrow-up-az" | "arrow-up-circle" | "arrow-up-down" | "arrow-up-from-dot" | "arrow-up-from-line" | "arrow-up-left" | "arrow-up-left-from-circle" | "arrow-up-left-from-square" | "arrow-up-left-square" | "arrow-up-narrow-wide" | "arrow-up-right" | "arrow-up-right-from-circle" | "arrow-up-right-from-square" | "arrow-up-right-square" | "arrow-up-square" | "arrow-up-to-line" | "arrow-up-wide-narrow" | "arrow-up-z-a" | "arrow-up-za" | "arrows-up-from-line" | "asterisk" | "asterisk-square" | "astroid" | "at-sign" | "atom" | "audio-lines" | "audio-lines-off" | "audio-lines-x" | "audio-waveform" | "award" | "axe" | "axis-3-d" | "axis-3d" | "baby" | "backpack" | "badge" | "badge-alert" | "badge-cent" | "badge-check" | "badge-dollar-sign" | "badge-euro" | "badge-help" | "badge-indian-rupee" | "badge-info" | "badge-japanese-yen" | "badge-minus" | "badge-percent" | "badge-plus" | "badge-pound-sterling" | "badge-question-mark" | "badge-russian-ruble" | "badge-swiss-franc" | "badge-turkish-lira" | "badge-x" | "baggage-claim" | "balloon" | "ban" | "banana" | "bandage" | "banknote" | "banknote-arrow-down" | "banknote-arrow-up" | "banknote-check" | "banknote-x" | "bar-chart" | "bar-chart-2" | "bar-chart-3" | "bar-chart-4" | "bar-chart-big" | "bar-chart-horizontal" | "bar-chart-horizontal-big" | "barcode" | "barrel" | "baseline" | "bath" | "battery" | "battery-charging" | "battery-full" | "battery-low" | "battery-medium" | "battery-plus" | "battery-warning" | "beaker" | "bean" | "bean-off" | "bed" | "bed-double" | "bed-single" | "beef" | "beef-off" | "beer" | "beer-off" | "bell" | "bell-check" | "bell-dot" | "bell-electric" | "bell-minus" | "bell-off" | "bell-plus" | "bell-ring" | "between-horizonal-end" | "between-horizonal-start" | "between-horizontal-end" | "between-horizontal-start" | "between-vertical-end" | "between-vertical-start" | "biceps-flexed" | "bike" | "binary" | "binoculars" | "biohazard" | "bird" | "birdhouse" | "bitcoin" | "blend" | "blender" | "blinds" | "blocks" | "bluetooth" | "bluetooth-connected" | "bluetooth-off" | "bluetooth-searching" | "bold" | "bolt" | "bomb" | "bone" | "bone-fracture" | "book" | "book-a" | "book-alert" | "book-audio" | "book-check" | "book-copy" | "book-dashed" | "book-down" | "book-headphones" | "book-heart" | "book-image" | "book-key" | "book-lock" | "book-marked" | "book-minus" | "book-open" | "book-open-check" | "book-open-text" | "book-plus" | "book-search" | "book-template" | "book-text" | "book-type" | "book-up" | "book-up-2" | "book-user" | "book-x" | "bookmark" | "bookmark-check" | "bookmark-minus" | "bookmark-off" | "bookmark-plus" | "bookmark-x" | "boom-box" | "bot" | "bot-message-square" | "bot-off" | "bottle-wine" | "bow-arrow" | "box" | "box-select" | "boxes" | "braces" | "brackets" | "brain" | "brain-circuit" | "brain-cog" | "brick-wall" | "brick-wall-fire" | "brick-wall-shield" | "briefcase" | "briefcase-business" | "briefcase-conveyor-belt" | "briefcase-medical" | "bring-to-front" | "broccoli" | "broom" | "broom-sparkles" | "brush" | "brush-cleaning" | "bubbles" | "bug" | "bug-off" | "bug-play" | "building" | "building-2" | "bus" | "bus-front" | "cable" | "cable-car" | "cake" | "cake-slice" | "calculator" | "calendar" | "calendar-1" | "calendar-arrow-down" | "calendar-arrow-up" | "calendar-check" | "calendar-check-2" | "calendar-clock" | "calendar-cog" | "calendar-days" | "calendar-fold" | "calendar-heart" | "calendar-minus" | "calendar-minus-2" | "calendar-off" | "calendar-plus" | "calendar-plus-2" | "calendar-range" | "calendar-search" | "calendar-sync" | "calendar-x" | "calendar-x-2" | "calendars" | "camera" | "camera-off" | "candlestick-chart" | "candy" | "candy-cane" | "candy-off" | "cannabis" | "cannabis-off" | "captions" | "captions-off" | "car" | "car-battery" | "car-front" | "car-taxi-front" | "caravan" | "card-sim" | "carrot" | "case-lower" | "case-sensitive" | "case-upper" | "cassette-tape" | "cast" | "castle" | "cat" | "cctv" | "cctv-off" | "chart-area" | "chart-bar" | "chart-bar-big" | "chart-bar-decreasing" | "chart-bar-increasing" | "chart-bar-stacked" | "chart-candlestick" | "chart-column" | "chart-column-big" | "chart-column-decreasing" | "chart-column-increasing" | "chart-column-stacked" | "chart-gantt" | "chart-line" | "chart-network" | "chart-no-axes-column" | "chart-no-axes-column-decreasing" | "chart-no-axes-column-increasing" | "chart-no-axes-combined" | "chart-no-axes-gantt" | "chart-pie" | "chart-scatter" | "chart-spline" | "check" | "check-check" | "check-circle" | "check-circle-2" | "check-line" | "check-square" | "check-square-2" | "chef-hat" | "cherry" | "chess-bishop" | "chess-king" | "chess-knight" | "chess-pawn" | "chess-queen" | "chess-rook" | "chevron-down" | "chevron-down-circle" | "chevron-down-square" | "chevron-first" | "chevron-last" | "chevron-left" | "chevron-left-circle" | "chevron-left-square" | "chevron-right" | "chevron-right-circle" | "chevron-right-square" | "chevron-up" | "chevron-up-circle" | "chevron-up-square" | "chevrons-down" | "chevrons-down-up" | "chevrons-left" | "chevrons-left-right" | "chevrons-left-right-ellipsis" | "chevrons-right" | "chevrons-right-left" | "chevrons-up" | "chevrons-up-down" | "church" | "cigarette" | "cigarette-off" | "circle" | "circle-alert" | "circle-arrow-down" | "circle-arrow-left" | "circle-arrow-out-down-left" | "circle-arrow-out-down-right" | "circle-arrow-out-up-left" | "circle-arrow-out-up-right" | "circle-arrow-right" | "circle-arrow-up" | "circle-check" | "circle-check-big" | "circle-chevron-down" | "circle-chevron-left" | "circle-chevron-right" | "circle-chevron-up" | "circle-dashed" | "circle-divide" | "circle-dollar-sign" | "circle-dot" | "circle-dot-dashed" | "circle-ellipsis" | "circle-equal" | "circle-euro" | "circle-fading-arrow-up" | "circle-fading-plus" | "circle-gauge" | "circle-help" | "circle-minus" | "circle-off" | "circle-parking" | "circle-parking-off" | "circle-pause" | "circle-percent" | "circle-pile" | "circle-play" | "circle-plus" | "circle-pound-sterling" | "circle-power" | "circle-question-mark" | "circle-slash" | "circle-slash-2" | "circle-slashed" | "circle-small" | "circle-star" | "circle-stop" | "circle-user" | "circle-user-round" | "circle-x" | "circuit-board" | "citrus" | "clapperboard" | "clipboard" | "clipboard-check" | "clipboard-clock" | "clipboard-copy" | "clipboard-edit" | "clipboard-list" | "clipboard-minus" | "clipboard-paste" | "clipboard-pen" | "clipboard-pen-line" | "clipboard-plus" | "clipboard-signature" | "clipboard-type" | "clipboard-x" | "clock" | "clock-1" | "clock-10" | "clock-11" | "clock-12" | "clock-2" | "clock-3" | "clock-4" | "clock-5" | "clock-6" | "clock-7" | "clock-8" | "clock-9" | "clock-alert" | "clock-arrow-down" | "clock-arrow-left" | "clock-arrow-right" | "clock-arrow-up" | "clock-check" | "clock-fading" | "clock-plus" | "closed-caption" | "cloud" | "cloud-alert" | "cloud-backup" | "cloud-check" | "cloud-cog" | "cloud-download" | "cloud-drizzle" | "cloud-fog" | "cloud-hail" | "cloud-lightning" | "cloud-moon" | "cloud-moon-rain" | "cloud-off" | "cloud-rain" | "cloud-rain-wind" | "cloud-snow" | "cloud-sun" | "cloud-sun-rain" | "cloud-sync" | "cloud-upload" | "cloudy" | "clover" | "club" | "code" | "code-2" | "code-square" | "code-xml" | "coffee" | "cog" | "coins" | "columns" | "columns-2" | "columns-3" | "columns-3-cog" | "columns-4" | "columns-settings" | "combine" | "command" | "compass" | "component" | "computer" | "concierge-bell" | "cone" | "construction" | "contact" | "contact-2" | "contact-round" | "container" | "contrast" | "cookie" | "cooking-pot" | "copy" | "copy-check" | "copy-minus" | "copy-plus" | "copy-slash" | "copy-x" | "copyleft" | "copyright" | "corner-down-left" | "corner-down-right" | "corner-left-down" | "corner-left-up" | "corner-right-down" | "corner-right-up" | "corner-up-left" | "corner-up-right" | "cpu" | "creative-commons" | "credit-card" | "croissant" | "crop" | "cross" | "crosshair" | "crown" | "cuboid" | "cup-soda" | "curly-braces" | "currency" | "cylinder" | "dam" | "database" | "database-arrow-down" | "database-arrow-up" | "database-backup" | "database-check" | "database-minus" | "database-plus" | "database-search" | "database-x" | "database-zap" | "decimals-arrow-left" | "decimals-arrow-right" | "delete" | "dessert" | "diameter" | "diamond" | "diamond-minus" | "diamond-percent" | "diamond-plus" | "dice-1" | "dice-2" | "dice-3" | "dice-4" | "dice-5" | "dice-6" | "dices" | "diff" | "disc" | "disc-2" | "disc-3" | "disc-album" | "divide" | "divide-circle" | "divide-square" | "dna" | "dna-off" | "dock" | "dog" | "dollar-sign" | "donut" | "door-closed" | "door-closed-locked" | "door-open" | "dot" | "dot-square" | "download" | "download-cloud" | "drafting-compass" | "drama" | "drill" | "drone" | "droplet" | "droplet-off" | "droplets" | "drum" | "drumstick" | "dumbbell" | "ear" | "ear-off" | "earth" | "earth-lock" | "eclipse" | "edit" | "edit-2" | "edit-3" | "egg" | "egg-fried" | "egg-off" | "eject" | "ellipse" | "ellipsis" | "ellipsis-vertical" | "equal" | "equal-approximately" | "equal-not" | "equal-square" | "eraser" | "ethernet-port" | "euro" | "ev-charger" | "expand" | "external-link" | "eye" | "eye-closed" | "eye-dashed" | "eye-off" | "face-angry" | "face-expressionless" | "face-grinning" | "face-neutral" | "face-slightly-frowning" | "face-slightly-smiling" | "face-slightly-smiling-plus" | "factory" | "fan" | "fast-forward" | "feather" | "fence" | "ferris-wheel" | "file" | "file-archive" | "file-audio" | "file-audio-2" | "file-axis-3-d" | "file-axis-3d" | "file-badge" | "file-badge-2" | "file-bar-chart" | "file-bar-chart-2" | "file-box" | "file-braces" | "file-braces-corner" | "file-chart-column" | "file-chart-column-increasing" | "file-chart-line" | "file-chart-pie" | "file-check" | "file-check-2" | "file-check-corner" | "file-clock" | "file-code" | "file-code-2" | "file-code-corner" | "file-cog" | "file-cog-2" | "file-diff" | "file-digit" | "file-down" | "file-edit" | "file-exclamation-point" | "file-headphone" | "file-heart" | "file-image" | "file-input" | "file-json" | "file-json-2" | "file-key" | "file-key-2" | "file-line-chart" | "file-lock" | "file-lock-2" | "file-minus" | "file-minus-2" | "file-minus-corner" | "file-music" | "file-output" | "file-pen" | "file-pen-line" | "file-pie-chart" | "file-play" | "file-plus" | "file-plus-2" | "file-plus-corner" | "file-question" | "file-question-mark" | "file-scan" | "file-search" | "file-search-2" | "file-search-corner" | "file-signal" | "file-signature" | "file-sliders" | "file-spreadsheet" | "file-stack" | "file-symlink" | "file-terminal" | "file-text" | "file-type" | "file-type-2" | "file-type-corner" | "file-up" | "file-user" | "file-video" | "file-video-2" | "file-video-camera" | "file-volume" | "file-volume-2" | "file-warning" | "file-x" | "file-x-2" | "file-x-corner" | "files" | "film" | "filter" | "filter-x" | "fingerprint" | "fingerprint-pattern" | "fire-extinguisher" | "fish" | "fish-off" | "fish-symbol" | "fishing-hook" | "fishing-rod" | "flag" | "flag-off" | "flag-triangle-left" | "flag-triangle-right" | "flame" | "flame-kindling" | "flashlight" | "flashlight-off" | "flask-conical" | "flask-conical-off" | "flask-round" | "flip-horizontal" | "flip-horizontal-2" | "flip-vertical" | "flip-vertical-2" | "flower" | "flower-2" | "focus" | "fold-horizontal" | "fold-vertical" | "folder" | "folder-archive" | "folder-bookmark" | "folder-check" | "folder-clock" | "folder-closed" | "folder-code" | "folder-cog" | "folder-cog-2" | "folder-dot" | "folder-down" | "folder-edit" | "folder-git" | "folder-git-2" | "folder-heart" | "folder-input" | "folder-kanban" | "folder-key" | "folder-lock" | "folder-minus" | "folder-open" | "folder-open-dot" | "folder-output" | "folder-pen" | "folder-plus" | "folder-root" | "folder-search" | "folder-search-2" | "folder-symlink" | "folder-sync" | "folder-tree" | "folder-up" | "folder-x" | "folders" | "footprints" | "fork-knife" | "fork-knife-crossed" | "forklift" | "form" | "form-input" | "forward" | "frame" | "frown" | "fuel" | "fullscreen" | "function-square" | "funnel" | "funnel-plus" | "funnel-x" | "gallery-horizontal" | "gallery-horizontal-end" | "gallery-thumbnails" | "gallery-vertical" | "gallery-vertical-end" | "gamepad" | "gamepad-2" | "gamepad-directional" | "gantt-chart" | "gantt-chart-square" | "gauge" | "gauge-circle" | "gavel" | "gem" | "georgian-lari" | "ghost" | "gift" | "git-branch" | "git-branch-minus" | "git-branch-plus" | "git-commit" | "git-commit-horizontal" | "git-commit-vertical" | "git-compare" | "git-compare-arrows" | "git-fork" | "git-graph" | "git-merge" | "git-merge-conflict" | "git-pull-request" | "git-pull-request-arrow" | "git-pull-request-closed" | "git-pull-request-create" | "git-pull-request-create-arrow" | "git-pull-request-draft" | "glass-water" | "glasses" | "globe" | "globe-2" | "globe-check" | "globe-lock" | "globe-off" | "globe-x" | "goal" | "gpu" | "grab" | "graduation-cap" | "grape" | "grid" | "grid-2-x-2" | "grid-2-x-2-check" | "grid-2-x-2-plus" | "grid-2-x-2-x" | "grid-2x2" | "grid-2x2-check" | "grid-2x2-plus" | "grid-2x2-x" | "grid-3-x-3" | "grid-3x2" | "grid-3x3" | "grip" | "grip-horizontal" | "grip-vertical" | "group" | "guitar" | "ham" | "hamburger" | "hammer" | "hand" | "hand-coins" | "hand-fist" | "hand-grab" | "hand-heart" | "hand-helping" | "hand-metal" | "hand-platter" | "handbag" | "handshake" | "hard-drive" | "hard-drive-download" | "hard-drive-upload" | "hard-hat" | "hash" | "hat-glasses" | "haze" | "hd" | "hdmi-port" | "heading" | "heading-1" | "heading-2" | "heading-3" | "heading-4" | "heading-5" | "heading-6" | "headphone-off" | "headphones" | "headset" | "heart" | "heart-crack" | "heart-handshake" | "heart-minus" | "heart-off" | "heart-plus" | "heart-pulse" | "heart-x" | "heater" | "helicopter" | "help-circle" | "helping-hand" | "hexagon" | "highlighter" | "history" | "home" | "hop" | "hop-off" | "hospital" | "hotel" | "hourglass" | "house" | "house-heart" | "house-plug" | "house-plus" | "house-wifi" | "ice-cream" | "ice-cream-2" | "ice-cream-bowl" | "ice-cream-cone" | "id-card" | "id-card-lanyard" | "image" | "image-down" | "image-minus" | "image-off" | "image-play" | "image-plus" | "image-up" | "image-upscale" | "images" | "import" | "inbox" | "indent" | "indent-decrease" | "indent-increase" | "indian-rupee" | "infinity" | "info" | "inspect" | "inspection-panel" | "italic" | "iteration-ccw" | "iteration-cw" | "japanese-yen" | "joystick" | "kanban" | "kanban-square" | "kanban-square-dashed" | "kayak" | "key" | "key-round" | "key-square" | "keyboard" | "keyboard-music" | "keyboard-off" | "lamp" | "lamp-ceiling" | "lamp-desk" | "lamp-floor" | "lamp-wall-down" | "lamp-wall-up" | "land-plot" | "landmark" | "languages" | "laptop" | "laptop-2" | "laptop-minimal" | "laptop-minimal-check" | "lasso" | "lasso-select" | "laugh" | "layer-arrow-down" | "layer-arrow-up" | "layers" | "layers-2" | "layers-3" | "layers-arrow-down" | "layers-arrow-up" | "layers-minus" | "layers-plus" | "layout" | "layout-dashboard" | "layout-freeform" | "layout-grid" | "layout-list" | "layout-panel-left" | "layout-panel-top" | "layout-template" | "leaf" | "leafy-green" | "lectern" | "lens-concave" | "lens-convex" | "letter-text" | "library" | "library-big" | "library-square" | "life-buoy" | "ligature" | "lightbulb" | "lightbulb-off" | "line-chart" | "line-dot-right-horizontal" | "line-squiggle" | "line-style" | "link" | "link-2" | "link-2-off" | "list" | "list-check" | "list-checks" | "list-chevrons-down-up" | "list-chevrons-up-down" | "list-clock" | "list-collapse" | "list-end" | "list-filter" | "list-filter-plus" | "list-indent-decrease" | "list-indent-increase" | "list-minus" | "list-music" | "list-ordered" | "list-plus" | "list-restart" | "list-sort-ascending" | "list-sort-descending" | "list-start" | "list-todo" | "list-tree" | "list-video" | "list-x" | "loader" | "loader-2" | "loader-circle" | "loader-pinwheel" | "locate" | "locate-fixed" | "locate-off" | "location-edit" | "lock" | "lock-keyhole" | "lock-keyhole-open" | "lock-open" | "log-in" | "log-out" | "logs" | "lollipop" | "luggage" | "m-square" | "magnet" | "mail" | "mail-badge" | "mail-check" | "mail-minus" | "mail-open" | "mail-plus" | "mail-question" | "mail-question-mark" | "mail-search" | "mail-warning" | "mail-x" | "mailbox" | "mails" | "map" | "map-minus" | "map-pin" | "map-pin-check" | "map-pin-check-inside" | "map-pin-house" | "map-pin-minus" | "map-pin-minus-inside" | "map-pin-off" | "map-pin-pen" | "map-pin-plus" | "map-pin-plus-inside" | "map-pin-search" | "map-pin-x" | "map-pin-x-inside" | "map-pinned" | "map-plus" | "mars" | "mars-stroke" | "martini" | "maximize" | "maximize-2" | "medal" | "megaphone" | "megaphone-off" | "meh" | "memory-stick" | "menu" | "menu-square" | "merge" | "message-circle" | "message-circle-check" | "message-circle-code" | "message-circle-dashed" | "message-circle-heart" | "message-circle-more" | "message-circle-off" | "message-circle-plus" | "message-circle-question" | "message-circle-question-mark" | "message-circle-reply" | "message-circle-warning" | "message-circle-x" | "message-square" | "message-square-check" | "message-square-code" | "message-square-dashed" | "message-square-diff" | "message-square-dot" | "message-square-heart" | "message-square-lock" | "message-square-more" | "message-square-off" | "message-square-plus" | "message-square-quote" | "message-square-reply" | "message-square-share" | "message-square-text" | "message-square-warning" | "message-square-x" | "messages-square" | "metronome" | "mic" | "mic-2" | "mic-audio-lines" | "mic-off" | "mic-signal" | "mic-vocal" | "microchip" | "microscope" | "microwave" | "midi-port" | "milestone" | "milk" | "milk-off" | "minimize" | "minimize-2" | "minus" | "minus-circle" | "minus-square" | "mirror-rectangular" | "mirror-round" | "monitor" | "monitor-check" | "monitor-cloud" | "monitor-cog" | "monitor-dot" | "monitor-down" | "monitor-off" | "monitor-pause" | "monitor-play" | "monitor-smartphone" | "monitor-speaker" | "monitor-stop" | "monitor-up" | "monitor-x" | "moon" | "moon-star" | "mop" | "mop-sparkles" | "more-horizontal" | "more-vertical" | "mosque" | "motorbike" | "mountain" | "mountain-snow" | "mouse" | "mouse-left" | "mouse-off" | "mouse-pointer" | "mouse-pointer-2" | "mouse-pointer-2-off" | "mouse-pointer-ban" | "mouse-pointer-click" | "mouse-pointer-square-dashed" | "mouse-right" | "move" | "move-3-d" | "move-3d" | "move-diagonal" | "move-diagonal-2" | "move-down" | "move-down-left" | "move-down-right" | "move-horizontal" | "move-left" | "move-right" | "move-up" | "move-up-left" | "move-up-right" | "move-vertical" | "music" | "music-2" | "music-3" | "music-4" | "navigation" | "navigation-2" | "navigation-2-off" | "navigation-off" | "network" | "newspaper" | "nfc" | "non-binary" | "notebook" | "notebook-pen" | "notebook-tabs" | "notebook-text" | "notepad-text" | "notepad-text-dashed" | "nut" | "nut-off" | "octagon" | "octagon-alert" | "octagon-minus" | "octagon-pause" | "octagon-x" | "omega" | "option" | "orbit" | "origami" | "outdent" | "package" | "package-2" | "package-check" | "package-minus" | "package-open" | "package-plus" | "package-search" | "package-x" | "paint-bucket" | "paint-roller" | "paintbrush" | "paintbrush-2" | "paintbrush-vertical" | "palette" | "palmtree" | "panda" | "panel-bottom" | "panel-bottom-close" | "panel-bottom-dashed" | "panel-bottom-inactive" | "panel-bottom-open" | "panel-left" | "panel-left-close" | "panel-left-dashed" | "panel-left-inactive" | "panel-left-open" | "panel-left-right-dashed" | "panel-right" | "panel-right-close" | "panel-right-dashed" | "panel-right-inactive" | "panel-right-open" | "panel-top" | "panel-top-bottom-dashed" | "panel-top-close" | "panel-top-dashed" | "panel-top-inactive" | "panel-top-open" | "panels-left-bottom" | "panels-left-right" | "panels-right-bottom" | "panels-top-bottom" | "panels-top-left" | "paper-bag" | "paperclip" | "parasol" | "parentheses" | "parking-circle" | "parking-circle-off" | "parking-meter" | "parking-square" | "parking-square-off" | "party-popper" | "pause" | "pause-circle" | "pause-octagon" | "paw-print" | "pc-case" | "pen" | "pen-box" | "pen-line" | "pen-off" | "pen-square" | "pen-tool" | "pencil" | "pencil-line" | "pencil-off" | "pencil-ruler" | "pencil-sparkles" | "pentagon" | "percent" | "percent-circle" | "percent-diamond" | "percent-square" | "person-standing" | "phi" | "philippine-peso" | "phone" | "phone-call" | "phone-forwarded" | "phone-incoming" | "phone-missed" | "phone-off" | "phone-outgoing" | "pi" | "pi-square" | "piano" | "pickaxe" | "picture-in-picture" | "picture-in-picture-2" | "pie-chart" | "piggy-bank" | "pilcrow" | "pilcrow-left" | "pilcrow-right" | "pilcrow-square" | "pill" | "pill-bottle" | "pin" | "pin-off" | "pipette" | "pizza" | "plane" | "plane-landing" | "plane-takeoff" | "play" | "play-circle" | "play-off" | "play-square" | "plug" | "plug-2" | "plug-zap" | "plug-zap-2" | "plus" | "plus-circle" | "plus-square" | "pocket-knife" | "podcast" | "podium" | "pointer" | "pointer-off" | "popcorn" | "popsicle" | "pound-sterling" | "power" | "power-circle" | "power-off" | "power-square" | "presentation" | "printer" | "printer-check" | "printer-x" | "projector" | "proportions" | "puzzle" | "pyramid" | "qr-code" | "quote" | "rabbit" | "radar" | "radiation" | "radical" | "radio" | "radio-off" | "radio-receiver" | "radio-tower" | "radius" | "rainbow" | "rat" | "ratio" | "receipt" | "receipt-cent" | "receipt-euro" | "receipt-indian-rupee" | "receipt-japanese-yen" | "receipt-pound-sterling" | "receipt-russian-ruble" | "receipt-swiss-franc" | "receipt-text" | "receipt-turkish-lira" | "rectangle-circle" | "rectangle-ellipsis" | "rectangle-goggles" | "rectangle-horizontal" | "rectangle-vertical" | "recycle" | "redo" | "redo-2" | "redo-dot" | "refresh-ccw" | "refresh-ccw-dot" | "refresh-cw" | "refresh-cw-off" | "refrigerator" | "regex" | "remove-formatting" | "repeat" | "repeat-1" | "repeat-2" | "repeat-off" | "replace" | "replace-all" | "reply" | "reply-all" | "rewind" | "ribbon" | "road" | "rocket" | "rocking-chair" | "roller-coaster" | "rose" | "rotate-3-d" | "rotate-3d" | "rotate-ccw" | "rotate-ccw-clock" | "rotate-ccw-key" | "rotate-ccw-square" | "rotate-cw" | "rotate-cw-fading-clock" | "rotate-cw-square" | "route" | "route-off" | "router" | "rows" | "rows-2" | "rows-3" | "rows-4" | "rss" | "ruler" | "ruler-dimension-line" | "russian-ruble" | "sailboat" | "salad" | "sandwich" | "satellite" | "satellite-dish" | "saudi-riyal" | "save" | "save-all" | "save-check" | "save-off" | "save-pen" | "save-plus" | "scale" | "scale-3-d" | "scale-3d" | "scaling" | "scan" | "scan-barcode" | "scan-box" | "scan-eye" | "scan-face" | "scan-heart" | "scan-line" | "scan-qr-code" | "scan-search" | "scan-square" | "scan-text" | "scatter-chart" | "school" | "school-2" | "scissors" | "scissors-line-dashed" | "scissors-square" | "scissors-square-dashed-bottom" | "scooter" | "screen-share" | "screen-share-off" | "scroll" | "scroll-text" | "search" | "search-alert" | "search-check" | "search-code" | "search-slash" | "search-x" | "section" | "send" | "send-horizonal" | "send-horizontal" | "send-to-back" | "separator-horizontal" | "separator-vertical" | "server" | "server-cog" | "server-crash" | "server-off" | "server-plus" | "settings" | "settings-2" | "shapes" | "share" | "share-2" | "sheet" | "shell" | "shelving-unit" | "shield" | "shield-alert" | "shield-ban" | "shield-check" | "shield-close" | "shield-cog" | "shield-cog-corner" | "shield-ellipsis" | "shield-half" | "shield-keyhole" | "shield-lock" | "shield-minus" | "shield-off" | "shield-plus" | "shield-question" | "shield-question-mark" | "shield-user" | "shield-x" | "ship" | "ship-wheel" | "shirt" | "shopping-bag" | "shopping-basket" | "shopping-cart" | "shovel" | "shower-head" | "shredder" | "shrimp" | "shrink" | "shrub" | "shuffle" | "sidebar" | "sidebar-close" | "sidebar-open" | "sigma" | "sigma-square" | "signal" | "signal-high" | "signal-low" | "signal-medium" | "signal-zero" | "signature" | "signpost" | "signpost-big" | "siren" | "skip-back" | "skip-forward" | "skull" | "slash" | "slash-square" | "slice" | "sliders" | "sliders-horizontal" | "sliders-vertical" | "smartphone" | "smartphone-charging" | "smartphone-nfc" | "smile" | "smile-plus" | "snail" | "snowflake" | "soap-dispenser-droplet" | "sofa" | "solar-panel" | "sort-asc" | "sort-desc" | "soup" | "space" | "spade" | "sparkle" | "sparkles" | "speaker" | "speech" | "spell-check" | "spell-check-2" | "spline" | "spline-pointer" | "split" | "split-square-horizontal" | "split-square-vertical" | "spool" | "sport-shoe" | "spotlight" | "spray-can" | "sprout" | "square" | "square-activity" | "square-arrow-down" | "square-arrow-down-left" | "square-arrow-down-right" | "square-arrow-left" | "square-arrow-out-down-left" | "square-arrow-out-down-right" | "square-arrow-out-up-left" | "square-arrow-out-up-right" | "square-arrow-right" | "square-arrow-right-enter" | "square-arrow-right-exit" | "square-arrow-up" | "square-arrow-up-left" | "square-arrow-up-right" | "square-asterisk" | "square-bottom-dashed-scissors" | "square-centerline-dashed-horizontal" | "square-centerline-dashed-vertical" | "square-chart-gantt" | "square-check" | "square-check-big" | "square-chevron-down" | "square-chevron-left" | "square-chevron-right" | "square-chevron-up" | "square-code" | "square-dashed" | "square-dashed-bottom" | "square-dashed-bottom-code" | "square-dashed-kanban" | "square-dashed-mouse-pointer" | "square-dashed-text" | "square-dashed-top-solid" | "square-dimensions" | "square-divide" | "square-dot" | "square-equal" | "square-function" | "square-gantt-chart" | "square-kanban" | "square-library" | "square-m" | "square-menu" | "square-minus" | "square-mouse-pointer" | "square-off" | "square-parking" | "square-parking-off" | "square-pause" | "square-pen" | "square-percent" | "square-pi" | "square-pilcrow" | "square-play" | "square-plus" | "square-power" | "square-radical" | "square-round-corner" | "square-scissors" | "square-sigma" | "square-slash" | "square-split-horizontal" | "square-split-vertical" | "square-square" | "square-stack" | "square-star" | "square-stop" | "square-terminal" | "square-text" | "square-user" | "square-user-round" | "square-x" | "squares-exclude" | "squares-intersect" | "squares-subtract" | "squares-unite" | "squircle" | "squircle-dashed" | "squirrel" | "stamp" | "star" | "star-check" | "star-half" | "star-minus" | "star-off" | "star-plus" | "star-x" | "stars" | "step-back" | "step-forward" | "stethoscope" | "sticker" | "sticky-note" | "sticky-note-check" | "sticky-note-minus" | "sticky-note-off" | "sticky-note-plus" | "sticky-note-x" | "sticky-notes" | "stone" | "stop-circle" | "store" | "stretch-horizontal" | "stretch-vertical" | "strikethrough" | "subscript" | "subtitles" | "summary" | "sun" | "sun-dim" | "sun-medium" | "sun-moon" | "sun-snow" | "sunrise" | "sunset" | "superscript" | "swatch-book" | "swiss-franc" | "switch-camera" | "sword" | "swords" | "syringe" | "table" | "table-2" | "table-cells-merge" | "table-cells-split" | "table-columns-split" | "table-config" | "table-of-contents" | "table-properties" | "table-rows-split" | "tablet" | "tablet-smartphone" | "tablets" | "tag" | "tag-plus" | "tag-x" | "tags" | "tally-1" | "tally-2" | "tally-3" | "tally-4" | "tally-5" | "tangent" | "target" | "telescope" | "tent" | "tent-tree" | "terminal" | "terminal-square" | "test-tube" | "test-tube-2" | "test-tube-diagonal" | "test-tubes" | "text" | "text-align-center" | "text-align-end" | "text-align-justify" | "text-align-start" | "text-cursor" | "text-cursor-input" | "text-initial" | "text-quote" | "text-search" | "text-select" | "text-selection" | "text-wrap" | "theater" | "thermometer" | "thermometer-snowflake" | "thermometer-sun" | "thumbs-down" | "thumbs-up" | "ticket" | "ticket-check" | "ticket-minus" | "ticket-percent" | "ticket-plus" | "ticket-slash" | "ticket-x" | "tickets" | "tickets-plane" | "timeline" | "timer" | "timer-off" | "timer-reset" | "toggle-left" | "toggle-right" | "toilet" | "tool-case" | "toolbox" | "tornado" | "torus" | "touchpad" | "touchpad-off" | "towel-rack" | "tower-control" | "toy-brick" | "tractor" | "traffic-cone" | "train" | "train-front" | "train-front-tunnel" | "train-track" | "tram-front" | "transgender" | "trash" | "trash-2" | "tree-deciduous" | "tree-palm" | "tree-pine" | "trees" | "trending-down" | "trending-up" | "trending-up-down" | "triangle" | "triangle-alert" | "triangle-dashed" | "triangle-right" | "trophy" | "truck" | "truck-electric" | "turkish-lira" | "turntable" | "turtle" | "tv" | "tv-2" | "tv-minimal" | "tv-minimal-play" | "type" | "type-outline" | "umbrella" | "umbrella-off" | "underline" | "undo" | "undo-2" | "undo-dot" | "unfold-horizontal" | "unfold-vertical" | "ungroup" | "university" | "unlink" | "unlink-2" | "unlock" | "unlock-keyhole" | "unplug" | "upload" | "upload-cloud" | "usb" | "usb-c-port" | "user" | "user-2" | "user-check" | "user-check-2" | "user-circle" | "user-circle-2" | "user-cog" | "user-cog-2" | "user-key" | "user-lock" | "user-minus" | "user-minus-2" | "user-pen" | "user-plus" | "user-plus-2" | "user-round" | "user-round-arrow-left" | "user-round-check" | "user-round-cog" | "user-round-key" | "user-round-minus" | "user-round-pen" | "user-round-plus" | "user-round-search" | "user-round-x" | "user-search" | "user-shield" | "user-square" | "user-square-2" | "user-star" | "user-x" | "user-x-2" | "users" | "users-2" | "users-round" | "utensils" | "utensils-crossed" | "utility-pole" | "van" | "variable" | "vault" | "vector-square" | "vegan" | "venetian-mask" | "venus" | "venus-and-mars" | "verified" | "vibrate" | "vibrate-off" | "video" | "video-off" | "videotape" | "view" | "voicemail" | "volleyball" | "volume" | "volume-1" | "volume-2" | "volume-off" | "volume-x" | "vote" | "wallet" | "wallet-2" | "wallet-cards" | "wallet-minimal" | "wallpaper" | "wand" | "wand-2" | "wand-sparkles" | "warehouse" | "washing-machine" | "watch" | "waves" | "waves-arrow-down" | "waves-arrow-up" | "waves-horizontal" | "waves-ladder" | "waves-vertical" | "waypoints" | "webcam" | "webcam-off" | "webhook" | "webhook-off" | "weight" | "weight-tilde" | "wheat" | "wheat-off" | "whole-word" | "wifi" | "wifi-cog" | "wifi-high" | "wifi-low" | "wifi-off" | "wifi-pen" | "wifi-sync" | "wifi-zero" | "wind" | "wind-arrow-down" | "wine" | "wine-off" | "workflow" | "worm" | "wrap-text" | "wrench" | "wrench-off" | "x" | "x-circle" | "x-line-top" | "x-octagon" | "x-square" | "zap" | "zap-off" | "zodiac-aquarius" | "zodiac-aries" | "zodiac-cancer" | "zodiac-capricorn" | "zodiac-gemini" | "zodiac-leo" | "zodiac-libra" | "zodiac-ophiuchus" | "zodiac-pisces" | "zodiac-sagittarius" | "zodiac-scorpio" | "zodiac-taurus" | "zodiac-virgo" | "zoom-in" | "zoom-out";
|
|
89
91
|
anchor?: string;
|
|
90
92
|
disabled?: boolean;
|
|
93
|
+
appearance?: "default" | "selection" | "revision";
|
|
91
94
|
children?: ReactNode;
|
|
92
95
|
}
|
|
93
96
|
export const Popover: ComponentType<PopoverProps>;
|
|
@@ -274,6 +277,8 @@ export interface TabsContentProps {
|
|
|
274
277
|
children?: ReactNode;
|
|
275
278
|
}
|
|
276
279
|
export const TabsContent: ComponentType<TabsContentProps>;
|
|
280
|
+
export type { DataTableProps, DataTableColumn, TableState, TableSource, CellContext, ListItemContext, TableScalar } from "./data-table.js";
|
|
281
|
+
export const DataTable: import("./data-table.js").DataTableComponent;
|
|
277
282
|
export interface TableProps {
|
|
278
283
|
label?: string;
|
|
279
284
|
caption?: string;
|
package/src/ui/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// 此文件由 scripts/generate-components.mjs 生成,请修改产品能力声明。
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { createUiComponents } from "./factory.js";
|
|
4
|
-
export const { useConversation, Conversation, ConversationRoot, ConversationMessages, ConversationComposer, ConversationHistory, ConversationNewButton, ConversationStopButton, ConversationStatus, ScrollViewport, TextSelection, Popover, NativeDOM, Icon, Stack, SplitLayout, SplitLayoutMain, SplitLayoutAside, SplitLayoutActions, Section, Banner, Badge, LoadingState, Skeleton, Card, Grid, EmptyState, ZhikeLink, Button, SearchInput, FilterSelect, Slider, Switch, LookupCell, Tabs, TabsList, TabsTrigger, TabsContent, Table, TableHeader, TableBody, TableRow, TableCell, Avatar, DescriptionList, DescriptionListItem, DescriptionListMenu, DescriptionListMenuItem, Divider, Chart, DashboardSurface, DashboardLayout, DashboardPanel, MetricReadout, AnalysisView, Json, Link, StatusBadge, TextBlock, TypographyTitle, TypographyBody, TypographyCaption, TableHeaderCell, WidgetTextWidget, WidgetTitle, WidgetTextPrimary, WidgetTextSecondary, WidgetLoading, WidgetDecoration, Typography, Widget } = createUiComponents(React);
|
|
4
|
+
export const { useConversation, Conversation, ConversationRoot, ConversationMessages, ConversationComposer, ConversationHistory, ConversationNewButton, ConversationStopButton, ConversationStatus, ScrollViewport, TextSelection, Popover, NativeDOM, Icon, Stack, SplitLayout, SplitLayoutMain, SplitLayoutAside, SplitLayoutActions, Section, Banner, Badge, LoadingState, Skeleton, Card, Grid, EmptyState, ZhikeLink, Button, SearchInput, FilterSelect, Slider, Switch, LookupCell, Tabs, TabsList, TabsTrigger, TabsContent, DataTable, Table, TableHeader, TableBody, TableRow, TableCell, Avatar, DescriptionList, DescriptionListItem, DescriptionListMenu, DescriptionListMenuItem, Divider, Chart, DashboardSurface, DashboardLayout, DashboardPanel, MetricReadout, AnalysisView, Json, Link, StatusBadge, TextBlock, TypographyTitle, TypographyBody, TypographyCaption, TableHeaderCell, WidgetTextWidget, WidgetTitle, WidgetTextPrimary, WidgetTextSecondary, WidgetLoading, WidgetDecoration, Typography, Widget } = createUiComponents(React);
|