54 |
💻
55 |
Offline Leet Practice
56 |
Starting the application... Please wait while we prepare your coding environment.
57 |
Loading
58 |
59 |
66 |
67 |
--------------------------------------------------------------------------------
/pages/api/delete-problems.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 | import fs from 'fs';
3 | import path from 'path';
4 |
5 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6 | if (req.method !== 'POST') {
7 | return res.status(405).json({ error: 'Method not allowed' });
8 | }
9 |
10 | try {
11 | const { ids } = req.body;
12 |
13 | if (!ids || !Array.isArray(ids) || ids.length === 0) {
14 | return res.status(400).json({ error: 'Problem IDs array is required' });
15 | }
16 |
17 | // Validate all IDs are strings
18 | if (!ids.every(id => typeof id === 'string')) {
19 | return res.status(400).json({ error: 'All IDs must be strings' });
20 | }
21 |
22 | const appRoot = process.env.APP_ROOT || process.cwd();
23 |
24 | // Read current problems
25 | const problemsPath = path.join(appRoot, 'public', 'problems.json');
26 | const problemsData = fs.readFileSync(problemsPath, 'utf8');
27 | const problems: any[] = JSON.parse(problemsData);
28 |
29 | // Create a set of IDs to delete for efficient lookup
30 | const idsToDelete = new Set(ids);
31 |
32 | // Filter out problems to delete
33 | const remainingProblems = problems.filter(p => !idsToDelete.has(p.id));
34 |
35 | // Calculate how many were actually deleted
36 | const deletedCount = problems.length - remainingProblems.length;
37 |
38 | if (deletedCount === 0) {
39 | return res.status(404).json({ error: 'No matching problems found to delete' });
40 | }
41 |
42 | // Save updated problems
43 | fs.writeFileSync(problemsPath, JSON.stringify(remainingProblems, null, 2));
44 |
45 | // Also sync to problems/problems.json
46 | const sourceProblemsPath = path.join(appRoot, 'problems', 'problems.json');
47 | fs.writeFileSync(sourceProblemsPath, JSON.stringify(remainingProblems, null, 2));
48 |
49 | return res.status(200).json({
50 | message: 'Problems deleted successfully',
51 | deletedCount,
52 | remainingCount: remainingProblems.length,
53 | });
54 | } catch (error) {
55 | console.error('Error deleting problems:', error);
56 | return res.status(500).json({
57 | error: error instanceof Error ? error.message : 'Failed to delete problems'
58 | });
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [v0.0.8] - 2025-12-08
8 | ### :sparkles: New Features
9 | - [`67f3808`](https://github.com/zxypro1/OfflineCodePractice/commit/67f380881f27cf6e866c8c8e9b455d03fac38905) - **desktop**: add Electron-based desktop app support for Offline Leet Practice *(commit by [@zxypro1](https://github.com/zxypro1))*
10 | - [`3645fc3`](https://github.com/zxypro1/OfflineCodePractice/commit/3645fc30352b076374220a3835ec2c7e55df0e60) - refactor and enhance Algorithm Practice desktop application
11 |
12 | ### :bug: Bug Fixes
13 | - [`500047e`](https://github.com/zxypro1/OfflineCodePractice/commit/500047e80cf7152540ef3a157fbd9b1a75f42c83) - **menu**: update documentation link to correct GitHub repository *(commit by [@zxypro1](https://github.com/zxypro1))*
14 |
15 | ### :wrench: Chores
16 | - [`c256daa`](https://github.com/zxypro1/OfflineCodePractice/commit/c256daa0e650261f062288bf95d651b3ed0f69b2) - **start-local**: rewrite and improve Windows startup script *(commit by [@zxypro1](https://github.com/zxypro1))*
17 |
18 |
19 | ## [v0.0.7] - 2025-08-26
20 | ### :bug: Bug Fixes
21 | - [`da72634`](https://github.com/zxypro1/OfflineLeetPractice/commit/da72634a44b05cf02707f76117ae59f1fe2fb6be) - 修正打包过程中获取标签的方式,确保使用正确的标签名称 *(commit by [@zxypro1](https://github.com/zxypro1))*
22 |
23 |
24 | ## [v0.0.6] - 2025-08-26
25 | ### :bug: Bug Fixes
26 | - [`4c6af69`](https://github.com/zxypro1/OfflineLeetPractice/commit/4c6af694b1f43e932b482bca6af12f85cea8949b) - 修正发布资产文件名以使用正确的标签引用 *(commit by [@zxypro1](https://github.com/zxypro1))*
27 |
28 |
29 | ## [v0.0.5] - 2025-08-26
30 | ### :wrench: Chores
31 | - [`e22d12a`](https://github.com/zxypro1/OfflineLeetPractice/commit/e22d12ae51de40e57872554dea10d440c4456c48) - 优化发布流程,移除依赖文件以减小打包体积 *(commit by [@zxypro1](https://github.com/zxypro1))*
32 |
33 |
34 | ## [v0.0.4] - 2025-08-26
35 | ### :bug: Bug Fixes
36 | - [`0f3745f`](https://github.com/zxypro1/OfflineLeetPractice/commit/0f3745f4cdfbfa264c889df49d3d5436d0b84dc8) - 将 CHANGELOG.md 提交分支从 master 更新为 main *(commit by [@zxypro1](https://github.com/zxypro1))*
37 |
38 | [v0.0.4]: https://github.com/zxypro1/OfflineLeetPractice/compare/v0.0.3...v0.0.4
39 | [v0.0.5]: https://github.com/zxypro1/OfflineLeetPractice/compare/v0.0.4...v0.0.5
40 | [v0.0.6]: https://github.com/zxypro1/OfflineLeetPractice/compare/v0.0.5...v0.0.6
41 | [v0.0.7]: https://github.com/zxypro1/OfflineLeetPractice/compare/v0.0.6...v0.0.7
42 | [v0.0.8]: https://github.com/zxypro1/OfflineCodePractice/compare/v0.0.7...v0.0.8
43 |
--------------------------------------------------------------------------------
/pages/api/update-problem.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 | import fs from 'fs';
3 | import path from 'path';
4 |
5 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6 | if (req.method !== 'POST') {
7 | return res.status(405).json({ error: 'Method not allowed' });
8 | }
9 |
10 | try {
11 | const { problem, originalId } = req.body;
12 |
13 | if (!problem) {
14 | return res.status(400).json({ error: 'Problem data is required' });
15 | }
16 |
17 | if (!originalId) {
18 | return res.status(400).json({ error: 'Original problem ID is required' });
19 | }
20 |
21 | // Validate required fields
22 | const requiredFields = ['id', 'title', 'difficulty', 'description', 'template', 'tests'];
23 | for (const field of requiredFields) {
24 | if (!problem[field]) {
25 | return res.status(400).json({ error: `Field '${field}' is required` });
26 | }
27 | }
28 |
29 | // Validate id format
30 | if (!/^[a-z0-9-]+$/.test(problem.id)) {
31 | return res.status(400).json({ error: 'ID must contain only lowercase letters, numbers, and hyphens' });
32 | }
33 |
34 | const appRoot = process.env.APP_ROOT || process.cwd();
35 | // Read current problems
36 | const problemsPath = path.join(appRoot, 'public', 'problems.json');
37 | const problemsData = fs.readFileSync(problemsPath, 'utf8');
38 | const problems = JSON.parse(problemsData);
39 |
40 | // Find the problem to update
41 | const problemIndex = problems.findIndex((p: any) => p.id === originalId);
42 | if (problemIndex === -1) {
43 | return res.status(404).json({ error: 'Problem not found' });
44 | }
45 |
46 | // If ID changed, check if new ID already exists
47 | if (problem.id !== originalId) {
48 | const existingProblem = problems.find((p: any) => p.id === problem.id);
49 | if (existingProblem) {
50 | return res.status(409).json({ error: 'Problem with this ID already exists' });
51 | }
52 | }
53 |
54 | // Update the problem
55 | problems[problemIndex] = problem;
56 |
57 | // Write to public/problems.json
58 | fs.writeFileSync(problemsPath, JSON.stringify(problems, null, 2));
59 |
60 | // Also sync to problems/problems.json
61 | const sourceProblemsPath = path.join(appRoot, 'problems', 'problems.json');
62 | if (fs.existsSync(path.dirname(sourceProblemsPath))) {
63 | fs.writeFileSync(sourceProblemsPath, JSON.stringify(problems, null, 2));
64 | }
65 |
66 | res.status(200).json({ message: 'Problem updated successfully', id: problem.id });
67 | } catch (error) {
68 | console.error('Error updating problem:', error);
69 | res.status(500).json({ error: 'Failed to update problem' });
70 | }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/DESKTOP-APP-GUIDE-zh.md:
--------------------------------------------------------------------------------
1 | # 桌面应用构建指南
2 |
3 | 本指南介绍如何将离线算法练习构建为跨平台桌面应用,支持 Windows、macOS 和 Linux。
4 |
5 | ## 概述
6 |
7 | 桌面应用是一个独立的、自包含的软件包,无需任何外部依赖。用户可以直接下载运行,无需安装 Node.js、Python 或任何开发工具。
8 |
9 | ## 技术架构
10 |
11 | - **前端框架**:Next.js + React
12 | - **桌面框架**:Electron
13 | - **代码执行**:浏览器端 WASM
14 | - JavaScript:浏览器原生执行
15 | - TypeScript:TypeScript 编译器转译
16 | - Python:Pyodide (CPython WASM)
17 |
18 | ## 前置要求(仅限构建)
19 |
20 | 以下要求仅适用于从源码构建的开发者:
21 |
22 | - Node.js >= 18.x
23 | - npm >= 8.x
24 | - 平台特定要求:
25 | - macOS 构建:macOS 系统
26 | - Windows 构建:Windows 系统或 Wine
27 | - Linux 构建:Linux 系统或 Docker
28 |
29 | ## 构建应用
30 |
31 | ### 开发模式
32 |
33 | ```bash
34 | npm install
35 | npm run build
36 | npm run electron:start
37 | ```
38 |
39 | ### 生产构建
40 |
41 | #### macOS
42 |
43 | ```bash
44 | ./build-mac.sh
45 | # 或
46 | npm run dist:mac
47 | ```
48 |
49 | 输出文件:
50 | - `dist/Algorithm Practice-x.x.x-macOS-x64.dmg`(Intel)
51 | - `dist/Algorithm Practice-x.x.x-macOS-arm64.dmg`(Apple Silicon)
52 |
53 | #### Windows
54 |
55 | PowerShell:
56 | ```powershell
57 | .\build-windows.ps1
58 | # 或
59 | npm run dist:win
60 | ```
61 |
62 | 命令提示符:
63 | ```cmd
64 | build-windows.bat
65 | ```
66 |
67 | 输出文件:
68 | - `dist/Algorithm Practice-x.x.x-Windows-x64.exe`(安装版)
69 | - `dist/Algorithm Practice-x.x.x-Windows-Portable.exe`(便携版)
70 |
71 | #### Linux
72 |
73 | ```bash
74 | npm run dist:linux
75 | ```
76 |
77 | 输出文件:
78 | - `dist/Algorithm Practice-x.x.x-Linux.AppImage`
79 | - `dist/Algorithm Practice-x.x.x-Linux.deb`
80 | - `dist/Algorithm Practice-x.x.x-Linux.rpm`
81 |
82 | #### 所有平台
83 |
84 | ```bash
85 | npm run dist:all
86 | ```
87 |
88 | ## 支持的语言
89 |
90 | | 语言 | 状态 | 实现方式 |
91 | |------|------|---------|
92 | | JavaScript | 支持 | 浏览器原生 |
93 | | TypeScript | 支持 | TypeScript 编译器 |
94 | | Python | 支持 | Pyodide (WASM) |
95 |
96 | 注意:需要原生编译器的语言(Java、C、C++、Go)在浏览器端 WASM 环境中不支持。
97 |
98 | ## 项目结构
99 |
100 | ```
101 | .
102 | ├── electron-main.js # Electron 主进程
103 | ├── electron-preload.js # Electron 预加载脚本
104 | ├── electron-builder.config.js # 构建配置
105 | ├── build/
106 | │ └── entitlements.mac.plist # macOS 权限配置
107 | ├── build-mac.sh # macOS 构建脚本
108 | ├── build-windows.ps1 # Windows PowerShell 构建脚本
109 | ├── build-windows.bat # Windows 批处理构建脚本
110 | └── dist/ # 构建输出目录
111 | ```
112 |
113 | ## 配置说明
114 |
115 | ### electron-builder.config.js
116 |
117 | 主要配置项:
118 |
119 | - `appId`:应用程序标识符
120 | - `productName`:应用显示名称
121 | - `files`:打包文件列表
122 | - `win/mac/linux`:各平台特定配置
123 |
124 | ### AI 服务商设置
125 |
126 | 桌面应用支持通过内置设置页面进行配置:
127 |
128 | - DeepSeek
129 | - OpenAI
130 | - Qwen(通义千问)
131 | - Claude(Anthropic)
132 | - Ollama(本地部署)
133 |
134 | 配置存储在 `~/.offline-leet-practice/config.json`。
135 |
136 | ## 故障排除
137 |
138 | ### macOS:安全警告
139 |
140 | 首次打开应用时,macOS 可能显示安全警告。
141 |
142 | 解决方法:
143 | 1. 打开系统偏好设置 > 安全性与隐私
144 | 2. 点击"仍要打开"
145 |
146 | 或在终端执行:
147 | ```bash
148 | xattr -cr "/Applications/Algorithm Practice.app"
149 | ```
150 |
151 | ### Windows:SmartScreen 警告
152 |
153 | Windows 可能对未签名的应用显示 SmartScreen 警告。
154 |
155 | 解决方法:点击"更多信息" > "仍要运行"
156 |
157 | ### 代码签名
158 |
159 | 生产环境分发建议进行代码签名:
160 |
161 | - **macOS**:需要 Apple Developer 证书
162 | - **Windows**:需要 EV 代码签名证书
163 |
164 | ## 更新日志
165 |
166 | ### v0.0.9
167 | - 迁移至纯 WASM 浏览器端代码执行
168 | - 新增 TypeScript 支持
169 | - 移除对服务器端执行的依赖
170 | - 改进 Electron 构建配置
171 | - 增强 AI 服务商设置页面
172 |
173 | ## 许可证
174 |
175 | MIT License
176 |
--------------------------------------------------------------------------------
/public/desktop-main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |