mirror of
https://github.com/foxhui/WebAI2API.git
synced 2026-06-16 21:03:59 +08:00
fix: 修复媒体文件路径缺失目录前缀 & WebUI 请求历史改进
This commit is contained in:
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.6.6] - 2026-04-12
|
||||
|
||||
### 🐛 Fixed
|
||||
- **WebUI**
|
||||
- 修复媒体文件服务接口未拼接目录前缀导致图片无法预览的问题 (ref #70)
|
||||
- 历史模块路径改为绝对路径,避免非项目根目录启动时路径解析失败
|
||||
|
||||
### ✨ Added
|
||||
- **WebUI**
|
||||
- 请求历史 Prompt 列支持点击弹窗预览完整内容
|
||||
- 预览弹窗新增「复制全文」按钮
|
||||
- 重发失败记录时自动删除旧的失败条目
|
||||
|
||||
## [3.6.5] - 2026-04-09
|
||||
|
||||
### ✨ Added
|
||||
|
||||
@@ -43,7 +43,8 @@ import {
|
||||
deleteByDateRange as deleteHistoryByDateRange,
|
||||
retryMediaDownload,
|
||||
getStats as getHistoryStats,
|
||||
getModelList as getHistoryModelList
|
||||
getModelList as getHistoryModelList,
|
||||
getMediaDir
|
||||
} from '../../../utils/history.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
@@ -519,7 +520,8 @@ export function createAdminRouter(context) {
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await fs.readFile(filepath);
|
||||
const fullPath = path.join(getMediaDir(), filepath);
|
||||
const data = await fs.readFile(fullPath);
|
||||
const ext = path.extname(filepath).toLowerCase();
|
||||
const mimeTypes = {
|
||||
'.png': 'image/png',
|
||||
|
||||
@@ -8,7 +8,7 @@ import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
const DATA_DIR = 'data/history';
|
||||
const DATA_DIR = path.join(process.cwd(), 'data', 'history');
|
||||
const DB_PATH = path.join(DATA_DIR, 'history.db');
|
||||
const MEDIA_DIR = path.join(DATA_DIR, 'media');
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -15,7 +15,8 @@ import {
|
||||
RocketOutlined,
|
||||
RedoOutlined,
|
||||
InboxOutlined,
|
||||
LoadingOutlined
|
||||
LoadingOutlined,
|
||||
CopyOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
@@ -52,6 +53,7 @@ const previewModalVisible = ref(false);
|
||||
const previewContent = ref('');
|
||||
const previewMediaType = ref('text'); // text, image, video
|
||||
const previewMediaUrl = ref('');
|
||||
const previewTitle = ref('快速预览');
|
||||
|
||||
// 媒体数据缓存 (blob URLs)
|
||||
const mediaCache = ref({});
|
||||
@@ -466,6 +468,7 @@ const handleRefresh = () => {
|
||||
const previewResponse = async (record) => {
|
||||
previewModalVisible.value = true;
|
||||
previewMediaType.value = 'text';
|
||||
previewTitle.value = '响应预览';
|
||||
if (record.status === 'failed') {
|
||||
previewContent.value = record.error_message || '未知错误';
|
||||
} else {
|
||||
@@ -473,6 +476,24 @@ const previewResponse = async (record) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 快速预览 Prompt 内容
|
||||
const previewPrompt = (record) => {
|
||||
previewModalVisible.value = true;
|
||||
previewMediaType.value = 'text';
|
||||
previewTitle.value = 'Prompt 预览';
|
||||
previewContent.value = record.prompt || '无内容';
|
||||
};
|
||||
|
||||
// 复制预览内容到剪贴板
|
||||
const copyPreviewContent = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(previewContent.value);
|
||||
message.success('已复制到剪贴板');
|
||||
} catch (e) {
|
||||
message.error('复制失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 快速预览媒体
|
||||
const previewMedia = async (record) => {
|
||||
const media = getFirstMedia(record);
|
||||
@@ -510,6 +531,7 @@ const closePreview = () => {
|
||||
previewContent.value = '';
|
||||
previewMediaUrl.value = '';
|
||||
previewMediaType.value = 'text';
|
||||
previewTitle.value = '快速预览';
|
||||
};
|
||||
|
||||
// 多选变化
|
||||
@@ -641,6 +663,20 @@ const sendRequest = () => {
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// 静默删除记录(不弹确认框)
|
||||
const silentDeleteRecord = async (id) => {
|
||||
try {
|
||||
await fetch('/admin/history', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
...settingsStore.getHeaders(),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ ids: [id] })
|
||||
});
|
||||
} catch (e) { /* 静默失败 */ }
|
||||
};
|
||||
|
||||
// 从历史记录重发
|
||||
const resendFromRecord = (record) => {
|
||||
const modelId = record.model_id || record.model_name;
|
||||
@@ -651,7 +687,15 @@ const resendFromRecord = (record) => {
|
||||
sendPrompt.value = record.prompt;
|
||||
}
|
||||
sendImageList.value = [];
|
||||
|
||||
// 如果原记录是失败状态(没有生成回复或图片),重发后删除旧记录
|
||||
const shouldDelete = record.status === 'failed';
|
||||
|
||||
sendRequest();
|
||||
|
||||
if (shouldDelete) {
|
||||
silentDeleteRecord(record.id);
|
||||
}
|
||||
};
|
||||
|
||||
// === 自动刷新 ===
|
||||
@@ -877,9 +921,9 @@ onUnmounted(() => {
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- Prompt 列:支持多行 -->
|
||||
<!-- Prompt 列:支持多行,点击弹出预览 -->
|
||||
<template v-if="column.key === 'prompt'">
|
||||
<div class="multiline-text">
|
||||
<div class="multiline-text clickable" @click="previewPrompt(record)" title="点击查看完整内容">
|
||||
{{ truncateText(record.prompt, 120) }}
|
||||
</div>
|
||||
</template>
|
||||
@@ -1067,7 +1111,13 @@ onUnmounted(() => {
|
||||
@cancel="closePreview"
|
||||
>
|
||||
<template #title>
|
||||
<span>快速预览</span>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span>{{ previewTitle }}</span>
|
||||
<a-button v-if="previewMediaType === 'text'" type="text" size="small" @click="copyPreviewContent">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
复制全文
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="previewMediaType === 'text'" class="preview-text-content">
|
||||
{{ previewContent }}
|
||||
|
||||
Reference in New Issue
Block a user