You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

250 lines
5.3 KiB

<script setup lang="ts">
definePageMeta({
layout: false,
});
interface ModelOption {
id: number;
name: string;
modelId: string;
providerName: string;
}
const models = ref<ModelOption[]>([]);
const preferredModelId = ref<number | null>(null);
const loading = ref(false);
const saving = ref(false);
async function loadModels() {
try {
const res = await $fetch<{ code: number; data: { list: any[] } }>("/api/llm/models");
const list = res.data?.list ?? [];
models.value = list.map((m) => ({
id: m.id,
name: m.name,
modelId: m.modelId,
providerName: m.providerName ?? "—",
}));
} catch {
models.value = [];
}
}
async function loadUserConfig() {
try {
const res = await $fetch<{ code: number; data: { config: Record<string, unknown> } }>(
"/api/config/me",
);
const val = res.data?.config?.preferredLlmModelId;
preferredModelId.value = typeof val === "number" && val > 0 ? val : null;
} catch {
preferredModelId.value = null;
}
}
async function handleSave() {
if (preferredModelId.value === null) {
const { $toast } = useNuxtApp();
$toast?.error?.("请选择一个模型");
return;
}
saving.value = true;
try {
await $fetch("/api/config/me", {
method: "PUT",
body: { key: "preferredLlmModelId", value: preferredModelId.value },
});
const { $toast } = useNuxtApp();
$toast?.success?.("默认模型已保存");
} catch {
const { $toast } = useNuxtApp();
$toast?.error?.("保存失败,请稍后重试");
} finally {
saving.value = false;
}
}
onMounted(async () => {
loading.value = true;
await Promise.all([loadModels(), loadUserConfig()]);
loading.value = false;
});
</script>
<template>
<div class="preferences-page">
<header class="page-header">
<h1 class="page-title">偏好设置</h1>
<p class="page-subtitle">管理您的个人偏好配置</p>
</header>
<div v-if="loading" class="loading-state">加载中...</div>
<div v-else class="settings-card">
<div class="card-header">
<h2 class="card-title">默认模型</h2>
<p class="card-desc">新建对话时将自动选中此模型</p>
</div>
<div class="card-body">
<div class="form-group">
<label class="form-label">偏好模型</label>
<select
v-model="preferredModelId"
class="form-select"
>
<option :value="null" disabled>请选择模型</option>
<option
v-for="m in models"
:key="m.id"
:value="m.id"
>
{{ m.name }} ({{ m.providerName }})
</option>
</select>
</div>
<div class="form-actions">
<button
type="button"
class="btn-primary"
:disabled="saving || preferredModelId === null"
@click="handleSave"
>
{{ saving ? "保存中..." : "保存" }}
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.preferences-page {
padding: 40px;
max-width: 800px;
}
.page-header {
margin-bottom: 32px;
}
.page-title {
font-family: var(--font-display);
font-size: 32px;
font-weight: 400;
color: var(--color-ink);
letter-spacing: -0.3px;
margin: 0 0 8px 0;
}
.page-subtitle {
font-size: 15px;
color: var(--color-muted);
margin: 0;
}
.loading-state {
padding: 40px;
text-align: center;
color: var(--color-muted);
font-size: 14px;
}
.settings-card {
background: var(--color-surface-card);
border-radius: 12px;
overflow: hidden;
}
.card-header {
padding: 20px 24px;
border-bottom: 1px solid var(--color-hairline);
background: var(--color-surface-soft);
}
.card-title {
font-size: 16px;
font-weight: 500;
color: var(--color-ink);
margin: 0 0 4px 0;
}
.card-desc {
font-size: 13px;
color: var(--color-muted);
margin: 0;
}
.card-body {
padding: 24px;
display: flex;
flex-direction: column;
gap: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-label {
font-size: 14px;
font-weight: 500;
color: var(--color-body-strong);
}
.form-select {
width: 100%;
padding: 12px 16px;
font-size: 14px;
color: var(--color-ink);
background: var(--color-canvas);
border: 1px solid var(--color-hairline);
border-radius: 8px;
transition: all 0.15s ease;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 40px;
}
.form-select:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(204, 120, 92, 0.15);
}
.form-actions {
padding-top: 8px;
}
.btn-primary {
padding: 12px 24px;
font-size: 14px;
font-weight: 500;
color: var(--color-on-primary);
background: var(--color-primary);
border: none;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s ease;
}
.btn-primary:hover {
background: var(--color-primary-active);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
@media (max-width: 768px) {
.preferences-page {
padding: 24px;
}
}
</style>