xinyu-pro 0.22.3 → 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.
@@ -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 apiKey = apiConfig?.apiKey || SERVER_API_KEY;
23
- const apiBase = apiConfig?.apiBase || SERVER_API_BASE;
24
- const model = apiConfig?.model || SERVER_MODEL;
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 未配置,请在设置中填写 API Key,或在 .env.local 中设置 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 apiKey = apiConfig?.apiKey || SERVER_API_KEY;
64
- const apiBase = apiConfig?.apiBase || SERVER_API_BASE;
65
- const model = apiConfig?.model || SERVER_MODEL;
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 apiKey = apiConfig?.apiKey || SERVER_API_KEY;
73
- const apiBase = apiConfig?.apiBase || SERVER_API_BASE;
74
- const model = apiConfig?.model || SERVER_MODEL;
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
- // 从数据库获取 API 设置
217
- const { execute } = await import('@/lib/db');
218
- const { ensureDb } = await import('@/lib/db-init');
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 apiKey = apiConfig?.apiKey || SERVER_API_KEY;
44
- const apiBase = apiConfig?.apiBase || SERVER_API_BASE;
45
- const model = apiConfig?.model || SERVER_MODEL;
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(
@@ -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'}
@@ -1333,7 +1333,8 @@ export default function SettingsPage() {
1333
1333
  </SettingCard>
1334
1334
  </ColumnLayout>
1335
1335
  );
1336
- case 'data':
1336
+
1337
+ case 'data':
1337
1338
  return (
1338
1339
  <ColumnLayout>
1339
1340
  <SettingCard title="游戏记录">
package/bin/cli.js CHANGED
@@ -5,6 +5,7 @@ const { join, resolve } = require('path');
5
5
  const { createInterface } = require('readline');
6
6
  const { execSync, spawn } = require('child_process');
7
7
  const { homedir } = require('os');
8
+ const path = require('path');
8
9
 
9
10
  const isWin = process.platform === 'win32';
10
11
  const APP_DIR = __dirname.includes('node_modules') ? resolve(__dirname, '..') : process.cwd();
@@ -12,6 +13,7 @@ const XINYU_HOME = join(homedir(), '.xinyu-pro');
12
13
  const RUN_DIR = XINYU_HOME;
13
14
 
14
15
  const args = process.argv.slice(2);
16
+ const VERSION_FLAG = args.includes('--version');
15
17
  const HELP_FLAG = args.includes('--help') || args.includes('-h');
16
18
  const DEV_FLAG = args.includes('--dev');
17
19
  const RESET_FLAG = args.includes('--reset');
@@ -44,6 +46,13 @@ function ask(question) {
44
46
  });
45
47
  }
46
48
 
49
+ function showVersion() {
50
+ const versionPath = path.join(APP_DIR, 'version.json');
51
+ const versionData = JSON.parse(readFileSync(versionPath, 'utf-8'));
52
+ console.log(`${versionData.name} ${versionData.version}`);
53
+ process.exit(0);
54
+ }
55
+
47
56
  function showHelp() {
48
57
  echo('');
49
58
  echo(' ╔══════════════════════════════════════╗', 'bold');
@@ -244,6 +253,11 @@ async function main() {
244
253
  echo(' ╚══════════════════════════════════════╝', 'bold');
245
254
  echo('');
246
255
 
256
+ if (VERSION_FLAG) {
257
+ showVersion();
258
+ return;
259
+ }
260
+
247
261
  if (HELP_FLAG) {
248
262
  showHelp();
249
263
  return;
@@ -69,43 +69,45 @@ export function ThemeSwitcher({ buttonStyle = {} }: { buttonStyle?: React.CSSPro
69
69
  更多
70
70
  </button>
71
71
  </div>
72
- {([...config.presets, ...config.importedThemes]).map((theme) => (
73
- <button
74
- key={theme.id}
75
- onClick={() => {
76
- setActiveThemeId(theme.id);
77
- }}
78
- className="w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors"
79
- style={{
80
- backgroundColor:
81
- activeTheme.id === theme.id ? 'var(--color-bg-tertiary)' : 'transparent',
82
- color: 'var(--color-text-primary)',
83
- }}
84
- >
85
- {/* 颜色预览 */}
86
- <div className="flex gap-1">
87
- <span
88
- className="w-4 h-4 rounded-full border"
89
- style={{
90
- backgroundColor: theme.variables['--color-bg-primary'],
91
- borderColor: theme.variables['--color-border'],
92
- }}
93
- />
94
- <span
95
- className="w-4 h-4 rounded-full"
96
- style={{ backgroundColor: theme.variables['--color-accent'] }}
97
- />
98
- </div>
99
- <div>
100
- <div className="text-sm font-medium">{theme.name}</div>
101
- </div>
102
- {activeTheme.id === theme.id && (
103
- <svg className="ml-auto" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
104
- <path d="M20 6L9 17l-5-5" />
105
- </svg>
106
- )}
107
- </button>
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
- function getDbPath(): string {
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
- const dbPath = getDbPath();
21
- if (existsSync(dbPath)) {
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(dbPath, '..');
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(getDbPath(), Buffer.from(data));
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",
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",
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