feat(kol): expand variable schema and polish KOL UI

- Add length/range limits to KOL variable definitions: max_length and
  default_value for input/textarea, min_value/max_value/default_number
  for number. Backend validates and normalizes; frontend renders limit
  inputs in KolVariableConfig and applies limits at runtime in
  KolDynamicForm.
- Sort workspace cards by most recent prompt/package update, falling
  back to granted_at. Adds updated_at to KolWorkspaceCard.
- Polish TemplatesView, WorkspaceView, and KOL package management UI;
  route create/update/publish/archive/delete toasts through i18n and
  expand package form copy and status labels.
- Remove stale KnowledgeView.vue.patch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-18 15:01:40 +08:00
parent 1dd316a34b
commit 79c65c1da7
26 changed files with 1506 additions and 235 deletions
+1 -4
View File
@@ -297,10 +297,7 @@ export const kolManageApi = {
}>>("/api/tenant/kol/manage/profile/avatar", formData);
markImageUploadRequestFinished(progressToken);
return {
...response.data.data,
url: resolveApiURL(response.data.data.url),
};
return response.data.data;
} catch (error) {
markImageUploadRequestFailed(progressToken);
throw error;
+170 -2
View File
@@ -4,6 +4,10 @@ import { newVariableId } from "./kol-variable-id";
const KOL_PLACEHOLDER_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
export const DEFAULT_KOL_TEXT_MAX_LENGTH = 20;
export const DEFAULT_KOL_NUMBER_MIN = 0;
export const DEFAULT_KOL_NUMBER_MAX = 10000;
function normalizePlaceholderKey(value: string): string {
return value.trim();
}
@@ -21,6 +25,32 @@ function normalizeVariableType(type?: KolVariableType): KolVariableType {
}
}
function normalizeFiniteNumber(value: unknown): number | undefined {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string" && value.trim()) {
const parsed = Number(value);
if (Number.isFinite(parsed)) {
return parsed;
}
}
return undefined;
}
function normalizePositiveInteger(value: unknown, fallback: number): number {
const normalized = normalizeFiniteNumber(value);
if (normalized === undefined) {
return fallback;
}
const rounded = Math.trunc(normalized);
return rounded > 0 ? rounded : fallback;
}
function clampNumber(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
export function buildKolPlaceholderToken(key: string): string {
return `{{${normalizePlaceholderKey(key)}}}`;
}
@@ -70,7 +100,7 @@ export function createKolVariableDefinition(
): KolVariableDefinition {
const normalizedKey = normalizePlaceholderKey(key);
return {
return normalizeKolVariableDefinition({
id: overrides.id?.trim() || newVariableId(),
key: normalizedKey,
label: overrides.label?.trim() || normalizedKey,
@@ -78,7 +108,145 @@ export function createKolVariableDefinition(
required: overrides.required ?? true,
placeholder: overrides.placeholder,
options: overrides.options,
max_length: overrides.max_length,
default_value: overrides.default_value,
min_value: overrides.min_value,
max_value: overrides.max_value,
default_number: overrides.default_number,
});
}
export function getKolVariableTextMaxLength(variable: KolVariableDefinition): number {
return normalizePositiveInteger(variable.max_length, DEFAULT_KOL_TEXT_MAX_LENGTH);
}
export function getKolVariableNumberMin(variable: KolVariableDefinition): number {
return normalizeFiniteNumber(variable.min_value) ?? DEFAULT_KOL_NUMBER_MIN;
}
export function getKolVariableNumberMax(variable: KolVariableDefinition): number {
const min = getKolVariableNumberMin(variable);
const max = normalizeFiniteNumber(variable.max_value) ?? DEFAULT_KOL_NUMBER_MAX;
return max >= min ? max : min;
}
export function normalizeKolVariableDefinition(variable: KolVariableDefinition): KolVariableDefinition {
const type = normalizeVariableType(variable.type);
const normalizedKey = normalizePlaceholderKey(variable.key || variable.label || variable.id);
const normalizedLabel = variable.label?.trim() || normalizedKey;
const normalizedOptions =
type === "select" || type === "checkbox"
? (variable.options || []).map((item) => item.trim()).filter(Boolean)
: undefined;
const baseVariable: KolVariableDefinition = {
...variable,
id: variable.id?.trim() || newVariableId(),
key: normalizedKey,
label: normalizedLabel,
type,
required: variable.required ?? true,
placeholder: variable.placeholder,
options: normalizedOptions,
};
if (type === "input" || type === "textarea") {
const maxLength = getKolVariableTextMaxLength(baseVariable);
const defaultValue =
typeof variable.default_value === "string"
? variable.default_value.slice(0, maxLength)
: undefined;
return {
...baseVariable,
max_length: maxLength,
default_value: defaultValue,
min_value: undefined,
max_value: undefined,
default_number: undefined,
};
}
if (type === "number") {
const minValue = getKolVariableNumberMin(baseVariable);
const maxValue = getKolVariableNumberMax(baseVariable);
const rawDefaultNumber = normalizeFiniteNumber(variable.default_number);
const defaultNumber =
rawDefaultNumber === undefined ? undefined : clampNumber(rawDefaultNumber, minValue, maxValue);
return {
...baseVariable,
options: undefined,
max_length: undefined,
default_value: undefined,
min_value: minValue,
max_value: maxValue,
default_number: defaultNumber,
};
}
return {
...baseVariable,
max_length: undefined,
default_value: undefined,
min_value: undefined,
max_value: undefined,
default_number: undefined,
};
}
export function getKolVariableInitialValue(variable: KolVariableDefinition): any {
const normalized = normalizeKolVariableDefinition(variable);
if (normalized.type === "checkbox") {
return [];
}
if (normalized.type === "number") {
return normalized.default_number;
}
if (normalized.type === "input" || normalized.type === "textarea") {
return normalized.default_value;
}
return undefined;
}
export function sanitizeKolVariableValue(variable: KolVariableDefinition, value: any): any {
const normalized = normalizeKolVariableDefinition(variable);
if (normalized.type === "number") {
if (value === null || value === undefined || value === "") {
return undefined;
}
const numericValue = normalizeFiniteNumber(value);
if (numericValue === undefined) {
return undefined;
}
return clampNumber(numericValue, getKolVariableNumberMin(normalized), getKolVariableNumberMax(normalized));
}
if (normalized.type === "input" || normalized.type === "textarea") {
if (value === null || value === undefined) {
return undefined;
}
return String(value).slice(0, getKolVariableTextMaxLength(normalized));
}
return value;
}
export function isKolVariableValueEmpty(variable: KolVariableDefinition, value: any): boolean {
const normalized = normalizeKolVariableDefinition(variable);
if (normalized.type === "checkbox") {
return !Array.isArray(value) || value.length === 0;
}
if (normalized.type === "number") {
return value === null || value === undefined || value === "";
}
if (typeof value === "string") {
return value.trim().length === 0;
}
return value === null || value === undefined;
}
export function syncKolVariablesWithContent(
@@ -95,7 +263,7 @@ export function syncKolVariablesWithContent(
}
byKey.set(
key,
createKolVariableDefinition(key, {
normalizeKolVariableDefinition({
...variable,
key,
label: variable.label?.trim() || key,