chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,60 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
|
||||
import { message } from "ant-design-vue";
|
||||
import { LeftOutlined, SaveOutlined, SendOutlined, EyeOutlined } from "@ant-design/icons-vue";
|
||||
import { EyeOutlined, LeftOutlined, SaveOutlined, SendOutlined } from '@ant-design/icons-vue'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import KolVariablePanel from "./KolVariablePanel.vue";
|
||||
import KolVariableConfig from "./KolVariableConfig.vue";
|
||||
import KolPromptEditArea from "./KolPromptEditArea.vue";
|
||||
import KolDynamicForm from "./KolDynamicForm.vue";
|
||||
import EditorAiAssistPanel from "@/components/editor-common/EditorAiAssistPanel.vue";
|
||||
import { kolManageApi } from "@/lib/api";
|
||||
import type { KolAssistRequest, KolAssistStreamEvent, 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 EditorAiAssistPanel from '@/components/editor-common/EditorAiAssistPanel.vue'
|
||||
import { kolManageApi } from '@/lib/api'
|
||||
import { formatError } from '@/lib/errors'
|
||||
import { mergeKolCardConfig, parseKolCardConfig } from '@/lib/kol-card-config'
|
||||
import {
|
||||
getKolVariableInitialValue,
|
||||
normalizeKolVariableDefinition,
|
||||
syncKolVariablesWithContent,
|
||||
} from "@/lib/kol-placeholders";
|
||||
} from '@/lib/kol-placeholders'
|
||||
import { buildKolPlatformOptions } from '@/lib/kol-platform-options'
|
||||
import type {
|
||||
KolAssistRequest,
|
||||
KolAssistStreamEvent,
|
||||
KolCardConfig,
|
||||
KolVariableDefinition,
|
||||
} from '@geo/shared-types'
|
||||
import KolDynamicForm from './KolDynamicForm.vue'
|
||||
import KolPromptEditArea from './KolPromptEditArea.vue'
|
||||
import KolVariableConfig from './KolVariableConfig.vue'
|
||||
import KolVariablePanel from './KolVariablePanel.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
promptId: number;
|
||||
}>();
|
||||
promptId: number
|
||||
}>()
|
||||
|
||||
type AiOptimizeStatus = "idle" | "generating" | "completed" | "error";
|
||||
type EditorAiAssistPlacement = "bottom" | "top";
|
||||
type AiOptimizeStatus = 'idle' | 'generating' | 'completed' | 'error'
|
||||
type EditorAiAssistPlacement = 'bottom' | 'top'
|
||||
type EditorAiAssistBoundary = {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
};
|
||||
left: number
|
||||
top: number
|
||||
right: number
|
||||
bottom: number
|
||||
}
|
||||
|
||||
type PromptSelectionAiSnapshot = {
|
||||
start: number;
|
||||
end: number;
|
||||
selectedText: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
placement: EditorAiAssistPlacement;
|
||||
boundary: EditorAiAssistBoundary;
|
||||
};
|
||||
start: number
|
||||
end: number
|
||||
selectedText: string
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
placement: EditorAiAssistPlacement
|
||||
boundary: EditorAiAssistBoundary
|
||||
}
|
||||
|
||||
type PromptSelectionAiPanelState = PromptSelectionAiSnapshot & {
|
||||
open: boolean;
|
||||
};
|
||||
open: boolean
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
back: [];
|
||||
}>();
|
||||
back: []
|
||||
}>()
|
||||
|
||||
const { t } = useI18n();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useI18n()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
function createDefaultPromptSelectionAiPanelState(): PromptSelectionAiPanelState {
|
||||
return {
|
||||
@@ -62,310 +67,312 @@ function createDefaultPromptSelectionAiPanelState(): PromptSelectionAiPanelState
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
placement: "bottom",
|
||||
placement: 'bottom',
|
||||
boundary: { left: 0, top: 0, right: 0, bottom: 0 },
|
||||
start: 0,
|
||||
end: 0,
|
||||
selectedText: "",
|
||||
};
|
||||
selectedText: '',
|
||||
}
|
||||
}
|
||||
|
||||
const content = ref("");
|
||||
const variables = ref<KolVariableDefinition[]>([]);
|
||||
const previewOpen = ref(false);
|
||||
const previewValues = ref<Record<string, any>>({});
|
||||
const aiBusy = ref(false);
|
||||
const selectionAiPanel = ref<PromptSelectionAiPanelState>(createDefaultPromptSelectionAiPanelState());
|
||||
const selectionAiInstruction = ref("");
|
||||
const selectionAiPreview = ref("");
|
||||
const selectionAiError = ref("");
|
||||
const selectionAiStatus = ref<AiOptimizeStatus>("idle");
|
||||
const cardConfig = ref<KolCardConfig>({});
|
||||
const content = ref('')
|
||||
const variables = ref<KolVariableDefinition[]>([])
|
||||
const previewOpen = ref(false)
|
||||
const previewValues = ref<Record<string, any>>({})
|
||||
const aiBusy = ref(false)
|
||||
const selectionAiPanel = ref<PromptSelectionAiPanelState>(
|
||||
createDefaultPromptSelectionAiPanelState(),
|
||||
)
|
||||
const selectionAiInstruction = ref('')
|
||||
const selectionAiPreview = ref('')
|
||||
const selectionAiError = ref('')
|
||||
const selectionAiStatus = ref<AiOptimizeStatus>('idle')
|
||||
const cardConfig = ref<KolCardConfig>({})
|
||||
const variableConfigRef = ref<{
|
||||
focusVariable: (key: string, options?: { focusInput?: boolean }) => void;
|
||||
} | null>(null);
|
||||
focusVariable: (key: string, options?: { focusInput?: boolean }) => void
|
||||
} | null>(null)
|
||||
const metadataForm = reactive({
|
||||
name: "",
|
||||
platform_hint: "通用",
|
||||
name: '',
|
||||
platform_hint: '通用',
|
||||
allow_web_search: false,
|
||||
allow_user_knowledge: true,
|
||||
});
|
||||
})
|
||||
|
||||
const { data: promptDetail } = useQuery({
|
||||
queryKey: computed(() => ["kol", "prompt", props.promptId]),
|
||||
queryKey: computed(() => ['kol', 'prompt', props.promptId]),
|
||||
queryFn: () => kolManageApi.getPrompt(props.promptId),
|
||||
});
|
||||
})
|
||||
|
||||
const platformOptions = computed(() => {
|
||||
const options = buildKolPlatformOptions(t("kol.manage.platformHintDefault"));
|
||||
const currentValue = metadataForm.platform_hint.trim();
|
||||
const options = buildKolPlatformOptions(t('kol.manage.platformHintDefault'))
|
||||
const currentValue = metadataForm.platform_hint.trim()
|
||||
if (currentValue && !options.some((option) => option.value === currentValue)) {
|
||||
return [{ label: currentValue, value: currentValue }, ...options];
|
||||
return [{ label: currentValue, value: currentValue }, ...options]
|
||||
}
|
||||
return options;
|
||||
});
|
||||
const selectionAiStreaming = computed(() => selectionAiStatus.value === "generating");
|
||||
const selectionAiHasPreview = computed(() => selectionAiPreview.value.trim().length > 0);
|
||||
return options
|
||||
})
|
||||
const selectionAiStreaming = computed(() => selectionAiStatus.value === 'generating')
|
||||
const selectionAiHasPreview = computed(() => selectionAiPreview.value.trim().length > 0)
|
||||
const selectionAiUiText = computed(() => ({
|
||||
title: t("article.editor.aiOptimize.title"),
|
||||
promptPlaceholder: t("kol.manage.editor.aiOptimizePromptPlaceholder"),
|
||||
generating: t("article.editor.aiOptimize.generating"),
|
||||
failed: t("article.editor.aiOptimize.messages.failed"),
|
||||
disclaimer: t("kol.manage.editor.aiOptimizeDisclaimer"),
|
||||
regenerate: t("article.editor.aiOptimize.regenerate"),
|
||||
close: t("article.editor.aiOptimize.close"),
|
||||
replace: t("kol.manage.editor.aiOptimizeReplace"),
|
||||
}));
|
||||
title: t('article.editor.aiOptimize.title'),
|
||||
promptPlaceholder: t('kol.manage.editor.aiOptimizePromptPlaceholder'),
|
||||
generating: t('article.editor.aiOptimize.generating'),
|
||||
failed: t('article.editor.aiOptimize.messages.failed'),
|
||||
disclaimer: t('kol.manage.editor.aiOptimizeDisclaimer'),
|
||||
regenerate: t('article.editor.aiOptimize.regenerate'),
|
||||
close: t('article.editor.aiOptimize.close'),
|
||||
replace: t('kol.manage.editor.aiOptimizeReplace'),
|
||||
}))
|
||||
|
||||
watch(
|
||||
() => promptDetail.value,
|
||||
(detail) => {
|
||||
if (detail) {
|
||||
const nextCardConfig = detail.latest_card_config_json ?? {};
|
||||
const parsedCardConfig = parseKolCardConfig(nextCardConfig);
|
||||
const nextCardConfig = detail.latest_card_config_json ?? {}
|
||||
const parsedCardConfig = parseKolCardConfig(nextCardConfig)
|
||||
|
||||
metadataForm.name = detail.name;
|
||||
metadataForm.platform_hint = detail.platform_hint || "通用";
|
||||
metadataForm.allow_web_search = parsedCardConfig.allow_web_search;
|
||||
metadataForm.allow_user_knowledge = parsedCardConfig.allow_user_knowledge;
|
||||
content.value = detail.latest_prompt_content ?? "";
|
||||
metadataForm.name = detail.name
|
||||
metadataForm.platform_hint = detail.platform_hint || '通用'
|
||||
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 ?? []).map((variable) =>
|
||||
normalizeKolVariableDefinition(variable),
|
||||
);
|
||||
cardConfig.value = { ...nextCardConfig };
|
||||
)
|
||||
cardConfig.value = { ...nextCardConfig }
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const publishMutation = useMutation({
|
||||
mutationFn: (payload: ReturnType<typeof buildPromptPayload>) =>
|
||||
kolManageApi.publishPrompt(props.promptId, payload),
|
||||
onSuccess: () => {
|
||||
message.success(t("kol.manage.editor.publish") + "成功");
|
||||
invalidatePromptQueries();
|
||||
message.success(t('kol.manage.editor.publish') + '成功')
|
||||
invalidatePromptQueries()
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(formatError(error));
|
||||
message.error(formatError(error))
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: (payload: ReturnType<typeof buildPromptPayload>) =>
|
||||
kolManageApi.savePrompt(props.promptId, payload),
|
||||
onSuccess: () => {
|
||||
message.success(t("common.save") + "成功");
|
||||
invalidatePromptQueries();
|
||||
message.success(t('common.save') + '成功')
|
||||
invalidatePromptQueries()
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(formatError(error));
|
||||
message.error(formatError(error))
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
let aiAssistAbortController: AbortController | null = null;
|
||||
let selectionAiAbortController: AbortController | null = null;
|
||||
let aiAssistAbortController: AbortController | null = null
|
||||
let selectionAiAbortController: AbortController | null = null
|
||||
|
||||
function applyAssistResult(event: KolAssistStreamEvent) {
|
||||
const nextContent = (event.content ?? "").trim();
|
||||
const nextContent = (event.content ?? '').trim()
|
||||
if (!nextContent) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
content.value = nextContent;
|
||||
content.value = nextContent
|
||||
variables.value = syncKolVariablesWithContent(
|
||||
nextContent,
|
||||
event.schema?.variables ?? variables.value,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function handleAssistStreamEvent(event: KolAssistStreamEvent): "completed" | "error" | null {
|
||||
if (event.type === "start") {
|
||||
aiBusy.value = true;
|
||||
return null;
|
||||
function handleAssistStreamEvent(event: KolAssistStreamEvent): 'completed' | 'error' | null {
|
||||
if (event.type === 'start') {
|
||||
aiBusy.value = true
|
||||
return null
|
||||
}
|
||||
|
||||
if (event.type === "delta") {
|
||||
aiBusy.value = true;
|
||||
if (typeof event.content === "string") {
|
||||
content.value = event.content;
|
||||
} else if (typeof event.delta === "string" && event.delta) {
|
||||
content.value += event.delta;
|
||||
if (event.type === 'delta') {
|
||||
aiBusy.value = true
|
||||
if (typeof event.content === 'string') {
|
||||
content.value = event.content
|
||||
} else if (typeof event.delta === 'string' && event.delta) {
|
||||
content.value += event.delta
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
if (event.type === "completed") {
|
||||
aiBusy.value = false;
|
||||
applyAssistResult(event);
|
||||
message.success("AI 处理完成");
|
||||
return "completed";
|
||||
if (event.type === 'completed') {
|
||||
aiBusy.value = false
|
||||
applyAssistResult(event)
|
||||
message.success('AI 处理完成')
|
||||
return 'completed'
|
||||
}
|
||||
|
||||
if (event.type === "error") {
|
||||
aiBusy.value = false;
|
||||
if (typeof event.content === "string" && event.content.trim()) {
|
||||
content.value = event.content;
|
||||
if (event.type === 'error') {
|
||||
aiBusy.value = false
|
||||
if (typeof event.content === 'string' && event.content.trim()) {
|
||||
content.value = event.content
|
||||
}
|
||||
message.error(event.error || "AI 处理失败");
|
||||
return "error";
|
||||
message.error(event.error || 'AI 处理失败')
|
||||
return 'error'
|
||||
}
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
function stopAiAssistStream() {
|
||||
aiAssistAbortController?.abort();
|
||||
aiAssistAbortController = null;
|
||||
aiBusy.value = false;
|
||||
aiAssistAbortController?.abort()
|
||||
aiAssistAbortController = null
|
||||
aiBusy.value = false
|
||||
}
|
||||
|
||||
async function runAiAssistStream(payload: KolAssistRequest) {
|
||||
stopAiAssistStream();
|
||||
stopAiAssistStream()
|
||||
|
||||
const controller = new AbortController();
|
||||
aiAssistAbortController = controller;
|
||||
aiBusy.value = true;
|
||||
let terminalState: "completed" | "error" | null = null;
|
||||
const controller = new AbortController()
|
||||
aiAssistAbortController = controller
|
||||
aiBusy.value = true
|
||||
let terminalState: 'completed' | 'error' | null = null
|
||||
|
||||
try {
|
||||
await kolManageApi.streamAssist(
|
||||
payload,
|
||||
{
|
||||
onEvent(event) {
|
||||
terminalState = handleAssistStreamEvent(event);
|
||||
terminalState = handleAssistStreamEvent(event)
|
||||
},
|
||||
onClose() {
|
||||
if (aiAssistAbortController === controller && terminalState === null) {
|
||||
aiBusy.value = false;
|
||||
aiBusy.value = false
|
||||
}
|
||||
},
|
||||
},
|
||||
controller.signal,
|
||||
);
|
||||
)
|
||||
|
||||
if (!controller.signal.aborted && terminalState === null) {
|
||||
aiBusy.value = false;
|
||||
message.error("AI 处理失败");
|
||||
aiBusy.value = false
|
||||
message.error('AI 处理失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
aiBusy.value = false;
|
||||
message.error(formatError(error));
|
||||
aiBusy.value = false
|
||||
message.error(formatError(error))
|
||||
} finally {
|
||||
if (aiAssistAbortController === controller) {
|
||||
aiAssistAbortController = null;
|
||||
aiAssistAbortController = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAiGenerate(description: string) {
|
||||
await runAiAssistStream({
|
||||
mode: "generate",
|
||||
mode: 'generate',
|
||||
description,
|
||||
prompt_id: props.promptId,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function resetSelectionAiOutput() {
|
||||
selectionAiPreview.value = "";
|
||||
selectionAiError.value = "";
|
||||
selectionAiStatus.value = "idle";
|
||||
selectionAiPreview.value = ''
|
||||
selectionAiError.value = ''
|
||||
selectionAiStatus.value = 'idle'
|
||||
}
|
||||
|
||||
function stopSelectionAiStream() {
|
||||
selectionAiAbortController?.abort();
|
||||
selectionAiAbortController = null;
|
||||
selectionAiAbortController?.abort()
|
||||
selectionAiAbortController = null
|
||||
|
||||
if (selectionAiStatus.value === "generating") {
|
||||
selectionAiStatus.value = selectionAiHasPreview.value ? "completed" : "idle";
|
||||
selectionAiError.value = "";
|
||||
if (selectionAiStatus.value === 'generating') {
|
||||
selectionAiStatus.value = selectionAiHasPreview.value ? 'completed' : 'idle'
|
||||
selectionAiError.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function closeSelectionAiPanel() {
|
||||
stopSelectionAiStream();
|
||||
selectionAiPanel.value = createDefaultPromptSelectionAiPanelState();
|
||||
selectionAiInstruction.value = "";
|
||||
resetSelectionAiOutput();
|
||||
stopSelectionAiStream()
|
||||
selectionAiPanel.value = createDefaultPromptSelectionAiPanelState()
|
||||
selectionAiInstruction.value = ''
|
||||
resetSelectionAiOutput()
|
||||
}
|
||||
|
||||
function handleOpenSelectionAiPanel(snapshot: PromptSelectionAiSnapshot) {
|
||||
stopAiAssistStream();
|
||||
stopSelectionAiStream();
|
||||
stopAiAssistStream()
|
||||
stopSelectionAiStream()
|
||||
selectionAiPanel.value = {
|
||||
open: true,
|
||||
...snapshot,
|
||||
};
|
||||
selectionAiInstruction.value = "";
|
||||
resetSelectionAiOutput();
|
||||
}
|
||||
selectionAiInstruction.value = ''
|
||||
resetSelectionAiOutput()
|
||||
}
|
||||
|
||||
function handleSelectionAiStreamEvent(event: KolAssistStreamEvent) {
|
||||
if (event.type === "start") {
|
||||
selectionAiStatus.value = "generating";
|
||||
selectionAiError.value = "";
|
||||
return;
|
||||
if (event.type === 'start') {
|
||||
selectionAiStatus.value = 'generating'
|
||||
selectionAiError.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
if (event.type === "delta") {
|
||||
selectionAiStatus.value = "generating";
|
||||
selectionAiError.value = "";
|
||||
if (typeof event.content === "string") {
|
||||
selectionAiPreview.value = event.content;
|
||||
} else if (typeof event.delta === "string") {
|
||||
selectionAiPreview.value += event.delta;
|
||||
if (event.type === 'delta') {
|
||||
selectionAiStatus.value = 'generating'
|
||||
selectionAiError.value = ''
|
||||
if (typeof event.content === 'string') {
|
||||
selectionAiPreview.value = event.content
|
||||
} else if (typeof event.delta === 'string') {
|
||||
selectionAiPreview.value += event.delta
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (event.type === "completed") {
|
||||
selectionAiStatus.value = "completed";
|
||||
selectionAiError.value = "";
|
||||
if (typeof event.content === "string") {
|
||||
selectionAiPreview.value = event.content;
|
||||
if (event.type === 'completed') {
|
||||
selectionAiStatus.value = 'completed'
|
||||
selectionAiError.value = ''
|
||||
if (typeof event.content === 'string') {
|
||||
selectionAiPreview.value = event.content
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (event.type === "error") {
|
||||
selectionAiStatus.value = selectionAiHasPreview.value ? "completed" : "error";
|
||||
selectionAiError.value = event.error || t("article.editor.aiOptimize.messages.failed");
|
||||
if (typeof event.content === "string" && event.content.trim()) {
|
||||
selectionAiPreview.value = event.content;
|
||||
if (event.type === 'error') {
|
||||
selectionAiStatus.value = selectionAiHasPreview.value ? 'completed' : 'error'
|
||||
selectionAiError.value = event.error || t('article.editor.aiOptimize.messages.failed')
|
||||
if (typeof event.content === 'string' && event.content.trim()) {
|
||||
selectionAiPreview.value = event.content
|
||||
}
|
||||
if (!selectionAiHasPreview.value) {
|
||||
message.error(selectionAiError.value);
|
||||
message.error(selectionAiError.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function generateSelectionAiPreview() {
|
||||
if (!selectionAiPanel.value.open) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const instruction = selectionAiInstruction.value.trim();
|
||||
const instruction = selectionAiInstruction.value.trim()
|
||||
if (!instruction) {
|
||||
message.warning(t("article.editor.aiOptimize.messages.promptRequired"));
|
||||
return;
|
||||
message.warning(t('article.editor.aiOptimize.messages.promptRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
stopSelectionAiStream();
|
||||
selectionAiPreview.value = "";
|
||||
selectionAiError.value = "";
|
||||
selectionAiStatus.value = "generating";
|
||||
stopSelectionAiStream()
|
||||
selectionAiPreview.value = ''
|
||||
selectionAiError.value = ''
|
||||
selectionAiStatus.value = 'generating'
|
||||
|
||||
const syncedVariables = syncVariablesFromContent();
|
||||
const controller = new AbortController();
|
||||
selectionAiAbortController = controller;
|
||||
const syncedVariables = syncVariablesFromContent()
|
||||
const controller = new AbortController()
|
||||
selectionAiAbortController = controller
|
||||
|
||||
try {
|
||||
await kolManageApi.streamAssist(
|
||||
{
|
||||
mode: "optimize",
|
||||
mode: 'optimize',
|
||||
description: instruction,
|
||||
current_content: selectionAiPanel.value.selectedText,
|
||||
schema: { variables: syncedVariables },
|
||||
@@ -375,49 +382,49 @@ async function generateSelectionAiPreview() {
|
||||
onEvent: handleSelectionAiStreamEvent,
|
||||
},
|
||||
controller.signal,
|
||||
);
|
||||
)
|
||||
|
||||
if (!controller.signal.aborted && selectionAiStatus.value === "generating") {
|
||||
selectionAiStatus.value = selectionAiHasPreview.value ? "completed" : "error";
|
||||
if (!controller.signal.aborted && selectionAiStatus.value === 'generating') {
|
||||
selectionAiStatus.value = selectionAiHasPreview.value ? 'completed' : 'error'
|
||||
}
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
selectionAiStatus.value = selectionAiHasPreview.value ? "completed" : "error";
|
||||
selectionAiError.value = formatError(error);
|
||||
selectionAiStatus.value = selectionAiHasPreview.value ? 'completed' : 'error'
|
||||
selectionAiError.value = formatError(error)
|
||||
if (!selectionAiHasPreview.value) {
|
||||
message.error(selectionAiError.value);
|
||||
message.error(selectionAiError.value)
|
||||
}
|
||||
} finally {
|
||||
if (selectionAiAbortController === controller) {
|
||||
selectionAiAbortController = null;
|
||||
selectionAiAbortController = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applySelectionAiReplacement() {
|
||||
const replacement = selectionAiPreview.value.trim();
|
||||
const replacement = selectionAiPreview.value.trim()
|
||||
if (!selectionAiPanel.value.open || !replacement) {
|
||||
if (!replacement) {
|
||||
message.warning(t("article.editor.aiOptimize.messages.empty"));
|
||||
message.warning(t('article.editor.aiOptimize.messages.empty'))
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const target = { ...selectionAiPanel.value };
|
||||
const currentSelectedText = content.value.slice(target.start, target.end);
|
||||
const target = { ...selectionAiPanel.value }
|
||||
const currentSelectedText = content.value.slice(target.start, target.end)
|
||||
if (currentSelectedText !== target.selectedText) {
|
||||
message.warning(t("article.editor.aiOptimize.messages.selectionChanged"));
|
||||
return;
|
||||
message.warning(t('article.editor.aiOptimize.messages.selectionChanged'))
|
||||
return
|
||||
}
|
||||
|
||||
const nextContent =
|
||||
content.value.slice(0, target.start) + replacement + content.value.slice(target.end);
|
||||
content.value = nextContent;
|
||||
variables.value = syncKolVariablesWithContent(nextContent, variables.value);
|
||||
closeSelectionAiPanel();
|
||||
content.value.slice(0, target.start) + replacement + content.value.slice(target.end)
|
||||
content.value = nextContent
|
||||
variables.value = syncKolVariablesWithContent(nextContent, variables.value)
|
||||
closeSelectionAiPanel()
|
||||
}
|
||||
|
||||
function buildPromptPayload(nextVariables: KolVariableDefinition[] = variables.value) {
|
||||
@@ -431,131 +438,131 @@ function buildPromptPayload(nextVariables: KolVariableDefinition[] = variables.v
|
||||
allow_web_search: metadataForm.allow_web_search,
|
||||
allow_user_knowledge: metadataForm.allow_user_knowledge,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function syncVariablesFromContent(): KolVariableDefinition[] {
|
||||
const syncedVariables = syncKolVariablesWithContent(content.value, variables.value);
|
||||
variables.value = syncedVariables;
|
||||
return syncedVariables;
|
||||
const syncedVariables = syncKolVariablesWithContent(content.value, variables.value)
|
||||
variables.value = syncedVariables
|
||||
return syncedVariables
|
||||
}
|
||||
|
||||
function buildPreviewValues(nextVariables: KolVariableDefinition[]): Record<string, any> {
|
||||
const nextValues: Record<string, any> = {};
|
||||
const nextValues: Record<string, any> = {}
|
||||
|
||||
nextVariables.forEach((variable) => {
|
||||
const fieldKey = variable.key?.trim() || variable.id;
|
||||
nextValues[fieldKey] = getKolVariableInitialValue(variable);
|
||||
});
|
||||
const fieldKey = variable.key?.trim() || variable.id
|
||||
nextValues[fieldKey] = getKolVariableInitialValue(variable)
|
||||
})
|
||||
|
||||
return nextValues;
|
||||
return nextValues
|
||||
}
|
||||
|
||||
function invalidatePromptQueries() {
|
||||
void queryClient.invalidateQueries({ queryKey: ["kol", "prompt", props.promptId] });
|
||||
void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] });
|
||||
void queryClient.invalidateQueries({ queryKey: ['kol', 'prompt', props.promptId] })
|
||||
void queryClient.invalidateQueries({ queryKey: ['kol', 'packages'] })
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ["kol", "packages", promptDetail.value?.package_id, "prompts"],
|
||||
});
|
||||
queryKey: ['kol', 'packages', promptDetail.value?.package_id, 'prompts'],
|
||||
})
|
||||
}
|
||||
|
||||
function validateBeforeSubmit(): boolean {
|
||||
if (!metadataForm.name.trim()) {
|
||||
message.warning(t("common.inputPlease") + t("common.title"));
|
||||
return false;
|
||||
message.warning(t('common.inputPlease') + t('common.title'))
|
||||
return false
|
||||
}
|
||||
if (!content.value.trim()) {
|
||||
message.warning("请输入提示词内容");
|
||||
return false;
|
||||
message.warning('请输入提示词内容')
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
const syncedVariables = syncVariablesFromContent();
|
||||
const syncedVariables = syncVariablesFromContent()
|
||||
if (!validateBeforeSubmit()) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
saveMutation.mutate(buildPromptPayload(syncedVariables));
|
||||
saveMutation.mutate(buildPromptPayload(syncedVariables))
|
||||
}
|
||||
|
||||
function handleSaveShortcut(event: KeyboardEvent) {
|
||||
if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "s") {
|
||||
return;
|
||||
if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== 's') {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (event.repeat || saveMutation.isPending.value || publishMutation.isPending.value) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
handleSave();
|
||||
handleSave()
|
||||
}
|
||||
|
||||
function handlePublish() {
|
||||
const syncedVariables = syncVariablesFromContent();
|
||||
const syncedVariables = syncVariablesFromContent()
|
||||
if (!validateBeforeSubmit()) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
publishMutation.mutate(buildPromptPayload(syncedVariables));
|
||||
publishMutation.mutate(buildPromptPayload(syncedVariables))
|
||||
}
|
||||
|
||||
function handlePreview() {
|
||||
const syncedVariables = syncVariablesFromContent();
|
||||
previewValues.value = buildPreviewValues(syncedVariables);
|
||||
previewOpen.value = true;
|
||||
const syncedVariables = syncVariablesFromContent()
|
||||
previewValues.value = buildPreviewValues(syncedVariables)
|
||||
previewOpen.value = true
|
||||
}
|
||||
|
||||
async function handleFocusVariable(key: string) {
|
||||
syncVariablesFromContent();
|
||||
await nextTick();
|
||||
variableConfigRef.value?.focusVariable(key, { focusInput: false });
|
||||
syncVariablesFromContent()
|
||||
await nextTick()
|
||||
variableConfigRef.value?.focusVariable(key, { focusInput: false })
|
||||
}
|
||||
|
||||
function getSelectPopupContainer(triggerNode: HTMLElement) {
|
||||
return triggerNode.parentElement ?? triggerNode;
|
||||
return triggerNode.parentElement ?? triggerNode
|
||||
}
|
||||
|
||||
function statusTagColor(status?: string): string {
|
||||
if (status === "active") return "green";
|
||||
if (status === "archived") return "orange";
|
||||
return "default";
|
||||
if (status === 'active') return 'green'
|
||||
if (status === 'archived') return 'orange'
|
||||
return 'default'
|
||||
}
|
||||
|
||||
function statusLabel(status?: string): string {
|
||||
if (status === "active") return t("kol.manage.status.active");
|
||||
if (status === "archived") return t("kol.manage.status.archived");
|
||||
return t("kol.manage.status.draft");
|
||||
if (status === 'active') return t('kol.manage.status.active')
|
||||
if (status === 'archived') return t('kol.manage.status.archived')
|
||||
return t('kol.manage.status.draft')
|
||||
}
|
||||
|
||||
function handleWindowPointerDown(event: PointerEvent) {
|
||||
if (!(selectionAiPanel.value.open && selectionAiStatus.value === "idle")) {
|
||||
return;
|
||||
if (!(selectionAiPanel.value.open && selectionAiStatus.value === 'idle')) {
|
||||
return
|
||||
}
|
||||
|
||||
const target = event.target;
|
||||
const target = event.target
|
||||
if (
|
||||
target instanceof Element &&
|
||||
(target.closest(".editor-ai-assist") || target.closest(".prompt-selection-ai-action"))
|
||||
(target.closest('.editor-ai-assist') || target.closest('.prompt-selection-ai-action'))
|
||||
) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
closeSelectionAiPanel();
|
||||
closeSelectionAiPanel()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("keydown", handleSaveShortcut);
|
||||
window.addEventListener("pointerdown", handleWindowPointerDown);
|
||||
});
|
||||
window.addEventListener('keydown', handleSaveShortcut)
|
||||
window.addEventListener('pointerdown', handleWindowPointerDown)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("keydown", handleSaveShortcut);
|
||||
window.removeEventListener("pointerdown", handleWindowPointerDown);
|
||||
stopAiAssistStream();
|
||||
stopSelectionAiStream();
|
||||
});
|
||||
window.removeEventListener('keydown', handleSaveShortcut)
|
||||
window.removeEventListener('pointerdown', handleWindowPointerDown)
|
||||
stopAiAssistStream()
|
||||
stopSelectionAiStream()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -563,19 +570,21 @@ onBeforeUnmount(() => {
|
||||
<div class="editor-topbar">
|
||||
<button class="back-btn" type="button" @click="emit('back')">
|
||||
<LeftOutlined />
|
||||
<span>{{ t("common.back") }}</span>
|
||||
<span>{{ t('common.back') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="editor-header">
|
||||
<div class="header-title">
|
||||
<div class="title-row">
|
||||
<span class="prompt-name">{{ metadataForm.name || promptDetail?.name || t("common.loading") }}</span>
|
||||
<span class="prompt-name">
|
||||
{{ metadataForm.name || promptDetail?.name || t('common.loading') }}
|
||||
</span>
|
||||
<a-tag v-if="promptDetail" :color="statusTagColor(promptDetail.status)">
|
||||
{{ statusLabel(promptDetail.status) }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<span class="prompt-subtitle">{{ t("kol.manage.editor.basicInfo") }}</span>
|
||||
<span class="prompt-subtitle">{{ t('kol.manage.editor.basicInfo') }}</span>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<a-button @click="handlePreview">
|
||||
@@ -599,7 +608,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
|
||||
<div class="editor-meta-card">
|
||||
<div class="editor-section-title">{{ t("kol.manage.editor.basicInfo") }}</div>
|
||||
<div class="editor-section-title">{{ t('kol.manage.editor.basicInfo') }}</div>
|
||||
<a-form layout="vertical" class="editor-meta-form">
|
||||
<a-form-item :label="t('common.title')" required>
|
||||
<a-input
|
||||
@@ -628,7 +637,9 @@ onBeforeUnmount(() => {
|
||||
<a-form-item label="生成文章时允许用户使用自己的知识库">
|
||||
<div class="editor-switch-field">
|
||||
<a-switch v-model:checked="metadataForm.allow_user_knowledge" />
|
||||
<span class="editor-switch-hint">默认开启,关闭后用户在生成页无法选择自己的知识库。</span>
|
||||
<span class="editor-switch-hint">
|
||||
默认开启,关闭后用户在生成页无法选择自己的知识库。
|
||||
</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -645,10 +656,7 @@ onBeforeUnmount(() => {
|
||||
@ai-optimize-selection="handleOpenSelectionAiPanel"
|
||||
@focus-variable="handleFocusVariable"
|
||||
/>
|
||||
<KolVariableConfig
|
||||
ref="variableConfigRef"
|
||||
v-model:variables="variables"
|
||||
/>
|
||||
<KolVariableConfig ref="variableConfigRef" v-model:variables="variables" />
|
||||
</div>
|
||||
|
||||
<a-modal
|
||||
@@ -657,10 +665,7 @@ onBeforeUnmount(() => {
|
||||
:footer="null"
|
||||
width="600px"
|
||||
>
|
||||
<KolDynamicForm
|
||||
:variables="variables"
|
||||
v-model:modelValue="previewValues"
|
||||
/>
|
||||
<KolDynamicForm v-model:modelValue="previewValues" :variables="variables" />
|
||||
</a-modal>
|
||||
|
||||
<EditorAiAssistPanel
|
||||
|
||||
Reference in New Issue
Block a user