fix: resolve 10 code review issues
High priority: - Fix concurrent race condition for view_count/like_count (atomic update) - Add route request ID tracking to prevent race conditions - Filter get_joke by status=approved (no pending content leak) - Add error feedback for like button Performance: - Optimize random joke query (avoid full table sort) - Limit page_size max to 100 (DoS prevention) Medium: - Add localStorage quota error handling - Handle empty AI response gracefully - Fix generate content title extraction Low: - Add rejected_jokes to stats API - Update dashboard to show rejected count
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="setting-page">
|
||||
<h2>AI 配置</h2>
|
||||
|
||||
<!-- AI 配置表单 -->
|
||||
<el-card style="margin-top: 20px">
|
||||
<template #header>
|
||||
<span>AI 配置</span>
|
||||
</template>
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="提供商">
|
||||
<el-select v-model="form.provider" style="width: 200px">
|
||||
<el-option label="NVIDIA NIM" value="nvidia" />
|
||||
<el-option label="OpenAI" value="openai" />
|
||||
<el-option label="Anthropic" value="anthropic" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="API 地址">
|
||||
<el-input v-model="form.api_base" placeholder="https://integrate.api.nvidia.com/v1" style="width: 400px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="API 密钥">
|
||||
<el-input v-model="form.api_key" type="password" show-password style="width: 400px" placeholder="nvapi-..." />
|
||||
</el-form-item>
|
||||
<el-form-item label="模型名称">
|
||||
<el-input v-model="form.model_name" style="width: 300px" placeholder="nvidia/llama-3.1-nemotron-70b-instruct" />
|
||||
</el-form-item>
|
||||
<el-form-item label="温度">
|
||||
<el-input-number v-model="form.temperature" :min="0" :max="2" :step="0.1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最大 Token">
|
||||
<el-input-number v-model="form.max_tokens" :min="1" :max="8192" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 爬虫配置 -->
|
||||
<el-card style="margin-top: 20px">
|
||||
<template #header>
|
||||
<span>爬虫配置</span>
|
||||
</template>
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="启用爬虫">
|
||||
<el-switch v-model="form.crawl_enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="搜索关键词">
|
||||
<el-input
|
||||
v-model="form.crawl_keywords"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
style="width: 400px"
|
||||
placeholder="多个关键词用逗号分隔,如:冷笑话,段子,谐音梗"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="每轮页数">
|
||||
<el-input-number v-model="form.max_pages_per_run" :min="1" :max="20" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div style="margin-top: 20px">
|
||||
<el-button type="primary" @click="handleSave" :loading="saving">保存配置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 历史配置 -->
|
||||
<el-card style="margin-top: 20px">
|
||||
<template #header>
|
||||
<span>历史配置</span>
|
||||
</template>
|
||||
<el-table :data="allSettings" style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column prop="provider" label="提供商" width="100" />
|
||||
<el-table-column prop="model_name" label="模型" />
|
||||
<el-table-column prop="api_base" label="API 地址" show-overflow-tooltip />
|
||||
<el-table-column prop="is_active" label="激活" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.is_active ? 'success' : 'info'" size="small">
|
||||
{{ row.is_active ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="warning" @click="handleToggle(row)" v-if="!row.is_active">激活</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getSettings, getActiveSetting, createSetting, updateSetting, toggleSetting, deleteSetting } from '@/api/setting'
|
||||
|
||||
const form = ref({
|
||||
provider: 'nvidia',
|
||||
api_base: 'https://integrate.api.nvidia.com/v1',
|
||||
api_key: '',
|
||||
model_name: 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
temperature: 0.7,
|
||||
max_tokens: 2048,
|
||||
crawl_enabled: false,
|
||||
crawl_keywords: '冷笑话,段子,谐音梗',
|
||||
max_pages_per_run: 3,
|
||||
})
|
||||
|
||||
const allSettings = ref([])
|
||||
const saving = ref(false)
|
||||
const editingId = ref(null)
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const active = await getActiveSetting()
|
||||
editingId.value = active.id
|
||||
form.value = {
|
||||
provider: active.provider,
|
||||
api_base: active.api_base,
|
||||
api_key: active.api_key,
|
||||
model_name: active.model_name,
|
||||
temperature: active.temperature,
|
||||
max_tokens: active.max_tokens,
|
||||
crawl_enabled: active.crawl_enabled,
|
||||
crawl_keywords: active.crawl_keywords || '',
|
||||
max_pages_per_run: active.max_pages_per_run,
|
||||
}
|
||||
} catch (e) {
|
||||
// 没有激活配置,使用默认值
|
||||
}
|
||||
|
||||
try {
|
||||
allSettings.value = await getSettings()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
if (editingId.value) {
|
||||
await updateSetting(editingId.value, form.value)
|
||||
ElMessage.success('保存成功')
|
||||
} else {
|
||||
const created = await createSetting(form.value)
|
||||
editingId.value = created.id
|
||||
await toggleSetting(created.id) // 设为激活
|
||||
ElMessage.success('创建并激活成功')
|
||||
}
|
||||
await loadData()
|
||||
} catch (e) {
|
||||
ElMessage.error('保存失败: ' + (e.message || ''))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = (row) => {
|
||||
editingId.value = row.id
|
||||
form.value = {
|
||||
provider: row.provider,
|
||||
api_base: row.api_base,
|
||||
api_key: row.api_key,
|
||||
model_name: row.model_name,
|
||||
temperature: row.temperature,
|
||||
max_tokens: row.max_tokens,
|
||||
crawl_enabled: row.crawl_enabled,
|
||||
crawl_keywords: row.crawl_keywords || '',
|
||||
max_pages_per_run: row.max_pages_per_run,
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggle = async (row) => {
|
||||
await ElMessageBox.confirm('确定激活此配置?')
|
||||
await toggleSetting(row.id)
|
||||
ElMessage.success('激活成功')
|
||||
await loadData()
|
||||
}
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
await ElMessageBox.confirm('确定删除此配置?')
|
||||
await deleteSetting(id)
|
||||
ElMessage.success('删除成功')
|
||||
if (editingId.value === id) {
|
||||
editingId.value = null
|
||||
}
|
||||
await loadData()
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.setting-page { max-width: 900px }
|
||||
</style>
|
||||
Reference in New Issue
Block a user