forked from b2894lxlx/tuanwei-admin
feat(ai): 写作模式拆成自由创作与模板两条路(issue 18)
自由创作不再解析自然语言:原话整段作为 userContent 下发,docType 留空,由后端转 「AI 创意助手」。选了文体才提交 docType 等结构化条件,走「内容创作与宣传助手」。 - writing-params.ts 删掉 parseWritingRequest、字段别名表与字数兜底表,只留选项常量。 - chatgpt/index.vue:字数选择器只在选中文体后出现;撤掉图片比例选择器(上游开始 节点无比例变量,只能拼进 user_content,等于替上游写 prompt);结果不再用入参补标题。 - gxmu/manuscriptGen:新增「自由创作」入口与 freeContent 文本域,模板模式才展开活动 信息等可选项。 - 顺带删除 chatgpt/index.bak.vue 旧备份。
This commit is contained in:
@@ -226,32 +226,20 @@
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
<span class="ratio-label">字数</span>
|
||||
<button
|
||||
v-for="item in WRITING_WORD_COUNT_OPTIONS"
|
||||
:key="item.value"
|
||||
class="ratio-option"
|
||||
type="button"
|
||||
:class="{ 'is-active': writingWordCount === item.value }"
|
||||
:disabled="loading"
|
||||
@click="writingWordCount = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="activeChatMode === 'image'" class="ratio-panel">
|
||||
<span class="ratio-label">图片比例</span>
|
||||
<button
|
||||
v-for="item in imageRatioOptions"
|
||||
:key="item.value"
|
||||
class="ratio-option"
|
||||
type="button"
|
||||
:class="{ 'is-active': imageRatio === item.value }"
|
||||
:disabled="loading"
|
||||
@click="imageRatio = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
<template v-if="writingTemplateMode">
|
||||
<span class="ratio-label">字数</span>
|
||||
<button
|
||||
v-for="item in WRITING_WORD_COUNT_OPTIONS"
|
||||
:key="item.value"
|
||||
class="ratio-option"
|
||||
type="button"
|
||||
:class="{ 'is-active': writingWordCount === item.value }"
|
||||
:disabled="loading"
|
||||
@click="writingWordCount = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="activeChatMode === 'image'" class="ratio-panel attach-panel">
|
||||
<span class="ratio-label">参考素材</span>
|
||||
@@ -348,11 +336,10 @@ import type { AiChatList } from "@/api/ai/aiChatList/model";
|
||||
import { sendMessage, stopMessage } from "@/api/oa/chatgpt";
|
||||
import { manuscriptGen } from "@/api/ai/manuscriptGen";
|
||||
import {
|
||||
parseWritingRequest,
|
||||
WRITING_DOC_TYPE_FREE,
|
||||
WRITING_DOC_TYPE_OPTIONS,
|
||||
WRITING_WORD_COUNT_OPTIONS
|
||||
} from "./writing-params";
|
||||
import type { WritingOverrides } from "./writing-params";
|
||||
import { creativeAssistant, uploadAiFile } from "@/api/ai/creativeAssistant";
|
||||
import type { CreativeAssistantUploadFile } from "@/api/ai/creativeAssistant/model";
|
||||
import { API_BASE_URL } from "@/config/setting";
|
||||
@@ -404,7 +391,6 @@ const loading = ref(false);
|
||||
const stopping = ref(false);
|
||||
const content = ref("");
|
||||
const activeChatMode = ref<ChatMode>("chat");
|
||||
const imageRatio = ref("1:1");
|
||||
/**
|
||||
* 图片模式的参考素材,对应 Dify「AI 创意助手」开始节点的 file-list 变量 `user_files`。
|
||||
*
|
||||
@@ -416,8 +402,8 @@ const imageRatio = ref("1:1");
|
||||
const imageFiles = ref<CreativeAttachment[]>([]);
|
||||
const uploadingFiles = ref(false);
|
||||
const fileInputRef = ref<HTMLInputElement>();
|
||||
// 写作模式的显式选择:默认 auto,即由 writing-params 从用户输入里自动识别
|
||||
const writingDocType = ref("auto");
|
||||
// 写作模式的显式选择:默认 auto = 不下发这两个字段的值,交给上游 Dify 识别
|
||||
const writingDocType = ref(WRITING_DOC_TYPE_FREE);
|
||||
const writingWordCount = ref("auto");
|
||||
const wsConnected = ref(false);
|
||||
const deletingChatId = ref<number>();
|
||||
@@ -462,13 +448,9 @@ const chatModeOptions: { label: string; value: ChatMode }[] = [
|
||||
{ label: "图片", value: "image" }
|
||||
];
|
||||
|
||||
const imageRatioOptions = [
|
||||
{ label: "1:1", value: "1:1" },
|
||||
{ label: "4:3", value: "4:3" },
|
||||
{ label: "3:4", value: "3:4" },
|
||||
{ label: "16:9", value: "16:9" },
|
||||
{ label: "9:16", value: "9:16" }
|
||||
];
|
||||
// 图片比例选择器已撤掉:上游创意助手开始节点没有比例变量(实测只有 user_content /
|
||||
// user_files),前端只能把它拼进 user_content —— 那是替上游写 prompt。
|
||||
// 要恢复这个功能,先在上游开始节点加变量,再按变量契约接上来。
|
||||
|
||||
/** 参考素材上限,与 Dify 开始节点 user_files 的 max_length 一致 */
|
||||
const IMAGE_FILE_LIMIT = 5;
|
||||
@@ -476,12 +458,17 @@ const IMAGE_FILE_EXTS = ["PNG", "JPG", "JPEG", "GIF", "WEBP", "BMP", "SVG"];
|
||||
const VIDEO_FILE_EXTS = ["MP4", "MOV", "MPEG", "MPGA", "AVI", "M4V"];
|
||||
const AUDIO_FILE_EXTS = ["MP3", "M4A", "WAV", "AMR", "AAC", "OGG", "WEBM"];
|
||||
|
||||
/** 选中文体 = 套模板走「内容创作与宣传助手」;否则是自由创作走「AI 创意助手」 */
|
||||
const writingTemplateMode = computed(
|
||||
() => writingDocType.value !== WRITING_DOC_TYPE_FREE
|
||||
);
|
||||
|
||||
const inputPlaceholder = computed(() => {
|
||||
if (activeChatMode.value === "writing") {
|
||||
return "直接描述需求即可,例如:帮我写一篇300字关于迎新晚会的推文,风格活泼,面向大一新生(也可在下方直接指定文体和字数)";
|
||||
return "描述你想创作的内容,例如:帮我写一段迎新晚会的主持人开场词,口语化一点(选了文体后才需要再定字数)";
|
||||
}
|
||||
if (activeChatMode.value === "image") {
|
||||
return "请输入图片生成需求,例如校园活动海报、公众号头图、志愿服务插画,并选择图片比例";
|
||||
return "请输入图片生成需求,例如校园活动海报、公众号头图、志愿服务插画";
|
||||
}
|
||||
return "说说你想问的问题";
|
||||
});
|
||||
@@ -1026,23 +1013,27 @@ const pickString = (...values: unknown[]) => {
|
||||
};
|
||||
|
||||
const buildWritingPayload = (query: string) => {
|
||||
// 界面上显式选了文体/字数就用显式值,否则由 writing-params 从自然语言里识别。
|
||||
// 过去这里没有表单,一律回落 doc_type=新闻稿 / Number=800,而上游提示词把这两项
|
||||
// 当作主控指令,会直接压掉用户原话里的诉求(实测见 .scratch/ai-assistant/probe/)。
|
||||
const overrides: WritingOverrides = {};
|
||||
if (writingDocType.value !== "auto") {
|
||||
overrides.docType = writingDocType.value;
|
||||
// 两条路,都不解析自然语言(2026-10-21 改造):
|
||||
// - 自由创作:原话整段作为 userContent,后端见 docType 为空就转「AI 创意助手」;
|
||||
// - 选了文体:原话整段作为 highlights,只带界面显式选过的 docType / number,
|
||||
// 其余可选字段留空交给上游。doc_type / Number 是上游提示词的**主控指令**,
|
||||
// 本地猜会猜错(见 issue 13)。
|
||||
const content = String(query || "").trim();
|
||||
if (writingDocType.value === WRITING_DOC_TYPE_FREE) {
|
||||
return { docType: "", userContent: content };
|
||||
}
|
||||
if (writingWordCount.value !== "auto") {
|
||||
overrides.number = Number(writingWordCount.value);
|
||||
}
|
||||
return {
|
||||
...parseWritingRequest(query, overrides),
|
||||
const payload: Record<string, unknown> = {
|
||||
docType: writingDocType.value,
|
||||
highlights: content,
|
||||
files: []
|
||||
};
|
||||
if (writingWordCount.value !== "auto") {
|
||||
payload.number = Number(writingWordCount.value);
|
||||
}
|
||||
return payload;
|
||||
};
|
||||
|
||||
const formatWritingResult = (payload: unknown, query: string) => {
|
||||
const formatWritingResult = (payload: unknown) => {
|
||||
if (typeof payload === "string") {
|
||||
return payload;
|
||||
}
|
||||
@@ -1076,7 +1067,9 @@ const formatWritingResult = (payload: unknown, query: string) => {
|
||||
data.choices?.[0]?.message?.content
|
||||
);
|
||||
if (title || body) {
|
||||
return `${title || buildWritingPayload(query).theme}\n\n${body}`;
|
||||
// 不再从入参里补标题:改造后没有「从原话抽主题」这一步,
|
||||
// 拼一个空标题只会在正文前多两行空行。
|
||||
return title ? `${title}\n\n${body}` : body;
|
||||
}
|
||||
// 不再 stringifyUnknown(payload):那会把 conversationId / taskId 这类
|
||||
// 内部标识符整包渲染给用户。
|
||||
@@ -1222,14 +1215,6 @@ const formatCreativeResult = (payload: unknown) => {
|
||||
});
|
||||
};
|
||||
|
||||
const buildImagePrompt = (query: string) => {
|
||||
return [
|
||||
query,
|
||||
`图片比例:${imageRatio.value}`,
|
||||
"请直接生成图片,优先输出图片文件或图片链接。"
|
||||
].join("\n");
|
||||
};
|
||||
|
||||
/** 按 MIME 或扩展名判定 Dify file-list 要求的 type(image/video/audio/document) */
|
||||
const resolveUploadFileType = (
|
||||
contentType: string,
|
||||
@@ -1309,7 +1294,13 @@ const onImageFilesPicked = async (event: Event) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 图片模式入参:user_content 一段文本 + user_files(Dify file-list 变量) */
|
||||
/**
|
||||
* 图片模式入参:`user_content` 就是**用户原话** + `user_files`(Dify file-list 变量)。
|
||||
*
|
||||
* 不往 `user_content` 里追加任何我们自己写的话(如「图片比例:1:1」
|
||||
* 「请直接生成图片…」):上游创意助手只有 `user_content` / `user_files` 两个变量,
|
||||
* 没有比例变量,拼进去等于替上游写 prompt。比例选择器已撤掉。
|
||||
*/
|
||||
const buildImagePayload = (query: string) => {
|
||||
const userFiles: CreativeAssistantUploadFile[] = imageFiles.value.map((item) => ({
|
||||
transfer_method: "local_file",
|
||||
@@ -1318,7 +1309,7 @@ const buildImagePayload = (query: string) => {
|
||||
url: ""
|
||||
}));
|
||||
return {
|
||||
user_content: buildImagePrompt(query),
|
||||
user_content: query,
|
||||
user_files: userFiles
|
||||
};
|
||||
};
|
||||
@@ -1340,7 +1331,7 @@ const sendModeMessage = async (
|
||||
if (typeof (result as any)?.taskId === "string") {
|
||||
currentTaskId.value = (result as any).taskId;
|
||||
}
|
||||
setResult(formatWritingResult(result, query));
|
||||
setResult(formatWritingResult(result));
|
||||
return;
|
||||
}
|
||||
if (activeChatMode.value === "image") {
|
||||
|
||||
Reference in New Issue
Block a user