feat: AI 周报摘要提示词自定义设置
- system_config 新增 ai_summary_prompt 配置键 - generate_summary 从数据库加载自定义提示词,为空则使用默认 SUMMARY_SYSTEM_PROMPT - 系统设置新增「AI 周报提示词」卡片,支持编辑和清空恢复默认 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,7 @@ async def update_config(
|
|||||||
):
|
):
|
||||||
"""Update a single config key. Only directors can change settings."""
|
"""Update a single config key. Only directors can change settings."""
|
||||||
value = body.get("value", "")
|
value = body.get("value", "")
|
||||||
valid_keys = {"notification_time", "holidays"}
|
valid_keys = {"notification_time", "holidays", "ai_summary_prompt"}
|
||||||
|
|
||||||
if key not in valid_keys:
|
if key not in valid_keys:
|
||||||
raise HTTPException(status_code=400, detail=f"不支持的配置项: {key}")
|
raise HTTPException(status_code=400, detail=f"不支持的配置项: {key}")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from app.config import settings
|
|||||||
from app.services.dashboard import get_weekly_report, get_week_range
|
from app.services.dashboard import get_weekly_report, get_week_range
|
||||||
from app.services.light_board import get_light_board
|
from app.services.light_board import get_light_board
|
||||||
from app.models.ai_summary import AISummary
|
from app.models.ai_summary import AISummary
|
||||||
|
from app.models.system_config import SystemConfig
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@@ -240,6 +241,12 @@ async def generate_summary(
|
|||||||
leaves=leave_overview.get("leave_list", []),
|
leaves=leave_overview.get("leave_list", []),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Load custom system prompt (director/leader can override via system settings)
|
||||||
|
custom_row = await db.get(SystemConfig, "ai_summary_prompt")
|
||||||
|
system_prompt = SUMMARY_SYSTEM_PROMPT
|
||||||
|
if custom_row and custom_row.value and custom_row.value.strip():
|
||||||
|
system_prompt = custom_row.value.strip()
|
||||||
|
|
||||||
# Call LLM
|
# Call LLM
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
if settings.AI_API_KEY:
|
if settings.AI_API_KEY:
|
||||||
@@ -248,7 +255,7 @@ async def generate_summary(
|
|||||||
payload = {
|
payload = {
|
||||||
"model": settings.AI_MODEL,
|
"model": settings.AI_MODEL,
|
||||||
"messages": [
|
"messages": [
|
||||||
{"role": "system", "content": SUMMARY_SYSTEM_PROMPT},
|
{"role": "system", "content": system_prompt},
|
||||||
{"role": "user", "content": user_prompt},
|
{"role": "user", "content": user_prompt},
|
||||||
],
|
],
|
||||||
"max_tokens": settings.AI_MAX_TOKENS,
|
"max_tokens": settings.AI_MAX_TOKENS,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ const notificationTimeLoading = ref(false)
|
|||||||
const holidays = ref('')
|
const holidays = ref('')
|
||||||
const holidaysLoading = ref(false)
|
const holidaysLoading = ref(false)
|
||||||
const holidaysRefreshing = ref(false)
|
const holidaysRefreshing = ref(false)
|
||||||
|
const aiPrompt = ref('')
|
||||||
|
const aiPromptLoading = ref(false)
|
||||||
|
|
||||||
const importLoading = ref(false)
|
const importLoading = ref(false)
|
||||||
const importFile = ref<File | null>(null)
|
const importFile = ref<File | null>(null)
|
||||||
@@ -38,6 +40,9 @@ onMounted(async () => {
|
|||||||
if (res.data?.holidays) {
|
if (res.data?.holidays) {
|
||||||
holidays.value = res.data.holidays
|
holidays.value = res.data.holidays
|
||||||
}
|
}
|
||||||
|
if (res.data?.ai_summary_prompt) {
|
||||||
|
aiPrompt.value = res.data.ai_summary_prompt
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -91,6 +96,16 @@ async function handleSaveHolidays() {
|
|||||||
} finally { holidaysLoading.value = false }
|
} finally { holidaysLoading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSaveAiPrompt() {
|
||||||
|
aiPromptLoading.value = true
|
||||||
|
try {
|
||||||
|
await api.put('/system-config/ai_summary_prompt', { value: aiPrompt.value })
|
||||||
|
ElMessage.success(aiPrompt.value.trim() ? 'AI 提示词已保存' : '已恢复默认提示词')
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e.response?.data?.detail || '保存失败')
|
||||||
|
} finally { aiPromptLoading.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
async function handleRefreshHolidays() {
|
async function handleRefreshHolidays() {
|
||||||
holidaysRefreshing.value = true
|
holidaysRefreshing.value = true
|
||||||
try {
|
try {
|
||||||
@@ -268,6 +283,36 @@ async function handleImportPreview() {
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<!-- AI Summary Prompt -->
|
||||||
|
<el-card class="setting-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header-title">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:6px; color: var(--gold)">
|
||||||
|
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
|
||||||
|
</svg>
|
||||||
|
AI 周报提示词
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-col" style="flex:1">
|
||||||
|
<label class="setting-label">自定义 System Prompt(清空则使用默认提示词)</label>
|
||||||
|
<div style="display:flex;gap:8px;align-items:flex-start">
|
||||||
|
<el-input
|
||||||
|
v-model="aiPrompt"
|
||||||
|
type="textarea"
|
||||||
|
:rows="6"
|
||||||
|
placeholder="留空使用默认提示词,输入自定义内容将替换默认提示词..."
|
||||||
|
style="flex:1"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" :loading="aiPromptLoading" @click="handleSaveAiPrompt" size="small">保存</el-button>
|
||||||
|
</div>
|
||||||
|
<p style="color:var(--warm-gray);font-size:12px;margin-top:6px">
|
||||||
|
自定义 AI 生成周报摘要时的行为风格。支持 Markdown 格式约束。留空则恢复默认提示词。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
<!-- Import -->
|
<!-- Import -->
|
||||||
<el-card class="setting-card">
|
<el-card class="setting-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
|
|||||||
Reference in New Issue
Block a user