smart-code-editor 1.0.0

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.
Files changed (63) hide show
  1. package/README.md +155 -0
  2. package/lib/adapters/vanilla/index.d.ts +3 -0
  3. package/lib/adapters/vanilla/index.d.ts.map +1 -0
  4. package/lib/config/languages.d.ts +21 -0
  5. package/lib/config/languages.d.ts.map +1 -0
  6. package/lib/config/runnerStrategies.d.ts +12 -0
  7. package/lib/config/runnerStrategies.d.ts.map +1 -0
  8. package/lib/config/runnerStrategies_v2.d.ts +31 -0
  9. package/lib/config/runnerStrategies_v2.d.ts.map +1 -0
  10. package/lib/config/themes.d.ts +54 -0
  11. package/lib/config/themes.d.ts.map +1 -0
  12. package/lib/core/BackendRunner.d.ts +78 -0
  13. package/lib/core/BackendRunner.d.ts.map +1 -0
  14. package/lib/core/CodeRunner.d.ts +32 -0
  15. package/lib/core/CodeRunner.d.ts.map +1 -0
  16. package/lib/core/LanguageManager.d.ts +41 -0
  17. package/lib/core/LanguageManager.d.ts.map +1 -0
  18. package/lib/core/LayoutManager.d.ts +59 -0
  19. package/lib/core/LayoutManager.d.ts.map +1 -0
  20. package/lib/core/MonacoWrapper.d.ts +63 -0
  21. package/lib/core/MonacoWrapper.d.ts.map +1 -0
  22. package/lib/core/SmartCodeEditor.d.ts +140 -0
  23. package/lib/core/SmartCodeEditor.d.ts.map +1 -0
  24. package/lib/dev-main.d.ts +2 -0
  25. package/lib/dev-main.d.ts.map +1 -0
  26. package/lib/index.cjs +242 -0
  27. package/lib/index.d.ts +5 -0
  28. package/lib/index.d.ts.map +1 -0
  29. package/lib/index.js +1369 -0
  30. package/lib/index.umd.cjs +242 -0
  31. package/lib/shims-vue.d.ts +4 -0
  32. package/lib/types/index.d.ts +101 -0
  33. package/lib/types/index.d.ts.map +1 -0
  34. package/lib/types/language.d.ts +37 -0
  35. package/lib/types/language.d.ts.map +1 -0
  36. package/lib/types/question.d.ts +75 -0
  37. package/lib/types/question.d.ts.map +1 -0
  38. package/lib/utils/loader.d.ts +9 -0
  39. package/lib/utils/loader.d.ts.map +1 -0
  40. package/lib/utils/markdown.d.ts +2 -0
  41. package/lib/utils/markdown.d.ts.map +1 -0
  42. package/package.json +72 -0
  43. package/src/adapters/vanilla/index.ts +7 -0
  44. package/src/adapters/vue/SmartCodeEditor.vue +1190 -0
  45. package/src/config/languages.ts +273 -0
  46. package/src/config/runnerStrategies.ts +261 -0
  47. package/src/config/runnerStrategies_v2.ts +182 -0
  48. package/src/config/themes.ts +37 -0
  49. package/src/core/BackendRunner.ts +329 -0
  50. package/src/core/CodeRunner.ts +107 -0
  51. package/src/core/LanguageManager.ts +108 -0
  52. package/src/core/LayoutManager.ts +268 -0
  53. package/src/core/MonacoWrapper.ts +173 -0
  54. package/src/core/SmartCodeEditor.ts +1015 -0
  55. package/src/dev-app.vue +488 -0
  56. package/src/dev-main.ts +7 -0
  57. package/src/index.ts +19 -0
  58. package/src/shims-vue.d.ts +4 -0
  59. package/src/types/index.ts +129 -0
  60. package/src/types/language.ts +44 -0
  61. package/src/types/question.ts +98 -0
  62. package/src/utils/loader.ts +69 -0
  63. package/src/utils/markdown.ts +89 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 主题配置
