xinyu-pro 0.22.4 → 0.22.6

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/page.tsx CHANGED
@@ -86,7 +86,7 @@ export default function HomePage() {
86
86
  }, []);
87
87
  const handleTemplateClick = (templateId: string) => { router.push(`/editor?mode=edit&templateId=${templateId}`); };
88
88
 
89
- /** 快速开始游戏(从主界面直接开始,携带模板配置的插件) */
89
+ /* 快速开始游戏(从主界面直接开始,携带模板配置的插件) */
90
90
  const handleQuickStart = useCallback(async (tpl: WorldCardTemplate, forceStart = false) => {
91
91
  const pluginIds = tpl.plugins || [];
92
92
  const installedIds = new Set(availablePlugins.map(p => p.id));
@@ -142,7 +142,7 @@ export default function HomePage() {
142
142
  }
143
143
  }
144
144
 
145
- router.push(`/game/${gameId}`);
145
+ navigate(`/game/${gameId}`);
146
146
  } catch (err) {
147
147
  console.error('快速开始游戏失败:', err);
148
148
  }
@@ -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'}
package/bin/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } = require('fs');
4
- const { join, resolve } = require('path');
5
- const { createInterface } = require('readline');
6
- const { execSync, spawn } = require('child_process');
7
- const { homedir } = require('os');
3
+ const {existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync} = require('fs');
4
+ const {join, resolve} = require('path');
5
+ const {createInterface} = require('readline');
6
+ const {execSync, spawn} = require('child_process');
7
+ const {homedir} = require('os');
8
8
  const path = require('path');
9
9
 
10
10
  const isWin = process.platform === 'win32';
@@ -13,272 +13,287 @@ const XINYU_HOME = join(homedir(), '.xinyu-pro');
13
13
  const RUN_DIR = XINYU_HOME;
14
14
 
15
15
  const args = process.argv.slice(2);
16
- const VERSION_FLAG = args.includes('--version');
16
+ const VERSION_FLAG = args.includes('--version') || args.includes('-v');
17
17
  const HELP_FLAG = args.includes('--help') || args.includes('-h');
18
18
  const DEV_FLAG = args.includes('--dev');
19
19
  const RESET_FLAG = args.includes('--reset');
20
+ const UNINSTALL_FLAG = args.includes('--uninstall') || args.includes('-unins');
20
21
  const PORT_INDEX = args.indexOf('--port');
21
22
  const CUSTOM_PORT = PORT_INDEX !== -1 ? args[PORT_INDEX + 1] : '3000';
22
23
 
23
24
  const SKIP_DIRS = new Set(['node_modules', '.next', 'data', '.git']);
24
25
 
25
26
  function echo(msg, type = 'info') {
26
- const colors = {
27
- info: '\x1b[36m',
28
- success: '\x1b[32m',
29
- warn: '\x1b[33m',
30
- error: '\x1b[31m',
31
- dim: '\x1b[2m',
32
- reset: '\x1b[0m',
33
- bold: '\x1b[1m',
34
- };
35
- const color = colors[type] || colors.info;
36
- console.log(`${color}${msg}${colors.reset}`);
27
+ const colors = {
28
+ info: '\x1b[36m',
29
+ success: '\x1b[32m',
30
+ warn: '\x1b[33m',
31
+ error: '\x1b[31m',
32
+ dim: '\x1b[2m',
33
+ reset: '\x1b[0m',
34
+ bold: '\x1b[1m',
35
+ };
36
+ const color = colors[type] || colors.info;
37
+ console.log(`${color}${msg}${colors.reset}`);
37
38
  }
38
39
 
