Merge pull request 'fix(admin): AI 状态接口使用共享 API 客户端 nonce' (#81) from worktree-fix-ai-status-403 into main

Reviewed-on: CYL/wordpress-theme-aksrt#81
This commit is contained in:
2026-07-12 14:35:00 +08:00
2 changed files with 50 additions and 31 deletions
@@ -5,4 +5,8 @@ export const extensionsApiMethods = {
testIpGeo(ip) { return this.request('/extensions/ip-test', { method: 'POST', body: JSON.stringify({ ip }) }) },
testContentAudit(text) { return this.request('/extensions/audit-test', { method: 'POST', body: JSON.stringify({ text }) }) },
/* AI 辅助(兼容页) */
getAiStatus() { return this.request('/ai/status') },
updateAiSettings(data) { return this.request('/ai/settings', { method: 'POST', data }) },
}
@@ -2,6 +2,10 @@
<div class="aksrt-page-container">
<PageHeader title="AI 辅助(兼容)" desc="兼容保留的 AI 内容辅助能力,包括 SEO 建议、摘要生成、标签建议等。" />
<div v-if="loadError" class="aksrt-ai__form-card aksrt-ai__load-error" role="alert">
<strong>加载失败:</strong>{{ loadError }}
</div>
<!-- 状态卡片 -->
<div class="aksrt-ai__status">
<div class="aksrt-ai__status-card">
@@ -131,8 +135,7 @@
<script>
import { ref, reactive, onMounted } from 'vue';
import PageHeader from '@/components/PageHeader.vue';
const API = '/wp-json/aksrt/v1/ai';
import api from '@/api';
export default {
name: 'ExtensionAI',
@@ -159,6 +162,7 @@ export default {
});
const saving = ref(false);
const loadError = ref('');
const features = [
{ key: 'seo', name: 'SEO 建议', desc: '生成标题、描述、关键词建议', scene: '文章编辑' },
@@ -167,22 +171,38 @@ export default {
{ 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;
function hydrateFromStatus(data) {
if (!data || typeof data !== 'object') return;
status.value = {
enabled: !!data.enabled,
provider: data.provider || 'none',
configured: !!data.configured,
model: data.model || '',
api_key: '',
rate_limit: data.rate_limit || 10,
available: !!data.available,
};
form.enabled = !!data.enabled;
form.provider = data.provider || 'none';
form.model = data.model || 'gpt-3.5-turbo';
form.rate_limit = data.rate_limit || 10;
if (data.api_base) {
form.api_base = data.api_base;
}
if (typeof data.max_tokens === 'number' && data.max_tokens > 0) {
form.max_tokens = data.max_tokens;
}
// 不回显 api_key
}
async function loadStatus() {
loadError.value = '';
try {
const json = await api.getAiStatus();
hydrateFromStatus(json.data || {});
} catch (e) {
console.error('Load AI status failed:', e);
loadError.value = e?.message || '加载 AI 状态失败';
}
}
@@ -190,27 +210,16 @@ export default {
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 || '保存失败');
}
const json = await api.updateAiSettings(payload);
alert(json.message || '设置已保存');
await loadStatus();
} catch (e) {
console.error('Save failed:', e);
alert(e?.message || '保存失败');
} finally {
saving.value = false;
}
@@ -218,7 +227,7 @@ export default {
onMounted(() => loadStatus());
return { status, form, saving, features, saveSettings };
return { status, form, saving, loadError, features, saveSettings };
},
};
</script>
@@ -290,6 +299,12 @@ export default {
color: #888;
margin-top: 4px;
}
.aksrt-ai__load-error {
background: #fdecea;
border-color: #f5c2c0;
color: #842029;
margin-bottom: 16px;
}
.aksrt-ai__notice {
background: #fff8e1;
border-color: #ffe082;