admin update
This commit is contained in:
@@ -65,58 +65,91 @@
|
||||
<main class="flex-1 overflow-y-auto bg-gray-50 p-8">
|
||||
<!-- 识别记录 Dashboard -->
|
||||
<div v-if="currentTab === 'dashboard'">
|
||||
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-2">最近识别记录</h2>
|
||||
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-2 flex items-center justify-between">
|
||||
<span>最近识别记录</span>
|
||||
<button @click="fetchHistory" class="bg-blue-500 hover:bg-blue-600 text-white py-1 px-3 rounded text-sm transition shadow-sm">
|
||||
<i class="fas fa-sync-alt mr-1"></i> 刷新
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
<div class="bg-white rounded-lg shadow overflow-hidden">
|
||||
<div class="bg-white rounded-lg shadow overflow-hidden border border-gray-100">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full leading-normal">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">时间</th>
|
||||
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">类型</th>
|
||||
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Prompt / 详情</th>
|
||||
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">状态</th>
|
||||
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">操作</th>
|
||||
<th class="px-5 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider w-32">时间</th>
|
||||
<th class="px-5 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider w-24">类型</th>
|
||||
<th class="px-5 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Prompt / 详情</th>
|
||||
<th class="px-5 py-3 border-b border-gray-200 bg-gray-50 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider w-24">耗时</th>
|
||||
<th class="px-5 py-3 border-b border-gray-200 bg-gray-50 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider w-24">状态</th>
|
||||
<th class="px-5 py-3 border-b border-gray-200 bg-gray-50 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider w-20">查看</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(record, index) in history" :key="index" class="hover:bg-gray-50">
|
||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||
{{ formatDate(record.timestamp) }}
|
||||
<tr v-for="(record, index) in history" :key="index" class="hover:bg-gray-50 transition duration-150">
|
||||
<td class="px-5 py-4 border-b border-gray-100 bg-white text-sm text-gray-600 whitespace-nowrap">
|
||||
<div class="font-medium">{{ formatDate(record.timestamp).split(' ')[0] }}</div>
|
||||
<div class="text-xs text-gray-400">{{ formatDate(record.timestamp).split(' ')[1] }}</div>
|
||||
</td>
|
||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||
<span :class="getTypeBadgeClass(record.type)" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full">
|
||||
<td class="px-5 py-4 border-b border-gray-100 bg-white text-sm">
|
||||
<span :class="getTypeBadgeClass(record.type)" class="px-2 py-1 text-xs font-semibold rounded-md shadow-sm">
|
||||
{{ record.type }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm max-w-xs truncate" :title="record.details">
|
||||
{{ record.details }}
|
||||
<td class="px-5 py-4 border-b border-gray-100 bg-white text-sm">
|
||||
<div class="flex flex-col gap-1">
|
||||
<!-- Prompt -->
|
||||
<div v-if="record.prompt" class="font-medium text-gray-800 break-words flex items-center gap-2">
|
||||
<i class="fas fa-keyboard text-gray-300 text-xs"></i>
|
||||
{{ record.prompt }}
|
||||
</div>
|
||||
|
||||
<!-- Translated Prompt -->
|
||||
<div v-if="record.final_prompt && record.final_prompt !== record.prompt" class="text-xs text-gray-500 flex items-center gap-2">
|
||||
<i class="fas fa-language text-blue-300 text-xs"></i>
|
||||
<span class="italic bg-gray-50 px-1 rounded">{{ record.final_prompt }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Details -->
|
||||
<div class="text-xs text-gray-400 mt-1 flex items-center gap-2">
|
||||
<i class="fas fa-info-circle text-gray-300"></i>
|
||||
{{ record.details }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||
<span :class="record.status === 'success' ? 'text-green-900 bg-green-200' : 'text-red-900 bg-red-200'" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full">
|
||||
<td class="px-5 py-4 border-b border-gray-100 bg-white text-sm text-center">
|
||||
<div v-if="record.duration" :class="getDurationClass(record.duration)" class="font-mono text-xs inline-block px-2 py-0.5 rounded">
|
||||
{{ record.duration.toFixed(2) }}s
|
||||
</div>
|
||||
<div v-else class="text-gray-300 text-xs">-</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 border-b border-gray-100 bg-white text-sm text-center">
|
||||
<span :class="record.status === 'success' ? 'text-green-700 bg-green-100 ring-1 ring-green-200' : (record.status === 'partial_success' ? 'text-yellow-700 bg-yellow-100 ring-1 ring-yellow-200' : 'text-red-700 bg-red-100 ring-1 ring-red-200')" class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full">
|
||||
<i :class="record.status === 'success' ? 'fas fa-check-circle' : (record.status === 'partial_success' ? 'fas fa-exclamation-circle' : 'fas fa-times-circle')" class="mr-1 mt-0.5"></i>
|
||||
{{ record.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||
<button v-if="record.result_path" @click="viewResult(record.result_path)" class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
查看
|
||||
<td class="px-5 py-4 border-b border-gray-100 bg-white text-sm text-center">
|
||||
<button v-if="record.result_path" @click="viewResult(record.result_path)" class="text-blue-500 hover:text-blue-700 transition transform hover:scale-110" title="查看结果">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</button>
|
||||
<span v-else class="text-gray-300 cursor-not-allowed">
|
||||
<i class="fas fa-eye-slash"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="history.length === 0">
|
||||
<td colspan="5" class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-center text-gray-500">
|
||||
暂无记录
|
||||
<td colspan="6" class="px-5 py-10 border-b border-gray-200 bg-white text-sm text-center text-gray-400">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl mb-3 text-gray-200"></i>
|
||||
暂无记录
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button @click="fetchHistory" class="bg-blue-500 hover:bg-blue-600 text-white py-1 px-3 rounded text-sm">
|
||||
<i class="fas fa-sync-alt"></i> 刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件管理 Files -->
|
||||
@@ -145,7 +178,7 @@
|
||||
<span class="text-xs text-gray-400">{{ file.count }} 项</span>
|
||||
</div>
|
||||
<!-- Image File -->
|
||||
<div v-else-if="isImage(file.name)" @click="previewImage(file.path)" class="flex flex-col items-center justify-center h-32">
|
||||
<div v-else-if="isImage(file.name)" @click="previewImage(file.url)" class="flex flex-col items-center justify-center h-32">
|
||||
<img :src="file.url" class="h-20 w-auto object-contain mb-2 rounded" loading="lazy">
|
||||
<span class="text-xs text-center break-all px-1 truncate w-full">{{ file.name }}</span>
|
||||
</div>
|
||||
@@ -456,17 +489,36 @@
|
||||
};
|
||||
|
||||
const viewResult = (path) => {
|
||||
// path like "results/..."
|
||||
// We need to parse this. If it's a directory, go to files tab. If image, preview.
|
||||
// For simplicity, let's assume it links to the folder in files tab
|
||||
// path format: "results/subdir/file.jpg" or "results/file.jpg"
|
||||
currentTab.value = 'files';
|
||||
// Extract folder name from path if possible, or just go to root
|
||||
const match = path.match(/results\/([^\/]+)/);
|
||||
if (match) {
|
||||
currentPath.value = match[1];
|
||||
|
||||
// Remove "results/" prefix
|
||||
// Note: path usually comes from backend as "results/..."
|
||||
let relativePath = path;
|
||||
if (relativePath.startsWith('results/')) {
|
||||
relativePath = relativePath.substring(8); // Remove "results/"
|
||||
}
|
||||
|
||||
// Check if it looks like a file (has extension)
|
||||
const isFile = /\.[a-zA-Z0-9]+$/.test(relativePath);
|
||||
|
||||
if (isFile) {
|
||||
// It's a file
|
||||
const lastSlashIndex = relativePath.lastIndexOf('/');
|
||||
let dirPath = '';
|
||||
|
||||
if (lastSlashIndex !== -1) {
|
||||
dirPath = relativePath.substring(0, lastSlashIndex);
|
||||
}
|
||||
|
||||
currentPath.value = dirPath;
|
||||
fetchFiles();
|
||||
|
||||
// Show preview immediately
|
||||
previewUrl.value = '/static/' + path;
|
||||
} else {
|
||||
currentPath.value = '';
|
||||
// It's likely a directory
|
||||
currentPath.value = relativePath;
|
||||
fetchFiles();
|
||||
}
|
||||
};
|
||||
@@ -483,11 +535,18 @@
|
||||
return new Date(ts * 1000).toLocaleString();
|
||||
};
|
||||
|
||||
const getDurationClass = (duration) => {
|
||||
if (duration < 2.0) return 'text-green-600 bg-green-50';
|
||||
if (duration < 5.0) return 'text-yellow-600 bg-yellow-50';
|
||||
return 'text-red-600 bg-red-50';
|
||||
};
|
||||
|
||||
const getTypeBadgeClass = (type) => {
|
||||
const map = {
|
||||
'general': 'bg-blue-100 text-blue-800',
|
||||
'tarot': 'bg-purple-100 text-purple-800',
|
||||
'face': 'bg-pink-100 text-pink-800'
|
||||
'general': 'bg-blue-50 text-blue-600 border border-blue-100',
|
||||
'tarot': 'bg-purple-50 text-purple-600 border border-purple-100',
|
||||
'tarot-recognize': 'bg-indigo-50 text-indigo-600 border border-indigo-100',
|
||||
'face': 'bg-pink-50 text-pink-600 border border-pink-100'
|
||||
};
|
||||
return map[type] || 'bg-gray-100 text-gray-800';
|
||||
};
|
||||
@@ -508,7 +567,7 @@
|
||||
currentTab, history, files, currentPath,
|
||||
enterDir, navigateUp, deleteFile, triggerCleanup,
|
||||
viewResult, previewImage, isImage, previewUrl,
|
||||
formatDate, getTypeBadgeClass, cleaning, deviceInfo,
|
||||
formatDate, getDurationClass, getTypeBadgeClass, cleaning, deviceInfo,
|
||||
currentModel, availableModels, updateModel,
|
||||
cleanupConfig, saveCleanupConfig,
|
||||
prompts, fetchPrompts, savePrompt, getPromptDescription
|
||||
|
||||
Reference in New Issue
Block a user