diff --git a/src/components/ai/ImageInput.vue b/src/components/ai/ImageInput.vue index 43bae1d..a8809c0 100644 --- a/src/components/ai/ImageInput.vue +++ b/src/components/ai/ImageInput.vue @@ -2,7 +2,7 @@ //! 图片输入预览 — 待发送图片缩略图列表,从 ChatInput.vue 提取 //! //! 职责:显示待发送图片缩略图、移除按钮。不含图片读取/粘贴/拖拽逻辑。 -//! 2026-08-06:支持 url 模式(file.1216.top 上传)与 dataUrl 模式(base64)双预览。 +//! 2026-08-06:支持 url 模式(文件服务上传)与 dataUrl 模式(base64)双预览。 import { useI18n } from 'vue-i18n' diff --git a/src/components/settings/AdvancedSection.vue b/src/components/settings/AdvancedSection.vue index 0b23d46..01dc02d 100644 --- a/src/components/settings/AdvancedSection.vue +++ b/src/components/settings/AdvancedSection.vue @@ -60,6 +60,17 @@ {{ $t('settings.descApprovalTimeout') }} + + + + + @@ -156,6 +167,17 @@ const customPrompt = ref(appSettings.get('custom_prompt', '')) // 三个 select 行改为自定义布局(不复用 SettingRow),saved-hint 由本地 savedVisible 状态驱动 const rowCustomPrompt = useTemplateRef>('rowCustomPrompt') +// 文件上传服务端点:与 fileUpload.ts 同 key(df-file-upload-base)。留空即不上传(图片本地 base64), +// 配置后下次发图即走该服务;trim 提交防空格误配 +const rowUploadBase = useTemplateRef>('rowUploadBase') +const fileUploadBase = ref(appSettings.get('df-file-upload-base', '')) + +function onUploadBaseChange() { + fileUploadBase.value = fileUploadBase.value.trim() + appSettings.set('df-file-upload-base', fileUploadBase.value) + rowUploadBase.value?.markSaved() +} + // 「已保存」提示可见性(2s 自动隐藏),key 对齐模板三行 const savedVisible = reactive>({ autoExecute: false, @@ -242,6 +264,11 @@ function onApprovalTimeoutChange() { word-break: break-all; line-height: 1.4; } +/* 端点输入走等宽字体(URL 逐字符核对更易读),对齐 AllowedDirsPanel 路径输入风格 */ +.upload-base-input { + min-width: 280px; + font-family: var(--df-font-mono, monospace); +} /* 自定义提示词行:整行堆叠,textarea 独立一行从左到右拉满。 覆盖 SettingRow 默认横排(label 左 + control 右)。 */ .prompt-row { diff --git a/src/components/settings/searchIndex.ts b/src/components/settings/searchIndex.ts index 019a676..a6ed510 100644 --- a/src/components/settings/searchIndex.ts +++ b/src/components/settings/searchIndex.ts @@ -90,5 +90,6 @@ export const SETTINGS_INDEX: SettingIndexEntry[] = [ { category: 'advanced', key: 'autoExecuteMode', labelKey: 'settings.labelAutoExecute', descKey: 'settings.descAutoExecute' }, { category: 'advanced', key: 'logLevel', labelKey: 'settings.labelLogLevel', descKey: 'settings.descLogLevel' }, { category: 'advanced', key: 'approvalTimeout', labelKey: 'settings.labelApprovalTimeout', descKey: 'settings.descApprovalTimeout' }, + { category: 'advanced', key: 'fileUploadBase', labelKey: 'settings.labelFileUploadBase', descKey: 'settings.descFileUploadBase' }, { category: 'advanced', key: 'connection', labelKey: 'settings.panelConnection', descKey: 'settings.emptyConnection' }, ] diff --git a/src/composables/ai/fileUpload.ts b/src/composables/ai/fileUpload.ts index 3c7ac2b..075b22d 100644 --- a/src/composables/ai/fileUpload.ts +++ b/src/composables/ai/fileUpload.ts @@ -1,13 +1,16 @@ -//! 桌面端文件服务上传(file.1216.top)— 图片/文件 → 公开 URL +//! 桌面端文件服务上传:可配置端点 POST /upload(multipart 'file' + X-Source 头),图片/文件转公开 URL。 +//! 默认不配置上传服务,图片走本地引用(base64);在设置页配置端点后启用上传。 +//! 未配置时 uploadFile 直接 reject,调用方(ChatInput)catch 后回退本地 base64,不阻断图片输入。 //! -//! 与小程序端 `apps/df-miniapp/src/utils/fileUpload.ts` 对称:复用 file.1216.top 的 -//! `POST /upload`(multipart 'file' + X-Source 头)→ 返回公开 URL。 -//! 桌面端是浏览器环境,用 fetch + FormData(区别于小程序 uni.uploadFile)。 -//! file.1216.top 已确认 CORS 支持(`Access-Control-Allow-Origin: *`,X-Source 在 allow-headers)。 +//! 与小程序端 `apps/df-miniapp/src/utils/fileUpload.ts` 对称:桌面端是浏览器环境, +//! 用 fetch + FormData(区别于小程序 uni.uploadFile)。端点需支持 CORS 与该响应结构: +//! `{"result":{"url":"..."},"retcode":0,"success":true}`。 -/** file.1216.top 上传地址(可经 appSettings 'df-file-upload-base' 覆盖) */ -export const DEFAULT_FILE_UPLOAD_BASE = 'https://file.1216.top' -/** X-Source 项目标识(文件存 file.1216.top 对应目录,项目隔离) */ +import { useAppSettingsStore } from '../../stores/appSettings' + +/** 上传服务根地址,默认空串:不配置上传服务,图片走本地引用;设置页 df-file-upload-base 可覆盖 */ +export const DEFAULT_FILE_UPLOAD_BASE = '' +/** X-Source 项目标识(服务端按来源目录隔离文件) */ export const FILE_UPLOAD_SOURCE = 'devflow-desktop' export interface UploadResult { @@ -15,11 +18,15 @@ export interface UploadResult { } /** - * 上传文件到 file.1216.top,返回公开 URL。失败 reject(调用方回退 base64)。 - * 响应结构:`{"result":{"url":"https://..."},"retcode":0,"success":true}` + * 上传文件到文件服务,返回公开 URL。未配置端点或上传失败时 reject,调用方回退 base64。 + * 端点取值:入参 base 优先,其次设置项 df-file-upload-base,最后 DEFAULT_FILE_UPLOAD_BASE(空)。 */ export async function uploadFile(file: File, base?: string): Promise { - const uploadBase = base || DEFAULT_FILE_UPLOAD_BASE + const configured = useAppSettingsStore().get('df-file-upload-base', '') + const uploadBase = (base || configured || DEFAULT_FILE_UPLOAD_BASE).trim() + if (!uploadBase) { + throw new Error('未配置文件上传服务端点') + } const form = new FormData() form.append('file', file, file.name) const res = await fetch(`${uploadBase}/upload`, { diff --git a/src/i18n/en/settings.ts b/src/i18n/en/settings.ts index 6a08e91..2b6092b 100644 --- a/src/i18n/en/settings.ts +++ b/src/i18n/en/settings.ts @@ -162,6 +162,11 @@ export default { descCustomPrompt: 'Appended to the AI system prompt to customize AI behavior', placeholderCustomPrompt: 'e.g.: You specialize in Rust development questions...', + // File upload endpoint + labelFileUploadBase: 'File upload endpoint', + descFileUploadBase: 'When set, chat images are uploaded to your own file service and turned into public URLs; leave empty to embed images locally (base64)', + placeholderFileUploadBase: 'e.g.: https://your-file-service.example.com', + labelStreamingMd: 'Streaming Markdown rendering', descStreamingMd: 'Render code blocks/lists/headings during streaming (unclosed code blocks degrade to plain text to avoid flicker); when off, only plain text is shown during streaming and full formatting applies after generation completes', diff --git a/src/i18n/zh-CN/settings.ts b/src/i18n/zh-CN/settings.ts index 0ee061d..89f1c3d 100644 --- a/src/i18n/zh-CN/settings.ts +++ b/src/i18n/zh-CN/settings.ts @@ -161,6 +161,11 @@ export default { descCustomPrompt: '追加到 AI 系统提示词末尾,可自定义 AI 行为偏好', placeholderCustomPrompt: '例如:你擅长回答 Rust 开发问题...', + // 文件上传服务端点 + labelFileUploadBase: '文件上传服务端点', + descFileUploadBase: '配置后聊天图片上传到你的文件服务并转为公开 URL;留空则图片以本地方式内嵌(base64)', + placeholderFileUploadBase: '例如:https://your-file-service.example.com', + labelStreamingMd: '流式 Markdown 渲染', descStreamingMd: '流式生成过程中渲染代码块/列表/标题格式(未闭合代码块降级纯文本防闪烁);关闭后流式期间仅显纯文本,生成完成后才渲染完整格式',