refactor(admin): drop AI connection config page for WP AI Connectors

Remove the "AI 连接配置" page under 扩展&增强 together with the theme's
own OpenAI-compatible provider path. AI generation (SEO, summary, tags)
now runs solely on the WordPress AI Client (wp_ai_client_prompt), with
connections configured through WordPress AI Connectors.

- AI.php keeps only the WP AI Client path: no provider config storage,
  no theme fallback, no rate limiting
- Drop /ai/status, /ai/settings and legacy /ai/seo-suggest routes plus
  their handlers
- Remove dead compat code: AISEO::suggest, AIPrompt::build_seo_prompt
  and the front-submit AI assist flow (its buttons were never rendered
  and its endpoints were never registered)
- Update SEO schema/meta-box guidance that pointed at the deleted page
- Add ai-config-removal contract test and refresh the toast and
  user-features contracts affected by the cleanup
This commit is contained in:
2026-10-13 19:32:34 +08:00
parent bd01199930
commit cdab7a5e5f
18 changed files with 110 additions and 931 deletions
+91
View File
@@ -0,0 +1,91 @@
'use strict';
/** AI 连接配置页已移除:主题只走 WordPress AI Connectors,生成能力与入口保留。 */
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const root = path.resolve(__dirname, '..', '..');
const read = (rel) => fs.readFileSync(path.join(root, rel), 'utf8');
const ai = read('src/Ai/AI.php');
const controller = read('src/Admin/AIController.php');
const seoSchema = read('assets/admin/src/schema/sections/global/seo.js');
const extensionsMenu = read('assets/admin/src/constants/sections/extensions.js');
const apiModule = read('assets/admin/src/api/modules/extensions.js');
const frontSubmit = read('assets/js/front-submit.js');
const submitContext = read('templates/parts/submit/context.php');
const submitPage = read('templates/pages/page-submit.php');
const removed = [
'aksrt_ai_settings',
'AI 连接配置',
'AI连接配置',
'ExtensionAI',
'extensions-ai',
'/ai/settings',
'/ai/status',
'ai-assist-btn',
'扩展 → AI',
];
const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
return ['node_modules', 'build', 'dist'].includes(entry.name) ? [] : walk(full);
}
return [full];
});
const sources = ['src', 'assets/admin/src', 'templates', 'assets/js', 'inc']
.flatMap((dir) => walk(path.join(root, dir)));
test('后台菜单、路由与 API 中不再有 AI 连接配置页', () => {
assert.doesNotMatch(extensionsMenu, /extensions-ai|ExtensionAI|AI 连接配置/);
assert.doesNotMatch(apiModule, /getAiStatus|updateAiSettings|generateSeo|suggestSummary|suggestTags/);
assert.ok(!fs.existsSync(path.join(root, 'assets/admin/src/pages/extensions/ExtensionAI.vue')));
assert.ok(!fs.existsSync(path.join(root, 'assets/admin/src/router/component-loaders/extensions.js')));
});
test('AI.php 只保留 WP AI Client 单路出口', () => {
assert.doesNotMatch(ai, /PROVIDER_OPENAI|PROVIDER_NONE/);
assert.doesNotMatch(ai, /get_config|update_config/);
assert.doesNotMatch(ai, /generate_via_theme|normalize_api_base/);
assert.doesNotMatch(ai, /is_theme_provider_available|resolve_backend/);
assert.doesNotMatch(ai, /check_rate_limit|record_call/);
assert.match(ai, /wp_ai_client_prompt/);
assert.match(ai, /function is_wp_ai_available/);
assert.match(ai, /function is_available/);
});
test('生成路由保留,连接配置与旧草稿兼容路由移除', () => {
assert.match(controller, /\/ai\/seo-generate/);
assert.match(controller, /\/ai\/summary-suggest/);
assert.match(controller, /\/ai\/tags-suggest/);
assert.doesNotMatch(controller, /\/ai\/seo-suggest/);
assert.doesNotMatch(controller, /function get_status|function update_settings|function seo_suggest/);
});
test('残留文案不再指向已删除的配置页', () => {
for (const [name, source] of [['AI.php', ai], ['AIController.php', controller], ['seo.js', seoSchema]]) {
assert.doesNotMatch(source, /AI 连接配置/, `${name} 仍引用 AI 连接配置页`);
assert.doesNotMatch(source, /扩展 → AI/, `${name} 仍引导到扩展菜单`);
}
});
test('前台投稿 AI 辅助死代码已清理', () => {
assert.doesNotMatch(frontSubmit, /bindAi|ai-assist-btn|cfg\.ai/);
assert.doesNotMatch(submitContext, /ai_available/);
assert.doesNotMatch(submitPage, /'ai'\s*=>|aiGenerating|aiFailed|aiFilled|needContent/);
});
test('全仓源码不再引用已删除的 AI 连接配置', () => {
const offenders = sources.filter((file) => {
const text = fs.readFileSync(file, 'utf8');
return removed.some((pattern) => text.includes(pattern));
});
assert.deepEqual(offenders.map((file) => path.relative(root, file)), []);
});