@shwfed/config 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/components/form/ai/fields-button.d.vue.ts +13 -0
- package/dist/runtime/components/form/ai/fields-button.vue +458 -0
- package/dist/runtime/components/form/ai/fields-button.vue.d.ts +13 -0
- package/dist/runtime/components/form/ai/fields-task.md +71 -0
- package/dist/runtime/components/form/config.d.vue.ts +1 -1
- package/dist/runtime/components/form/config.vue +4 -36
- package/dist/runtime/components/form/config.vue.d.ts +1 -1
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.d.vue.ts +18 -18
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue.d.ts +18 -18
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetime/config.d.vue.ts +4 -4
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetime/config.vue.d.ts +4 -4
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.d.vue.ts +22 -22
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue.d.ts +22 -22
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.time/config.d.vue.ts +2 -2
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.time/config.vue.d.ts +2 -2
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.d.vue.ts +4 -4
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.vue.d.ts +4 -4
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.d.vue.ts +12 -12
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue +87 -11
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue.d.ts +12 -12
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/runtime.vue +18 -6
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.d.ts +1 -1
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.js +11 -4
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.switch/config.d.vue.ts +10 -10
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.switch/config.vue.d.ts +10 -10
- package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.d.vue.ts +12 -12
- package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.vue.d.ts +12 -12
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.d.vue.ts +16 -2
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue +37 -0
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue.d.ts +16 -2
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/row.d.vue.ts +1 -0
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/row.vue +13 -4
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/row.vue.d.ts +1 -0
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/runtime.vue +111 -22
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.d.ts +15 -1
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.js +9 -1
- package/dist/runtime/components/form/index.vue +5 -4
- package/dist/runtime/components/form/unit-config.d.vue.ts +16 -0
- package/dist/runtime/components/form/unit-config.vue +30 -3
- package/dist/runtime/components/form/unit-config.vue.d.ts +16 -0
- package/dist/runtime/components/form/utils/cel-scope.d.ts +13 -0
- package/dist/runtime/components/form/utils/cel-scope.js +32 -0
- package/dist/runtime/components/form/utils/schema-meta.d.ts +13 -0
- package/dist/runtime/components/form/utils/schema-meta.js +15 -0
- package/dist/runtime/components/table/ai/columns-task.md +10 -1
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.markdown/config.vue +2 -2
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.markdown/runtime.vue +14 -4
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.markdown/schema.js +3 -2
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.text/config.vue +2 -2
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.text/runtime.vue +14 -4
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.text/schema.js +3 -2
- package/dist/runtime/components/table/utils/shared.d.ts +2 -1
- package/dist/runtime/components/ui/date-range-picker/DateRangePickerDateTimePanel.d.vue.ts +1 -1
- package/dist/runtime/components/ui/date-range-picker/DateRangePickerDateTimePanel.vue.d.ts +1 -1
- package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.d.vue.ts +1 -1
- package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.vue.d.ts +1 -1
- package/dist/runtime/share/expression.d.ts +1 -2
- package/dist/runtime/share/slot-renderer.vue +7 -6
- package/dist/runtime/utils/interpolate.d.ts +1 -0
- package/dist/runtime/utils/interpolate.js +7 -0
- package/dist/runtime/vendor/cel-js/CLAUDE.md +1 -1
- package/dist/runtime/vendor/cel-js/PROMPT.md +6 -1
- package/dist/runtime/vendor/cel-js/lib/http-builder.d.ts +3 -2
- package/dist/runtime/vendor/cel-js/lib/http-builtins.d.ts +4 -3
- package/dist/runtime/vendor/cel-js/lib/http-builtins.js +4 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Environment } from '../../../vendor/cel-js/lib/index.js';
|
|
2
|
+
type AnyRecord = Record<string, any>;
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
configure?: (env: Environment) => void;
|
|
5
|
+
fields?: ReadonlyArray<AnyRecord>;
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
8
|
+
apply: (value: AnyRecord[]) => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
onApply?: ((value: AnyRecord[]) => any) | undefined;
|
|
11
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
declare const _default: typeof __VLS_export;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Icon } from "@iconify/vue";
|
|
3
|
+
import { Either, ParseResult, Schema } from "effect";
|
|
4
|
+
import { computed, ref } from "vue";
|
|
5
|
+
import { toast } from "vue-sonner";
|
|
6
|
+
import ShwfedModal from "../../modal.vue";
|
|
7
|
+
import { Button } from "../../ui/button";
|
|
8
|
+
import { Markdown } from "../../ui/markdown";
|
|
9
|
+
import { Textarea } from "../../ui/textarea";
|
|
10
|
+
import { composeCelPrompt } from "../../../utils/ai/cel-prompt";
|
|
11
|
+
import { StructuredOutputDecodeError, ai as $ai } from "../../../utils/ai";
|
|
12
|
+
import { registerFormVariablesIfAbsent } from "../schema";
|
|
13
|
+
import { listStructFields } from "../utils/schema-meta";
|
|
14
|
+
import { buildFormCelScope } from "../utils/cel-scope";
|
|
15
|
+
import { FIELDS, findField } from "../utils/resolve";
|
|
16
|
+
import TASK from "./fields-task.md?raw";
|
|
17
|
+
defineOptions({ name: "ShwfedFormAiFieldsButton" });
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
configure: { type: Function, required: false },
|
|
20
|
+
fields: { type: Array, required: false }
|
|
21
|
+
});
|
|
22
|
+
const emit = defineEmits(["apply"]);
|
|
23
|
+
const SUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
24
|
+
"com.shwfed.form.field.text",
|
|
25
|
+
"com.shwfed.form.field.textarea",
|
|
26
|
+
"com.shwfed.form.field.number",
|
|
27
|
+
"com.shwfed.form.field.numberrange",
|
|
28
|
+
"com.shwfed.form.field.date",
|
|
29
|
+
"com.shwfed.form.field.daterange",
|
|
30
|
+
"com.shwfed.form.field.datetime",
|
|
31
|
+
"com.shwfed.form.field.datetimerange",
|
|
32
|
+
"com.shwfed.form.field.time",
|
|
33
|
+
"com.shwfed.form.field.timerange",
|
|
34
|
+
"com.shwfed.form.field.switch",
|
|
35
|
+
"com.shwfed.form.field.combobox.single",
|
|
36
|
+
"com.shwfed.form.field.checkbox.group",
|
|
37
|
+
"com.shwfed.form.field.radio.group",
|
|
38
|
+
"com.shwfed.form.field.upload"
|
|
39
|
+
]);
|
|
40
|
+
const NOOP_CONFIGURE = () => {
|
|
41
|
+
};
|
|
42
|
+
const LocaleEntry = Schema.Struct({
|
|
43
|
+
locale: Schema.String,
|
|
44
|
+
message: Schema.String
|
|
45
|
+
}).annotations({ identifier: "LocaleEntry" });
|
|
46
|
+
const LocaleArray = Schema.Array(LocaleEntry);
|
|
47
|
+
const ItemDraft = Schema.Struct({
|
|
48
|
+
id: Schema.optional(Schema.String),
|
|
49
|
+
label: Schema.optional(LocaleArray),
|
|
50
|
+
value: Schema.optional(Schema.Struct({
|
|
51
|
+
kind: Schema.String,
|
|
52
|
+
value: Schema.Union(Schema.String, Schema.Number)
|
|
53
|
+
})),
|
|
54
|
+
tooltip: Schema.optional(LocaleArray)
|
|
55
|
+
});
|
|
56
|
+
const PresetDraft = Schema.Struct({
|
|
57
|
+
id: Schema.optional(Schema.String),
|
|
58
|
+
label: Schema.optional(LocaleArray),
|
|
59
|
+
value: Schema.optional(Schema.String),
|
|
60
|
+
start: Schema.optional(Schema.String),
|
|
61
|
+
end: Schema.optional(Schema.String)
|
|
62
|
+
});
|
|
63
|
+
const FieldDraft = Schema.Struct({
|
|
64
|
+
id: Schema.optional(Schema.String).annotations({
|
|
65
|
+
description: "Stable field id. Call the `gen_uuid` tool to obtain it \u2014 never invent UUIDs."
|
|
66
|
+
}),
|
|
67
|
+
type: Schema.String.annotations({
|
|
68
|
+
description: 'Field type \u2014 must match a `type` listed under "Available field types".'
|
|
69
|
+
}),
|
|
70
|
+
compatibilityDate: Schema.String.annotations({
|
|
71
|
+
description: "Compatibility date for the field type \u2014 copy verbatim from its heading."
|
|
72
|
+
}),
|
|
73
|
+
displayName: Schema.optional(Schema.String),
|
|
74
|
+
hidden: Schema.optional(Schema.String),
|
|
75
|
+
label: Schema.optional(LocaleArray),
|
|
76
|
+
placeholder: Schema.optional(LocaleArray),
|
|
77
|
+
startPlaceholder: Schema.optional(LocaleArray),
|
|
78
|
+
endPlaceholder: Schema.optional(LocaleArray),
|
|
79
|
+
tooltip: Schema.optional(LocaleArray),
|
|
80
|
+
description: Schema.optional(LocaleArray),
|
|
81
|
+
trueLabel: Schema.optional(LocaleArray),
|
|
82
|
+
falseLabel: Schema.optional(LocaleArray),
|
|
83
|
+
templateLabel: Schema.optional(LocaleArray),
|
|
84
|
+
orientation: Schema.optional(Schema.String),
|
|
85
|
+
binding: Schema.optional(Schema.String),
|
|
86
|
+
disabled: Schema.optional(Schema.String),
|
|
87
|
+
readonly: Schema.optional(Schema.String),
|
|
88
|
+
derived: Schema.optional(Schema.Struct({
|
|
89
|
+
mode: Schema.optional(Schema.String),
|
|
90
|
+
expression: Schema.optional(Schema.String)
|
|
91
|
+
})),
|
|
92
|
+
precision: Schema.optional(Schema.Number),
|
|
93
|
+
roundingMode: Schema.optional(Schema.String),
|
|
94
|
+
step: Schema.optional(Schema.Number),
|
|
95
|
+
min: Schema.optional(Schema.String),
|
|
96
|
+
max: Schema.optional(Schema.String),
|
|
97
|
+
rows: Schema.optional(Schema.Number),
|
|
98
|
+
format: Schema.optional(Schema.String),
|
|
99
|
+
valueFormat: Schema.optional(Schema.String),
|
|
100
|
+
presets: Schema.optional(Schema.Array(PresetDraft)),
|
|
101
|
+
rangeSeparatorIcon: Schema.optional(Schema.String),
|
|
102
|
+
numberOfMonths: Schema.optional(Schema.Number),
|
|
103
|
+
items: Schema.optional(Schema.Array(ItemDraft)),
|
|
104
|
+
multiple: Schema.optional(Schema.Boolean),
|
|
105
|
+
accept: Schema.optional(Schema.Array(Schema.String)),
|
|
106
|
+
maxFileSize: Schema.optional(Schema.Number),
|
|
107
|
+
maxTotalSize: Schema.optional(Schema.Number),
|
|
108
|
+
maxFiles: Schema.optional(Schema.Number),
|
|
109
|
+
template: Schema.optional(Schema.String),
|
|
110
|
+
templateIcon: Schema.optional(Schema.String)
|
|
111
|
+
});
|
|
112
|
+
const FieldsDraft = Schema.Struct({
|
|
113
|
+
fields: Schema.Array(FieldDraft).annotations({
|
|
114
|
+
description: "The NEW fields to append to the form. Emit only the fields the user asked to add \u2014 never echo existing fields."
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
function effectiveConfigure(env) {
|
|
118
|
+
registerFormVariablesIfAbsent(env);
|
|
119
|
+
props.configure?.(env);
|
|
120
|
+
}
|
|
121
|
+
function renderRegisterables() {
|
|
122
|
+
const byType = /* @__PURE__ */ new Map();
|
|
123
|
+
for (const f of FIELDS) {
|
|
124
|
+
if (!SUPPORTED_TYPES.has(f.type)) continue;
|
|
125
|
+
const prev = byType.get(f.type);
|
|
126
|
+
if (!prev || f.compatibilityDate > prev.compatibilityDate) byType.set(f.type, f);
|
|
127
|
+
}
|
|
128
|
+
const entries = [...byType.values()].sort((a, b) => a.type.localeCompare(b.type));
|
|
129
|
+
const lines = ["# Available field types", ""];
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
lines.push(`## \`${entry.type}\` (compatibilityDate \`${entry.compatibilityDate}\`)`);
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push(`Renders: ${entry.metadata.name}.`);
|
|
134
|
+
lines.push("");
|
|
135
|
+
for (const info of listStructFields(entry.schema(NOOP_CONFIGURE))) {
|
|
136
|
+
if (info.name === "type" || info.name === "compatibilityDate") continue;
|
|
137
|
+
const req = info.optional ? "" : " (required)";
|
|
138
|
+
const title = info.title ? ` \u2014 ${info.title}` : "";
|
|
139
|
+
const desc = info.description ? `: ${info.description}` : "";
|
|
140
|
+
lines.push(`- \`${info.name}\`${req}${title}${desc}`);
|
|
141
|
+
}
|
|
142
|
+
lines.push("");
|
|
143
|
+
}
|
|
144
|
+
return lines.join("\n");
|
|
145
|
+
}
|
|
146
|
+
const open = ref(false);
|
|
147
|
+
const context = ref("");
|
|
148
|
+
const submitting = ref(false);
|
|
149
|
+
const tick = ref(0);
|
|
150
|
+
const description = [
|
|
151
|
+
"\u63CF\u8FF0\u4F60\u60F3**\u65B0\u589E\u7684\u5B57\u6BB5**\uFF08\u7C7B\u578B\u3001\u6807\u7B7E\u3001\u7ED1\u5B9A\u8DEF\u5F84\u7B49\uFF09\uFF0C\u53EF\u9644\u5B57\u6BB5\u542B\u4E49\u6216 PRD\u3002",
|
|
152
|
+
"",
|
|
153
|
+
"AI \u53EA\u4F1A**\u65B0\u589E**\u5B57\u6BB5\uFF0C\u4E0D\u4F1A\u6539\u52A8\u5DF2\u6709\u5B57\u6BB5\u3002\u751F\u6210\u7684\u5B57\u6BB5\u9ED8\u8BA4**\u672A\u653E\u7F6E**\uFF0C\u9700\u5728\u5E03\u5C40\u7F16\u8F91\u5668\u7684\u300C\u672A\u653E\u7F6E\u300D\u4E2D\u624B\u52A8\u62D6\u5165\u3002"
|
|
154
|
+
].join("\n");
|
|
155
|
+
const canSubmit = computed(() => !submitting.value && context.value.trim().length > 0);
|
|
156
|
+
function onTrigger() {
|
|
157
|
+
open.value = true;
|
|
158
|
+
}
|
|
159
|
+
function collectErrors(fields) {
|
|
160
|
+
const out = [];
|
|
161
|
+
fields.forEach((field, i) => {
|
|
162
|
+
const prefix = `fields[${i}]`;
|
|
163
|
+
const type = field.type;
|
|
164
|
+
if (typeof type !== "string" || !SUPPORTED_TYPES.has(type)) {
|
|
165
|
+
out.push({
|
|
166
|
+
path: `${prefix}.type`,
|
|
167
|
+
message: `Field type "${type}" is not allowed. Use one of the types listed under "Available field types".`
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const entry = findField(type, field.compatibilityDate);
|
|
172
|
+
if (!entry) {
|
|
173
|
+
out.push({
|
|
174
|
+
path: `${prefix}.type`,
|
|
175
|
+
message: `Unknown field type "${type}@${field.compatibilityDate}". Copy the compatibilityDate verbatim from the type's heading.`
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (entry.deprecated) {
|
|
180
|
+
out.push({
|
|
181
|
+
path: `${prefix}.compatibilityDate`,
|
|
182
|
+
message: `compatibilityDate "${field.compatibilityDate}" for "${type}" is deprecated. Use "${entry.supersededBy}" instead.`
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const decoded = Schema.decodeUnknownEither(entry.schema(effectiveConfigure))(field);
|
|
187
|
+
if (Either.isLeft(decoded)) {
|
|
188
|
+
const issues = ParseResult.ArrayFormatter.formatErrorSync(decoded.left);
|
|
189
|
+
for (const issue of issues) {
|
|
190
|
+
const sub = issue.path.map(String).join(".");
|
|
191
|
+
out.push({ path: sub ? `${prefix}.${sub}` : prefix, message: issue.message });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
function buildRetryFeedback(errors) {
|
|
198
|
+
const lines = errors.map(({ path, message }) => `- \`${path}\`: ${message}`);
|
|
199
|
+
return [
|
|
200
|
+
"The previous output failed validation. Fix only the listed paths and return the corrected object \u2014 keep the other fields identical.",
|
|
201
|
+
"",
|
|
202
|
+
"Reminders:",
|
|
203
|
+
'- Only emit fields whose `type` is in the "Available field types" list \u2014 never actions / table / list / markdown / remote-dropdown fields.',
|
|
204
|
+
"- Copy each type's `compatibilityDate` verbatim from its heading; never use a deprecated date.",
|
|
205
|
+
"- The `fields` array is APPENDED \u2014 emit only the new fields, never echo existing ones.",
|
|
206
|
+
"",
|
|
207
|
+
"Errors:",
|
|
208
|
+
...lines
|
|
209
|
+
].join("\n");
|
|
210
|
+
}
|
|
211
|
+
function buildShapeFeedback(message) {
|
|
212
|
+
return [
|
|
213
|
+
"The previous output did not match the required JSON shape. Re-emit the structured output matching the schema exactly. The output must be a JSON object with a required `fields` array. Each field needs at minimum `type` and `compatibilityDate`.",
|
|
214
|
+
"",
|
|
215
|
+
"Decoder error:",
|
|
216
|
+
message
|
|
217
|
+
].join("\n");
|
|
218
|
+
}
|
|
219
|
+
function tryUnwrap(raw) {
|
|
220
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
221
|
+
const entries = Object.entries(raw);
|
|
222
|
+
if (entries.length !== 1) return null;
|
|
223
|
+
const [, inner] = entries[0];
|
|
224
|
+
let candidate = inner;
|
|
225
|
+
if (typeof candidate === "string") {
|
|
226
|
+
try {
|
|
227
|
+
candidate = JSON.parse(candidate);
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return null;
|
|
233
|
+
if (!("fields" in candidate)) return null;
|
|
234
|
+
return candidate;
|
|
235
|
+
}
|
|
236
|
+
const genUuidTool = {
|
|
237
|
+
name: "gen_uuid",
|
|
238
|
+
description: "Generate a fresh RFC 4122 UUID string. Call this whenever you need an `id` \u2014 for a field, an option inside `items`, or an entry inside `presets`. Returns the UUID directly as a plain string; use it verbatim. Never invent UUIDs yourself.",
|
|
239
|
+
inputJsonSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
240
|
+
execute: () => crypto.randomUUID()
|
|
241
|
+
};
|
|
242
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
243
|
+
function sanitizeField(draft) {
|
|
244
|
+
const cleaned = {};
|
|
245
|
+
for (const [k, v] of Object.entries(draft)) {
|
|
246
|
+
if (v === void 0) continue;
|
|
247
|
+
if (typeof v === "string" && v.trim() === "") continue;
|
|
248
|
+
cleaned[k] = v;
|
|
249
|
+
}
|
|
250
|
+
const incomingId = typeof cleaned.id === "string" ? cleaned.id : void 0;
|
|
251
|
+
const id = incomingId && UUID_RE.test(incomingId) ? incomingId : crypto.randomUUID();
|
|
252
|
+
const entry = findField(draft.type, draft.compatibilityDate);
|
|
253
|
+
return { ...entry?.defaults?.() ?? {}, ...cleaned, id };
|
|
254
|
+
}
|
|
255
|
+
function evaluate(value) {
|
|
256
|
+
const fields = value.fields.map(sanitizeField);
|
|
257
|
+
const errors = collectErrors(fields);
|
|
258
|
+
if (errors.length > 0) {
|
|
259
|
+
return { kind: "retry", previousOutput: value, feedback: buildRetryFeedback(errors) };
|
|
260
|
+
}
|
|
261
|
+
return { kind: "ok", fields };
|
|
262
|
+
}
|
|
263
|
+
async function callModel(prompt, system, retry) {
|
|
264
|
+
try {
|
|
265
|
+
const value = await $ai.generateObject({
|
|
266
|
+
prompt,
|
|
267
|
+
system,
|
|
268
|
+
schema: FieldsDraft,
|
|
269
|
+
objectName: "fields",
|
|
270
|
+
tools: [genUuidTool],
|
|
271
|
+
...retry ? { retry } : {},
|
|
272
|
+
onProgress: () => {
|
|
273
|
+
tick.value++;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return evaluate(value);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
if (err instanceof StructuredOutputDecodeError) {
|
|
279
|
+
const unwrapped = tryUnwrap(err.rawOutput);
|
|
280
|
+
if (unwrapped !== null) {
|
|
281
|
+
const decoded = Schema.decodeUnknownEither(FieldsDraft)(unwrapped);
|
|
282
|
+
if (Either.isRight(decoded)) return evaluate(decoded.right);
|
|
283
|
+
}
|
|
284
|
+
return { kind: "retry", previousOutput: err.rawOutput, feedback: buildShapeFeedback(err.message) };
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function buildUserPrompt() {
|
|
290
|
+
return [
|
|
291
|
+
context.value.trim(),
|
|
292
|
+
"",
|
|
293
|
+
"# Current configuration",
|
|
294
|
+
"",
|
|
295
|
+
"```json",
|
|
296
|
+
JSON.stringify({ fields: props.fields ?? [] }, null, 2),
|
|
297
|
+
"```"
|
|
298
|
+
].join("\n");
|
|
299
|
+
}
|
|
300
|
+
function salvage(previousOutput) {
|
|
301
|
+
const candidate = previousOutput && typeof previousOutput === "object" ? previousOutput : null;
|
|
302
|
+
const usable = candidate && "fields" in candidate ? candidate : tryUnwrap(previousOutput);
|
|
303
|
+
if (!usable) return [];
|
|
304
|
+
const decoded = Schema.decodeUnknownEither(FieldsDraft)(usable);
|
|
305
|
+
if (Either.isLeft(decoded)) return [];
|
|
306
|
+
return decoded.right.fields.map(sanitizeField).filter((f) => {
|
|
307
|
+
const entry = findField(f.type, f.compatibilityDate);
|
|
308
|
+
return SUPPORTED_TYPES.has(f.type) && !!entry && !entry.deprecated;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
async function onSubmit() {
|
|
312
|
+
if (!canSubmit.value) return;
|
|
313
|
+
submitting.value = true;
|
|
314
|
+
tick.value = 0;
|
|
315
|
+
try {
|
|
316
|
+
const baseSystem = composeCelPrompt({
|
|
317
|
+
scope: [
|
|
318
|
+
{
|
|
319
|
+
name: "\u5B57\u6BB5\u5185 CEL \u8868\u8FBE\u5F0F\uFF08\u9690\u85CF\u6761\u4EF6\u3001\u7981\u7528\u6761\u4EF6\u3001\u53EA\u8BFB\u6761\u4EF6\u3001\u6D3E\u751F\u503C\u3001\u6700\u503C\u7B49\uFF09\u53EF\u7528\u53D8\u91CF",
|
|
320
|
+
context: buildFormCelScope(props.configure ?? NOOP_CONFIGURE)
|
|
321
|
+
}
|
|
322
|
+
],
|
|
323
|
+
task: TASK
|
|
324
|
+
});
|
|
325
|
+
const system = `${baseSystem}
|
|
326
|
+
|
|
327
|
+
${renderRegisterables()}`;
|
|
328
|
+
const prompt = buildUserPrompt();
|
|
329
|
+
let outcome = await callModel(prompt, system);
|
|
330
|
+
if (outcome.kind === "retry") {
|
|
331
|
+
tick.value = 0;
|
|
332
|
+
outcome = await callModel(prompt, system, {
|
|
333
|
+
previousOutput: outcome.previousOutput,
|
|
334
|
+
feedback: outcome.feedback
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (outcome.kind === "ok") {
|
|
338
|
+
if (outcome.fields.length > 0) {
|
|
339
|
+
emit("apply", outcome.fields);
|
|
340
|
+
toast.success("\u5DF2\u6839\u636E\u63CF\u8FF0\u751F\u6210\u5B57\u6BB5");
|
|
341
|
+
} else {
|
|
342
|
+
toast.message("AI \u672A\u8FD4\u56DE\u4EFB\u4F55\u53EF\u65B0\u589E\u7684\u5B57\u6BB5");
|
|
343
|
+
}
|
|
344
|
+
open.value = false;
|
|
345
|
+
context.value = "";
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const salvaged = salvage(outcome.previousOutput);
|
|
349
|
+
if (salvaged.length > 0) {
|
|
350
|
+
emit("apply", salvaged);
|
|
351
|
+
toast.message("AI \u8F93\u51FA\u672A\u5B8C\u5168\u901A\u8FC7\u6821\u9A8C\uFF0C\u5DF2\u5E94\u7528\u5176\u4E2D\u53EF\u7528\u7684\u5B57\u6BB5\uFF0C\u8BF7\u68C0\u67E5");
|
|
352
|
+
open.value = false;
|
|
353
|
+
context.value = "";
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
toast.error("AI \u751F\u6210\u5931\u8D25", { description: "\u6A21\u578B\u672A\u8FD4\u56DE\u53EF\u5E94\u7528\u7684\u5B57\u6BB5\uFF0C\u8BF7\u8C03\u6574\u63CF\u8FF0\u540E\u91CD\u8BD5" });
|
|
357
|
+
} catch (err) {
|
|
358
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
359
|
+
toast.error("AI \u751F\u6210\u5931\u8D25", { description: message });
|
|
360
|
+
} finally {
|
|
361
|
+
submitting.value = false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
</script>
|
|
365
|
+
|
|
366
|
+
<template>
|
|
367
|
+
<Button
|
|
368
|
+
variant="ghost"
|
|
369
|
+
size="sm"
|
|
370
|
+
:disabled="submitting"
|
|
371
|
+
@click="onTrigger"
|
|
372
|
+
>
|
|
373
|
+
<Icon
|
|
374
|
+
icon="fluent:magic-wand-20-regular"
|
|
375
|
+
class="size-4"
|
|
376
|
+
/>
|
|
377
|
+
<span>AI 生成字段</span>
|
|
378
|
+
</Button>
|
|
379
|
+
|
|
380
|
+
<ShwfedModal
|
|
381
|
+
v-model:open="open"
|
|
382
|
+
content-width="48rem"
|
|
383
|
+
:dismissible="!submitting"
|
|
384
|
+
>
|
|
385
|
+
<template #title>
|
|
386
|
+
<span class="flex items-center gap-2">
|
|
387
|
+
<Icon
|
|
388
|
+
icon="fluent:magic-wand-20-regular"
|
|
389
|
+
class="size-5"
|
|
390
|
+
/>
|
|
391
|
+
AI 生成字段
|
|
392
|
+
</span>
|
|
393
|
+
</template>
|
|
394
|
+
<template #description>
|
|
395
|
+
<Markdown
|
|
396
|
+
:source="description"
|
|
397
|
+
block
|
|
398
|
+
class="prose prose-sm prose-zinc max-w-none"
|
|
399
|
+
/>
|
|
400
|
+
</template>
|
|
401
|
+
|
|
402
|
+
<form
|
|
403
|
+
class="flex flex-col gap-2 py-2"
|
|
404
|
+
@submit.prevent="onSubmit"
|
|
405
|
+
>
|
|
406
|
+
<Textarea
|
|
407
|
+
v-model="context"
|
|
408
|
+
placeholder="任意上下文,建议包含: - 想要的字段:例如「姓名文本框、年龄数值、是否在职开关」 - 字段含义、绑定路径或示例数据 - 校验 / 默认值 / 只读等额外要求"
|
|
409
|
+
wrap="soft"
|
|
410
|
+
class="field-sizing-fixed h-72 max-h-[60vh] resize-none overflow-auto break-all whitespace-pre-wrap font-mono text-xs"
|
|
411
|
+
:disabled="submitting"
|
|
412
|
+
/>
|
|
413
|
+
</form>
|
|
414
|
+
|
|
415
|
+
<template #footer>
|
|
416
|
+
<div class="flex items-center gap-2">
|
|
417
|
+
<div
|
|
418
|
+
v-if="submitting"
|
|
419
|
+
class="mr-auto flex items-center gap-2 text-xs text-muted-foreground"
|
|
420
|
+
aria-live="polite"
|
|
421
|
+
>
|
|
422
|
+
<span
|
|
423
|
+
:key="tick"
|
|
424
|
+
class="ai-progress-dot"
|
|
425
|
+
:class="tick > 0 ? 'is-streaming' : 'is-waiting'"
|
|
426
|
+
/>
|
|
427
|
+
<span>{{ tick > 0 ? "\u6B63\u5728\u63A5\u6536\u6A21\u578B\u8F93\u51FA\u2026" : "\u6B63\u5728\u8BF7\u6C42\u6A21\u578B\u2026" }}</span>
|
|
428
|
+
</div>
|
|
429
|
+
<Button
|
|
430
|
+
type="button"
|
|
431
|
+
size="sm"
|
|
432
|
+
:disabled="submitting"
|
|
433
|
+
@click="open = false"
|
|
434
|
+
>
|
|
435
|
+
<Icon icon="fluent:dismiss-20-regular" />
|
|
436
|
+
取消
|
|
437
|
+
</Button>
|
|
438
|
+
<Button
|
|
439
|
+
type="button"
|
|
440
|
+
variant="primary"
|
|
441
|
+
size="sm"
|
|
442
|
+
:disabled="!canSubmit"
|
|
443
|
+
@click="onSubmit"
|
|
444
|
+
>
|
|
445
|
+
<Icon
|
|
446
|
+
:icon="submitting ? 'fluent:arrow-sync-20-regular' : 'fluent:sparkle-20-regular'"
|
|
447
|
+
:class="submitting ? 'animate-spin' : ''"
|
|
448
|
+
/>
|
|
449
|
+
生成
|
|
450
|
+
</Button>
|
|
451
|
+
</div>
|
|
452
|
+
</template>
|
|
453
|
+
</ShwfedModal>
|
|
454
|
+
</template>
|
|
455
|
+
|
|
456
|
+
<style scoped>
|
|
457
|
+
.ai-progress-dot{background-color:var(--primary,currentColor);border-radius:9999px;display:inline-block;height:.5rem;width:.5rem}.ai-progress-dot.is-waiting{animation:ai-progress-waiting 1.2s ease-in-out infinite}.ai-progress-dot.is-streaming{animation:ai-progress-kick .32s ease-out}@keyframes ai-progress-waiting{0%,to{opacity:.35;transform:scale(1)}50%{opacity:1;transform:scale(1.15)}}@keyframes ai-progress-kick{0%{opacity:1;transform:scale(1.6)}to{opacity:.7;transform:scale(1)}}
|
|
458
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Environment } from '../../../vendor/cel-js/lib/index.js';
|
|
2
|
+
type AnyRecord = Record<string, any>;
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
configure?: (env: Environment) => void;
|
|
5
|
+
fields?: ReadonlyArray<AnyRecord>;
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
8
|
+
apply: (value: AnyRecord[]) => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
onApply?: ((value: AnyRecord[]) => any) | undefined;
|
|
11
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
declare const _default: typeof __VLS_export;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Generate **new form fields** from the user's free-form description. The output is a single struct with one required field: `fields` — an array of field objects.
|
|
2
|
+
|
|
3
|
+
The `fields` array is **APPENDED** to the form — it is **not** a replacement. The host adds whatever you emit to the existing fields and leaves every existing field untouched. So:
|
|
4
|
+
|
|
5
|
+
- Emit **only** the fields the user is asking to add. Do **not** echo existing fields back.
|
|
6
|
+
- The generated fields are left **unplaced** — they do not appear on any layout. The user drags them onto a layout themselves afterwards. Do not worry about layout / placement.
|
|
7
|
+
|
|
8
|
+
# Hard rules
|
|
9
|
+
|
|
10
|
+
1. **Allowlist only.** Every field's `type` + `compatibilityDate` pair **must** come from the "Available field types" list in this prompt. Never emit a `type` outside that list — in particular never emit an actions field, a nested table field, a list/repeater field, a markdown field, or a remote (server-driven) dropdown. If the user asks for one of those, pick the closest allowed field or omit it.
|
|
11
|
+
2. **Use the latest `compatibilityDate`.** For each `type`, copy the `compatibilityDate` shown in its heading verbatim. Never emit an older/deprecated date.
|
|
12
|
+
3. **Leave CEL fields empty unless explicitly asked.** Fields whose value is a CEL expression — `hidden`, `disabled`, `readonly`, `derived`, the number `min` / `max`, the upload `template`, date `presets` values — should be **omitted** unless the user explicitly describes that behavior (e.g. "在状态为已提交时只读", "默认填入当前用户"). Do not invent conditions.
|
|
13
|
+
4. **Always call `gen_uuid` for every `id`.** Each field needs a stable `id`; every option inside `items` and every entry inside `presets` also needs its own `id`. Call the `gen_uuid` tool once per id you need — never invent UUIDs yourself.
|
|
14
|
+
5. **Bare optional CEL access.** When you do write a CEL expression, reference form state with bare optional access — `form.?status`, `form.?user.?role`. Never wrap in coercions (`string(...)`, `int(...)`) and never add `.orValue(...)` defaults.
|
|
15
|
+
|
|
16
|
+
# Field fields
|
|
17
|
+
|
|
18
|
+
Each field object needs at minimum:
|
|
19
|
+
|
|
20
|
+
- `id`: a fresh UUID from the `gen_uuid` tool.
|
|
21
|
+
- `type`: one of the allowlisted types.
|
|
22
|
+
- `compatibilityDate`: copied verbatim from that type's heading.
|
|
23
|
+
- Type-specific fields — see "Available field types" below; each lists its own fields with titles and descriptions.
|
|
24
|
+
|
|
25
|
+
Common optional fields most input types support:
|
|
26
|
+
|
|
27
|
+
- `displayName`: a short editor-only name for the field, shown in the field list. Plain string, not localized. Prefer setting this so the user can tell the generated fields apart.
|
|
28
|
+
- `label`: the localized label rendered before the input (locale array — see below).
|
|
29
|
+
- `placeholder`: localized placeholder text.
|
|
30
|
+
- `tooltip`: localized help text shown on label hover.
|
|
31
|
+
- `binding`: a `dot-prop` path string the field writes into form state, e.g. `user.name`, `order.amount`. Always a **single** string path (range fields accept a single path too). Derive sensible paths from the user's prose; omit if the user gives no hint.
|
|
32
|
+
- `orientation`: `'vertical'` (label above, default) or `'floating'` (label inside the input).
|
|
33
|
+
|
|
34
|
+
# Value shapes
|
|
35
|
+
|
|
36
|
+
**Localized strings** (`label`, `placeholder`, `tooltip`, …) are arrays of `{ "locale": ..., "message": ... }`. The `zh` entry **must be the first element**. Other locales are limited to `en` / `ja` / `ko`. Usually emit just the `zh` entry:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
"label": [{ "locale": "zh", "message": "姓名" }]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**`items`** (for `combobox.single`, `checkbox.group`, `radio.group`) — always populate this when you emit one of those types. Each item is:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{ "id": "<gen_uuid>", "label": [{ "locale": "zh", "message": "启用" }], "value": { "kind": "text", "value": "enabled" } }
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`value.kind` is `"text"` (with a string `value`) or `"number"` (with a numeric `value`). An optional `tooltip` (locale array) may be added.
|
|
49
|
+
|
|
50
|
+
**`derived`** — only when the user explicitly asks for a computed / auto-filled value:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
"derived": { "mode": "formula", "expression": "form.?price * form.?qty" }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`mode` is `"formula"` (read-only, always the expression's result) or `"prefill"` (editable, expression fills until the user edits).
|
|
57
|
+
|
|
58
|
+
**`presets`** (date / daterange fields) — each entry is `{ id, label, value }` for single-date fields or `{ id, label, start, end }` for range fields, where `value` / `start` / `end` are CEL expressions returning a `Date` (e.g. `now`, `now.offset(-7, "days")`). Only emit when the user asks for quick-pick presets.
|
|
59
|
+
|
|
60
|
+
# CEL scope
|
|
61
|
+
|
|
62
|
+
CEL expressions in form fields run with `form` (the current form state, `dyn`), `now` (current `Date`), and any host-registered variables — see "Variables In Scope" above.
|
|
63
|
+
|
|
64
|
+
# Current configuration
|
|
65
|
+
|
|
66
|
+
The form's existing fields are appended to the user prompt under `Current configuration` as `{ "fields": [...] }`. Use it only to:
|
|
67
|
+
|
|
68
|
+
- keep naming / `binding` paths consistent with what already exists, and
|
|
69
|
+
- avoid emitting a field that duplicates an existing one's `binding`.
|
|
70
|
+
|
|
71
|
+
Do **not** copy those existing fields into your output — they stay in the form regardless. Your `fields` array is purely the **new** fields to add.
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
provideCELContext
|
|
6
|
-
} from "../../utils/cel-context";
|
|
3
|
+
import { provideCELContext } from "../../utils/cel-context";
|
|
7
4
|
import { ExpressionEditor } from "../ui/expression-editor";
|
|
8
5
|
import { Field, FieldLabel } from "../ui/field";
|
|
9
6
|
import { Markdown } from "../ui/markdown";
|
|
@@ -13,6 +10,7 @@ import {
|
|
|
13
10
|
getStructFieldTitle
|
|
14
11
|
} from "./schema";
|
|
15
12
|
import ShwfedFormUnitConfig, {} from "./unit-config.vue";
|
|
13
|
+
import { buildFormCelScope } from "./utils/cel-scope";
|
|
16
14
|
defineOptions({ name: "ShwfedFormConfig" });
|
|
17
15
|
const config = defineModel({ type: Object, ...{ required: true } });
|
|
18
16
|
const props = defineProps({
|
|
@@ -23,38 +21,7 @@ const configure = props.configure ?? (() => {
|
|
|
23
21
|
const formConfigSchema = FormConfig(configure);
|
|
24
22
|
const generalFieldTitle = (f) => getStructFieldTitle(formConfigSchema, f) ?? f;
|
|
25
23
|
const generalFieldDescription = (f) => getStructFieldDescription(formConfigSchema, f);
|
|
26
|
-
|
|
27
|
-
const probe = new Environment({ unlistedVariablesAreDyn: false });
|
|
28
|
-
const baseline = new Set(probe.getDefinitions().variables.map((v) => v.name));
|
|
29
|
-
configure(probe);
|
|
30
|
-
const out = {
|
|
31
|
-
form: {
|
|
32
|
-
type: "dyn",
|
|
33
|
-
label: "\u8868\u5355\u503C",
|
|
34
|
-
description: "\u5F53\u524D\u8868\u5355\u72B6\u6001",
|
|
35
|
-
value: void 0
|
|
36
|
-
},
|
|
37
|
-
// Mirrors the runtime `now` binding in `form/index.vue`. Editor never
|
|
38
|
-
// evaluates so we leave `value` undefined.
|
|
39
|
-
now: {
|
|
40
|
-
type: "Date",
|
|
41
|
-
label: "now",
|
|
42
|
-
description: "\u5F53\u524D\u65E5\u671F/\u65F6\u95F4",
|
|
43
|
-
value: void 0
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
for (const v of probe.getDefinitions().variables) {
|
|
47
|
-
if (baseline.has(v.name)) continue;
|
|
48
|
-
out[v.name] = {
|
|
49
|
-
type: v.type,
|
|
50
|
-
label: v.label ?? v.name,
|
|
51
|
-
description: v.description ?? void 0,
|
|
52
|
-
value: void 0
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return out;
|
|
56
|
-
})();
|
|
57
|
-
provideCELContext(advertisedContext);
|
|
24
|
+
provideCELContext(buildFormCelScope(configure));
|
|
58
25
|
const unitModel = computed({
|
|
59
26
|
get: () => ({ fields: config.value.fields, layouts: config.value.layouts }),
|
|
60
27
|
set: (next) => {
|
|
@@ -90,6 +57,7 @@ function updateReadonly(value) {
|
|
|
90
57
|
v-model="unitModel"
|
|
91
58
|
v-model:selection="selection"
|
|
92
59
|
:extras="EXTRAS"
|
|
60
|
+
:configure="configure"
|
|
93
61
|
>
|
|
94
62
|
<template #extras-pane>
|
|
95
63
|
<div class="flex flex-col gap-4">
|