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.
 
 
 
 

340 lines
7.7 KiB

<script setup lang="ts">
import { useAuthSession } from "~/composables/useAuthSession";
definePageMeta({
layout: "default",
});
const auth = useAuthSession();
const { $toast } = useNuxtApp() as any;
const systemPrompt = ref("");
const defaultModelId = ref<number | null>(null);
const publicToolSlugs = ref<string[]>([]);
const loading = ref(false);
const saving = ref(false);
interface ModelOption {
id: number;
name: string;
modelId: string;
}
const models = ref<ModelOption[]>([]);
const availableTools = ref<{ slug: string; name: string; description: string }[]>([]);
async function loadConfig() {
loading.value = true;
try {
const res = await $fetch<{ code: number; data: any }>("/api/admin/agent-config");
const d = res.data;
systemPrompt.value = d.systemPrompt ?? "";
defaultModelId.value = d.defaultModelId ?? null;
publicToolSlugs.value = d.publicToolSlugs ?? [];
} catch {
$toast?.error?.("加载配置失败");
} finally {
loading.value = false;
}
}
async function loadModels() {
try {
const res = await $fetch<{ code: number; data: { list: any[] } }>(
"/api/admin/llm/models",
);
const list = res.data?.list ?? [];
models.value = list.map((m) => ({ id: m.id, name: m.name, modelId: m.modelId }));
} catch {
models.value = [];
}
}
async function loadTools() {
try {
const res = await $fetch<{ code: number; data: { list: any[] } }>(
"/api/agent-tools",
);
availableTools.value = (res.data?.list ?? []).map((t) => ({
slug: t.slug,
name: t.name,
description: t.description ?? "",
}));
} catch {
availableTools.value = [];
}
}
async function saveSystemPrompt() {
saving.value = true;
try {
await $fetch("/api/admin/agent-config/system-prompt", {
method: "PUT",
body: { systemPrompt: systemPrompt.value },
});
$toast?.success?.("系统提示词已保存");
} catch {
$toast?.error?.("保存失败");
} finally {
saving.value = false;
}
}
async function saveDefaultModel() {
saving.value = true;
try {
await $fetch("/api/admin/agent-config/default-model", {
method: "PUT",
body: { modelId: defaultModelId.value },
});
$toast?.success?.("默认模型已保存");
} catch {
$toast?.error?.("保存失败");
} finally {
saving.value = false;
}
}
async function savePublicTools() {
saving.value = true;
try {
await $fetch("/api/admin/agent-config/public-tools", {
method: "PUT",
body: { publicToolSlugs: publicToolSlugs.value },
});
$toast?.success?.("公开工具白名单已保存");
} catch {
$toast?.error?.("保存失败");
} finally {
saving.value = false;
}
}
function toggleTool(slug: string) {
const idx = publicToolSlugs.value.indexOf(slug);
if (idx >= 0) {
publicToolSlugs.value.splice(idx, 1);
} else {
publicToolSlugs.value.push(slug);
}
}
onMounted(async () => {
await Promise.all([loadConfig(), loadModels(), loadTools()]);
});
</script>
<template>
<div class="admin-agent-config">
<h1 class="page-title">Agent 配置</h1>
<div v-if="loading" class="loading-state">加载中...</div>
<template v-else>
<!-- System Prompt -->
<section class="config-section">
<h2 class="section-title">系统提示词</h2>
<p class="section-desc">设置 agent 对话的全局系统提示词</p>
<textarea
v-model="systemPrompt"
class="config-textarea"
rows="8"
placeholder="输入系统提示词..."
/>
<button class="save-btn" :disabled="saving" @click="saveSystemPrompt">
保存
</button>
</section>
<!-- Default Model -->
<section class="config-section">
<h2 class="section-title">默认模型</h2>
<p class="section-desc">新对话的默认模型选择</p>
<select v-model="defaultModelId" class="config-select">
<option :value="null"> 未设置 </option>
<option v-for="m in models" :key="m.id" :value="m.id">
{{ m.name }} ({{ m.modelId }})
</option>
</select>
<button class="save-btn" :disabled="saving" @click="saveDefaultModel">
保存
</button>
</section>
<!-- Public Tools -->
<section class="config-section">
<h2 class="section-title">公开工具白名单</h2>
<p class="section-desc">未登录用户可自动执行的工具(无需审批)</p>
<div class="tools-list">
<label
v-for="t in availableTools"
:key="t.slug"
class="tool-item"
>
<input
type="checkbox"
:checked="publicToolSlugs.includes(t.slug)"
@change="toggleTool(t.slug)"
/>
<div class="tool-info">
<div class="tool-name">{{ t.name }}</div>
<div class="tool-desc">{{ t.description }}</div>
</div>
</label>
<div v-if="availableTools.length === 0" class="empty-tools">
暂无可用工具
</div>
</div>
<button class="save-btn" :disabled="saving" @click="savePublicTools">
保存
</button>
</section>
</template>
</div>
</template>
<style scoped>
.admin-agent-config {
max-width: 800px;
margin: 0 auto;
padding: 32px 24px;
}
.page-title {
font-family: var(--font-display);
font-size: 28px;
font-weight: 400;
color: var(--color-ink);
margin: 0 0 32px;
}
.loading-state {
text-align: center;
padding: 48px;
color: var(--color-muted);
}
.config-section {
margin-bottom: 40px;
padding: 24px;
background: var(--color-surface-card);
border: 1px solid var(--color-hairline);
border-radius: 16px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: var(--color-ink);
margin: 0 0 4px;
}
.section-desc {
font-size: 13px;
color: var(--color-muted);
margin: 0 0 16px;
}
.config-textarea {
width: 100%;
padding: 12px 14px;
border: 1px solid var(--color-hairline);
border-radius: 10px;
font-family: "SF Mono", "Fira Code", monospace;
font-size: 13px;
line-height: 1.6;
color: var(--color-ink);
background: var(--color-canvas);
resize: vertical;
outline: none;
transition: border-color 0.15s;
}
.config-textarea:focus {
border-color: var(--color-primary);
}
.config-select {
padding: 8px 12px;
border: 1px solid var(--color-hairline);
border-radius: 8px;
font-size: 14px;
color: var(--color-ink);
background: var(--color-canvas);
outline: none;
cursor: pointer;
}
.save-btn {
margin-top: 16px;
padding: 8px 20px;
background: var(--color-primary);
color: var(--color-on-primary);
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.15s;
}
.save-btn:hover:not(:disabled) {
background: var(--color-primary-active);
}
.save-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.tools-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.tool-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px;
background: var(--color-surface-soft);
border: 1px solid var(--color-hairline);
border-radius: 10px;
cursor: pointer;
transition: border-color 0.15s;
}
.tool-item:hover {
border-color: var(--color-primary);
}
.tool-item input[type="checkbox"] {
margin-top: 2px;
accent-color: var(--color-primary);
}
.tool-info {
flex: 1;
}
.tool-name {
font-size: 14px;
font-weight: 500;
color: var(--color-ink);
}
.tool-desc {
font-size: 12px;
color: var(--color-muted);
margin-top: 2px;
}
.empty-tools {
padding: 24px;
text-align: center;
font-size: 13px;
color: var(--color-muted);
}
</style>