39
40
  function ask(question) {
40
- const rl = createInterface({ input: process.stdin, output: process.stdout });
41
- return new Promise((resolve) => {
42
- rl.question(question, (answer) => {
43
- rl.close();
44
- resolve(answer.trim());
41
+ const rl = createInterface({input: process.stdin, output: process.stdout});
42
+ return new Promise((resolve) => {
43
+ rl.question(question, (answer) => {
44
+ rl.close();
45
+ resolve(answer.trim());
46
+ });
45
47
  });
46
- });
47
48
  }
48
49
 
49
50
  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);
51
+ const versionPath = path.join(APP_DIR, 'version.json');
52
+ const versionData = JSON.parse(readFileSync(versionPath, 'utf-8'));
53
+ console.log(`${versionData.name} ${versionData.version}`);
54
+ process.exit(0);
54
55
  }
55
56
 
56
57
  function showHelp() {
57
- echo('');
58
- echo(' ╔══════════════════════════════════════╗', 'bold');
59
- echo(' ║ 星语 Pro (xinyu-pro) ║', 'bold');
60
- echo(' ║ AI 驱动的互动叙事平台 ║', 'bold');
61
- echo(' ╚══════════════════════════════════════╝', 'bold');
62
- echo('');
63
- echo(' 用法:', 'bold');
64
- echo(' $ xinyu 启动服务器');
65
- echo(' $ xinyu --dev 开发模式(热更新)');
66
- echo(' $ xinyu --port 4000 指定端口');
67
- echo(' $ xinyu --reset 重新安装并启动');
68
- echo(' $ xinyu --help 显示帮助');
69
- echo('');
70
- echo(' 环境要求:', 'bold');
71
- echo(' • Node.js >= 18');
72
- echo('');
73
- process.exit(0);
58
+ echo(' ╔══════════════════════════════════════╗', 'bold');
59
+ echo(' ║ 星语 Pro (xinyu-pro) ║', 'bold');
60
+ echo(' ║ AI 驱动的互动叙事平台 ║', 'bold');
61
+ echo(' ╚══════════════════════════════════════╝', 'bold');
62
+ echo('');
63
+ echo(' 用法:', 'bold');
64
+ echo(' $ xinyu 启动服务器');
65
+ echo(' $ xinyu --dev 开发模式(热更新)');
66
+ echo(' $ xinyu --port 4000 指定端口');
67
+ echo(' $ xinyu --reset 重新安装并启动');
68
+ echo(' $ xinyu --help 显示帮助');
69
+ echo('');
70
+ echo(' 环境要求:', 'bold');
71
+ echo(' Node.js >= 18');
72
+ echo('');
73
+ process.exit(0);
74
74
  }
75
75
 
76
76
  function checkNodeVersion() {
77
- const [major] = process.versions.node.split('.').map(Number);
78
- if (major < 18) {
79
- echo(` ✗ Node.js 版本过低: ${process.versions.node},需要 >= 18`, 'error');
80
- process.exit(1);
81
- }
77
+ const [major] = process.versions.node.split('.').map(Number);
78
+ if (major < 18) {
79
+ echo(` ✗ Node.js 版本过低: ${process.versions.node},需要 >= 18`, 'error');
80
+ process.exit(1);
81
+ }
82
82
  }
83
83
 
