From 79c65c1da756f20bc3050ce159eb0a3737b8c47a Mon Sep 17 00:00:00 2001 From: liangxu Date: Sat, 18 Apr 2026 15:01:40 +0800 Subject: [PATCH] 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) --- .../src/components/kol/KolDynamicForm.vue | 44 ++- .../src/components/kol/KolPackageCard.vue | 8 +- .../components/kol/KolPackageFormModal.vue | 172 ++++++++++- .../src/components/kol/KolPersonaCard.vue | 4 +- .../src/components/kol/KolPromptEditor.vue | 12 +- .../src/components/kol/KolVariableConfig.vue | 158 +++++++++- apps/admin-web/src/i18n/messages/en-US.ts | 49 ++- apps/admin-web/src/i18n/messages/zh-CN.ts | 49 ++- apps/admin-web/src/lib/api.ts | 5 +- apps/admin-web/src/lib/kol-placeholders.ts | 172 ++++++++++- .../src/views/KnowledgeView.vue.patch | 10 - apps/admin-web/src/views/KolGenerateView.vue | 10 +- apps/admin-web/src/views/KolManageView.vue | 169 +++++++---- .../src/views/KolPackageDetailView.vue | 7 +- apps/admin-web/src/views/TemplatesView.vue | 261 ++++++++++++---- apps/admin-web/src/views/WorkspaceView.vue | 231 +++++++++++--- .../plans/2026-04-18-kol-profile-page.md | 2 +- packages/shared-types/src/index.ts | 6 + .../tenant/app/kol_variable_renderer.go | 281 +++++++++++++++++- .../tenant/app/kol_variable_renderer_test.go | 76 +++++ .../internal/tenant/app/workspace_service.go | 1 + server/internal/tenant/domain/workspace.go | 1 + .../tenant/repository/generated/models.go | 3 + .../repository/generated/workspace.sql.go | 5 +- .../tenant/repository/queries/workspace.sql | 3 +- .../tenant/repository/workspace_repo.go | 2 + 26 files changed, 1506 insertions(+), 235 deletions(-) delete mode 100644 apps/admin-web/src/views/KnowledgeView.vue.patch diff --git a/apps/admin-web/src/components/kol/KolDynamicForm.vue b/apps/admin-web/src/components/kol/KolDynamicForm.vue index afa0c85..a160e06 100644 --- a/apps/admin-web/src/components/kol/KolDynamicForm.vue +++ b/apps/admin-web/src/components/kol/KolDynamicForm.vue @@ -1,7 +1,12 @@ @@ -53,8 +67,10 @@ function fieldKey(variable: KolVariableDefinition): string { @@ -63,7 +79,7 @@ function fieldKey(variable: KolVariableDefinition): string { :value="modelValue[fieldKey(variable)]" :placeholder="variable.placeholder || t('common.selectPlease')" :options="selectOptions(variable)" - @update:value="(val: string) => updateField(fieldKey(variable), val)" + @update:value="(val: string) => updateField(variable, fieldKey(variable), val)" /> @@ -71,8 +87,10 @@ function fieldKey(variable: KolVariableDefinition): string { @@ -80,7 +98,7 @@ function fieldKey(variable: KolVariableDefinition): string { diff --git a/apps/admin-web/src/components/kol/KolPackageCard.vue b/apps/admin-web/src/components/kol/KolPackageCard.vue index 4d50d9e..d7cbf2c 100644 --- a/apps/admin-web/src/components/kol/KolPackageCard.vue +++ b/apps/admin-web/src/components/kol/KolPackageCard.vue @@ -1,8 +1,11 @@ + + diff --git a/apps/admin-web/src/components/kol/KolPersonaCard.vue b/apps/admin-web/src/components/kol/KolPersonaCard.vue index 024b039..b7faed3 100644 --- a/apps/admin-web/src/components/kol/KolPersonaCard.vue +++ b/apps/admin-web/src/components/kol/KolPersonaCard.vue @@ -6,7 +6,7 @@ import { message } from "ant-design-vue"; import { EditOutlined, UserOutlined, LoadingOutlined } from "@ant-design/icons-vue"; import type { KolProfile } from "@geo/shared-types"; -import { kolManageApi } from "@/lib/api"; +import { kolManageApi, resolveApiURL } from "@/lib/api"; import { formatError } from "@/lib/errors"; const props = defineProps<{ @@ -22,7 +22,7 @@ const draftBio = ref(props.profile.bio ?? ""); const avatarUploading = ref(false); const fileInput = ref(null); -const avatarURL = computed(() => props.profile.avatar_url ?? ""); +const avatarURL = computed(() => resolveApiURL(props.profile.avatar_url)); const updateMutation = useMutation({ mutationFn: kolManageApi.updateProfile, diff --git a/apps/admin-web/src/components/kol/KolPromptEditor.vue b/apps/admin-web/src/components/kol/KolPromptEditor.vue index 5f21dee..ab8dc84 100644 --- a/apps/admin-web/src/components/kol/KolPromptEditor.vue +++ b/apps/admin-web/src/components/kol/KolPromptEditor.vue @@ -14,7 +14,11 @@ import type { KolCardConfig, KolVariableDefinition } from "@geo/shared-types"; import { formatError } from "@/lib/errors"; import { buildKolPlatformOptions } from "@/lib/kol-platform-options"; import { mergeKolCardConfig, parseKolCardConfig } from "@/lib/kol-card-config"; -import { syncKolVariablesWithContent } from "@/lib/kol-placeholders"; +import { + getKolVariableInitialValue, + normalizeKolVariableDefinition, + syncKolVariablesWithContent, +} from "@/lib/kol-placeholders"; const props = defineProps<{ promptId: number; @@ -69,7 +73,9 @@ watch( metadataForm.allow_web_search = parsedCardConfig.allow_web_search; metadataForm.allow_user_knowledge = parsedCardConfig.allow_user_knowledge; content.value = detail.latest_prompt_content ?? ""; - variables.value = detail.latest_schema_json?.variables ?? []; + variables.value = (detail.latest_schema_json?.variables ?? []).map((variable) => + normalizeKolVariableDefinition(variable), + ); cardConfig.value = { ...nextCardConfig }; } }, @@ -178,7 +184,7 @@ function buildPreviewValues(nextVariables: KolVariableDefinition[]): Record { const fieldKey = variable.key?.trim() || variable.id; - nextValues[fieldKey] = variable.type === "checkbox" ? [] : undefined; + nextValues[fieldKey] = getKolVariableInitialValue(variable); }); return nextValues; diff --git a/apps/admin-web/src/components/kol/KolVariableConfig.vue b/apps/admin-web/src/components/kol/KolVariableConfig.vue index 01d5aa5..7be3fb2 100644 --- a/apps/admin-web/src/components/kol/KolVariableConfig.vue +++ b/apps/admin-web/src/components/kol/KolVariableConfig.vue @@ -4,6 +4,15 @@ import { nextTick, ref } from "vue"; import { CloseOutlined, DownOutlined } from "@ant-design/icons-vue"; import type { KolVariableDefinition, KolVariableType } from "@geo/shared-types"; import { useI18n } from "vue-i18n"; +import { + DEFAULT_KOL_NUMBER_MAX, + DEFAULT_KOL_NUMBER_MIN, + DEFAULT_KOL_TEXT_MAX_LENGTH, + getKolVariableNumberMax, + getKolVariableNumberMin, + getKolVariableTextMaxLength, + normalizeKolVariableDefinition, +} from "@/lib/kol-placeholders"; const props = defineProps<{ variables: KolVariableDefinition[]; @@ -33,7 +42,10 @@ type FocusVariableOptions = { function updateVariable(index: number, updates: Partial) { const newVariables = [...props.variables]; - newVariables[index] = { ...newVariables[index], ...updates }; + newVariables[index] = normalizeKolVariableDefinition({ + ...newVariables[index], + ...updates, + }); emit("update:variables", newVariables); } @@ -55,6 +67,73 @@ function handleTypeChange(index: number, type: KolVariableType) { updateVariable(index, { type }); } +function handleTextMaxLengthChange(index: number, value: number | null) { + const maxLength = + typeof value === "number" && Number.isFinite(value) && value > 0 + ? Math.trunc(value) + : DEFAULT_KOL_TEXT_MAX_LENGTH; + const currentVariable = normalizeKolVariableDefinition(props.variables[index]); + + updateVariable(index, { + max_length: maxLength, + default_value: currentVariable.default_value?.slice(0, maxLength), + }); +} + +function handleNumberMinChange(index: number, value: number | null) { + const currentVariable = normalizeKolVariableDefinition(props.variables[index]); + const minValue = + typeof value === "number" && Number.isFinite(value) ? value : DEFAULT_KOL_NUMBER_MIN; + const maxValue = Math.max(getKolVariableNumberMax(currentVariable), minValue); + const defaultNumber = + currentVariable.default_number === undefined + ? undefined + : Math.min(maxValue, Math.max(minValue, currentVariable.default_number)); + + updateVariable(index, { + min_value: minValue, + max_value: maxValue, + default_number: defaultNumber, + }); +} + +function handleNumberMaxChange(index: number, value: number | null) { + const currentVariable = normalizeKolVariableDefinition(props.variables[index]); + const minValue = getKolVariableNumberMin(currentVariable); + const maxValue = + typeof value === "number" && Number.isFinite(value) + ? Math.max(value, minValue) + : Math.max(DEFAULT_KOL_NUMBER_MAX, minValue); + const defaultNumber = + currentVariable.default_number === undefined + ? undefined + : Math.min(maxValue, Math.max(minValue, currentVariable.default_number)); + + updateVariable(index, { + min_value: minValue, + max_value: maxValue, + default_number: defaultNumber, + }); +} + +function handleDefaultNumberChange(index: number, value: number | null) { + updateVariable(index, { + default_number: value === null ? undefined : value, + }); +} + +function textLimit(variable: KolVariableDefinition): number { + return getKolVariableTextMaxLength(variable); +} + +function numberMin(variable: KolVariableDefinition): number { + return getKolVariableNumberMin(variable); +} + +function numberMax(variable: KolVariableDefinition): number { + return getKolVariableNumberMax(variable); +} + function setCardRef(key: string, el: Element | ComponentPublicInstance | null) { const element = el instanceof HTMLElement @@ -180,6 +259,41 @@ defineExpose({ /> + +
+ +
{{{{ variable.key || variable.id }}}}
@@ -274,6 +424,12 @@ defineExpose({ color: #8c8c8c; } +.form-item-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + .form-item-row { display: flex; justify-content: space-between; diff --git a/apps/admin-web/src/i18n/messages/en-US.ts b/apps/admin-web/src/i18n/messages/en-US.ts index cad40e2..8e295db 100644 --- a/apps/admin-web/src/i18n/messages/en-US.ts +++ b/apps/admin-web/src/i18n/messages/en-US.ts @@ -79,8 +79,8 @@ const enUS = { contentManagement: "Content Management", knowledge: "Knowledge Base", images: "Image Management", - kolMarket: "Refined Templates", - kolMarketplace: "Refined Templates", + kolMarket: "Refined Template Marketplace", + kolMarketplace: "Refined Template Marketplace", kolWorkspace: "KOL Workspace", kolManage: "Prompt Management", kolDashboard: "Dashboard", @@ -186,6 +186,8 @@ const enUS = { }, manage: { title: "KOL Prompt Management", + packageTitle: "Packages", + promptTitle: "Prompts", createPackage: "Create package", editPackage: "Edit package", publishPackage: "Publish", @@ -193,8 +195,46 @@ const enUS = { createPrompt: "Create prompt", activatePrompt: "Go live", archivePrompt: "Take offline", + confirmDeletePackage: "Are you sure you want to delete this package?", + confirmDeletePrompt: "Are you sure you want to delete this prompt?", + emptyPromptSelection: "Select a package on the left to view its prompts", platformHint: "Platform", platformHintDefault: "General", + packageStatus: { + draft: "Draft", + published: "Published", + archived: "Archived", + }, + messages: { + packageCreated: "Package created successfully", + packageUpdated: "Package updated successfully", + packagePublished: "Package published successfully", + packageArchived: "Package archived successfully", + packageDeleted: "Package deleted successfully", + promptCreated: "Prompt created successfully", + promptActivated: "Prompt is live", + promptArchived: "Prompt is offline", + promptDeleted: "Prompt deleted successfully", + }, + packageForm: { + tags: "Tags", + tagsPlaceholder: "Type a tag and press Enter", + priceNote: "Price Note", + priceNotePlaceholder: "For example: Contact me for pricing", + cover: "Cover Image", + coverHint: "Choose from the image library or upload a local image and convert it to WebP automatically.", + selectCover: "Select cover image", + changeCover: "Change cover image", + removeCover: "Remove cover image", + noCover: "No cover image selected", + nameRequired: "Package title is required", + coverPicker: { + title: "Select package cover", + localTab: "Local upload", + emptyTitle: "Choose an image for this package cover", + emptyHint: "Choose from the image library or upload a local image and convert it to WebP automatically.", + }, + }, status: { draft: "Draft", active: "Live", @@ -221,6 +261,11 @@ const enUS = { required: "Required", placeholder: "Placeholder", options: "Options (comma separated)", + maxLength: "Max length", + defaultValue: "Default value", + minValue: "Min number", + maxValue: "Max number", + defaultNumber: "Default number", }, }, dashboard: { diff --git a/apps/admin-web/src/i18n/messages/zh-CN.ts b/apps/admin-web/src/i18n/messages/zh-CN.ts index f0eaa2a..74ed22e 100644 --- a/apps/admin-web/src/i18n/messages/zh-CN.ts +++ b/apps/admin-web/src/i18n/messages/zh-CN.ts @@ -79,8 +79,8 @@ const zhCN = { contentManagement: "内容管理", knowledge: "知识库", images: "图片管理", - kolMarket: "精调模版", - kolMarketplace: "精调模版", + kolMarket: "精调模版市场", + kolMarketplace: "精调模版市场", kolWorkspace: "KOL 工作台", kolManage: "提示词管理", kolDashboard: "数据看板", @@ -186,6 +186,8 @@ const zhCN = { }, manage: { title: "KOL 提示词管理", + packageTitle: "订阅包", + promptTitle: "提示词", createPackage: "创建订阅包", editPackage: "编辑包", publishPackage: "发布", @@ -193,8 +195,46 @@ const zhCN = { createPrompt: "新增提示词", activatePrompt: "上线", archivePrompt: "下线", + confirmDeletePackage: "确定要删除这个订阅包吗?", + confirmDeletePrompt: "确定要删除这个提示词吗?", + emptyPromptSelection: "请选择左侧订阅包以查看提示词", platformHint: "平台", platformHintDefault: "通用", + packageStatus: { + draft: "草稿", + published: "已发布", + archived: "已归档", + }, + messages: { + packageCreated: "订阅包创建成功", + packageUpdated: "订阅包更新成功", + packagePublished: "订阅包发布成功", + packageArchived: "订阅包归档成功", + packageDeleted: "订阅包删除成功", + promptCreated: "提示词创建成功", + promptActivated: "提示词已上线", + promptArchived: "提示词已下线", + promptDeleted: "提示词删除成功", + }, + packageForm: { + tags: "标签", + tagsPlaceholder: "输入标签后按回车", + priceNote: "价格说明", + priceNotePlaceholder: "例如:联系我获取报价", + cover: "封面图", + coverHint: "支持从素材库选择,也支持本地上传后自动转为 WebP。", + selectCover: "选择封面图", + changeCover: "更换封面图", + removeCover: "移除封面图", + noCover: "暂未设置封面图", + nameRequired: "请填写订阅包标题", + coverPicker: { + title: "选择订阅包封面", + localTab: "本地上传", + emptyTitle: "选择一张图片作为订阅包封面", + emptyHint: "支持从素材库选择,也支持本地上传后自动转为 WebP。", + }, + }, status: { draft: "草稿", active: "已上线", @@ -221,6 +261,11 @@ const zhCN = { required: "必填", placeholder: "提示语", options: "选项(逗号分隔)", + maxLength: "字数限制", + defaultValue: "默认值", + minValue: "最小数字", + maxValue: "最大数字", + defaultNumber: "默认数字", }, }, dashboard: { diff --git a/apps/admin-web/src/lib/api.ts b/apps/admin-web/src/lib/api.ts index 721dce9..2f2f97f 100644 --- a/apps/admin-web/src/lib/api.ts +++ b/apps/admin-web/src/lib/api.ts @@ -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; diff --git a/apps/admin-web/src/lib/kol-placeholders.ts b/apps/admin-web/src/lib/kol-placeholders.ts index d6e2de3..5983d1d 100644 --- a/apps/admin-web/src/lib/kol-placeholders.ts +++ b/apps/admin-web/src/lib/kol-placeholders.ts @@ -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, diff --git a/apps/admin-web/src/views/KnowledgeView.vue.patch b/apps/admin-web/src/views/KnowledgeView.vue.patch deleted file mode 100644 index 2a94319..0000000 --- a/apps/admin-web/src/views/KnowledgeView.vue.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- /Users/liangxu/Documents/test/geo-rankly/apps/admin-web/src/views/KnowledgeView.vue -+++ /Users/liangxu/Documents/test/geo-rankly/apps/admin-web/src/views/KnowledgeView.vue -@@ -37,6 +37,5 @@ - const itemForm = reactive({ -- group_id: undefined as number | undefined, -- name: "", -+ group_ids: [] as number[], - url: "", - content: "", - }); diff --git a/apps/admin-web/src/views/KolGenerateView.vue b/apps/admin-web/src/views/KolGenerateView.vue index 8ad8031..8806d05 100644 --- a/apps/admin-web/src/views/KolGenerateView.vue +++ b/apps/admin-web/src/views/KolGenerateView.vue @@ -9,6 +9,11 @@ import { kolGenerateApi } from "@/lib/api"; import KolDynamicForm from "@/components/kol/KolDynamicForm.vue"; import KnowledgeGroupSelect from "@/components/KnowledgeGroupSelect.vue"; import { parseKolCardConfig } from "@/lib/kol-card-config"; +import { + getKolVariableInitialValue, + isKolVariableValueEmpty, + normalizeKolVariableDefinition, +} from "@/lib/kol-placeholders"; const route = useRoute(); const router = useRouter(); @@ -42,7 +47,7 @@ watch( if (schema?.schema_json?.variables) { const newValues: Record = {}; schema.schema_json.variables.forEach((v) => { - newValues[fieldKey(v.key, v.id)] = v.type === "checkbox" ? [] : undefined; + newValues[fieldKey(v.key, v.id)] = getKolVariableInitialValue(normalizeKolVariableDefinition(v)); }); values.value = newValues; enableWebSearch.value = false; @@ -78,7 +83,8 @@ function handleSubmit() { const missingLabels: string[] = []; schema.schema_json.variables.forEach((v) => { - if (v.required && !values.value[fieldKey(v.key, v.id)]) { + const normalizedVariable = normalizeKolVariableDefinition(v); + if (normalizedVariable.required && isKolVariableValueEmpty(normalizedVariable, values.value[fieldKey(v.key, v.id)])) { missingLabels.push(v.label); } }); diff --git a/apps/admin-web/src/views/KolManageView.vue b/apps/admin-web/src/views/KolManageView.vue index 1b18b14..14135c5 100644 --- a/apps/admin-web/src/views/KolManageView.vue +++ b/apps/admin-web/src/views/KolManageView.vue @@ -45,8 +45,8 @@ const { data: prompts, isPending: promptsPending } = useQuery({ const createPackageMutation = useMutation({ mutationFn: (body: CreateKolPackageRequest) => kolManageApi.createPackage(body), onSuccess: () => { - message.success(t("common.create") + "成功"); - queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); + message.success(t("kol.manage.messages.packageCreated")); + void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); }, onError: (error) => message.error(formatError(error)), }); @@ -55,8 +55,8 @@ const updatePackageMutation = useMutation({ mutationFn: (data: { id: number; body: CreateKolPackageRequest }) => kolManageApi.updatePackage(data.id, data.body), onSuccess: () => { - message.success(t("common.edit") + "成功"); - queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); + message.success(t("kol.manage.messages.packageUpdated")); + void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); }, onError: (error) => message.error(formatError(error)), }); @@ -64,37 +64,40 @@ const updatePackageMutation = useMutation({ const publishPackageMutation = useMutation({ mutationFn: (id: number) => kolManageApi.publishPackage(id), onSuccess: () => { - message.success(t("kol.manage.publishPackage") + "成功"); - queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); + message.success(t("kol.manage.messages.packagePublished")); + void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); }, + onError: (error) => message.error(formatError(error)), }); const archivePackageMutation = useMutation({ mutationFn: (id: number) => kolManageApi.archivePackage(id), onSuccess: () => { - message.success(t("kol.manage.archivePackage") + "成功"); - queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); + message.success(t("kol.manage.messages.packageArchived")); + void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); }, + onError: (error) => message.error(formatError(error)), }); const deletePackageMutation = useMutation({ mutationFn: (id: number) => kolManageApi.deletePackage(id), onSuccess: (_, id) => { - message.success(t("common.delete") + "成功"); - queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); + message.success(t("kol.manage.messages.packageDeleted")); + void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); if (selectedPackageId.value === id) { selectedPackageId.value = null; editingPromptId.value = null; } }, + onError: (error) => message.error(formatError(error)), }); const createPromptMutation = useMutation({ mutationFn: (body: CreateKolPromptRequest) => kolManageApi.createPrompt(selectedPackageId.value!, body), onSuccess: () => { - message.success(t("kol.manage.createPrompt") + "成功"); - queryClient.invalidateQueries({ + message.success(t("kol.manage.messages.promptCreated")); + void queryClient.invalidateQueries({ queryKey: ["kol", "packages", selectedPackageId.value, "prompts"], }); }, @@ -111,7 +114,7 @@ const togglePromptStatusMutation = useMutation({ }, onSuccess: (_, variables) => { message.success( - t(variables.nextStatus === "active" ? "kol.manage.activatePrompt" : "kol.manage.archivePrompt") + "成功" + t(variables.nextStatus === "active" ? "kol.manage.messages.promptActivated" : "kol.manage.messages.promptArchived") ); void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); void queryClient.invalidateQueries({ @@ -131,7 +134,7 @@ const deletePromptMutation = useMutation({ deletingPromptId.value = id; }, onSuccess: (_, id) => { - message.success(t("common.delete") + "成功"); + message.success(t("kol.manage.messages.promptDeleted")); void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }); void queryClient.invalidateQueries({ queryKey: ["kol", "packages", selectedPackageId.value, "prompts"], @@ -180,7 +183,7 @@ function handlePackageSubmit(body: CreateKolPackageRequest) { function handleDeletePackage(id: number) { Modal.confirm({ title: t("common.delete") + "?", - content: "确定要删除这个订阅包吗?", + content: t("kol.manage.confirmDeletePackage"), onOk: () => deletePackageMutation.mutate(id), }); } @@ -201,7 +204,7 @@ function handleCloseEditor() { function handleDeletePrompt(id: number) { Modal.confirm({ title: t("common.delete") + "?", - content: "确定要删除这个提示词吗?", + content: t("kol.manage.confirmDeletePrompt"), onOk: () => deletePromptMutation.mutate(id), }); } @@ -211,6 +214,12 @@ function promptStatusLabel(status: string) { if (status === "archived") return t("kol.manage.status.archived"); return t("kol.manage.status.draft"); } + +function packageStatusLabel(status: string) { + if (status === "published") return t("kol.manage.packageStatus.published"); + if (status === "archived") return t("kol.manage.packageStatus.archived"); + return t("kol.manage.packageStatus.draft"); +}