feat(ai): 创意助手各页下线参考素材上传,图片模式改传 mode(issue 19)
- 聊天页「图片」模式与独立创意助手页都删掉上传入口、上传状态与相关样式;
上游出图工具只做文生图,开始节点的 file-list 变量没有任何节点消费(issue 16)。
- 图片模式请求体改为 { user_content, mode: 'image' },由后端包装意图声明;
独立页不传 mode(通用入口,视频/脚本/图片靠用户文字说明)。
- CreativeAssistantRequest 增加 mode 字段,user_files 保留待上游补齐后回退。
This commit is contained in:
@@ -16,6 +16,15 @@ export interface CreativeAssistantUploadFile {
|
||||
export interface CreativeAssistantRequest {
|
||||
user_content?: string;
|
||||
user_contentString?: string;
|
||||
/**
|
||||
* 端上的模式。`image` 时后端会把 user_content 包装成一张「生成图片」的意图声明,
|
||||
* 让上游「问题分类器」不把画面描述判成文案/脚本分支(见 issue 19)。
|
||||
*/
|
||||
mode?: 'chat' | 'writing' | 'image';
|
||||
/**
|
||||
* 参考素材。2026-10 起端上已不再发送:上游出图工具只做文生图,没有任何节点消费它
|
||||
* (见 issue 16)。字段暂时保留,便于回退。
|
||||
*/
|
||||
user_files?: CreativeAssistantUploadFile[];
|
||||
}
|
||||
|
||||
|
||||
@@ -30,40 +30,6 @@
|
||||
placeholder="请输入用户的文本指令或需求描述,例如:根据活动照片和通知文档,生成一条公众号推文封面及 60 秒短视频脚本。"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="上传附件">
|
||||
<a-upload
|
||||
:multiple="true"
|
||||
:show-upload-list="false"
|
||||
:accept="acceptTypes"
|
||||
:customRequest="handleUpload"
|
||||
>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
添加附件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<div class="upload-tip">
|
||||
支持图片、视频、文档、音频。
|
||||
</div>
|
||||
<div v-if="uploadedFiles.length" class="file-list">
|
||||
<div
|
||||
v-for="(item, index) in uploadedFiles"
|
||||
:key="`${item.url || item.path || item.name}-${index}`"
|
||||
class="file-item"
|
||||
>
|
||||
<div class="file-main">
|
||||
<div class="file-badge">{{ getFileTypeLabel(item) }}</div>
|
||||
<div class="file-text">
|
||||
<div class="file-name">{{ item.name || getFileName(item) }}</div>
|
||||
<div class="file-url">{{ resolveFileUrl(item) || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="file-remove" @click="removeUploadedFile(index)">移除</a>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="submit-bar">
|
||||
@@ -97,11 +63,11 @@
|
||||
则「已执行但失败 / 未识别到输出」时 hasAnyResult 为假,
|
||||
会落回这个空状态,把下方 result-body 里的 status-alert
|
||||
(失败文案 / 兜底文案)整个盖住 —— 用户看到的仍是
|
||||
「填写文本指令并上传附件后执行工作流」,等于什么都没发生(见 issue 10)。
|
||||
「填写文本指令后执行工作流」,等于什么都没发生(见 issue 10)。
|
||||
-->
|
||||
<a-empty
|
||||
v-if="!loading && !hasAnyResult && !workflowResponse"
|
||||
description="填写文本指令并上传附件后执行工作流"
|
||||
description="填写文本指令后执行工作流"
|
||||
/>
|
||||
|
||||
<div v-else class="result-body">
|
||||
@@ -222,10 +188,8 @@
|
||||
import 'highlight.js/styles/default.css';
|
||||
import { computed, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||
import { messageLoading } from 'ele-admin-pro';
|
||||
import ByteMdViewer from '@/components/ByteMdViewer/index.vue';
|
||||
import { creativeAssistant, uploadAiFile } from '@/api/ai/creativeAssistant';
|
||||
import { creativeAssistant } from '@/api/ai/creativeAssistant';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
import type {
|
||||
CreativeAssistantOutputs,
|
||||
@@ -236,8 +200,6 @@
|
||||
|
||||
type ExampleType = 'video' | 'script' | 'image';
|
||||
|
||||
const acceptTypes =
|
||||
'image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.md';
|
||||
const plugins = ref([
|
||||
gfm({
|
||||
locale: zh_HansGfm
|
||||
@@ -247,12 +209,11 @@
|
||||
|
||||
const loading = ref(false);
|
||||
const content = ref('');
|
||||
const uploadedFiles = ref<Partial<FileRecord>[]>([]);
|
||||
const workflowResponse = ref<CreativeAssistantResponse | null>(null);
|
||||
const workflowOutputs = ref<CreativeAssistantOutputs | null>(null);
|
||||
|
||||
const canSubmit = computed(() => {
|
||||
return !!content.value.trim() || uploadedFiles.value.length > 0;
|
||||
return !!content.value.trim();
|
||||
});
|
||||
|
||||
const videoFiles = computed(() => workflowOutputs.value?.video_result || []);
|
||||
@@ -316,12 +277,6 @@
|
||||
return 'default';
|
||||
});
|
||||
const loadingTitle = computed(() => {
|
||||
if (uploadedFiles.value.length && content.value.trim()) {
|
||||
return '正在分析指令并处理附件';
|
||||
}
|
||||
if (uploadedFiles.value.length) {
|
||||
return '正在解析附件并生成结果';
|
||||
}
|
||||
return '正在根据文本指令生成内容';
|
||||
});
|
||||
const loadingDescription = computed(() => {
|
||||
@@ -375,25 +330,6 @@
|
||||
return name.split('.').pop() || '';
|
||||
};
|
||||
|
||||
const DOCUMENT_FILE_EXTS = new Set([
|
||||
'TXT',
|
||||
'MD',
|
||||
'MARKDOWN',
|
||||
'PDF',
|
||||
'HTML',
|
||||
'XLSX',
|
||||
'XLS',
|
||||
'DOCX',
|
||||
'CSV',
|
||||
'EML',
|
||||
'MSG',
|
||||
'PPTX',
|
||||
'PPT',
|
||||
'XML',
|
||||
'EPUB'
|
||||
]);
|
||||
const AUDIO_FILE_EXTS = new Set(['MP3', 'M4A', 'WAV', 'WEBM', 'AMR']);
|
||||
const VIDEO_FILE_EXTS = new Set(['MP4', 'MOV', 'MPEG', 'MPGA']);
|
||||
|
||||
const isImageFile = (file?: Partial<FileRecord> | null) => {
|
||||
const contentType = String(file?.contentType || '').toLowerCase();
|
||||
@@ -419,26 +355,6 @@
|
||||
return ['mp3', 'wav', 'aac', 'm4a', 'ogg'].includes(getFileExt(file));
|
||||
};
|
||||
|
||||
const getUploadFileType = (
|
||||
file?: Partial<FileRecord> | null
|
||||
): 'image' | 'document' | 'audio' | 'video' => {
|
||||
const contentType = String(file?.contentType || '').toLowerCase();
|
||||
const ext = getFileExt(file).toUpperCase();
|
||||
if (contentType.startsWith('image/')) {
|
||||
return 'image';
|
||||
}
|
||||
if (AUDIO_FILE_EXTS.has(ext)) {
|
||||
return 'audio';
|
||||
}
|
||||
if (VIDEO_FILE_EXTS.has(ext)) {
|
||||
return 'video';
|
||||
}
|
||||
if (DOCUMENT_FILE_EXTS.has(ext)) {
|
||||
return 'document';
|
||||
}
|
||||
return 'document';
|
||||
};
|
||||
|
||||
const getFileTypeLabel = (file?: Partial<FileRecord> | null) => {
|
||||
if (isImageFile(file)) {
|
||||
return '图片';
|
||||
@@ -459,70 +375,31 @@
|
||||
|
||||
const resetAll = () => {
|
||||
content.value = '';
|
||||
uploadedFiles.value = [];
|
||||
clearResult();
|
||||
};
|
||||
|
||||
const removeUploadedFile = (index: number) => {
|
||||
uploadedFiles.value = uploadedFiles.value.filter((_, current) => current !== index);
|
||||
};
|
||||
|
||||
const handleUpload = async (options: any) => {
|
||||
const rawFile = options.file as File;
|
||||
if (rawFile.size / 1024 / 1024 > 100) {
|
||||
message.error('单个附件不能超过 100MB');
|
||||
options.onError?.(new Error('文件过大'));
|
||||
return;
|
||||
}
|
||||
const hide = messageLoading({
|
||||
content: '上传中..',
|
||||
duration: 0,
|
||||
mask: true
|
||||
});
|
||||
try {
|
||||
// 必须用创意助手自己的应用凭证上传:Dify 的 /files/upload 按应用鉴权,
|
||||
// 用别的应用的 Key 上传,工作流的 file-list 变量会报 Invalid upload file。
|
||||
const result = await uploadAiFile(rawFile, 'creativeAssistant');
|
||||
uploadedFiles.value = [
|
||||
...uploadedFiles.value,
|
||||
{
|
||||
id: result.id,
|
||||
name: result.name || rawFile.name,
|
||||
path: result.path,
|
||||
url: result.url,
|
||||
downloadUrl: result.downloadUrl,
|
||||
contentType: result.contentType
|
||||
}
|
||||
];
|
||||
options.onSuccess?.(result);
|
||||
message.success('附件上传成功');
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '上传失败');
|
||||
options.onError?.(e);
|
||||
} finally {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 请求体。
|
||||
*
|
||||
* <p>附件已下线:上游出图工具只做文生图,开始节点的 file-list 变量没有任何节点消费
|
||||
* (见 issue 16),继续传只是让用户白等上传。
|
||||
*
|
||||
* <p>不传 {@code mode}:这个页面是通用的创意助手入口,用户可能写视频、脚本或图片需求,
|
||||
* 由用户的文字自己说明意图;只有聊天页「图片」模式这类**用户显式选了功能**的地方
|
||||
* 才传 {@code mode=image} 让后端包装提示词(见 issue 19)。
|
||||
*/
|
||||
const buildRequestPayload = () => {
|
||||
const payload: any = {
|
||||
user_content: content.value.trim(),
|
||||
user_contentString: content.value.trim()
|
||||
const value = content.value.trim();
|
||||
return {
|
||||
user_content: value,
|
||||
user_contentString: value
|
||||
};
|
||||
if (uploadedFiles.value.length) {
|
||||
payload.user_files = uploadedFiles.value.map((item) => ({
|
||||
transfer_method: 'local_file',
|
||||
type: getUploadFileType(item),
|
||||
upload_file_id: item.id,
|
||||
url: ''
|
||||
}));
|
||||
}
|
||||
return payload;
|
||||
};
|
||||
|
||||
const runWorkflow = async () => {
|
||||
if (!canSubmit.value) {
|
||||
message.warning('请先输入文本指令或上传附件');
|
||||
message.warning('请先输入文本指令');
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
@@ -557,7 +434,7 @@
|
||||
const fillExample = (type: ExampleType) => {
|
||||
if (type === 'video') {
|
||||
content.value =
|
||||
'请根据我上传的活动照片和通知文档,生成一条 45 秒校园活动宣传短视频,画面节奏明快,配一段适合配音的解说文案,并输出视频结果。';
|
||||
'以「青年志愿者进社区」为主题,生成一条 45 秒校园活动宣传短视频,画面节奏明快,配一段适合配音的解说文案,并输出视频结果。';
|
||||
return;
|
||||
}
|
||||
if (type === 'script') {
|
||||
@@ -566,7 +443,7 @@
|
||||
return;
|
||||
}
|
||||
content.value =
|
||||
'请根据上传的活动照片、LOGO 和文案素材,生成一套公众号头图与活动海报排版,风格青春、明亮,输出图片文件。';
|
||||
'以「毕业季」为主题,生成一套公众号头图与活动海报排版,风格青春、明亮,输出图片文件。';
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -645,38 +522,6 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
margin-top: 10px;
|
||||
color: #88715f;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
margin-top: 14px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: #faf5ef;
|
||||
border: 1px solid #c7e8ff;
|
||||
}
|
||||
|
||||
.file-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.file-badge {
|
||||
flex-shrink: 0;
|
||||
min-width: 48px;
|
||||
@@ -689,35 +534,12 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.file-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.file-name,
|
||||
.file-url,
|
||||
.result-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
color: #3b2c20;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.file-url {
|
||||
margin-top: 4px;
|
||||
color: #8c7462;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.file-remove {
|
||||
flex-shrink: 0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.submit-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
+11
-212
@@ -334,44 +334,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeChatMode === 'image'" class="ratio-panel attach-panel">
|
||||
<span class="ratio-label">参考素材</span>
|
||||
<button
|
||||
class="ratio-option"
|
||||
type="button"
|
||||
:disabled="loading || uploadingFiles || imageFiles.length >= IMAGE_FILE_LIMIT"
|
||||
@click="pickImageFiles"
|
||||
>
|
||||
{{ uploadingFiles ? "上传中..." : "添加附件" }}
|
||||
</button>
|
||||
<span class="attach-hint">
|
||||
最多 {{ IMAGE_FILE_LIMIT }} 个,支持图片 / 视频 / 音频 / 文档
|
||||
</span>
|
||||
<ul v-if="imageFiles.length" class="attach-list">
|
||||
<li
|
||||
v-for="(item, index) in imageFiles"
|
||||
:key="item.id"
|
||||
class="attach-item"
|
||||
>
|
||||
<span class="attach-name" :title="item.name">{{ item.name }}</span>
|
||||
<button
|
||||
class="attach-remove"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="removeImageFile(index)"
|
||||
>
|
||||
移除
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
class="attach-input"
|
||||
type="file"
|
||||
multiple
|
||||
@change="onImageFilesPicked"
|
||||
/>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="content"
|
||||
:placeholder="inputPlaceholder"
|
||||
@@ -434,8 +396,7 @@ import {
|
||||
WRITING_EVENT_TYPE_OPTIONS,
|
||||
WRITING_WORD_COUNT_OPTIONS
|
||||
} from "./writing-params";
|
||||
import { creativeAssistant, uploadAiFile } from "@/api/ai/creativeAssistant";
|
||||
import type { CreativeAssistantUploadFile } from "@/api/ai/creativeAssistant/model";
|
||||
import { creativeAssistant } from "@/api/ai/creativeAssistant";
|
||||
import { API_BASE_URL } from "@/config/setting";
|
||||
import type { Menu } from "@/api/system/menu/model";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
@@ -460,13 +421,6 @@ interface HistorySection {
|
||||
items: AiChatList[];
|
||||
}
|
||||
|
||||
/** 图片模式的参考素材(上传成功后保留 Dify 返回的 file id 与应用内类型) */
|
||||
interface CreativeAttachment {
|
||||
id: string;
|
||||
name: string;
|
||||
type: CreativeAssistantUploadFile["type"];
|
||||
}
|
||||
|
||||
interface AuthorityTreeItem extends Menu {
|
||||
children?: AuthorityTreeItem[];
|
||||
}
|
||||
@@ -485,17 +439,6 @@ const loading = ref(false);
|
||||
const stopping = ref(false);
|
||||
const content = ref("");
|
||||
const activeChatMode = ref<ChatMode>("chat");
|
||||
/**
|
||||
* 图片模式的参考素材,对应 Dify「AI 创意助手」开始节点的 file-list 变量 `user_files`。
|
||||
*
|
||||
* 两个硬约束(都来自上游 `/parameters` 实测):
|
||||
* 1. `max_length = 5`,最多 5 个;
|
||||
* 2. **必须用创意助手自己的应用凭证上传** —— Dify 的 /files/upload 按应用鉴权,
|
||||
* 用别的应用(如 chat)的 Key 上传,工作流引用该 id 会直接报 `Invalid upload file`。
|
||||
*/
|
||||
const imageFiles = ref<CreativeAttachment[]>([]);
|
||||
const uploadingFiles = ref(false);
|
||||
const fileInputRef = ref<HTMLInputElement>();
|
||||
// 写作模式的显式选择:默认 auto = 不下发这两个字段的值,交给上游 Dify 识别
|
||||
const writingDocType = ref(WRITING_DOC_TYPE_FREE);
|
||||
const writingWordCount = ref("auto");
|
||||
@@ -539,7 +482,7 @@ const userName = computed(
|
||||
const showAdminEntry = computed(() => !isOnlyStudent());
|
||||
|
||||
const canSend = computed(() => {
|
||||
if (loading.value || uploadingFiles.value) {
|
||||
if (loading.value) {
|
||||
return false;
|
||||
}
|
||||
if (content.value.trim().length > 0) {
|
||||
@@ -567,12 +510,7 @@ const chatModeOptions: { label: string; value: ChatMode }[] = [
|
||||
// 图片比例选择器已撤掉:上游创意助手开始节点没有比例变量(实测只有 user_content /
|
||||
// user_files),前端只能把它拼进 user_content —— 那是替上游写 prompt。
|
||||
// 要恢复这个功能,先在上游开始节点加变量,再按变量契约接上来。
|
||||
|
||||
/** 参考素材上限,与 Dify 开始节点 user_files 的 max_length 一致 */
|
||||
const IMAGE_FILE_LIMIT = 5;
|
||||
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"];
|
||||
// user_files 本身也已不再上传:开始节点挂着这个变量,但没有任何下游节点消费它(issue 16)。
|
||||
|
||||
/** 选中文体 = 套模板走「内容创作与宣传助手」;否则是自由创作走「AI 创意助手」 */
|
||||
const writingTemplateMode = computed(
|
||||
@@ -1367,102 +1305,20 @@ const formatCreativeResult = (payload: unknown) => {
|
||||
});
|
||||
};
|
||||
|
||||
/** 按 MIME 或扩展名判定 Dify file-list 要求的 type(image/video/audio/document) */
|
||||
const resolveUploadFileType = (
|
||||
contentType: string,
|
||||
fileName: string
|
||||
): CreativeAssistantUploadFile["type"] => {
|
||||
const mime = String(contentType || "").toLowerCase();
|
||||
if (mime.startsWith("image/")) {
|
||||
return "image";
|
||||
}
|
||||
if (mime.startsWith("video/")) {
|
||||
return "video";
|
||||
}
|
||||
if (mime.startsWith("audio/")) {
|
||||
return "audio";
|
||||
}
|
||||
const ext = String(fileName || "").split(".").pop()?.toUpperCase() || "";
|
||||
if (IMAGE_FILE_EXTS.includes(ext)) {
|
||||
return "image";
|
||||
}
|
||||
if (VIDEO_FILE_EXTS.includes(ext)) {
|
||||
return "video";
|
||||
}
|
||||
if (AUDIO_FILE_EXTS.includes(ext)) {
|
||||
return "audio";
|
||||
}
|
||||
return "document";
|
||||
};
|
||||
|
||||
const pickImageFiles = () => {
|
||||
if (loading.value || uploadingFiles.value) {
|
||||
return;
|
||||
}
|
||||
fileInputRef.value?.click();
|
||||
};
|
||||
|
||||
const removeImageFile = (index: number) => {
|
||||
imageFiles.value.splice(index, 1);
|
||||
};
|
||||
|
||||
const onImageFilesPicked = async (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const picked = Array.from(input.files || []);
|
||||
// 清空 value,否则连续选同一个文件不会再触发 change
|
||||
input.value = "";
|
||||
if (!picked.length) {
|
||||
return;
|
||||
}
|
||||
const remain = IMAGE_FILE_LIMIT - imageFiles.value.length;
|
||||
if (remain <= 0) {
|
||||
message.warning(`参考素材最多 ${IMAGE_FILE_LIMIT} 个`);
|
||||
return;
|
||||
}
|
||||
const selected = picked.slice(0, remain);
|
||||
if (picked.length > remain) {
|
||||
message.warning(`参考素材最多 ${IMAGE_FILE_LIMIT} 个,本次只上传前 ${remain} 个`);
|
||||
}
|
||||
uploadingFiles.value = true;
|
||||
try {
|
||||
for (const file of selected) {
|
||||
// app 必须传 creativeAssistant:Dify 按应用鉴权上传文件,
|
||||
// 用别的应用凭证上传的 id,工作流会报 Invalid upload file。
|
||||
const record = await uploadAiFile(file, "creativeAssistant");
|
||||
imageFiles.value.push({
|
||||
id: String(record.id),
|
||||
name: record.name || file.name,
|
||||
type: resolveUploadFileType(
|
||||
record.contentType || file.type,
|
||||
record.name || file.name
|
||||
)
|
||||
});
|
||||
}
|
||||
message.success("参考素材已上传");
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "参考素材上传失败");
|
||||
} finally {
|
||||
uploadingFiles.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 图片模式入参:`user_content` 就是**用户原话** + `user_files`(Dify file-list 变量)。
|
||||
* 图片模式入参。
|
||||
*
|
||||
* 不往 `user_content` 里追加任何我们自己写的话(如「图片比例:1:1」
|
||||
* 「请直接生成图片…」):上游创意助手只有 `user_content` / `user_files` 两个变量,
|
||||
* 没有比例变量,拼进去等于替上游写 prompt。比例选择器已撤掉。
|
||||
* <p>只下发两样东西:用户原话 {@code user_content},以及 {@code mode=image}。
|
||||
* 参考素材({@code user_files})已下线:上游出图工具只做文生图,开始节点虽然挂着
|
||||
* 这个 file-list 变量,但没有任何节点消费它(见 issue 16),传上去只是空等上传。
|
||||
*
|
||||
* <p>{@code mode} 不写进 user_content,由后端在 image 模式下统一包装成
|
||||
* 「生成图片」的意图声明 —— 上游「问题分类器」因此不会把画面描述判成文案分支。
|
||||
*/
|
||||
const buildImagePayload = (query: string) => {
|
||||
const userFiles: CreativeAssistantUploadFile[] = imageFiles.value.map((item) => ({
|
||||
transfer_method: "local_file",
|
||||
type: item.type,
|
||||
upload_file_id: item.id,
|
||||
url: ""
|
||||
}));
|
||||
return {
|
||||
user_content: query,
|
||||
user_files: userFiles
|
||||
mode: "image"
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1579,10 +1435,6 @@ const onStopGenerate = async () => {
|
||||
};
|
||||
|
||||
const onSend = async () => {
|
||||
if (uploadingFiles.value) {
|
||||
message.warning("参考素材正在上传,请稍候");
|
||||
return;
|
||||
}
|
||||
if (!canSend.value) {
|
||||
return;
|
||||
}
|
||||
@@ -2433,59 +2285,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.attach-hint {
|
||||
color: #8aa4bd;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.attach-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.attach-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 100%;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid rgba(21, 144, 255, 0.28);
|
||||
border-radius: 8px;
|
||||
background: #f5fbff;
|
||||
color: #1590ff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.attach-name {
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.attach-remove {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #8aa4bd;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.attach-remove:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.attach-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input-shell textarea {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
|
||||
Reference in New Issue
Block a user