fix: Enhance Kol Variable Rendering and Management
- Updated the regex for placeholder matching to support more flexible key formats. - Refactored variable storage to allow both ID and key lookups in rendering. - Improved schema validation to check for duplicate keys and enforce non-empty keys. - Added new utility functions for variable value lookup and display name generation. - Enhanced tests to cover new key-based variable scenarios. - Refactored KolPrompt repository to simplify prompt storage and management, including the removal of obsolete revision handling. - Introduced new API endpoints for saving, activating, and archiving prompts. - Added new utility functions for handling Kol placeholders and platform options in the admin web. - Implemented database migrations to simplify prompt storage structure and ensure data integrity.
This commit is contained in:
@@ -33,12 +33,11 @@ import type {
|
||||
KolAssistTask,
|
||||
KolPackageSummary,
|
||||
KolPackageDetail,
|
||||
PublishKolPromptRequest,
|
||||
KolSubscription,
|
||||
KolSubscriptionPromptCard,
|
||||
KolProfile,
|
||||
KolPromptDetail,
|
||||
KolPromptRevision,
|
||||
KolSaveDraftRequest,
|
||||
KolSubscriptionPromptSchema,
|
||||
KolGenerateRequest,
|
||||
KolGenerateResponse,
|
||||
@@ -323,15 +322,27 @@ export const kolManageApi = {
|
||||
deletePrompt(id: number) {
|
||||
return apiClient.remove<null>(`/api/tenant/kol/manage/prompts/${id}`);
|
||||
},
|
||||
saveDraft(promptId: number, body: KolSaveDraftRequest) {
|
||||
return apiClient.post<KolPromptRevision, KolSaveDraftRequest>(
|
||||
`/api/tenant/kol/manage/prompts/${promptId}/draft`,
|
||||
savePrompt(promptId: number, body: PublishKolPromptRequest) {
|
||||
return apiClient.post<KolPromptDetail, PublishKolPromptRequest>(
|
||||
`/api/tenant/kol/manage/prompts/${promptId}/save`,
|
||||
body,
|
||||
);
|
||||
},
|
||||
publishPrompt(promptId: number) {
|
||||
return apiClient.post<{ ok: boolean }, Record<string, never>>(
|
||||
publishPrompt(promptId: number, body: PublishKolPromptRequest) {
|
||||
return apiClient.post<KolPromptDetail, PublishKolPromptRequest>(
|
||||
`/api/tenant/kol/manage/prompts/${promptId}/publish`,
|
||||
body,
|
||||
);
|
||||
},
|
||||
activatePrompt(promptId: number) {
|
||||
return apiClient.put<KolPromptDetail, Record<string, never>>(
|
||||
`/api/tenant/kol/manage/prompts/${promptId}/activate`,
|
||||
{},
|
||||
);
|
||||
},
|
||||
archivePrompt(promptId: number) {
|
||||
return apiClient.put<KolPromptDetail, Record<string, never>>(
|
||||
`/api/tenant/kol/manage/prompts/${promptId}/archive`,
|
||||
{},
|
||||
);
|
||||
},
|
||||
@@ -348,7 +359,7 @@ export const kolManageApi = {
|
||||
|
||||
export const kolMarketplaceApi = {
|
||||
list(params: { industry?: string; keyword?: string; limit?: number; offset?: number }) {
|
||||
return apiClient.get<KolPackageSummary[]>("/api/tenant/kol/marketplace", { params });
|
||||
return apiClient.get<KolPackageSummary[]>("/api/tenant/kol/marketplace/packages", { params });
|
||||
},
|
||||
detail(id: number) {
|
||||
return apiClient.get<KolPackageDetail>(`/api/tenant/kol/marketplace/packages/${id}`);
|
||||
@@ -360,12 +371,10 @@ export const kolMarketplaceApi = {
|
||||
);
|
||||
},
|
||||
mySubscriptions() {
|
||||
return apiClient.get<KolSubscription[]>("/api/tenant/kol/marketplace/my-subscriptions");
|
||||
return apiClient.get<KolSubscription[]>("/api/tenant/kol/subscriptions");
|
||||
},
|
||||
mySubscriptionPrompts() {
|
||||
return apiClient.get<KolSubscriptionPromptCard[]>(
|
||||
"/api/tenant/kol/marketplace/my-subscription-prompts",
|
||||
);
|
||||
return apiClient.get<KolSubscriptionPromptCard[]>("/api/tenant/kol/subscription-prompts");
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import type { KolVariableDefinition, KolVariableType } from "@geo/shared-types";
|
||||
|
||||
import { newVariableId } from "./kol-variable-id";
|
||||
|
||||
const KOL_PLACEHOLDER_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
||||
|
||||
function normalizePlaceholderKey(value: string): string {
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function normalizeVariableType(type?: KolVariableType): KolVariableType {
|
||||
switch (type) {
|
||||
case "textarea":
|
||||
case "select":
|
||||
case "number":
|
||||
case "checkbox":
|
||||
return type;
|
||||
case "input":
|
||||
default:
|
||||
return "input";
|
||||
}
|
||||
}
|
||||
|
||||
export function buildKolPlaceholderToken(key: string): string {
|
||||
return `{{${normalizePlaceholderKey(key)}}}`;
|
||||
}
|
||||
|
||||
export function extractKolPlaceholderKeys(content: string): string[] {
|
||||
const text = String(content ?? "");
|
||||
const seen = new Set<string>();
|
||||
const keys: string[] = [];
|
||||
|
||||
for (const match of text.matchAll(KOL_PLACEHOLDER_RE)) {
|
||||
const key = normalizePlaceholderKey(match[1] ?? "");
|
||||
if (!key || seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
export function findKolPlaceholderKeyAtPosition(content: string, position: number): string | null {
|
||||
const text = String(content ?? "");
|
||||
if (!text || position < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const match of text.matchAll(KOL_PLACEHOLDER_RE)) {
|
||||
const token = match[0] ?? "";
|
||||
const start = match.index ?? -1;
|
||||
const end = start + token.length;
|
||||
if (start < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (position >= start && position <= end) {
|
||||
return normalizePlaceholderKey(match[1] ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createKolVariableDefinition(
|
||||
key: string,
|
||||
overrides: Partial<KolVariableDefinition> = {},
|
||||
): KolVariableDefinition {
|
||||
const normalizedKey = normalizePlaceholderKey(key);
|
||||
|
||||
return {
|
||||
id: overrides.id?.trim() || newVariableId(),
|
||||
key: normalizedKey,
|
||||
label: overrides.label?.trim() || normalizedKey,
|
||||
type: normalizeVariableType(overrides.type),
|
||||
required: overrides.required ?? true,
|
||||
placeholder: overrides.placeholder,
|
||||
options: overrides.options,
|
||||
};
|
||||
}
|
||||
|
||||
export function syncKolVariablesWithContent(
|
||||
content: string,
|
||||
variables: KolVariableDefinition[],
|
||||
): KolVariableDefinition[] {
|
||||
const keys = extractKolPlaceholderKeys(content);
|
||||
const byKey = new Map<string, KolVariableDefinition>();
|
||||
|
||||
for (const variable of variables) {
|
||||
const key = normalizePlaceholderKey(variable.key || variable.label || "");
|
||||
if (!key || byKey.has(key)) {
|
||||
continue;
|
||||
}
|
||||
byKey.set(
|
||||
key,
|
||||
createKolVariableDefinition(key, {
|
||||
...variable,
|
||||
key,
|
||||
label: variable.label?.trim() || key,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return keys.map((key) => {
|
||||
const existing = byKey.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
return createKolVariableDefinition(key);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface KolPlatformOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export function buildKolPlatformOptions(defaultLabel: string): KolPlatformOption[] {
|
||||
return [
|
||||
{ label: defaultLabel, value: "通用" },
|
||||
{ label: "小红书", value: "xiaohongshu" },
|
||||
{ label: "微信", value: "wechat" },
|
||||
{ label: "豆包", value: "doubao" },
|
||||
{ label: "DeepSeek", value: "deepseek" },
|
||||
{ label: "ChatGPT", value: "chatgpt" },
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user