mirror of
https://github.com/Tencent/WeKnora.git
synced 2025-11-25 19:37:45 +08:00
fix(ui): Fix Ollama Model Download Progress
This commit is contained in:
@@ -64,10 +64,16 @@
|
|||||||
<t-input v-model="formData.llm.modelName" placeholder="例如: qwen3:0.6b"
|
<t-input v-model="formData.llm.modelName" placeholder="例如: qwen3:0.6b"
|
||||||
@blur="onModelNameChange('llm')"
|
@blur="onModelNameChange('llm')"
|
||||||
@input="onModelNameInput('llm')"
|
@input="onModelNameInput('llm')"
|
||||||
@keyup.enter="onModelNameChange('llm')" />
|
@keyup.enter="onModelNameChange('llm')"
|
||||||
|
:clearable="!modelStatus.llm.downloading" />
|
||||||
<div class="model-status-icon">
|
<div class="model-status-icon">
|
||||||
|
<!-- 下载状态:优先显示 -->
|
||||||
|
<div v-if="formData.llm.source === 'local' && formData.llm.modelName && modelStatus.llm.downloading" class="model-download-status">
|
||||||
|
<span class="download-percentage">{{ modelStatus.llm.progress.toFixed(1) }}%</span>
|
||||||
|
</div>
|
||||||
|
<!-- 其他状态:非下载时显示 -->
|
||||||
<t-icon
|
<t-icon
|
||||||
v-if="formData.llm.source === 'local' && formData.llm.modelName && modelStatus.llm.checked"
|
v-else-if="formData.llm.source === 'local' && formData.llm.modelName && modelStatus.llm.checked"
|
||||||
:name="modelStatus.llm.available ? 'check-circle-filled' : 'close-circle-filled'"
|
:name="modelStatus.llm.available ? 'check-circle-filled' : 'close-circle-filled'"
|
||||||
:class="['status-icon', modelStatus.llm.available ? 'installed' : 'not-installed']"
|
:class="['status-icon', modelStatus.llm.available ? 'installed' : 'not-installed']"
|
||||||
:title="modelStatus.llm.available ? '已安装' : '未安装'"
|
:title="modelStatus.llm.available ? '已安装' : '未安装'"
|
||||||
@@ -78,12 +84,6 @@
|
|||||||
class="status-icon unknown"
|
class="status-icon unknown"
|
||||||
title="未检查"
|
title="未检查"
|
||||||
/>
|
/>
|
||||||
<t-icon
|
|
||||||
v-else-if="formData.llm.source === 'local' && formData.llm.modelName && modelStatus.llm.downloading"
|
|
||||||
name="loading"
|
|
||||||
class="status-icon downloading spinning"
|
|
||||||
title="下载中"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 下载按钮:未安装时显示 -->
|
<!-- 下载按钮:未安装时显示 -->
|
||||||
<div v-if="formData.llm.source === 'local' && formData.llm.modelName && modelStatus.llm.checked && !modelStatus.llm.available && !modelStatus.llm.downloading" class="download-action">
|
<div v-if="formData.llm.source === 'local' && formData.llm.modelName && modelStatus.llm.checked && !modelStatus.llm.available && !modelStatus.llm.downloading" class="download-action">
|
||||||
@@ -102,15 +102,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
|
|
||||||
<!-- 下载进度:下载中时显示 -->
|
|
||||||
<div v-if="formData.llm.source === 'local' && formData.llm.modelName && modelStatus.llm.downloading" class="download-progress">
|
|
||||||
<div class="progress-info">
|
|
||||||
<t-icon name="loading" class="loading-icon spinning" />
|
|
||||||
<span class="progress-text">下载中</span>
|
|
||||||
</div>
|
|
||||||
<t-progress :percentage="Number(modelStatus.llm.progress.toFixed(1))" :show-info="false" size="small" class="progress-bar" />
|
|
||||||
<div class="progress-message">{{ modelStatus.llm.message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 远程 API 配置区域 -->
|
<!-- 远程 API 配置区域 -->
|
||||||
@@ -187,10 +178,16 @@
|
|||||||
@blur="onModelNameChange('embedding')"
|
@blur="onModelNameChange('embedding')"
|
||||||
@input="onModelNameInput('embedding')"
|
@input="onModelNameInput('embedding')"
|
||||||
@keyup.enter="onModelNameChange('embedding')"
|
@keyup.enter="onModelNameChange('embedding')"
|
||||||
:disabled="hasFiles" />
|
:disabled="hasFiles"
|
||||||
|
:clearable="!modelStatus.embedding.downloading" />
|
||||||
<div class="model-status-icon">
|
<div class="model-status-icon">
|
||||||
|
<!-- 下载状态:优先显示 -->
|
||||||
|
<div v-if="formData.embedding.source === 'local' && formData.embedding.modelName && modelStatus.embedding.downloading" class="model-download-status">
|
||||||
|
<span class="download-percentage">{{ modelStatus.embedding.progress.toFixed(1) }}%</span>
|
||||||
|
</div>
|
||||||
|
<!-- 其他状态:非下载时显示 -->
|
||||||
<t-icon
|
<t-icon
|
||||||
v-if="formData.embedding.source === 'local' && formData.embedding.modelName && modelStatus.embedding.checked"
|
v-else-if="formData.embedding.source === 'local' && formData.embedding.modelName && modelStatus.embedding.checked"
|
||||||
:name="modelStatus.embedding.available ? 'check-circle-filled' : 'close-circle-filled'"
|
:name="modelStatus.embedding.available ? 'check-circle-filled' : 'close-circle-filled'"
|
||||||
:class="['status-icon', modelStatus.embedding.available ? 'installed' : 'not-installed']"
|
:class="['status-icon', modelStatus.embedding.available ? 'installed' : 'not-installed']"
|
||||||
:title="modelStatus.embedding.available ? '已安装' : '未安装'"
|
:title="modelStatus.embedding.available ? '已安装' : '未安装'"
|
||||||
@@ -201,12 +198,6 @@
|
|||||||
class="status-icon unknown"
|
class="status-icon unknown"
|
||||||
title="未检查"
|
title="未检查"
|
||||||
/>
|
/>
|
||||||
<t-icon
|
|
||||||
v-else-if="formData.embedding.source === 'local' && formData.embedding.modelName && modelStatus.embedding.downloading"
|
|
||||||
name="loading"
|
|
||||||
class="status-icon downloading spinning"
|
|
||||||
title="下载中"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 下载按钮:未安装时显示 -->
|
<!-- 下载按钮:未安装时显示 -->
|
||||||
<div v-if="formData.embedding.source === 'local' && formData.embedding.modelName && modelStatus.embedding.checked && !modelStatus.embedding.available && !modelStatus.embedding.downloading" class="download-action">
|
<div v-if="formData.embedding.source === 'local' && formData.embedding.modelName && modelStatus.embedding.checked && !modelStatus.embedding.available && !modelStatus.embedding.downloading" class="download-action">
|
||||||
@@ -248,15 +239,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
|
|
||||||
<!-- 下载进度:下载中时显示 -->
|
|
||||||
<div v-if="formData.embedding.source === 'local' && formData.embedding.modelName && modelStatus.embedding.downloading" class="download-progress">
|
|
||||||
<div class="progress-info">
|
|
||||||
<t-icon name="loading" class="loading-icon spinning" />
|
|
||||||
<span class="progress-text">下载中 {{ modelStatus.embedding.progress.toFixed(1) }}%</span>
|
|
||||||
</div>
|
|
||||||
<t-progress :percentage="Number(modelStatus.embedding.progress.toFixed(1))" :show-info="false" size="small" class="progress-bar" />
|
|
||||||
<div class="progress-message">{{ modelStatus.embedding.message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 远程 Embedding API 配置 -->
|
<!-- 远程 Embedding API 配置 -->
|
||||||
@@ -417,10 +399,16 @@
|
|||||||
<t-input v-model="formData.multimodal.vlm.modelName" placeholder="例如: qwen2.5vl:3b"
|
<t-input v-model="formData.multimodal.vlm.modelName" placeholder="例如: qwen2.5vl:3b"
|
||||||
@blur="onModelNameChange('vlm')"
|
@blur="onModelNameChange('vlm')"
|
||||||
@input="onModelNameInput('vlm')"
|
@input="onModelNameInput('vlm')"
|
||||||
@keyup.enter="onModelNameChange('vlm')" />
|
@keyup.enter="onModelNameChange('vlm')"
|
||||||
|
:clearable="!modelStatus.vlm.downloading" />
|
||||||
<div class="model-status-icon">
|
<div class="model-status-icon">
|
||||||
|
<!-- 下载状态:优先显示环形进度条 -->
|
||||||
|
<div v-if="formData.multimodal.vlm.interfaceType === 'ollama' && formData.multimodal.vlm.modelName && modelStatus.vlm.downloading" class="model-download-status">
|
||||||
|
<span class="download-percentage">{{ modelStatus.vlm.progress.toFixed(1) }}%</span>
|
||||||
|
</div>
|
||||||
|
<!-- 其他状态:非下载时显示 -->
|
||||||
<t-icon
|
<t-icon
|
||||||
v-if="formData.multimodal.vlm.interfaceType === 'ollama' && formData.multimodal.vlm.modelName && modelStatus.vlm.checked"
|
v-else-if="formData.multimodal.vlm.interfaceType === 'ollama' && formData.multimodal.vlm.modelName && modelStatus.vlm.checked"
|
||||||
:name="modelStatus.vlm.available ? 'check-circle-filled' : 'close-circle-filled'"
|
:name="modelStatus.vlm.available ? 'check-circle-filled' : 'close-circle-filled'"
|
||||||
:class="['status-icon', modelStatus.vlm.available ? 'installed' : 'not-installed']"
|
:class="['status-icon', modelStatus.vlm.available ? 'installed' : 'not-installed']"
|
||||||
:title="modelStatus.vlm.available ? '已安装' : '未安装'"
|
:title="modelStatus.vlm.available ? '已安装' : '未安装'"
|
||||||
@@ -431,12 +419,6 @@
|
|||||||
class="status-icon unknown"
|
class="status-icon unknown"
|
||||||
title="未检查"
|
title="未检查"
|
||||||
/>
|
/>
|
||||||
<t-icon
|
|
||||||
v-else-if="formData.multimodal.vlm.interfaceType === 'ollama' && formData.multimodal.vlm.modelName && modelStatus.vlm.downloading"
|
|
||||||
name="loading"
|
|
||||||
class="status-icon downloading spinning"
|
|
||||||
title="下载中"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 下载按钮:未安装时显示 -->
|
<!-- 下载按钮:未安装时显示 -->
|
||||||
<div v-if="formData.multimodal.vlm.interfaceType === 'ollama' && formData.multimodal.vlm.modelName && modelStatus.vlm.checked && !modelStatus.vlm.available && !modelStatus.vlm.downloading" class="download-action">
|
<div v-if="formData.multimodal.vlm.interfaceType === 'ollama' && formData.multimodal.vlm.modelName && modelStatus.vlm.checked && !modelStatus.vlm.available && !modelStatus.vlm.downloading" class="download-action">
|
||||||
@@ -454,16 +436,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
|
|
||||||
<!-- 下载进度:下载中时显示 -->
|
|
||||||
<div v-if="formData.multimodal.vlm.interfaceType === 'ollama' && formData.multimodal.vlm.modelName && modelStatus.vlm.downloading" class="download-progress">
|
|
||||||
<div class="progress-info">
|
|
||||||
<t-icon name="loading" class="loading-icon spinning" />
|
|
||||||
<span class="progress-text">下载中 {{ modelStatus.vlm.progress.toFixed(1) }}%</span>
|
|
||||||
</div>
|
|
||||||
<t-progress :percentage="Number(modelStatus.vlm.progress.toFixed(1))" :show-info="false" size="small" class="progress-bar" />
|
|
||||||
<div class="progress-message">{{ modelStatus.vlm.message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<t-form-item label="接口类型" name="multimodal.vlm.interfaceType">
|
<t-form-item label="接口类型" name="multimodal.vlm.interfaceType">
|
||||||
@@ -1115,6 +1087,11 @@ const checkAllOllamaModels = async () => {
|
|||||||
modelsToCheck.push(formData.embedding.modelName);
|
modelsToCheck.push(formData.embedding.modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加VLM模型检查
|
||||||
|
if (formData.multimodal.enabled && isVlmOllama.value && formData.multimodal.vlm.modelName) {
|
||||||
|
modelsToCheck.push(formData.multimodal.vlm.modelName);
|
||||||
|
}
|
||||||
|
|
||||||
if (modelsToCheck.length === 0) return;
|
if (modelsToCheck.length === 0) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1130,6 +1107,12 @@ const checkAllOllamaModels = async () => {
|
|||||||
modelStatus.embedding.checked = true;
|
modelStatus.embedding.checked = true;
|
||||||
modelStatus.embedding.available = result.models[formData.embedding.modelName] || false;
|
modelStatus.embedding.available = result.models[formData.embedding.modelName] || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新VLM模型状态
|
||||||
|
if (formData.multimodal.enabled && isVlmOllama.value && formData.multimodal.vlm.modelName) {
|
||||||
|
modelStatus.vlm.checked = true;
|
||||||
|
modelStatus.vlm.available = result.models[formData.multimodal.vlm.modelName] || false;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('检查模型状态失败:', error);
|
console.error('检查模型状态失败:', error);
|
||||||
}
|
}
|
||||||
@@ -1139,9 +1122,12 @@ const checkAllOllamaModels = async () => {
|
|||||||
const downloadModel = async (type: 'llm' | 'embedding' | 'vlm', modelName: string) => {
|
const downloadModel = async (type: 'llm' | 'embedding' | 'vlm', modelName: string) => {
|
||||||
// 防止重复点击
|
// 防止重复点击
|
||||||
if (modelStatus[type].downloading) {
|
if (modelStatus[type].downloading) {
|
||||||
|
console.log(`模型 ${modelName} 正在下载中,忽略重复点击`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`开始下载模型: ${type} - ${modelName}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 立即更新状态,防止重复点击
|
// 立即更新状态,防止重复点击
|
||||||
modelStatus[type].downloading = true;
|
modelStatus[type].downloading = true;
|
||||||
@@ -1337,10 +1323,10 @@ const onModelSourceChange = async (type: 'llm' | 'embedding') => {
|
|||||||
|
|
||||||
const onModelNameChange = async (type: 'llm' | 'embedding' | 'vlm') => {
|
const onModelNameChange = async (type: 'llm' | 'embedding' | 'vlm') => {
|
||||||
if (type === 'vlm') {
|
if (type === 'vlm') {
|
||||||
// 总是重置VLM模型状态
|
// 总是重置VLM模型状态(但不清除下载状态)
|
||||||
modelStatus.vlm.checked = false;
|
modelStatus.vlm.checked = false;
|
||||||
modelStatus.vlm.available = false;
|
modelStatus.vlm.available = false;
|
||||||
modelStatus.vlm.downloading = false;
|
// 不清除 downloading 状态,避免中断正在进行的下载
|
||||||
|
|
||||||
if (formData.multimodal.enabled && isVlmOllama.value && formData.multimodal.vlm.modelName) {
|
if (formData.multimodal.enabled && isVlmOllama.value && formData.multimodal.vlm.modelName) {
|
||||||
if (ollamaStatus.available) {
|
if (ollamaStatus.available) {
|
||||||
@@ -1353,10 +1339,10 @@ const onModelNameChange = async (type: 'llm' | 'embedding' | 'vlm') => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 总是重置模型状态
|
// 总是重置模型状态(但不清除下载状态)
|
||||||
modelStatus[type].checked = false;
|
modelStatus[type].checked = false;
|
||||||
modelStatus[type].available = false;
|
modelStatus[type].available = false;
|
||||||
modelStatus[type].downloading = false;
|
// 不清除 downloading 状态,避免中断正在进行的下载
|
||||||
|
|
||||||
if (formData[type].source === 'local' && formData[type].modelName) {
|
if (formData[type].source === 'local' && formData[type].modelName) {
|
||||||
if (ollamaStatus.available) {
|
if (ollamaStatus.available) {
|
||||||
@@ -1377,15 +1363,17 @@ const onModelNameInput = (type: 'llm' | 'embedding' | 'vlm') => {
|
|||||||
clearTimeout(inputDebounceTimers[type]);
|
clearTimeout(inputDebounceTimers[type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置模型状态
|
// 重置模型状态(但不清除下载状态)
|
||||||
if (type === 'vlm') {
|
if (type === 'vlm') {
|
||||||
modelStatus.vlm.checked = false;
|
modelStatus.vlm.checked = false;
|
||||||
modelStatus.vlm.available = false;
|
modelStatus.vlm.available = false;
|
||||||
modelStatus.vlm.message = '';
|
modelStatus.vlm.message = '';
|
||||||
|
// 不清除 downloading 状态,避免中断正在进行的下载
|
||||||
} else {
|
} else {
|
||||||
modelStatus[type].checked = false;
|
modelStatus[type].checked = false;
|
||||||
modelStatus[type].available = false;
|
modelStatus[type].available = false;
|
||||||
modelStatus[type].message = '';
|
modelStatus[type].message = '';
|
||||||
|
// 不清除 downloading 状态,避免中断正在进行的下载
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置防抖延迟
|
// 设置防抖延迟
|
||||||
@@ -1520,6 +1508,14 @@ const checkAllConfiguredModels = async () => {
|
|||||||
await checkEmbeddingModelStatus();
|
await checkEmbeddingModelStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查VLM模型
|
||||||
|
if (formData.multimodal.enabled && formData.multimodal.vlm.modelName) {
|
||||||
|
if (isVlmOllama.value && ollamaStatus.available) {
|
||||||
|
await checkAllOllamaModels();
|
||||||
|
}
|
||||||
|
// VLM远程API校验可以在这里添加
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDimensionInput = (event: any) => {
|
const onDimensionInput = (event: any) => {
|
||||||
@@ -1673,6 +1669,17 @@ const onMultimodalChange = async () => {
|
|||||||
if (formData.multimodal.vlm.interfaceType === 'ollama' && !formData.multimodal.vlm.baseUrl) {
|
if (formData.multimodal.vlm.interfaceType === 'ollama' && !formData.multimodal.vlm.baseUrl) {
|
||||||
formData.multimodal.vlm.baseUrl = 'http://localhost:11434/v1';
|
formData.multimodal.vlm.baseUrl = 'http://localhost:11434/v1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果选择了Ollama接口,检查Ollama状态
|
||||||
|
if (formData.multimodal.vlm.interfaceType === 'ollama' && !ollamaStatus.checked) {
|
||||||
|
await checkOllama();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果禁用多模态,重置VLM模型状态
|
||||||
|
modelStatus.vlm.checked = false;
|
||||||
|
modelStatus.vlm.available = false;
|
||||||
|
modelStatus.vlm.downloading = false;
|
||||||
|
modelStatus.vlm.message = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1769,8 +1776,26 @@ const checkRerankModelStatus = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 添加一些基本的多模态函数(简化版)
|
// 添加一些基本的多模态函数(简化版)
|
||||||
const onVlmInterfaceTypeChange = () => {
|
const onVlmInterfaceTypeChange = async () => {
|
||||||
console.log('VLM interface type changed:', formData.multimodal.vlm.interfaceType);
|
console.log('VLM interface type changed:', formData.multimodal.vlm.interfaceType);
|
||||||
|
|
||||||
|
// 重置VLM模型状态
|
||||||
|
modelStatus.vlm.checked = false;
|
||||||
|
modelStatus.vlm.available = false;
|
||||||
|
modelStatus.vlm.downloading = false;
|
||||||
|
modelStatus.vlm.message = '';
|
||||||
|
|
||||||
|
// 如果切换到Ollama接口,检查Ollama状态
|
||||||
|
if (formData.multimodal.vlm.interfaceType === 'ollama') {
|
||||||
|
if (!ollamaStatus.checked) {
|
||||||
|
await checkOllama();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经有模型名称,立即检查模型状态
|
||||||
|
if (formData.multimodal.vlm.modelName && ollamaStatus.available) {
|
||||||
|
await checkAllOllamaModels();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onVlmBaseUrlChange = async () => {
|
const onVlmBaseUrlChange = async () => {
|
||||||
@@ -2160,31 +2185,43 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.model-status-icon {
|
.model-status-icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 24px;
|
min-width: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-status-icon .status-icon {
|
.model-status-icon .status-icon {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-status-icon .status-icon.installed {
|
.model-status-icon .status-icon.installed {
|
||||||
color: #00a870;
|
color: #16a34a;
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-status-icon .status-icon.not-installed {
|
.model-status-icon .status-icon.not-installed {
|
||||||
color: #e34d59;
|
color: #dc2626;
|
||||||
|
background: #fef2f2;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-status-icon .status-icon.unknown {
|
.model-status-icon .status-icon.unknown {
|
||||||
color: #d54941;
|
color: #d97706;
|
||||||
|
background: #fffbeb;
|
||||||
|
border: 1px solid #fed7aa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-status-icon .status-icon.downloading {
|
.model-status-icon .status-icon.downloading {
|
||||||
color: #0052d9;
|
color: #16a34a;
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-action {
|
.download-action {
|
||||||
@@ -2192,23 +2229,25 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.download-action .download-btn {
|
.download-action .download-btn {
|
||||||
height: 28px;
|
height: 24px;
|
||||||
width: 28px;
|
width: 24px;
|
||||||
min-width: 28px;
|
min-width: 24px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 50%;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: linear-gradient(135deg, #07c05f, #00a651);
|
background: #16a34a;
|
||||||
border: none;
|
border: 1px solid #15803d;
|
||||||
color: white;
|
color: white;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
box-shadow: 0 2px 8px rgba(7, 192, 95, 0.2);
|
box-shadow: 0 1px 3px rgba(22, 163, 74, 0.2);
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: linear-gradient(135deg, #00a651, #008f47);
|
background: #15803d;
|
||||||
box-shadow: 0 4px 12px rgba(7, 192, 95, 0.3);
|
border-color: #166534;
|
||||||
|
box-shadow: 0 2px 6px rgba(22, 163, 74, 0.3);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
@@ -2223,56 +2262,21 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-progress {
|
.model-download-status {
|
||||||
margin-top: 12px;
|
display: inline-flex;
|
||||||
padding: 12px 16px;
|
align-items: center;
|
||||||
background: linear-gradient(135deg, #f0fdf4, #f8fafc);
|
gap: 6px;
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #dcfce7;
|
|
||||||
box-shadow: 0 1px 4px rgba(7, 192, 95, 0.08);
|
|
||||||
|
|
||||||
.progress-info {
|
.download-progress-circle {
|
||||||
display: flex;
|
flex-shrink: 0;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
.loading-icon {
|
|
||||||
color: #07c05f;
|
|
||||||
font-size: 14px;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-text {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #166534;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.download-percentage {
|
||||||
margin-bottom: 8px;
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
:deep(.t-progress__bar) {
|
color: #15803d;
|
||||||
background: linear-gradient(90deg, #07c05f, #00a651);
|
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
||||||
border-radius: 4px;
|
white-space: nowrap;
|
||||||
height: 6px;
|
|
||||||
box-shadow: 0 1px 2px rgba(7, 192, 95, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.t-progress__track) {
|
|
||||||
background-color: #e5e7eb;
|
|
||||||
border-radius: 4px;
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-message {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6b7280;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.2;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2774,6 +2778,17 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 表单样式优化
|
// 表单样式优化
|
||||||
:deep(.t-form-item__label) {
|
:deep(.t-form-item__label) {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
Reference in New Issue
Block a user