导入 AKSRT 主题开发源码
This commit is contained in:
1 parent
9681dd3d48
commit
1268a55dd0
595 files changed
+127705
No files matched your search
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader title="AI 子系统" desc="配置 AI 内容辅助能力,包括 SEO 建议、摘要生成、标签建议等。" />
|
||||
|
||||
<!-- 状态卡片 -->
|
||||
<div class="aksrt-ai__status">
|
||||
<div class="aksrt-ai__status-card">
|
||||
<div class="aksrt-ai__status-icon" :class="status.enabled ? 'aksrt-ai__status-icon--ok' : 'aksrt-ai__status-icon--off'">
|
||||
<i :class="status.enabled ? 'fas fa-robot' : 'fas fa-robot'"></i>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-info">
|
||||
<div class="aksrt-ai__status-label">AI 状态</div>
|
||||
<div class="aksrt-ai__status-value">{{ status.enabled ? '已启用' : '已关闭' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-card">
|
||||
<div class="aksrt-ai__status-icon" :class="status.configured ? 'aksrt-ai__status-icon--ok' : 'aksrt-ai__status-icon--warn'">
|
||||
<i class="fas fa-key"></i>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-info">
|
||||
<div class="aksrt-ai__status-label">API Key</div>
|
||||
<div class="aksrt-ai__status-value">{{ status.configured ? '已配置' : '未配置' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-card">
|
||||
<div class="aksrt-ai__status-icon aksrt-ai__status-icon--info">
|
||||
<i class="fas fa-brain"></i>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-info">
|
||||
<div class="aksrt-ai__status-label">模型</div>
|
||||
<div class="aksrt-ai__status-value">{{ status.model || '未设置' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-card">
|
||||
<div class="aksrt-ai__status-icon" :class="status.available ? 'aksrt-ai__status-icon--ok' : 'aksrt-ai__status-icon--warn'">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-info">
|
||||
<div class="aksrt-ai__status-label">可用状态</div>
|
||||
<div class="aksrt-ai__status-value">{{ status.available ? '可用' : '不可用' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<div class="aksrt-ai__form-card">
|
||||
<h3>基本配置</h3>
|
||||
<div class="aksrt-form-group">
|
||||
<label>
|
||||
<input type="checkbox" v-model="form.enabled"> 启用 AI 内容辅助
|
||||
</label>
|
||||
<p class="aksrt-form-hint">开启后可在投稿、编辑等场景使用 AI 辅助功能</p>
|
||||
</div>
|
||||
<div class="aksrt-form-group">
|
||||
<label>AI Provider</label>
|
||||
<select v-model="form.provider" class="aksrt-select">
|
||||
<option value="none">不使用</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider !== 'none'">
|
||||
<label>API Key</label>
|
||||
<input v-model="form.api_key" type="password" class="aksrt-input" placeholder="sk-...">
|
||||
<p class="aksrt-form-hint">保存后不会回显完整密钥</p>
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider === 'openai'">
|
||||
<label>API Base URL</label>
|
||||
<input v-model="form.api_base" type="text" class="aksrt-input" placeholder="https://api.openai.com/v1">
|
||||
<p class="aksrt-form-hint">可替换为兼容 OpenAI 接口的第三方服务</p>
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider !== 'none'">
|
||||
<label>模型</label>
|
||||
<input v-model="form.model" type="text" class="aksrt-input" placeholder="gpt-3.5-turbo">
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider !== 'none'">
|
||||
<label>最大 Token 数</label>
|
||||
<input v-model.number="form.max_tokens" type="number" min="100" max="4000" class="aksrt-input">
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider !== 'none'">
|
||||
<label>每分钟请求限制</label>
|
||||
<input v-model.number="form.rate_limit" type="number" min="1" max="100" class="aksrt-input">
|
||||
</div>
|
||||
<div class="aksrt-form-actions">
|
||||
<button class="aksrt-button aksrt-button--primary" :disabled="saving" @click="saveSettings">
|
||||
{{ saving ? '保存中...' : '保存设置' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能说明 -->
|
||||
<div class="aksrt-ai__form-card">
|
||||
<h3>支持的 AI 功能</h3>
|
||||
<table class="aksrt-table aksrt-table--sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>功能</th>
|
||||
<th>说明</th>
|
||||
<th>使用场景</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="feat in features" :key="feat.key">
|
||||
<td>{{ feat.name }}</td>
|
||||
<td>{{ feat.desc }}</td>
|
||||
<td>{{ feat.scene }}</td>
|
||||
<td>
|
||||
<span class="aksrt-badge" :class="status.available ? 'aksrt-badge--success' : 'aksrt-badge--warning'">
|
||||
{{ status.available ? '可用' : '未启用' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 重要提示 -->
|
||||
<div class="aksrt-ai__form-card aksrt-ai__notice">
|
||||
<h3><i class="fas fa-exclamation-triangle"></i> 重要说明</h3>
|
||||
<ul>
|
||||
<li>AI 生成的内容仅作为<strong>建议参考</strong>,不会自动应用到线上内容</li>
|
||||
<li>所有 AI 输出需要<strong>人工审核确认</strong>后才能发布</li>
|
||||
<li>AI 默认关闭,需管理员显式启用并配置 API Key</li>
|
||||
<li>API Key 不会回显到前端,不会通过 <code>wp_localize_script</code> 透出</li>
|
||||
<li>内置速率限制,防止滥用</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
|
||||
const API = '/wp-json/aksrt/v1/ai';
|
||||
|
||||
export default {
|
||||
name: 'ExtensionAI',
|
||||
components: { PageHeader },
|
||||
setup() {
|
||||
const status = ref({
|
||||
enabled: false,
|
||||
provider: 'none',
|
||||
configured: false,
|
||||
model: '',
|
||||
api_key: '',
|
||||
rate_limit: 10,
|
||||
available: false,
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
enabled: false,
|
||||
provider: 'none',
|
||||
api_key: '',
|
||||
api_base: 'https://api.openai.com/v1',
|
||||
model: 'gpt-3.5-turbo',
|
||||
max_tokens: 1000,
|
||||
rate_limit: 10,
|
||||
});
|
||||
|
||||
const saving = ref(false);
|
||||
|
||||
const features = [
|
||||
{ key: 'seo', name: 'SEO 建议', desc: '生成标题、描述、关键词建议', scene: '文章编辑' },
|
||||
{ key: 'summary', name: '摘要生成', desc: '自动生成文章摘要', scene: '文章编辑、投稿' },
|
||||
{ key: 'tags', name: '标签建议', desc: '根据内容推荐标签', scene: '文章编辑、投稿' },
|
||||
{ key: 'excerpt', name: '摘要建议', desc: '生成吸引人的列表摘要', scene: '文章编辑' },
|
||||
];
|
||||
|
||||
async function loadStatus() {
|
||||
try {
|
||||
const res = await fetch(`${API}/status`, {
|
||||
headers: { 'X-WP-Nonce': window.aksrtAdmin?.nonce || '' },
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
status.value = json.data;
|
||||
form.enabled = json.data.enabled || false;
|
||||
form.provider = json.data.provider || 'none';
|
||||
form.model = json.data.model || 'gpt-3.5-turbo';
|
||||
form.rate_limit = json.data.rate_limit || 10;
|
||||
// 不回显 api_key
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Load AI status failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
saving.value = true;
|
||||
try {
|
||||
const payload = { ...form };
|
||||
// 如果密钥为空且已配置,发送掩码
|
||||
if (!payload.api_key && status.value.configured) {
|
||||
payload.api_key = '***';
|
||||
}
|
||||
const res = await fetch(`${API}/settings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': window.aksrtAdmin?.nonce || '',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
alert('设置已保存');
|
||||
loadStatus();
|
||||
} else {
|
||||
alert(json.message || '保存失败');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Save failed:', e);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadStatus());
|
||||
|
||||
return { status, form, saving, features, saveSettings };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-ai__status {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.aksrt-ai__status-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
.aksrt-ai__status-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
.aksrt-ai__status-icon--ok {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
.aksrt-ai__status-icon--warn {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
.aksrt-ai__status-icon--off {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
.aksrt-ai__status-icon--info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
.aksrt-ai__status-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
.aksrt-ai__status-label {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.aksrt-ai__form-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.aksrt-ai__form-card h3 {
|
||||
margin: 0 0 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.aksrt-form-hint {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.aksrt-ai__notice {
|
||||
background: #fff8e1;
|
||||
border-color: #ffe082;
|
||||
}
|
||||
.aksrt-ai__notice h3 {
|
||||
color: #e65100;
|
||||
}
|
||||
.aksrt-ai__notice ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.aksrt-ai__notice li {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.aksrt-ai__notice code {
|
||||
background: rgba(0,0,0,0.06);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user