3
+ */
4
+
5
+ export const THEME_CONFIGS = {
6
+ light: {
7
+ id: "vs",
8
+ name: "VS Light",
9
+ displayName: "浅色主题",
10
+ },
11
+ dark: {
12
+ id: "vs-dark",
13
+ name: "VS Dark",
14
+ displayName: "深色主题",
15
+ },
16
+ "high-contrast": {
17
+ id: "hc-black",
18
+ name: "High Contrast",
19
+ displayName: "高对比度",
20
+ },
21
+ } as const;
22
+
23
+ export type ThemeId = keyof typeof THEME_CONFIGS;
24
+
25
+ /**
26
+ * 获取所有主题
27
+ */
28
+ export function getAllThemes() {
29
+ return Object.values(THEME_CONFIGS);
30
+ }
31
+
32
+ /**
33
+ * 获取主题配置
34
+ */
35
+ export function getThemeConfig(id: ThemeId) {
36
+ return THEME_CONFIGS[id];
37
+ }
@@ -0,0 +1,329 @@
1
+ /**
2
+ * 后端代码运行器
3
+ * 连接到 code-backend Rust 服务进行代码执行
4
+ */
5
+
6
+ import type { RunResult, TestCase, TestCaseResult } from "../types";
7
+
8
+ export interface BackendConfig {
9
+ /** API 基础 URL */
10
+ apiUrl?: string;
11
+ /** 请求超时时间(毫秒) */
12
+ timeout?: number;
13
+ /** 是否启用调试日志 */
14
+ debug?: boolean;
15
+ }
16
+
17
+ export interface BackendResponse {
18
+ stdout: string;
19
+ stderr: string;
20
+ exit_code: number;
21
+ execution_time_ms: number;
22
+ error: string;
23
+ test_results?: TestCaseResult[];
24
+ }
25
+
26
+ /**
27
+ * 后端运行器
28
+ * 通过 HTTP API 调用 code-backend 服务
29
+ */
30
+ export class BackendRunner {
31
+ private config: Required<BackendConfig>;
32
+
33
+ constructor(config: BackendConfig = {}) {
34
+ this.config = {
35
+ apiUrl: config.apiUrl || "http://192.168.60.98:8080",
36
+ timeout: config.timeout || 30000,
37
+ debug: config.debug || false,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * 运行代码
43
+ */
44
+ async run(
45
+ language: string,
46
+ code: string,
47
+ testCases?: TestCase[],
48
+ questionId?: number,
49
+ ): Promise<RunResult> {
50
+ const startTime = performance.now();
51
+
52
+ try {
53
+ if (this.config.debug) {
54
+ console.log("[BackendRunner] Running code:", {
55
+ language,
56
+ codeLength: code.length,
57
+ testCasesCount: testCases?.length || 0,
58
+ questionId,
59
+ });
60
+ }
61
+
62
+ const response = await this.fetchWithTimeout(
63
+ `${this.config.apiUrl}/api/v1/run`,
64
+ {
65
+ method: "POST",
66
+ headers: {
67
+ "Content-Type": "application/json",
68
+ },
69
+ body: JSON.stringify({
70
+ language,
71
+ code,
72
+ stdin: "",
73
+ testCases: testCases || null,
74
+ questionId: questionId || null,
75
+ }),
76
+ },
77
+ this.config.timeout,
78
+ );
79
+
80
+ if (!response.ok) {
81
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
82
+ }
83
+
84
+ const result: BackendResponse = await response.json();
85
+
86
+ if (this.config.debug) {
87
+ console.log("[BackendRunner] Response:", result);
88
+ }
89
+
90
+ const executionTime =
91
+ result.execution_time_ms || performance.now() - startTime;
92
+
93
+ // 转换为前端格式
94
+ return this.convertResponse(result, executionTime);
95
+ } catch (error: any) {
96
+ if (this.config.debug) {
97
+ console.error("[BackendRunner] Error:", error);
98
+ }
99
+
100
+ return {
101
+ output: "",
102
+ error: this.formatError(error),
103
+ executionTime: performance.now() - startTime,
104
+ status: "error",
105
+ };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 提交代码(异步执行)
111
+ */
112
+ async submit(
113
+ language: string,
114
+ code: string,
115
+ testCases?: TestCase[],
116
+ questionId?: number,
117
+ ): Promise<string> {
118
+ try {
119
+ const response = await this.fetchWithTimeout(
120
+ `${this.config.apiUrl}/api/v1/submit`,
121
+ {
122
+ method: "POST",
123
+ headers: {
124
+ "Content-Type": "application/json",
125
+ },
126
+ body: JSON.stringify({
127
+ language,
128
+ code,
129
+ stdin: "",
130
+ testCases: testCases || null,
131
+ questionId: questionId || null,
132
+ }),
133
+ },
134
+ this.config.timeout,
135
+ );
136
+
137
+ if (!response.ok) {
138
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
139
+ }
140
+
141
+ const result = await response.json();
142
+ return result.submissionId;
143
+ } catch (error: any) {
144
+ throw new Error(`提交失败: ${this.formatError(error)}`);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * 查询提交状态
150
+ */
151
+ async getStatus(submissionId: string): Promise<{
152
+ status: "Pending" | "Running" | "Completed" | "Failed";
153
+ result?: BackendResponse;
154
+ }> {
155
+ try {
156
+ const response = await fetch(
157
+ `${this.config.apiUrl}/api/v1/status/${submissionId}`,
158
+ );
159
+
160
+ if (!response.ok) {
161
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
162
+ }
163
+
164
+ const data = await response.json();
165
+ return data;
166
+ } catch (error: any) {
167
+ throw new Error(`查询状态失败: ${this.formatError(error)}`);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * 轮询等待结果
173
+ */
174
+ async waitForResult(
175
+ submissionId: string,
176
+ maxAttempts: number = 30,
177
+ interval: number = 1000,
178
+ ): Promise<RunResult> {
179
+ for (let i = 0; i < maxAttempts; i++) {
180
+ const status = await this.getStatus(submissionId);
181
+
182
+ if (status.status === "Completed" && status.result) {
183
+ return this.convertResponse(
184
+ status.result,
185
+ status.result.execution_time_ms,
186
+ );
187
+ }
188
+
189
+ if (status.status === "Failed") {
190
+ throw new Error("执行失败");
191
+ }
192
+
193
+ // 等待
194
+ await new Promise((resolve) => setTimeout(resolve, interval));
195
+ }
196
+
197
+ throw new Error("等待超时");
198
+ }
199
+
200
+ /**
201
+ * 获取题目信息
202
+ */
203
+ async getQuestion(questionId?: number): Promise<any> {
204
+ try {
205
+ const url = questionId
206
+ ? `${this.config.apiUrl}/api/v1/question?id=${questionId}`
207
+ : `${this.config.apiUrl}/api/v1/question`;
208
+
209
+ const response = await fetch(url);
210
+
211
+ if (!response.ok) {
212
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
213
+ }
214
+
215
+ return await response.json();
216
+ } catch (error: any) {
217
+ throw new Error(`获取题目失败: ${this.formatError(error)}`);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * 检查后端健康状态
223
+ */
224
+ async healthCheck(): Promise<boolean> {
225
+ try {
226
+ const response = await this.fetchWithTimeout(
227
+ `${this.config.apiUrl}/api/v1/question`,
228
+ { method: "GET" },
229
+ 5000,
230
+ );
231
+ return response.ok;
232
+ } catch {
233
+ return false;
234
+ }
235
+ }
236
+
237
+ /**
238
+ * 转换后端响应为前端格式
239
+ */
240
+ private convertResponse(
241
+ backendResult: BackendResponse,
242
+ executionTime: number,
243
+ ): RunResult {
244
+ const hasTestResults =
245
+ backendResult.test_results && backendResult.test_results.length > 0;
246
+
247
+ // 确定整体状态
248
+ let status: RunResult["status"] = "success";
249
+ if (backendResult.error) {
250
+ status = "error";
251
+ } else if (backendResult.exit_code !== 0) {
252
+ status = "error";
253
+ } else if (hasTestResults) {
254
+ const hasError = backendResult.test_results!.some(
255
+ (t) => t.status === "error",
256
+ );
257
+ const hasFailed = backendResult.test_results!.some(
258
+ (t) => t.status === "failed",
259
+ );
260
+ if (hasError || hasFailed) {
261
+ status = "error";
262
+ }
263
+ }
264
+
265
+ return {
266
+ output: backendResult.stdout,
267
+ error: backendResult.stderr || backendResult.error || undefined,
268
+ executionTime,
269
+ status,
270
+ testResults: backendResult.test_results,
271
+ };
272
+ }
273
+
274
+ /**
275
+ * 格式化错误信息
276
+ */
277
+ private formatError(error: any): string {
278
+ if (error.name === "AbortError") {
279
+ return `请求超时(${this.config.timeout}ms)`;
280
+ }
281
+
282
+ if (error.message) {
283
+ return error.message;
284
+ }
285
+
286
+ return String(error);
287
+ }
288
+
289
+ /**
290
+ * 带超时的 fetch
291
+ */
292
+ private fetchWithTimeout(
293
+ url: string,
294
+ options: RequestInit,
295
+ timeout: number,
296
+ ): Promise<Response> {
297
+ return Promise.race([
298
+ fetch(url, options),
299
+ new Promise<Response>((_, reject) => {
300
+ setTimeout(() => {
301
+ const error = new Error("Request timeout");
302
+ error.name = "AbortError";
303
+ reject(error);
304
+ }, timeout);
305
+ }),
306
+ ]);
307
+ }
308
+
309
+ /**
310
+ * 更新配置
311
+ */
312
+ setConfig(config: Partial<BackendConfig>): void {
313
+ this.config = { ...this.config, ...config };
314
+ }
315
+
316
+ /**
317
+ * 获取当前配置
318
+ */
319
+ getConfig(): Readonly<Required<BackendConfig>> {
320
+ return { ...this.config };
321
+ }
322
+ }
323
+
324
+ /**
325
+ * 创建默认的后端运行器实例
326
+ */
327
+ export function createBackendRunner(config?: BackendConfig): BackendRunner {
328
+ return new BackendRunner(config);
329
+ }
@@ -0,0 +1,107 @@
1
+ import type { RunResult } from "../types";
2
+ import { getRunnerStrategy } from "../config/runnerStrategies";
3
+
4
+ /**
5
+ * 代码运行引擎
6
+ * 使用策略模式支持多种语言
7
+ */
8
+ export class CodeRunner {
9
+ private timeout: number;
10
+ private abortController: AbortController | null = null;
11
+
12
+ constructor(timeout: number = 5000) {
13
+ this.timeout = timeout;
14
+ }
15
+
16
+ /**
17
+ * 运行代码
18
+ */
19
+ async run(
20
+ code: string,
21
+ language: string,
22
+ testCases?: import("../types").TestCase[],
23
+ ): Promise<RunResult> {
24
+ // 获取对应语言的运行策略
25
+ const strategy = getRunnerStrategy(language);
26
+
27
+ if (!strategy) {
28
+ return {
29
+ output: "",
30
+ error: `语言 "${language}" 不支持运行`,
31
+ executionTime: 0,
32
+ status: "error",
33
+ };
34
+ }
35
+
36
+ // 创建超时控制
37
+ this.abortController = new AbortController();
38
+ const timeoutId = setTimeout(() => {
39
+ this.abortController?.abort();
40
+ }, this.timeout);
41
+
42
+ try {
43
+ // 执行代码
44
+ const result = await Promise.race([
45
+ strategy(code, testCases),
46
+ this.createTimeoutPromise(),
47
+ ]);
48
+
49
+ clearTimeout(timeoutId);
50
+ return result;
51
+ } catch (error: any) {
52
+ clearTimeout(timeoutId);
53
+
54
+ if (error.name === "AbortError") {
55
+ return {
56
+ output: "",
57
+ error: `执行超时(${this.timeout}ms)`,
58
+ executionTime: this.timeout,
59
+ status: "timeout",
60
+ };
61
+ }
62
+
63
+ return {
64
+ output: "",
65
+ error: error.message || String(error),
66
+ executionTime: 0,
67
+ status: "error",
68
+ };
69
+ } finally {
70
+ this.abortController = null;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 创建超时 Promise
76
+ */
77
+ private createTimeoutPromise(): Promise<RunResult> {
78
+ return new Promise((_, reject) => {
79
+ this.abortController?.signal.addEventListener("abort", () => {
80
+ const error = new Error("Execution timeout");
81
+ error.name = "AbortError";
82
+ reject(error);
83
+ });
84
+ });
85
+ }
86
+
87
+ /**
88
+ * 取消运行
89
+ */
90
+ cancel(): void {
91
+ this.abortController?.abort();
92
+ }
93
+
94
+ /**
95
+ * 设置超时时间
96
+ */
97
+ setTimeout(timeout: number): void {
98
+ this.timeout = timeout;
99
+ }
100
+
101
+ /**
102
+ * 销毁
103
+ */
104
+ destroy(): void {
105
+ this.cancel();
106
+ }
107
+ }
@@ -0,0 +1,108 @@
1
+ import type { LanguageConfig } from "../types";
2
+ import { LANGUAGE_CONFIGS, getLanguageConfig } from "../config/languages";
3
+
4
+ /**
5
+ * 语言管理器
6
+ * 负责管理支持的编程语言,从配置文件读取
7
+ */
8
+ export class LanguageManager {
9
+ private languages: Map<string, LanguageConfig> = new Map();
10
+ private currentLanguage: LanguageConfig;
11
+
12
+ constructor(defaultLanguage: string = "javascript") {
13
+ // 从配置加载所有语言
14
+ LANGUAGE_CONFIGS.forEach((config) => {
15
+ this.languages.set(config.id, config);
16
+ });
17
+
18
+ // 设置默认语言
19
+ const lang = this.languages.get(defaultLanguage);
20
+ if (!lang) {
21
+ throw new Error(`Language "${defaultLanguage}" not found in config`);
22
+ }
23
+ this.currentLanguage = lang;
24
+ }
25
+
26
+ /**
27
+ * 获取指定语言配置
28
+ */
29
+ getLanguage(id: string): LanguageConfig | undefined {
30
+ return this.languages.get(id);
31
+ }
32
+
33
+ /**
34
+ * 获取所有语言
35
+ */
36
+ getAllLanguages(): LanguageConfig[] {
37
+ return Array.from(this.languages.values());
38
+ }
39
+
40
+ /**
41
+ * 切换语言
42
+ */
43
+ switchLanguage(languageId: string): LanguageConfig {
44
+ const lang = this.languages.get(languageId);
45
+ if (!lang) {
46
+ throw new Error(`Language "${languageId}" not supported`);
47
+ }
48
+ this.currentLanguage = lang;
49
+ return this.currentLanguage;
50
+ }
51
+
52
+ /**
53
+ * 获取当前语言
54
+ */
55
+ getCurrentLanguage(): LanguageConfig {
56
+ return this.currentLanguage;
57
+ }
58
+
59
+ /**
60
+ * 检查语言是否支持
61
+ */
62
+ isSupported(languageId: string): boolean {
63
+ return this.languages.has(languageId);
64
+ }
65
+
66
+ /**
67
+ * 获取可运行的语言列表
68
+ */
69
+ getRunnableLanguages(): LanguageConfig[] {
70
+ return Array.from(this.languages.values()).filter((lang) => lang.canRun);
71
+ }
72
+
73
+ /**
74
+ * 获取语言的代码模板(支持外部配置)
75
+ * 优先级: languageTemplates > questionConfig > fallbackTemplate > 空字符串
76
+ */
77
+ getTemplate(
78
+ languageId: string,
79
+ questionConfig?: import("../types/question").QuestionConfig,
80
+ languageTemplates?: import("../types/question").LanguageTemplates,
81
+ ): string {
82
+ // 优先级 1: 直接传入的 languageTemplates
83
+ if (
84
+ languageTemplates &&
85
+ languageTemplates[languageId] !== undefined &&
86
+ languageTemplates[languageId] !== null
87
+ ) {
88
+ return languageTemplates[languageId];
89
+ }
90
+
91
+ // 优先级 2: questionConfig 中的模板
92
+ if (
93
+ questionConfig?.languageTemplates?.[languageId] !== undefined &&
94
+ questionConfig?.languageTemplates?.[languageId] !== null
95
+ ) {
96
+ return questionConfig.languageTemplates[languageId];
97
+ }
98
+
99
+ // 优先级 3: 语言的 fallbackTemplate
100
+ const langConfig = this.languages.get(languageId);
101
+ if (langConfig && (langConfig as any).fallbackTemplate) {
102
+ return (langConfig as any).fallbackTemplate;
103
+ }
104
+
105
+ // 优先级 4: 空字符串(允许用户从空白开始)
106
+ return "";
107
+ }
108
+ }