xinyu-pro 0.22.4 → 0.22.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/api/chat/route.ts +6 -8
- package/app/api/generate-svg/route.ts +5 -7
- package/app/api/generate-theme/route.ts +5 -7
- package/app/api/plugins/scan/route.ts +6 -21
- package/app/api/suggest-fields/route.ts +5 -7
- package/app/settings/page.tsx +1 -1
- package/components/ui/ThemeSwitcher.tsx +39 -37
- package/lib/db.ts +5 -8
- package/lib/get-ai-config.ts +33 -0
- package/package.json +1 -3
- package/version.json +7 -6
- package/.env.example +0 -17
- package/plugins/xinyu.auto-start-and-choices.xye +0 -0
- package/plugins/xinyu.bag-system.xye +0 -0
- package/plugins/xinyu.cache-optimizer.xye +0 -0
- package/plugins/xinyu.character-studio.xye +0 -0
- package/plugins/xinyu.dice-arbiter.xye +0 -0
- package/plugins/xinyu.game-auto-start-choices.xye +0 -0
- package/plugins/xinyu.markdown-render.xye +0 -0
- package/plugins/xinyu.slot-ui-beautify.xye +0 -0
- package/plugins/xinyu.world-info.xye +0 -0
package/app/api/chat/route.ts
CHANGED
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
4
|
import { buildSystemPrompt } from '@/lib/prompt-builder';
|
|
5
|
-
|
|
6
|
-
const SERVER_API_KEY = process.env.AI_API_KEY || '';
|
|
7
|
-
const SERVER_API_BASE = process.env.AI_API_BASE || 'https://api.openai.com/v1';
|
|
8
|
-
const SERVER_MODEL = process.env.AI_MODEL || 'gpt-4o';
|
|
5
|
+
import { getAiConfig } from '@/lib/get-ai-config';
|
|
9
6
|
|
|
10
7
|
export async function POST(request: NextRequest) {
|
|
11
8
|
try {
|
|
@@ -19,15 +16,16 @@ export async function POST(request: NextRequest) {
|
|
|
19
16
|
);
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
19
|
+
const aiConfig = await getAiConfig();
|
|
20
|
+
const apiKey = apiConfig?.apiKey || aiConfig.apiKey;
|
|
21
|
+
const apiBase = apiConfig?.apiBase || aiConfig.apiBase;
|
|
22
|
+
const model = apiConfig?.model || aiConfig.model;
|
|
25
23
|
const temperature = apiConfig?.temperature ?? 0.8;
|
|
26
24
|
const maxTokens = apiConfig?.maxTokens ?? 1024;
|
|
27
25
|
|
|
28
26
|
if (!apiKey) {
|
|
29
27
|
return NextResponse.json(
|
|
30
|
-
{ error: 'AI API Key
|
|
28
|
+
{ error: 'AI API Key 未配置,请在设置页面中填写 API Key' },
|
|
31
29
|
{ status: 500 }
|
|
32
30
|
);
|
|
33
31
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
// app/api/generate-svg/route.ts - AI 生成 SVG 世界卡片
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
|
|
5
|
-
const SERVER_API_KEY = process.env.AI_API_KEY || '';
|
|
6
|
-
const SERVER_API_BASE = process.env.AI_API_BASE || 'https://api.openai.com/v1';
|
|
7
|
-
const SERVER_MODEL = process.env.AI_MODEL || 'gpt-4o';
|
|
4
|
+
import { getAiConfig } from '@/lib/get-ai-config';
|
|
8
5
|
|
|
9
6
|
interface GenerateSvgRequest {
|
|
10
7
|
worldData: Record<string, unknown>;
|
|
@@ -60,9 +57,10 @@ export async function POST(request: NextRequest) {
|
|
|
60
57
|
return NextResponse.json({ error: '缺少世界设定信息' }, { status: 400 });
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
60
|
+
const aiConfig = await getAiConfig();
|
|
61
|
+
const apiKey = apiConfig?.apiKey || aiConfig.apiKey;
|
|
62
|
+
const apiBase = apiConfig?.apiBase || aiConfig.apiBase;
|
|
63
|
+
const model = apiConfig?.model || aiConfig.model;
|
|
66
64
|
|
|
67
65
|
if (!apiKey) {
|
|
68
66
|
return NextResponse.json(
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
// app/api/generate-theme/route.ts - AI 生成配色方案
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
|
|
5
|
-
const SERVER_API_KEY = process.env.AI_API_KEY || '';
|
|
6
|
-
const SERVER_API_BASE = process.env.AI_API_BASE || 'https://api.openai.com/v1';
|
|
7
|
-
const SERVER_MODEL = process.env.AI_MODEL || 'gpt-4o';
|
|
4
|
+
import { getAiConfig } from '@/lib/get-ai-config';
|
|
8
5
|
|
|
9
6
|
interface GenerateThemeRequest {
|
|
10
7
|
prompt: string;
|
|
@@ -69,9 +66,10 @@ export async function POST(request: NextRequest) {
|
|
|
69
66
|
return NextResponse.json({ error: '请描述你想要的配色风格' }, { status: 400 });
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
69
|
+
const aiConfig = await getAiConfig();
|
|
70
|
+
const apiKey = apiConfig?.apiKey || aiConfig.apiKey;
|
|
71
|
+
const apiBase = apiConfig?.apiBase || aiConfig.apiBase;
|
|
72
|
+
const model = apiConfig?.model || aiConfig.model;
|
|
75
73
|
|
|
76
74
|
if (!apiKey) {
|
|
77
75
|
return NextResponse.json({ error: 'AI API Key 未配置' }, { status: 500 });
|
|
@@ -213,24 +213,9 @@ export async function POST(request: NextRequest) {
|
|
|
213
213
|
/** 尝试使用 AI API 进行深度分析 */
|
|
214
214
|
async function aiAnalysis(code: string, manifest?: ScanRequest['manifest']): Promise<ScanResult | null> {
|
|
215
215
|
try {
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
await ensureDb();
|
|
220
|
-
const [rows] = await execute('SELECT value FROM app_settings WHERE key = ?', ['app_settings']);
|
|
221
|
-
|
|
222
|
-
let apiKey = process.env.AI_API_KEY || '';
|
|
223
|
-
let apiBase = process.env.AI_API_BASE || 'https://api.openai.com/v1';
|
|
224
|
-
let model = process.env.AI_MODEL || 'gpt-4o';
|
|
225
|
-
|
|
226
|
-
if (rows.length > 0 && rows[0].value) {
|
|
227
|
-
const settings = typeof rows[0].value === 'string' ? JSON.parse(rows[0].value) : rows[0].value;
|
|
228
|
-
if (settings.aiApiKey) apiKey = settings.aiApiKey;
|
|
229
|
-
if (settings.aiApiBase) apiBase = settings.aiApiBase;
|
|
230
|
-
if (settings.aiModel) model = settings.aiModel;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (!apiKey) return null;
|
|
216
|
+
const { getAiConfig } = await import('@/lib/get-ai-config');
|
|
217
|
+
const config = await getAiConfig();
|
|
218
|
+
if (!config.apiKey) return null;
|
|
234
219
|
|
|
235
220
|
const systemPrompt = `你是一个代码安全分析专家。分析以下插件代码的安全性,返回 JSON 格式的安全报告。
|
|
236
221
|
JSON 格式:
|
|
@@ -244,14 +229,14 @@ JSON 格式:
|
|
|
244
229
|
|
|
245
230
|
检测维度:网络请求、DOM 操作、数据访问、代码执行(eval/Function)、恶意模式、权限需求。`;
|
|
246
231
|
|
|
247
|
-
const response = await fetch(`${apiBase}/chat/completions`, {
|
|
232
|
+
const response = await fetch(`${config.apiBase}/chat/completions`, {
|
|
248
233
|
method: 'POST',
|
|
249
234
|
headers: {
|
|
250
235
|
'Content-Type': 'application/json',
|
|
251
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
236
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
252
237
|
},
|
|
253
238
|
body: JSON.stringify({
|
|
254
|
-
model,
|
|
239
|
+
model: config.model,
|
|
255
240
|
messages: [
|
|
256
241
|
{ role: 'system', content: systemPrompt },
|
|
257
242
|
{ role: 'user', content: `插件信息:${JSON.stringify(manifest || {})}\n\n插件代码:\n${code}` },
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
// app/api/suggest-fields/route.ts - AI 智能补全字段 API
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
|
|
5
|
-
const SERVER_API_KEY = process.env.AI_API_KEY || '';
|
|
6
|
-
const SERVER_API_BASE = process.env.AI_API_BASE || 'https://api.openai.com/v1';
|
|
7
|
-
const SERVER_MODEL = process.env.AI_MODEL || 'gpt-4o';
|
|
4
|
+
import { getAiConfig } from '@/lib/get-ai-config';
|
|
8
5
|
|
|
9
6
|
const SYSTEM_PROMPT = `你是一个 RPG 世界卡片设计专家。根据模板名称和已有字段的键名、类型,为每个字段智能补全默认值、占位文本、选项等内容。
|
|
10
7
|
|
|
@@ -40,9 +37,10 @@ export async function POST(request: NextRequest) {
|
|
|
40
37
|
const body = await request.json();
|
|
41
38
|
const { templateName, existingFields, apiConfig } = body;
|
|
42
39
|
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
40
|
+
const aiConfig = await getAiConfig();
|
|
41
|
+
const apiKey = apiConfig?.apiKey || aiConfig.apiKey;
|
|
42
|
+
const apiBase = apiConfig?.apiBase || aiConfig.apiBase;
|
|
43
|
+
const model = apiConfig?.model || aiConfig.model;
|
|
46
44
|
|
|
47
45
|
if (!apiKey) {
|
|
48
46
|
return NextResponse.json(
|
package/app/settings/page.tsx
CHANGED
|
@@ -703,7 +703,7 @@ export default function SettingsPage() {
|
|
|
703
703
|
</SettingCard>
|
|
704
704
|
|
|
705
705
|
<SettingCard title="连接配置">
|
|
706
|
-
<SettingRow label="API Key" description="
|
|
706
|
+
<SettingRow label="API Key" description="保存后存储在服务端数据库中,游戏会话自动读取">
|
|
707
707
|
<div className="relative">
|
|
708
708
|
<input
|
|
709
709
|
type={showKey ? 'text' : 'password'}
|
|
@@ -69,43 +69,45 @@ export function ThemeSwitcher({ buttonStyle = {} }: { buttonStyle?: React.CSSPro
|
|
|
69
69
|
更多
|
|
70
70
|
</button>
|
|
71
71
|
</div>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<div
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
72
|
+
<div className="flex flex-col gap-2 max-h-[300px] overflow-auto">
|
|
73
|
+
{([...config.presets, ...config.importedThemes]).map((theme) => (
|
|
74
|
+
<button
|
|
75
|
+
key={theme.id}
|
|
76
|
+
onClick={() => {
|
|
77
|
+
setActiveThemeId(theme.id);
|
|
78
|
+
}}
|
|
79
|
+
className="w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors"
|
|
80
|
+
style={{
|
|
81
|
+
backgroundColor:
|
|
82
|
+
activeTheme.id === theme.id ? 'var(--color-bg-tertiary)' : 'transparent',
|
|
83
|
+
color: 'var(--color-text-primary)',
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{/* 颜色预览 */}
|
|
87
|
+
<div className="flex gap-1">
|
|
88
|
+
<span
|
|
89
|
+
className="w-4 h-4 rounded-full border"
|
|
90
|
+
style={{
|
|
91
|
+
backgroundColor: theme.variables['--color-bg-primary'],
|
|
92
|
+
borderColor: theme.variables['--color-border'],
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
<span
|
|
96
|
+
className="w-4 h-4 rounded-full"
|
|
97
|
+
style={{ backgroundColor: theme.variables['--color-accent'] }}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
<div>
|
|
101
|
+
<div className="text-sm font-medium">{theme.name}</div>
|
|
102
|
+
</div>
|
|
103
|
+
{activeTheme.id === theme.id && (
|
|
104
|
+
<svg className="ml-auto" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
105
|
+
<path d="M20 6L9 17l-5-5" />
|
|
106
|
+
</svg>
|
|
107
|
+
)}
|
|
108
|
+
</button>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
109
111
|
|
|
110
112
|
{/* {config.customTheme && (
|
|
111
113
|
<>
|
package/lib/db.ts
CHANGED
|
@@ -6,9 +6,7 @@ let SQL: SqlJsStatic | null = null;
|
|
|
6
6
|
let db: SqlJsDatabase | null = null;
|
|
7
7
|
let initPromise: Promise<void> | null = null;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
return process.env.DB_PATH || join(process.cwd(), 'data', 'xinyu.db');
|
|
11
|
-
}
|
|
9
|
+
const DB_PATH = join(process.cwd(), 'data', 'xinyu.db');
|
|
12
10
|
|
|
13
11
|
async function ensureDb(): Promise<SqlJsDatabase> {
|
|
14
12
|
if (db) return db;
|
|
@@ -17,12 +15,11 @@ async function ensureDb(): Promise<SqlJsDatabase> {
|
|
|
17
15
|
if (!SQL) SQL = await initSqlJs({
|
|
18
16
|
locateFile: (file: string) => join(process.cwd(), 'public', file),
|
|
19
17
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const buffer = readFileSync(dbPath);
|
|
18
|
+
if (existsSync(DB_PATH)) {
|
|
19
|
+
const buffer = readFileSync(DB_PATH);
|
|
23
20
|
db = new SQL.Database(buffer);
|
|
24
21
|
} else {
|
|
25
|
-
const dir = join(
|
|
22
|
+
const dir = join(DB_PATH, '..');
|
|
26
23
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
27
24
|
db = new SQL.Database();
|
|
28
25
|
}
|
|
@@ -36,7 +33,7 @@ async function ensureDb(): Promise<SqlJsDatabase> {
|
|
|
36
33
|
function persist(): void {
|
|
37
34
|
if (db) {
|
|
38
35
|
const data = db.export();
|
|
39
|
-
writeFileSync(
|
|
36
|
+
writeFileSync(DB_PATH, Buffer.from(data));
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
39
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execute } from '@/lib/db';
|
|
2
|
+
import { ensureDb } from '@/lib/db-init';
|
|
3
|
+
|
|
4
|
+
export interface AiConfig {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
apiBase: string;
|
|
7
|
+
model: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function getAiConfig(): Promise<AiConfig> {
|
|
11
|
+
const defaults = {
|
|
12
|
+
apiKey: '',
|
|
13
|
+
apiBase: 'https://api.openai.com/v1',
|
|
14
|
+
model: 'gpt-4o',
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
await ensureDb();
|
|
18
|
+
const [rows] = await execute('SELECT value FROM app_settings WHERE key = ?', ['app_settings']);
|
|
19
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
20
|
+
const row = rows[0] as Record<string, unknown>;
|
|
21
|
+
const raw = row.value;
|
|
22
|
+
const settings = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
23
|
+
return {
|
|
24
|
+
apiKey: settings.aiApiKey || defaults.apiKey,
|
|
25
|
+
apiBase: settings.aiApiBase || defaults.apiBase,
|
|
26
|
+
model: settings.aiModel || defaults.model,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error('Failed to read AI config from database, using defaults:', e);
|
|
31
|
+
}
|
|
32
|
+
return defaults;
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xinyu-pro",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "星语 Pro - AI 驱动的互动叙事平台",
|
|
6
6
|
"author": "RestRegular",
|
|
@@ -13,14 +13,12 @@
|
|
|
13
13
|
"app/",
|
|
14
14
|
"components/",
|
|
15
15
|
"lib/",
|
|
16
|
-
"plugins/",
|
|
17
16
|
"public/",
|
|
18
17
|
"styles/",
|
|
19
18
|
"next.config.mjs",
|
|
20
19
|
"tailwind.config.ts",
|
|
21
20
|
"tsconfig.json",
|
|
22
21
|
"postcss.config.mjs",
|
|
23
|
-
".env.example",
|
|
24
22
|
"version.json"
|
|
25
23
|
],
|
|
26
24
|
"engines": {
|
package/version.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "0.22.
|
|
3
|
-
"name": "xinyu-pro",
|
|
4
|
-
"displayName": "星语 Pro",
|
|
5
|
-
"description": "AI 驱动的互动叙事平台"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"version": "0.22.5",
|
|
3
|
+
"name": "xinyu-pro",
|
|
4
|
+
"displayName": "星语 Pro",
|
|
5
|
+
"description": "AI 驱动的互动叙事平台",
|
|
6
|
+
"updatedAt": "2026-05-24T08:24:57.439Z"
|
|
7
|
+
}
|
package/.env.example
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# AI 大模型 API 密钥
|
|
2
|
-
AI_API_KEY=sk-your-api-key-here
|
|
3
|
-
|
|
4
|
-
# AI 模型名称(可选,默认 gpt-4o)
|
|
5
|
-
AI_MODEL=gpt-4o
|
|
6
|
-
|
|
7
|
-
# AI API 地址(可选,默认 OpenAI,可替换为兼容接口)
|
|
8
|
-
AI_API_BASE=https://api.openai.com/v1
|
|
9
|
-
|
|
10
|
-
# GitHub Personal Access Token(需具备 repo 权限)
|
|
11
|
-
GITHUB_TOKEN=ghp_your-token-here
|
|
12
|
-
|
|
13
|
-
# GitHub 用户名
|
|
14
|
-
GITHUB_USERNAME=your-username
|
|
15
|
-
|
|
16
|
-
# 数据库文件路径(可选,默认 data/xinyu.db)
|
|
17
|
-
DB_PATH=data/xinyu.db
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|