Files
X-Financial/web/src/views/LlmSettingsPanel.vue

183 lines
6.9 KiB
Vue
Raw Normal View History

<template>
<div class="model-config-surface">
<section class="settings-card model-table-card">
<div class="card-head model-table-toolbar">
<div class="card-title-with-icon">
<div class="model-icon-box purple">
<span class="model-icon-text">AI</span>
</div>
<div>
<h4>模型配置</h4>
<p>集中维护大语言模型Embedding Rerank 模型接入</p>
</div>
</div>
<div class="card-head-actions">
<button class="add-model-button" type="button" @click="openAddModelDialog">
<i class="mdi mdi-plus"></i>
<span>添加模型</span>
</button>
</div>
</div>
<div class="model-table-wrap">
<table class="model-config-table">
<thead>
<tr>
<th>模型类型</th>
<th>供应商</th>
<th>model_id</th>
<th>接口地址</th>
<th>API Key</th>
<th>连通性</th>
<th class="model-action-col">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="model in modelRows" :key="model.slot">
<td>
<span class="model-type-pill">
<span>{{ getModelTypeLabel(model.type) }}</span>
</span>
</td>
<td>
<strong class="model-provider-name">{{ model.provider }}</strong>
</td>
<td>
<code class="model-id-text">{{ model.modelId }}</code>
</td>
<td>
<span class="model-url-text" :title="model.url">{{ model.url }}</span>
</td>
<td>
<span class="secret-state" :class="{ configured: model.apiKeyConfigured }">
<i :class="model.apiKeyConfigured ? 'mdi mdi-database-lock' : 'mdi mdi-key-outline'"></i>
<span>{{ model.apiKeyConfigured ? '已配置' : '未配置' }}</span>
</span>
</td>
<td>
<span
v-if="getModelTestState(model.slot).message"
class="test-feedback-inline"
:class="`is-${getModelTestState(model.slot).status}`"
>
<i
:class="
getModelTestState(model.slot).status === 'success'
? 'mdi mdi-check-circle'
: getModelTestState(model.slot).status === 'testing'
? 'mdi mdi-loading mdi-spin'
: 'mdi mdi-alert-circle'
"
></i>
<span>{{ getModelTestState(model.slot).message }}</span>
</span>
<span v-else class="test-feedback-inline is-idle">待测试</span>
</td>
<td>
<div class="model-row-actions">
<button
class="icon-action-button"
type="button"
:disabled="isModelTesting(model.slot)"
title="测试模型"
@click="testModelConnection(model)"
>
<span>{{ isModelTesting(model.slot) ? '测试中' : '测试' }}</span>
</button>
<button class="icon-action-button" type="button" title="编辑模型" @click="openEditModelDialog(model)">
<span>编辑</span>
</button>
<button
class="icon-action-button danger"
type="button"
title="删除模型"
:disabled="isFixedModelSlot(model.slot)"
@click="removeModel(model)"
>
<span>删除</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</section>
<div v-if="modelDialogOpen" class="model-dialog-overlay" @click.self="closeModelDialog">
<section class="model-dialog" role="dialog" aria-modal="true" aria-labelledby="model-dialog-title">
<header class="model-dialog-head">
<div>
<h4 id="model-dialog-title">{{ modelDraft.slot ? '编辑模型' : '添加模型' }}</h4>
<p>配置供应商URL密钥model_id 和模型类型</p>
</div>
<button class="icon-action-button" type="button" title="关闭" @click="closeModelDialog">
<span>关闭</span>
</button>
</header>
<div class="form-grid model-dialog-form">
<label class="field">
<span><em>*</em> 供应商</span>
<EnterpriseSelect
v-model="modelDraft.provider"
:options="providerOptions"
placeholder="选择供应商"
@change="applyProviderPresetToDraft"
/>
</label>
<label class="field">
<span><em>*</em> 接口地址</span>
<input v-model="modelDraft.url" type="text" placeholder="https://api.example.com/v1" />
</label>
<label class="field">
<span>API Key</span>
<input
v-model="modelDraft.apiKey"
type="password"
autocomplete="off"
:placeholder="modelDraft.apiKeyConfigured ? '已配置,如需修改请重新输入' : '保存后加密存储'"
@focus="clearDraftSecretMask"
/>
</label>
<label class="field">
<span><em>*</em> model_id</span>
<input v-model="modelDraft.modelId" type="text" placeholder="例如 gpt-5.4-mini" />
</label>
<div class="field field-full">
<span><em>*</em> 模型类型</span>
<div class="model-type-segment" :class="{ disabled: isEditingFixedModel }">
<button
v-for="option in MODEL_TYPE_OPTIONS"
:key="option.value"
type="button"
:class="{ active: modelDraft.type === option.value }"
:disabled="isEditingFixedModel"
@click="selectDraftModelType(option.value)"
>
<span>{{ option.label }}</span>
</button>
</div>
</div>
</div>
<footer class="model-dialog-actions">
<button class="secondary-button" type="button" @click="closeModelDialog">取消</button>
<button class="save-button compact" type="button" @click="saveModelDialog">
<span>保存模型</span>
</button>
</footer>
</section>
</div>
</div>
</template>
<script src="./scripts/LlmSettingsPanel.js"></script>
<style scoped src="../assets/styles/views/settings-view-form.css"></style>
<style scoped src="../assets/styles/views/settings-view.css"></style>