84
84
  function copyRecursive(src, dest) {
85
- mkdirSync(dest, { recursive: true });
86
- const entries = readdirSync(src, { withFileTypes: true });
87
- for (const entry of entries) {
88
- if (SKIP_DIRS.has(entry.name)) continue;
89
- if (entry.name.startsWith('.env') && entry.name !== '.env.example') continue;
90
- const srcPath = join(src, entry.name);
91
- const destPath = join(dest, entry.name);
92
- if (entry.isDirectory()) {
93
- copyRecursive(srcPath, destPath);
94
- } else {
95
- if (!existsSync(destPath)) {
96
- try {
97
- mkdirSync(join(dest, '..'), { recursive: true });
98
- writeFileSync(destPath, readFileSync(srcPath));
99
- } catch { }
100
- }
85
+ mkdirSync(dest, {recursive: true});
86
+ const entries = readdirSync(src, {withFileTypes: true});
87
+ for (const entry of entries) {
88
+ if (SKIP_DIRS.has(entry.name)) continue;
89
+ if (entry.name.startsWith('.env') && entry.name !== '.env.example') continue;
90
+ const srcPath = join(src, entry.name);
91
+ const destPath = join(dest, entry.name);
92
+ if (entry.isDirectory()) {
93
+ copyRecursive(srcPath, destPath);
94
+ } else {
95
+ if (!existsSync(destPath)) {
96
+ try {
97
+ mkdirSync(join(dest, '..'), {recursive: true});
98
+ writeFileSync(destPath, readFileSync(srcPath));
99
+ } catch {
100
+ }
101
+ }
102
+ }
101
103
  }
102
- }
103
104
  }
104
105
 
105
106
  function ensureAppSetup() {
106
- const isFresh = !existsSync(join(RUN_DIR, 'package.json'));
107
-
108
- if (isFresh || RESET_FLAG) {
109
- if (RESET_FLAG && existsSync(RUN_DIR)) {
110
- echo(' ■ 正在重置应用...', 'info');
111
- try {
112
- const rmDir = join(RUN_DIR, '..', '.xinyu-pro-backup');
113
- if (existsSync(rmDir)) {
114
- execSync(isWin ? `rmdir /s /q "${rmDir}"` : `rm -rf "${rmDir}"`, { shell: isWin });
107
+ const isFresh = !existsSync(join(RUN_DIR, 'package.json'));
108
+
109
+ if (isFresh || RESET_FLAG) {
110
+ if (RESET_FLAG && existsSync(RUN_DIR)) {
111
+ echo(' ■ 正在重置应用...', 'info');
112
+ try {
113
+ const rmDir = join(RUN_DIR, '..', '.xinyu-pro-backup');
114
+ if (existsSync(rmDir)) {
115
+ execSync(isWin ? `rmdir /s /q "${rmDir}"` : `rm -rf "${rmDir}"`, {shell: isWin});
116
+ }
117
+ execSync(isWin ? `move "${RUN_DIR}" "${rmDir}"` : `mv "${RUN_DIR}" "${rmDir}"`, {shell: isWin});
118
+ } catch {
119
+ }
115
120
  }
116
- execSync(isWin ? `move "${RUN_DIR}" "${rmDir}"` : `mv "${RUN_DIR}" "${rmDir}"`, { shell: isWin });
117
- } catch { }
118
- }
119
121
 
120
- echo(' ■ 正在初始化应用目录...', 'info');
121
- echo(' ' + RUN_DIR, 'dim');
122
- mkdirSync(RUN_DIR, { recursive: true });
123
- copyRecursive(APP_DIR, RUN_DIR);
124
-
125
- const envTarget = join(RUN_DIR, '.env.local');
126
- if (!existsSync(envTarget)) {
127
- const envExample = join(RUN_DIR, '.env.example');
128
- if (existsSync(envExample)) {
129
- writeFileSync(envTarget, readFileSync(envExample, 'utf-8'), 'utf-8');
130
- }
122
+ echo(' ■ 正在初始化应用目录...', 'info');
123
+ echo(' ' + RUN_DIR, 'dim');
124
+ mkdirSync(RUN_DIR, {recursive: true});
125
+ copyRecursive(APP_DIR, RUN_DIR);
126
+
127
+ const envTarget = join(RUN_DIR, '.env.local');
128
+ if (!existsSync(envTarget)) {
129
+ const envExample = join(RUN_DIR, '.env.example');
130
+ if (existsSync(envExample)) {
131
+ writeFileSync(envTarget, readFileSync(envExample, 'utf-8'), 'utf-8');
132
+ }
133
+ }
134
+ return true;
131
135
  }
132
- return true;
133
- }
134
136
 
135
- return false;
137
+ return false;
136
138
  }
137
139
 
138
140
  function installDependencies() {
139
- if (!existsSync(join(RUN_DIR, 'node_modules'))) {
140
- echo(' ■ 正在安装依赖...', 'info');
141
- execSync('npm install', { cwd: RUN_DIR, stdio: 'inherit', shell: isWin });
142
- return true;
143
- }
144
- return false;
141
+ if (!existsSync(join(RUN_DIR, 'node_modules'))) {
142
+ echo(' ■ 正在安装依赖...', 'info');
143
+ execSync('npm install', {cwd: RUN_DIR, stdio: 'inherit', shell: isWin});
144
+ return true;
145
+ }
146
+ return false;
145
147
  }
146
148
 
147
149
  function runBuild() {
148
- echo(' ■ 正在构建应用(首次需要 1-2 分钟)...', 'info');
149
- try {
150
- execSync('npx next build', { cwd: RUN_DIR, stdio: 'inherit', shell: isWin });
151
- echo(' ✓ 构建完成', 'success');
152
- } catch {
153
- echo(' ✗ 构建失败,请检查错误信息', 'error');
154
- process.exit(1);
155
- }
150
+ echo(' ■ 正在构建应用(首次需要 1-2 分钟)...', 'info');
151
+ try {
152
+ execSync('npx next build', {cwd: RUN_DIR, stdio: 'inherit', shell: isWin});
153
+ echo(' ✓ 构建完成', 'success');
154
+ } catch {
155
+ echo(' ✗ 构建失败,请检查错误信息', 'error');
156
+ process.exit(1);
157
+ }
156
158
  }
157
159
 
158
160
  function getNextBin() {
159
- const binDir = join(RUN_DIR, 'node_modules', '.bin');
160
- if (isWin) {
161
- const cmdPath = join(binDir, 'next.cmd');
162
- if (existsSync(cmdPath)) return cmdPath;
163
- }
164
- const unixPath = join(binDir, 'next');
165
- if (existsSync(unixPath)) return unixPath;
166
- return 'next';
161
+ const binDir = join(RUN_DIR, 'node_modules', '.bin');
162
+ if (isWin) {
163
+ const cmdPath = join(binDir, 'next.cmd');
164
+ if (existsSync(cmdPath)) return cmdPath;
165
+ }
166
+ const unixPath = join(binDir, 'next');
167
+ if (existsSync(unixPath)) return unixPath;
168
+ return 'next';
167
169
  }
168
170
 
169
- function setupEnvInteractive() {
170
- const envPath = join(RUN_DIR, '.env.local');
171
- const examplePath = join(RUN_DIR, '.env.example');
171
+ function startServer(port, devMode) {
172
+ const nextBin = getNextBin();
173
+ const action = devMode ? 'dev' : 'start';
174
+
175
+ echo(` ╔══════════════════════════════════════╗`, 'success');
176
+ echo(` ║ 星语 Pro 服务器已启动 ║`, 'success');
177
+ echo(` ║ 访问地址: http://localhost:${port} ║`, 'success');
178
+ echo(` ║ 工作目录: ${RUN_DIR}`, 'dim');
179
+ echo(` ║ 按 Ctrl+C 停止 ║`, 'success');
180
+ echo(` ╚══════════════════════════════════════╝`, 'success');
181
+ echo('');
172
182
 
173
- if (existsSync(envPath)) {
174
- const content = readFileSync(envPath, 'utf-8');
175
- if (content.includes('your-api-key')) {
176
- echo(' ⚠ .env.local 中存在占位值,建议修改', 'warn');
177
- }
178
- return;
179
- }
183
+ const child = spawn(nextBin, [action, '-p', port], {
184
+ cwd: RUN_DIR,
185
+ stdio: 'inherit',
186
+ shell: isWin,
187
+ env: {...process.env, PORT: port},
188
+ });
180
189
 
181
- if (!existsSync(examplePath)) {
182
- echo('未找到 .env.example', 'error');
183
- process.exit(1);
184
- }
190
+ child.on('error', (err) => {
191
+ echo(`启动失败: ${err.message}`, 'error');
192
+ process.exit(1);
193
+ });
185
194
 
186
- const example = readFileSync(examplePath, 'utf-8');
187
- writeFileSync(envPath, example, 'utf-8');
188
- echo(' 已创建 .env.local', 'success');
195
+ child.on('exit', (code) => {
196
+ if (code !== 0 && code !== null) {
197
+ echo(` 进程异常退出 (code: ${code})`, 'error');
198
+ }
199
+ process.exit(code || 0);
200
+ });
189
201
 
190
- const run = async () => {
191
- echo('');
192
- echo(' ── 填写配置(直接回车可跳过) ──', 'bold');
193
- const apiKey = await ask(' AI API 密钥 (AI_API_KEY): ');
194
- if (apiKey) {
195
- let c = readFileSync(envPath, 'utf-8');
196
- writeFileSync(envPath, c.replace(/AI_API_KEY=.*/, `AI_API_KEY=${apiKey}`), 'utf-8');
197
- }
198
- echo(' ✓ 配置完成', 'success');
199
- echo(' ✎ 编辑: ' + envPath, 'dim');
200
- };
201
- return run();
202
+ process.on('SIGINT', () => {
203
+ echo('');
204
+ echo(' 正在关闭...', 'info');
205
+ child.kill('SIGINT');
206
+ process.exit(0);
207
+ });
208
+
209
+ process.on('SIGTERM', () => {
210
+ child.kill('SIGTERM');
211
+ process.exit(0);
212
+ });
202
213
  }
203
214
 
204
- function startServer(port, devMode) {
205
- const nextBin = getNextBin();
206
- const action = devMode ? 'dev' : 'start';
207
-
208
- echo('');
209
- echo(` ╔══════════════════════════════════════╗`, 'success');
210
- echo(` ║ 星语 Pro 服务器已启动 ║`, 'success');
211
- echo(` ║ 访问地址: http://localhost:${port} ║`, 'success');
212
- echo(` ║ 工作目录: ${RUN_DIR}`, 'dim');
213
- echo(` ║ 按 Ctrl+C 停止 ║`, 'success');
214
- echo(` ╚══════════════════════════════════════╝`, 'success');
215
- echo('');
216
-
217
- const child = spawn(nextBin, [action, '-p', port], {
218
- cwd: RUN_DIR,
219
- stdio: 'inherit',
220
- shell: isWin,
221
- env: { ...process.env, PORT: port },
222
- });
223
-
224
- child.on('error', (err) => {
225
- echo(` ✗ 启动失败: ${err.message}`, 'error');
226
- process.exit(1);
227
- });
215
+ async function handleUninstall() {
216
+ echo(' 正在卸载星语 Pro...', 'warn');
228
217
 
229
- child.on('exit', (code) => {
230
- if (code !== 0 && code !== null) {
231
- echo(` ✗ 进程异常退出 (code: ${code})`, 'error');
218
+ // 确认卸载
219
+ const confirm = await ask(' 确认删除 ~/.xinyu-pro 目录吗?(y/N): ');
220
+ if (confirm.toLowerCase() !== 'y') {
221
+ echo(' 已取消卸载', 'info');
222
+ process.exit(0);
232
223
  }
233
- process.exit(code || 0);
234
- });
235
224
 
236
- process.on('SIGINT', () => {
237
- echo('');
238
- echo(' ■ 正在关闭...', 'info');
239
- child.kill('SIGINT');
240
- process.exit(0);
241
- });
225
+ echo(' ■ 正在删除应用目录...', 'info');
226
+
227
+ try {
228
+ if (existsSync(RUN_DIR)) {
229
+ // 删除主目录
230
+ if (isWin) {
231
+ execSync(`rmdir /s /q "${RUN_DIR}"`, {shell: isWin});
232
+ } else {
233
+ execSync(`rm -rf "${RUN_DIR}"`, {shell: isWin});
234
+ }
235
+ echo(` ✓ 已删除 ${RUN_DIR}`, 'success');
236
+ } else {
237
+ echo(' 未找到安装目录', 'warn');
238
+ }
242
239
 
243
- process.on('SIGTERM', () => {
244
- child.kill('SIGTERM');
245
- process.exit(0);
246
- });
240
+ // 删除备份目录(如果存在)
241
+ const backupDir = join(RUN_DIR, '..', '.xinyu-pro-backup');
242
+ if (existsSync(backupDir)) {
243
+ if (isWin) {
244
+ execSync(`rmdir /s /q "${backupDir}"`, {shell: isWin});
245
+ } else {
246
+ execSync(`rm -rf "${backupDir}"`, {shell: isWin});
247
+ }
248
+ echo(' ✓ 已删除备份目录', 'success');
249
+ }
250
+
251
+ echo(' ✓ 卸载完成!', 'success');
252
+ echo(' 感谢使用星语 Pro,期待再次相见 👋', 'info');
253
+ process.exit(0);
254
+ } catch (err) {
255
+ echo(` ✗ 卸载失败: ${err.message}`, 'error');
256
+ process.exit(1);
257
+ }
247
258
  }
248
259
 
249
260
  async function main() {
250
- echo('');
251
- echo(' ╔══════════════════════════════════════╗', 'bold');
252
- echo(' ║ 星语 Pro (xinyu-pro) ║', 'bold');
253
- echo(' ╚══════════════════════════════════════╝', 'bold');
254
- echo('');
255
-
256
- if (VERSION_FLAG) {
257
- showVersion();
258
- return;
259
- }
260
-
261
- if (HELP_FLAG) {
262
- showHelp();
263
- return;
264
- }
265
-
266
- checkNodeVersion();
267
- ensureAppSetup();
268
- installDependencies();
269
- await setupEnvInteractive();
270
-
271
- if (!DEV_FLAG && !existsSync(join(RUN_DIR, '.next', 'BUILD_ID'))) {
272
- echo(' ⚠ 未检测到构建产物,自动构建...', 'warn');
273
- echo(' (首次构建需要下载依赖并编译,请耐心等待)', 'dim');
274
261
  echo('');
275
- runBuild();
276
- }
262
+ echo(' ╔══════════════════════════════════════╗', 'bold');
263
+ echo(' ║ 星语 Pro (xinyu-pro) ║', 'bold');
264
+ echo(' ╚══════════════════════════════════════╝', 'bold');
265
+ echo('');
266
+
267
+ if (VERSION_FLAG) {
268
+ showVersion();
269
+ return;
270
+ }
277
271
 
278
- startServer(CUSTOM_PORT, DEV_FLAG);
272
+ if (HELP_FLAG) {
273
+ showHelp();
274
+ return;
275
+ }
276
+
277
+ if (UNINSTALL_FLAG) {
278
+ await handleUninstall();
279
+ return;
280
+ }
281
+
282
+ checkNodeVersion();
283
+ ensureAppSetup();
284
+ installDependencies();
285
+
286
+ if (!DEV_FLAG && !existsSync(join(RUN_DIR, '.next', 'BUILD_ID'))) {
287
+ echo(' ⚠ 未检测到构建产物,自动构建...', 'warn');
288
+ echo(' (首次构建需要下载依赖并编译,请耐心等待)', 'dim');
289
+ echo('');
290
+ runBuild();
291
+ }
292
+
293
+ startServer(CUSTOM_PORT, DEV_FLAG);
279
294
  }
280
295
 
281
296
  main().catch((e) => {
282
- echo(` ✗ 启动失败: ${e.message}`, 'error');
283
- process.exit(1);
297
+ echo(` ✗ 启动失败: ${e.message}`, 'error');
298
+ process.exit(1);
284
299
  });
@@ -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
  <>