-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js.broken
More file actions
2446 lines (2226 loc) · 98.9 KB
/
Copy pathapp.js.broken
File metadata and controls
2446 lines (2226 loc) · 98.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
const content = {
ko: {
runReady: "준비됨",
runDone: "실행 완료",
runSaved: "저장 완료",
runError: "실행 오류",
outputReady:
"실행 결과가 여기에 표시됩니다.\n\n힌트:\n- 대부분의 언어는 클라우드 런타임으로 실제 실행됩니다.\n- HTML은 미리보기 프레임에서 렌더링됩니다.\n- Arduino/ESP-IDF는 회로도 템플릿을 함께 제공합니다.",
unsupportedTag: "미지원",
runtimeDefaultStatus: "기본 로컬 런타임(localhost)을 사용 중입니다.",
runtimeUsingStatus: "사용 중 런타임",
runtimeHeadersJsonError: "Headers는 JSON 객체 형식이어야 합니다.",
runtimeEndpointError: "Endpoint는 http/https URL이어야 합니다.",
runtimeSavedMessage: "런타임 설정이 저장되었습니다. 다시 실행해 보세요.",
runtimeResetMessage: "런타임 설정이 기본값으로 초기화되었습니다.",
runtimeConfigErrorTitle: "런타임 설정 오류",
runTooFast: "요청이 너무 빠릅니다. 1초 후 다시 실행해 주세요.",
htmlPreviewRendered: "HTML 미리보기를 아래 프레임에 렌더링했습니다.",
runtimeNotPrepared: "현재 언어는 클라우드 런타임이 준비되지 않았습니다.",
emptyRunResult: "실행 결과가 비어 있습니다.",
runFailedTitle: "실행 실패",
runtimeUnsupportedMessage: "현재 런타임에서 지원되지 않는 언어는 선택창에서 자동으로 비활성화됩니다.",
terminalReady: "vpr-terminal ready\ntype: help",
terminalHelp:
"사용 가능한 명령:\n- help\n- clear\n- runtimes\n- run\n- pip install <패키지>\n- python <코드>\n- bash <명령>",
guiHintDefault:
"Python GUI 라이브러리(pygame/tkinter/pyqt 등)는 브라우저에서 네이티브 창으로 바로 열 수 없습니다. 대신 웹 렌더링 가능한 코드(HTML/Canvas) 또는 pygbag/pyscript 경로를 안내합니다.",
guiHintDetected:
"GUI 라이브러리 코드가 감지되었습니다.\n- 브라우저 환경에서는 네이티브 윈도우를 직접 띄울 수 없습니다.\n- 권장: pygame -> pygbag, tkinter/pyqt -> 데스크톱 Python 실행",
ideaFallback: "예제를 찾는 중이라면, 간단한 계산기나 숫자 맞추기 게임부터 시작해 보세요.",
learningIntro: "선택한 언어 학습 포인트",
errorEmpty: "오류 메시지를 입력하면 원인과 해결 방향을 제안합니다.",
saveEmpty: "프로젝트 이름을 먼저 입력하세요.",
saveMetaEmpty: "아직 저장된 프로젝트가 없습니다.",
libraryEmpty: "저장된 프로젝트가 없습니다. 저장 버튼으로 현재 코드를 보관해 보세요.",
homeEmpty: "아직 저장된 프로젝트가 없습니다. 에디터 페이지에서 첫 프로젝트를 저장해 보세요.",
},
en: {
runReady: "Ready",
runDone: "Run complete",
runSaved: "Saved",
runError: "Run error",
outputReady:
"Execution results will appear here.\n\nHints:\n- Most languages run on a cloud runtime.\n- HTML renders in preview frame.\n- Arduino/ESP-IDF include a circuit template panel.",
unsupportedTag: "Unsupported",
runtimeDefaultStatus: "Using default local runtime (localhost).",
runtimeUsingStatus: "Runtime in use",
runtimeHeadersJsonError: "Headers must be a JSON object.",
runtimeEndpointError: "Endpoint must be an http/https URL.",
runtimeSavedMessage: "Runtime settings saved. Try running again.",
runtimeResetMessage: "Runtime settings reset to defaults.",
runtimeConfigErrorTitle: "Runtime settings error",
runTooFast: "Requests are too frequent. Try again in 1 second.",
htmlPreviewRendered: "HTML preview rendered in the frame below.",
runtimeNotPrepared: "Cloud runtime is not configured for this language.",
emptyRunResult: "Execution returned no output.",
runFailedTitle: "Execution failed",
runtimeUnsupportedMessage: "Languages not supported by the current runtime are automatically disabled in the selector.",
terminalReady: "vpr-terminal ready\ntype: help",
terminalHelp:
"Available commands:\n- help\n- clear\n- runtimes\n- run\n- pip install <package>\n- python <code>\n- bash <command>",
guiHintDefault:
"Native Python GUI frameworks (pygame/tkinter/pyqt) cannot open desktop windows directly in a browser runtime. Use web-renderable code (HTML/Canvas) or pygbag/pyscript paths.",
guiHintDetected:
"GUI framework code detected.\n- Native desktop windows cannot be opened directly in browser runtime.\n- Recommended: pygame -> pygbag, tkinter/pyqt -> desktop Python run",
ideaFallback: "If you need a starter idea, build a calculator or a number guessing game first.",
learningIntro: "Learning points for the selected language",
errorEmpty: "Paste an error message to get a likely cause and fix direction.",
saveEmpty: "Enter a project name first.",
saveMetaEmpty: "No saved project yet.",
libraryEmpty: "No saved projects yet. Use Save to keep the current draft.",
homeEmpty: "No saved projects yet. Save your first project from the editor page.",
},
};
const storageKey = "vpr-projects";
const lastProjectKey = "vpr-last-project";
const localeKey = "vpr-locale";
const pendingSnippetKey = "vpr-pending-snippet";
const usersKey = "vpr-users";
const sessionUserKey = "vpr-session-user";
const publicSnippetsKey = "vpr-public-snippets";
const runThrottleKey = "vpr-last-run-ts";
const runtimeConfigKey = "vpr-runtime-config";
const circuitStateKey = "vpr-circuit-state";
const defaultRuntimeEndpoint = "http://localhost:2000/api/v2/execute";
const defaultRuntimeTimeoutMs = 3000;
let runtimeCatalogCache = { endpoint: "", runtimes: null };
const languageSamples = {
javascript: {
label: "JavaScript",
extension: "js",
runner: { language: "javascript", version: "18.15.0" },
guide: [
"브라우저 DOM과 런타임 API를 구분하세요.",
"작은 함수 단위로 분리하면 디버깅이 쉬워집니다.",
"실행 전 입력값 검증을 습관화하세요.",
],
code: "console.log('Hello from JavaScript');\nconsole.log(2 + 3);",
},
typescript: {
label: "TypeScript",
extension: "ts",
runner: { language: "typescript", version: "5.0.3" },
guide: ["타입 추론을 활용하되 핵심 API는 명시 타입을 지정하세요.", "interface와 type의 용도를 구분하세요."],
code: "const sum = (a: number, b: number): number => a + b;\nconsole.log(sum(2, 5));",
},
python: {
label: "Python",
extension: "py",
runner: { language: "python", version: "3.10.0" },
guide: ["들여쓰기 정렬을 항상 일정하게 유지하세요.", "Traceback 마지막 줄부터 원인 파악을 시작하세요."],
code: "def greet(name):\n return f'Hello, {name}'\n\nprint(greet('VPR'))",
},
c: {
label: "C",
extension: "c",
runner: { language: "c", version: "10.2.0" },
guide: ["헤더, 타입, 포인터 초기화를 꼼꼼히 확인하세요.", "경계 검사 없는 배열 접근을 피하세요."],
code: "#include <stdio.h>\n\nint main(void) {\n printf(\"Hello from C\\n\");\n return 0;\n}",
},
cpp: {
label: "C++",
extension: "cpp",
runner: { language: "c++", version: "10.2.0" },
guide: ["컴파일 에러와 런타임 에러를 분리해 해석하세요.", "STL 사용 시 범위 체크를 우선하세요."],
code: "#include <iostream>\n\nint main() {\n std::cout << \"Hello from C++\\n\";\n return 0;\n}",
},
csharp: {
label: "C#",
extension: "cs",
runner: { language: "csharp", version: "6.12.0" },
guide: ["null 가능 참조를 고려한 방어 코드를 작성하세요.", "작은 메서드 단위로 책임을 분리하세요."],
code: "using System;\n\nclass Program {\n static void Main() {\n Console.WriteLine(\"Hello from C#\");\n }\n}",
},
java: {
label: "Java",
extension: "java",
runner: { language: "java", version: "15.0.2" },
guide: ["클래스명과 파일명 일치를 지키세요.", "예외 메시지를 그대로 숨기지 마세요."],
code: "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello from Java\");\n }\n}",
},
go: {
label: "Go",
extension: "go",
runner: { language: "go", version: "1.16.2" },
guide: ["error 반환값을 무시하지 마세요.", "패키지 구조를 단순하게 유지하세요."],
code: "package main\n\nimport \"fmt\"\n\nfunc main() {\n fmt.Println(\"Hello from Go\")\n}",
},
rust: {
label: "Rust",
extension: "rs",
runner: { language: "rust", version: "1.68.2" },
guide: ["소유권 에러를 타입 설계 문제로 보세요.", "panic보다 Result 기반 처리로 확장성을 확보하세요."],
code: "fn main() {\n println!(\"Hello from Rust\");\n}",
},
php: {
label: "PHP",
extension: "php",
runner: { language: "php", version: "8.2.3" },
guide: ["입력값은 필터링 후 사용하세요.", "예외 기반 오류 처리 규칙을 통일하세요."],
code: "<?php\necho \"Hello from PHP\\n\";\n",
},
ruby: {
label: "Ruby",
extension: "rb",
runner: { language: "ruby", version: "3.0.1" },
guide: ["메서드 책임을 작게 유지하세요.", "동적 타입이라도 입력 검증은 필수입니다."],
code: "puts 'Hello from Ruby'",
},
kotlin: {
label: "Kotlin",
extension: "kt",
runner: { language: "kotlin", version: "1.8.20" },
guide: ["null-safe 연산자를 적극 활용하세요.", "데이터 클래스와 확장 함수를 과도하게 남용하지 마세요."],
code: "fun main() {\n println(\"Hello from Kotlin\")\n}",
},
swift: {
label: "Swift",
extension: "swift",
runner: { language: "swift", version: "5.3.3" },
guide: ["옵셔널 언래핑 경로를 명확히 하세요.", "값 타입/참조 타입 선택 이유를 분명히 하세요."],
code: "print(\"Hello from Swift\")",
},
html: {
label: "HTML",
extension: "html",
mode: "preview",
guide: ["시맨틱 태그를 우선 사용하세요.", "스타일/동작 분리를 유지하세요."],
code: "<!DOCTYPE html>\n<html lang=\"ko\">\n <body>\n <h1>Hello from HTML</h1>\n <p>Preview works in the panel.</p>\n </body>\n</html>",
},
arduino: {
label: "Arduino",
extension: "ino",
mode: "circuit",
guide: ["핀 번호와 전원 연결을 먼저 설계하세요.", "센서 전압 범위 확인 후 연결하세요."],
code: "void setup() {\n Serial.begin(9600);\n pinMode(13, OUTPUT);\n}\n\nvoid loop() {\n digitalWrite(13, HIGH);\n delay(500);\n digitalWrite(13, LOW);\n delay(500);\n}",
},
espidf: {
label: "ESP-IDF",
extension: "c",
mode: "circuit",
guide: ["ESP32 핀맵과 전원 요구사항을 먼저 확인하세요.", "FreeRTOS task 분리를 작게 시작하세요."],
code: "#include <stdio.h>\n#include \"freertos/FreeRTOS.h\"\n#include \"freertos/task.h\"\n\nvoid app_main(void) {\n while (1) {\n printf(\"Hello from ESP-IDF\\n\");\n vTaskDelay(1000 / portTICK_PERIOD_MS);\n }\n}",
},
};
const ideaTemplates = [
{
match: /게임|game/i,
text: "초보자용 게임 아이디어\n1. 숫자 맞추기 게임\n2. 반응 속도 측정 게임\n3. 사칙연산 퀴즈 게임",
},
{
match: /임베디드|arduino|esp/i,
text: "임베디드 아이디어\n1. 온습도 모니터\n2. LED 패턴 제어기\n3. Wi-Fi 상태 알림 장치",
},
{
match: /웹|html|js|javascript/i,
text: "웹 프로젝트 아이디어\n1. 메모장형 온라인 에디터\n2. 학습 타이머\n3. 에러 메시지 해설 페이지",
},
];
const errorGuides = [
{
language: "all",
match: /syntaxerror|invalid syntax|unexpected token|parse error/i,
ko: "문법 오류입니다. 괄호/따옴표/세미콜론/콜론 누락을 먼저 확인하고 최근 수정 라인을 우선 점검하세요.",
en: "Syntax error detected. Check missing brackets/quotes/semicolons/colons first, then inspect recently edited lines.",
},
{
language: "all",
match: /null|undefined|none|nil|cannot read properties/i,
ko: "초기화되지 않은 값 접근일 가능성이 큽니다. null 체크, 기본값, 반환값 유효성을 확인하세요.",
en: "Likely uninitialized value access. Validate null checks, default values, and return value assumptions.",
},
{
language: "python",
match: /indentationerror|taberror/i,
ko: "Python 들여쓰기 오류입니다. 공백/탭 혼용 여부와 블록 정렬을 통일하세요.",
en: "Python indentation issue. Avoid mixed tabs/spaces and align block indentation consistently.",
},
{
language: "python",
match: /modulenotfounderror|no module named/i,
ko: "패키지가 런타임에 없습니다. 터미널에서 pip install 시도 후, 여전히 실패하면 런타임 이미지에 패키지를 포함해야 합니다.",
en: "Package is missing in runtime. Try pip install in terminal; if it still fails, include the package in runtime image.",
},
{
language: "c",
match: /undefined reference|ld returned|implicit declaration/i,
ko: "C 링크/선언 오류입니다. 함수 선언/정의와 헤더 포함을 점검하고, 심볼 이름 오타를 확인하세요.",
en: "C link/declaration issue. Verify function declarations/definitions, included headers, and symbol spelling.",
},
{
language: "cpp",
match: /no matching function|std::|template|undefined reference/i,
ko: "C++ 타입/템플릿 또는 링크 오류입니다. 시그니처 일치, 네임스페이스, 링크 대상을 확인하세요.",
en: "C++ type/template or linker issue. Check signature match, namespaces, and linked targets.",
},
{
language: "java",
match: /cannot find symbol|class .* is public|exception in thread|javac/i,
ko: "Java 컴파일/클래스 오류입니다. 클래스명-파일명 일치, import, static/main 시그니처를 확인하세요.",
en: "Java compile/class issue. Verify class/file name match, imports, and static main signature.",
},
{
language: "csharp",
match: /cs\d{4}|namespace|type or namespace name/i,
ko: "C# 컴파일 오류입니다. using/namespace, 접근 제한자, Main 메서드 시그니처를 확인하세요.",
en: "C# compile issue. Check using/namespace declarations, access modifiers, and Main method signature.",
},
{
language: "go",
match: /undefined:|cannot use|expected .* found|panic:/i,
ko: "Go 타입/참조 오류입니다. import 누락, 타입 일치, error 처리 누락 여부를 확인하세요.",
en: "Go type/reference issue. Check missing imports, type compatibility, and error handling paths.",
},
{
language: "rust",
match: /borrowed value|moved value|trait bound|cannot borrow/i,
ko: "Rust 소유권/borrow 오류입니다. 참조 수명, 소유권 이동, trait 구현 충족 여부를 점검하세요.",
en: "Rust ownership/borrow issue. Review lifetimes, move semantics, and trait bound satisfaction.",
},
{
language: "php",
match: /fatal error|parse error|undefined variable|call to undefined function/i,
ko: "PHP 런타임 오류입니다. 변수 초기화, 함수/파일 include, 세미콜론 누락을 확인하세요.",
en: "PHP runtime issue. Check variable initialization, includes, and missing semicolons.",
},
{
language: "ruby",
match: /undefined method|nomethoderror|syntax error/i,
ko: "Ruby 메서드/문법 오류입니다. 객체 타입과 메서드 존재 여부, 블록 문법을 확인하세요.",
en: "Ruby method/syntax issue. Verify object type, method existence, and block syntax.",
},
{
language: "kotlin",
match: /unresolved reference|type mismatch|expecting/i,
ko: "Kotlin 컴파일 오류입니다. import, null-safe 타입, 함수 시그니처를 점검하세요.",
en: "Kotlin compile issue. Check imports, null-safe types, and function signatures.",
},
{
language: "swift",
match: /cannot find|use of unresolved identifier|value of optional type/i,
ko: "Swift 식별자/옵셔널 처리 오류입니다. 타입 추론과 optional unwrapping 경로를 확인하세요.",
en: "Swift identifier/optional handling issue. Review type inference and optional unwrapping paths.",
},
];
const snippets = [
{
title: "숫자 맞추기 게임",
description: "조건문과 반복문을 연습하는 입문용 콘솔 프로젝트",
language: "javascript",
code: "const answer = 7;\nconst guess = 5;\nif (guess === answer) {\n console.log('정답입니다!');\n} else {\n console.log('다시 시도하세요.');\n}",
},
{
title: "HTML 포트폴리오 페이지",
description: "태그 구조와 CSS 레이아웃을 배우기 좋은 예제",
language: "html",
code: "<!DOCTYPE html>\n<html lang=\"ko\">\n <body>\n <main><h1>홍길동 포트폴리오</h1><p>웹 프론트엔드 입문 학습 중입니다.</p></main>\n </body>\n</html>",
},
{
title: "Arduino Blink",
description: "LED 제어와 핀 설정 입문 예제",
language: "arduino",
code: languageSamples.arduino.code,
},
{
title: "Python ToDo CLI",
description: "리스트/루프/입력 처리를 연습하는 간단한 할 일 관리기",
language: "python",
code: "tasks = []\n\nwhile True:\n cmd = input('add/list/quit > ').strip()\n if cmd == 'add':\n tasks.append(input('task: ').strip())\n elif cmd == 'list':\n for i, task in enumerate(tasks, 1):\n print(f'{i}. {task}')\n elif cmd == 'quit':\n break\n",
},
{
title: "Go REST Shape",
description: "간단한 JSON API 응답 구조를 만드는 Go 기본 예제",
language: "go",
code: "package main\n\nimport (\n \"encoding/json\"\n \"fmt\"\n)\n\nfunc main() {\n payload := map[string]any{\"ok\": true, \"service\": \"vpr\"}\n b, _ := json.MarshalIndent(payload, \"\", \" \")\n fmt.Println(string(b))\n}",
},
{
title: "C Pointer Safety",
description: "포인터와 배열 경계 체크를 함께 연습하는 예제",
language: "c",
code: "#include <stdio.h>\n\nint main(void) {\n int arr[3] = {1, 2, 3};\n for (int i = 0; i < 3; i++) {\n printf(\"%d\\n\", arr[i]);\n }\n return 0;\n}",
},
{
title: "C++ Vector Search",
description: "STL vector와 find 알고리즘 사용 예제",
language: "cpp",
code: "#include <iostream>\n#include <vector>\n#include <algorithm>\n\nint main() {\n std::vector<int> nums = {3, 7, 11, 19};\n auto it = std::find(nums.begin(), nums.end(), 11);\n std::cout << (it != nums.end() ? \"found\" : \"not found\") << std::endl;\n return 0;\n}",
},
{
title: "Java Class Starter",
description: "객체 생성과 메서드 호출 흐름을 보는 Java 입문 예제",
language: "java",
code: "class Greeter {\n String say(String name) {\n return \"Hello, \" + name;\n }\n}\n\npublic class Main {\n public static void main(String[] args) {\n Greeter g = new Greeter();\n System.out.println(g.say(\"VPR\"));\n }\n}",
},
{
title: "C# LINQ Mini",
description: "필터링과 집계를 동시에 연습하는 C# LINQ 예제",
language: "csharp",
code: "using System;\nusing System.Linq;\n\nclass Program {\n static void Main() {\n var nums = new[] { 2, 5, 9, 12, 17 };\n var sum = nums.Where(n => n % 2 == 1).Sum();\n Console.WriteLine(sum);\n }\n}",
},
{
title: "Rust Result Flow",
description: "Result 기반 오류 처리를 연습하는 Rust 예제",
language: "rust",
code: "fn divide(a: i32, b: i32) -> Result<i32, String> {\n if b == 0 {\n return Err(\"divide by zero\".to_string());\n }\n Ok(a / b)\n}\n\nfn main() {\n match divide(10, 2) {\n Ok(v) => println!(\"{}\", v),\n Err(e) => println!(\"error: {}\", e),\n }\n}",
},
{
title: "Kotlin Data Class",
description: "data class와 컬렉션 매핑을 연습하는 Kotlin 예제",
language: "kotlin",
code: "data class User(val name: String, val score: Int)\n\nfun main() {\n val users = listOf(User(\"A\", 50), User(\"B\", 80))\n val names = users.filter { it.score >= 60 }.map { it.name }\n println(names)\n}",
},
{
title: "Swift Optional Guard",
description: "옵셔널 처리와 guard 패턴을 익히는 Swift 예제",
language: "swift",
code: "func printName(_ name: String?) {\n guard let safe = name else {\n print(\"no name\")\n return\n }\n print(\"name: \\(safe)\")\n}\n\nprintName(\"VPR\")",
},
{
title: "PHP Assoc Array",
description: "연관 배열과 반복문을 사용하는 PHP 기초 예제",
language: "php",
code: "<?php\n$user = [\"name\" => \"vpr\", \"level\" => 1];\nforeach ($user as $k => $v) {\n echo $k . \": \" . $v . PHP_EOL;\n}\n",
},
{
title: "Ruby Class Basics",
description: "클래스/인스턴스/메서드 기본 구조를 익히는 Ruby 예제",
language: "ruby",
code: "class Bot\n def greet(name)\n \"Hello, #{name}\"\n end\nend\n\nbot = Bot.new\nputs bot.greet('VPR')",
},
];
let currentLocale = localStorage.getItem(localeKey) || "ko";
function tr(key) {
return content[currentLocale]?.[key] ?? key;
}
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function sanitizeName(value) {
return String(value).trim().replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 20);
}
function getProjects() {
try {
return JSON.parse(localStorage.getItem(storageKey) || "[]");
} catch {
return [];
}
}
function setProjects(projects) {
localStorage.setItem(storageKey, JSON.stringify(projects));
}
function getUsers() {
try {
return JSON.parse(localStorage.getItem(usersKey) || "[]");
} catch {
return [];
}
}
function setUsers(users) {
localStorage.setItem(usersKey, JSON.stringify(users));
}
function getCurrentUser() {
return localStorage.getItem(sessionUserKey);
}
function setCurrentUser(username) {
if (username) {
localStorage.setItem(sessionUserKey, username);
} else {
localStorage.removeItem(sessionUserKey);
}
}
function getPublicSnippets() {
try {
return JSON.parse(localStorage.getItem(publicSnippetsKey) || "[]");
} catch {
return [];
}
}
function setPublicSnippets(items) {
localStorage.setItem(publicSnippetsKey, JSON.stringify(items));
}
function formatTimestamp(timestamp) {
return new Intl.DateTimeFormat(currentLocale === "ko" ? "ko-KR" : "en-US", {
dateStyle: "medium",
timeStyle: "short",
}).format(new Date(timestamp));
}
function buildIdeaResponse(prompt) {
const found = ideaTemplates.find((template) => template.match.test(prompt));
return found ? found.text : content[currentLocale].ideaFallback;
}
function buildErrorResponse(message, languageKey = "all") {
if (!message.trim()) {
return content[currentLocale].errorEmpty;
}
const found = errorGuides.find((guide) => {
const languageMatched = guide.language === "all" || guide.language === languageKey;
return languageMatched && guide.match.test(message);
});
return found
? found[currentLocale]
: currentLocale === "ko"
? "오류 유형이 명확하지 않습니다. 최근 변경 코드와 에러 라인을 우선 확인하세요."
: "Error type is unclear. Start with recently changed lines and the reported line number.";
}
function detectErrorLine(message) {
const patterns = [/line\s+(\d+)/i, /:(\d+):(\d+)/, /at\s+line\s+(\d+)/i];
for (const pattern of patterns) {
const matched = String(message || "").match(pattern);
if (matched && matched[1]) {
const line = Number(matched[1]);
if (!Number.isNaN(line) && line > 0) {
return line;
}
}
}
return null;
}
function getLineContext(code, lineNumber) {
const lines = String(code || "").split("\n");
if (!lineNumber || !lines.length) {
return { target: "", before: "", after: "" };
}
const index = Math.max(0, Math.min(lines.length - 1, lineNumber - 1));
return {
target: lines[index] || "",
before: lines[index - 1] || "",
after: lines[index + 1] || "",
};
}
function buildFixTemplate(languageKey, targetLine) {
if (/python/.test(languageKey)) {
return `# 기존: ${targetLine || "(문제 라인)"}\n# 수정: 값 확인 후 접근\nif value is not None:\n print(value)`;
}
if (/javascript|typescript/.test(languageKey)) {
return `// 기존: ${targetLine || "(problematic line)"}\n// 수정: optional chaining + 기본값\nconst safe = obj?.value ?? defaultValue;`;
}
if (/c|cpp/.test(languageKey)) {
return `// 기존: ${targetLine || "(problematic line)"}\n// 수정: 포인터/인덱스 가드\nif (ptr != NULL && idx >= 0 && idx < len) {\n /* ... */\n}`;
}
if (/java|csharp|kotlin|swift/.test(languageKey)) {
return `// 기존: ${targetLine || "(problematic line)"}\n// 수정: null/optional 검사 후 사용\nif (value != null) {\n // ...\n}`;
}
return `// 기존: ${targetLine || "(problematic line)"}\n// 수정: 입력값/참조값 유효성 검사를 먼저 수행하세요.`;
}
function buildDetailedErrorReview(message, languageKey, fileName, code) {
const basic = buildErrorResponse(message, languageKey);
const line = detectErrorLine(message);
const context = getLineContext(code, line);
const fixTemplate = buildFixTemplate(languageKey, context.target.trim());
const logicalHint =
context.target.trim().length > 0
? `문제가 날 가능성이 높은 로직: ${context.target.trim()}`
: "문제가 날 가능성이 높은 로직: 에러 메시지에 포함된 함수 호출/변수 접근 부분";
const sectionTitle = currentLocale === "ko" ? "상세 분석" : "Detailed Review";
const fileLabel = currentLocale === "ko" ? "파일" : "File";
const lineLabel = currentLocale === "ko" ? "라인" : "Line";
const proposalLabel = currentLocale === "ko" ? "수정 제안" : "Fix Proposal";
return [
basic,
"",
`[${sectionTitle}]`,
`${fileLabel}: ${fileName || "(unknown file)"}`,
`${lineLabel}: ${line || "(line info not found)"}`,
logicalHint,
context.before ? `${currentLocale === "ko" ? "이전 라인" : "Previous line"}: ${context.before.trim()}` : "",
context.after ? `${currentLocale === "ko" ? "다음 라인" : "Next line"}: ${context.after.trim()}` : "",
"",
`[${proposalLabel}]`,
fixTemplate,
]
.filter(Boolean)
.join("\n");
}
function buildCoachResponse(languageKey, code, goal) {
if (!String(code || "").trim()) {
return currentLocale === "ko" ? "코드를 입력하면 개선 포인트를 제안합니다." : "Paste code to get optimization suggestions.";
}
const lines = String(code).split("\n");
const tips = [];
if (lines.length > 80) {
tips.push(currentLocale === "ko" ? "파일이 길어서 함수 단위 분리가 필요합니다." : "The file is long; split logic into smaller functions.");
}
if (/console\.log|print\(/.test(code) && !/error|warn|logger/i.test(code)) {
tips.push(currentLocale === "ko" ? "디버그 출력과 실제 로그 체계를 분리하세요." : "Separate debugging prints from structured logging.");
}
if (/while\s*\(true\)|for\s*\(;;\)/.test(code)) {
tips.push(currentLocale === "ko" ? "무한 루프에는 종료 조건 또는 타임아웃 가드를 두세요." : "Infinite loops should have an exit condition or timeout guard.");
}
if (/fetch\(|axios\.|requests\./.test(code) && !/try\s*\{|catch|except/.test(code)) {
tips.push(currentLocale === "ko" ? "네트워크 호출에 예외 처리와 재시도 전략을 추가하세요." : "Add error handling and retry strategy to network calls.");
}
const goalLine = goal
? currentLocale === "ko"
? `목표 반영: ${goal}`
: `Goal focus: ${goal}`
: currentLocale === "ko"
? "목표를 입력하면 더 정확한 추천이 가능합니다."
: "Add a goal for more targeted advice.";
return [
currentLocale === "ko" ? `[AI 코드 코치] ${languageSamples[languageKey]?.label || languageKey}` : `[AI Code Coach] ${languageSamples[languageKey]?.label || languageKey}`,
goalLine,
"",
...(tips.length
? tips.map((tip, index) => `${index + 1}. ${tip}`)
: [currentLocale === "ko" ? "코드 구조가 비교적 단순합니다. 함수 분리와 테스트 케이스 추가를 추천합니다." : "Code looks fairly simple. Consider function decomposition and additional tests."]),
].join("\n");
}
function buildPlannerResponse(prompt) {
if (!String(prompt || "").trim()) {
return currentLocale === "ko" ? "원하는 기능을 입력하면 단계별 구현 계획을 제공합니다." : "Describe features to receive a phased implementation plan.";
}
const phasesKo = [
"1. 요구사항 분해: 사용자 흐름/입력/출력/오류 조건 정의",
"2. 데이터 모델 설계: 저장 구조와 키 이름 확정",
"3. 화면 설계: 페이지/컴포넌트/상태 전이 작성",
"4. 핵심 로직 구현: 최소 기능(MVP)부터 연결",
"5. 예외 처리 강화: 입력 검증, 실패 폴백, 로그 정리",
"6. 테스트: 대표 시나리오 + 경계 케이스 검증",
];
const phasesEn = [
"1. Break requirements into user flow, input-output, and failure cases",
"2. Design data model and key naming",
"3. Define pages/components and state transitions",
"4. Implement core MVP path first",
"5. Add validation, fallback paths, and logging",
"6. Test happy path and edge cases",
];
return `${currentLocale === "ko" ? "[AI 설계 플랜]" : "[AI Architecture Plan]"}\n요청: ${prompt}\n\n${
currentLocale === "ko" ? phasesKo.join("\n") : phasesEn.join("\n")
}`;
}
function applyLocaleSelections() {
document.querySelectorAll("#localeSelect").forEach((select) => {
select.value = currentLocale;
select.addEventListener("change", (event) => {
currentLocale = event.target.value;
localStorage.setItem(localeKey, currentLocale);
location.reload();
});
});
}
function initSidebarToggle() {
const topbar = document.querySelector(".topbar");
const sidebar = document.querySelector(".sidebar");
if (!topbar || !sidebar) {
return;
}
const button = document.createElement("button");
button.type = "button";
button.className = "sidebar-toggle";
button.textContent = "Menu";
button.setAttribute("aria-label", "Toggle sidebar");
topbar.prepend(button);
button.addEventListener("click", () => {
document.body.classList.toggle("sidebar-open");
});
document.querySelectorAll(".menu-item").forEach((item) => {
item.addEventListener("click", () => {
document.body.classList.remove("sidebar-open");
});
});
}
function renderAuthShortcut() {
const sessionUser = getCurrentUser();
document.querySelectorAll(".topbar-actions").forEach((container) => {
const wrapper = document.createElement("div");
wrapper.className = "auth-inline";
if (sessionUser) {
wrapper.innerHTML = `
<span class="auth-chip">@${escapeHtml(sessionUser)}</span>
<a class="ghost-button" href="profile.html">프로필</a>`;
} else {
wrapper.innerHTML = `<a class="ghost-button" href="login.html">로그인</a>`;
}
container.appendChild(wrapper);
});
}
function populateLanguageSelect(select) {
if (!select) {
return;
}
select.innerHTML = Object.entries(languageSamples)
.map(([value, sample]) => `<option value="${value}">${sample.label}</option>`)
.join("");
}
function renderLearningGuide(target, languageKey) {
if (!target) {
return;
}
const sample = languageSamples[languageKey];
target.innerHTML = `${content[currentLocale].learningIntro}<br /><br />${sample.guide
.map((line) => `- ${line}`)
.join("<br />")}`;
}
function resetPreview(previewFrame) {
if (!previewFrame) {
return;
}
previewFrame.srcdoc = `
<body style="font-family: sans-serif; padding: 24px; color: #475569; background: #fff;">
<h3 style="margin-top: 0;">Preview standby</h3>
<p>HTML 코드를 실행하면 이 영역에 실제 결과가 표시됩니다.</p>
</body>`;
}
function getDefaultCircuitModel(languageKey) {
if (languageKey === "arduino") {
return {
nodes: [
{ id: "a-board", type: "module", name: "Arduino UNO", pin: "5V/GND", x: 34, y: 38 },
{ id: "a-bread", type: "module", name: "Breadboard", pin: "+ / - rail", x: 250, y: 42 },
{ id: "a-res", type: "resistor", name: "Resistor", pin: "220R", x: 430, y: 128 },
{ id: "a-led", type: "led", name: "LED", pin: "D13", x: 620, y: 130 },
],
wires: [
{ from: "a-board", to: "a-bread" },
{ from: "a-board", to: "a-res" },
{ from: "a-res", to: "a-led" },
],
};
}
return {
nodes: [
{ id: "e-esp32", type: "module", name: "ESP32 DevKit", pin: "3V3/GND", x: 34, y: 38 },
{ id: "e-sensor", type: "sensor", name: "Sensor", pin: "VCC/GND", x: 260, y: 54 },
{ id: "e-res", type: "resistor", name: "Resistor", pin: "220R", x: 462, y: 136 },
{ id: "e-led", type: "led", name: "LED", pin: "GPIO2", x: 650, y: 140 },
],
wires: [
{ from: "e-esp32", to: "e-sensor" },
{ from: "e-esp32", to: "e-res" },
{ from: "e-res", to: "e-led" },
],
};
}
async function hashPassword(raw) {
const encoder = new TextEncoder();
const data = encoder.encode(raw);
const digest = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(digest))
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
}
function canRunNow() {
const last = Number(localStorage.getItem(runThrottleKey) || 0);
const now = Date.now();
if (now - last < 1200) {
return false;
}
localStorage.setItem(runThrottleKey, String(now));
return true;
}
function getRuntimeConfig() {
try {
const parsed = JSON.parse(localStorage.getItem(runtimeConfigKey) || "{}");
const endpoint = String(parsed.endpoint || defaultRuntimeEndpoint).trim();
const headers = parsed.headers && typeof parsed.headers === "object" ? parsed.headers : {};
return { endpoint, headers };
} catch {
return { endpoint: defaultRuntimeEndpoint, headers: {} };
}
}
function setRuntimeConfig(config) {
localStorage.setItem(runtimeConfigKey, JSON.stringify(config));
}
function getStoredCircuitStates() {
try {
return JSON.parse(localStorage.getItem(circuitStateKey) || "{}");
} catch {
return {};
}
}
function setStoredCircuitStates(stateMap) {
localStorage.setItem(circuitStateKey, JSON.stringify(stateMap));
}
function getRuntimesEndpoint(executeEndpoint) {
if (executeEndpoint.includes("/piston/execute")) {
return executeEndpoint.replace("/piston/execute", "/piston/runtimes");
}
if (executeEndpoint.endsWith("/execute")) {
return executeEndpoint.replace(/\/execute$/, "/runtimes");
}
return `${executeEndpoint}/runtimes`;
}
async function getRuntimeCatalog(runtimeConfig, headers) {
if (runtimeCatalogCache.endpoint === runtimeConfig.endpoint && Array.isArray(runtimeCatalogCache.runtimes)) {
return runtimeCatalogCache.runtimes;
}
const response = await fetch(getRuntimesEndpoint(runtimeConfig.endpoint), {
method: "GET",
headers,
});
if (!response.ok) {
return null;
}
const runtimes = await response.json();
runtimeCatalogCache = { endpoint: runtimeConfig.endpoint, runtimes };
return runtimes;
}
function getRuntimeMatches(runtimes, runtimeLanguage) {
if (!Array.isArray(runtimes)) {
return [];
}
const normalized = String(runtimeLanguage || "").toLowerCase();
return runtimes.filter((item) => {
const language = String(item.language || "").toLowerCase();
const aliases = Array.isArray(item.aliases) ? item.aliases.map((alias) => String(alias).toLowerCase()) : [];
return language === normalized || aliases.includes(normalized);
});
}
async function executeRemote(runtime, code) {
const runtimeConfig = getRuntimeConfig();
const headers = { "Content-Type": "application/json", ...runtimeConfig.headers };
const runtimeCatalog = await getRuntimeCatalog(runtimeConfig, headers);
const runtimeMatches = getRuntimeMatches(runtimeCatalog, runtime.language);
if (runtimeCatalog && !runtimeMatches.length) {
const supportedList = runtimeCatalog
.slice(0, 12)
.map((item) => item.language)
.join(", ");
throw new Error(
`현재 런타임에서 ${runtime.language} 언어를 지원하지 않습니다.\n지원 예시: ${supportedList}${runtimeCatalog.length > 12 ? ", ..." : ""}`
);
}
let targetVersion = runtime.version || "*";
if (runtimeMatches.length && targetVersion !== "*") {
const hasRequestedVersion = runtimeMatches.some((item) => String(item.version) === String(targetVersion));
if (!hasRequestedVersion) {
targetVersion = runtimeMatches[0].version;
}
}
const response = await fetch(runtimeConfig.endpoint, {
method: "POST",
headers,
body: JSON.stringify({
language: runtime.language,
version: targetVersion,
files: [{ content: code }],
stdin: "",
args: [],
compile_timeout: defaultRuntimeTimeoutMs,
run_timeout: defaultRuntimeTimeoutMs,
}),
});
if (!response.ok) {
if (response.status === 401 && runtimeConfig.endpoint.includes("emkc.org")) {
throw new Error(
"공개 Piston API가 화이트리스트 전용으로 전환되었습니다.\n실행 결과 패널의 '런타임 설정'에서 자체 엔드포인트와 헤더를 저장해 주세요."
);
}
let details = "";
try {
const errorJson = await response.json();
details = errorJson.message || errorJson.error || JSON.stringify(errorJson);
} catch {
try {
details = await response.text();
} catch {
details = "";
}
}
if (details) {
throw new Error(`Runtime request failed (${response.status})\n${details}`);
}
throw new Error(`Runtime request failed (${response.status})`);
}
return response.json();
}
function initHomePage() {
const summary = document.getElementById("homeProjectSummary");
if (!summary) {
return;
}
const currentUser = getCurrentUser();
const projects = getProjects().sort((left, right) => right.updatedAt - left.updatedAt);
if (!projects.length) {
summary.innerHTML = `
<strong>${currentLocale === "ko" ? "개인 홈" : "Personal Home"}</strong>
<p>${currentUser ? `@${escapeHtml(currentUser)}` : currentLocale === "ko" ? "게스트" : "Guest"}</p>
<p>${content[currentLocale].homeEmpty}</p>
<div class="home-persona-grid">
<div class="persona-chip">${currentLocale === "ko" ? "오늘의 추천: JavaScript 미니 프로젝트" : "Today: JavaScript mini project"}</div>
<div class="persona-chip">${currentLocale === "ko" ? "AI 도우미에서 설계 플랜 먼저 작성" : "Draft architecture in AI Assistant first"}</div>
<div class="persona-chip">${currentLocale === "ko" ? "에러 해결에서 파일/라인 기반 수정 추천 사용" : "Use file-line fix recommendations in Errors"}</div>
</div>`;
return;
}
const latest = projects[0];
const dominantLanguage =
projects
.map((project) => project.language)
.sort((a, b, arr) => arr.filter((v) => v === a).length - arr.filter((v) => v === b).length)
.pop() || latest.language;
summary.innerHTML = `
<strong>${currentLocale === "ko" ? "개인 홈" : "Personal Home"}</strong>
<p>${currentUser ? `@${escapeHtml(currentUser)}` : currentLocale === "ko" ? "게스트" : "Guest"}</p>
<strong>최근 프로젝트</strong>
<p>${escapeHtml(latest.name)}</p>
<strong>언어</strong>
<p>${escapeHtml(languageSamples[latest.language]?.label || latest.language)}</p>
<strong>${currentLocale === "ko" ? "자주 쓰는 언어" : "Frequent Language"}</strong>
<p>${escapeHtml(languageSamples[dominantLanguage]?.label || dominantLanguage)}</p>
<strong>마지막 저장</strong>
<p>${formatTimestamp(latest.updatedAt)}</p>
<div class="home-persona-grid">
<div class="persona-chip">${currentLocale === "ko" ? "추천: 최근 프로젝트를 리팩터링" : "Suggested: refactor latest project"}</div>
<div class="persona-chip">${currentLocale === "ko" ? "터미널에서 runtimes로 실행 환경 점검" : "Check runtimes from terminal"}</div>
<div class="persona-chip">${currentLocale === "ko" ? "커뮤니티에 오늘 코드 공유하기" : "Share today's code in community"}</div>
</div>
<a class="primary-button small" href="editor.html">이어서 작업</a>`;
}
function initEditorPage() {
const languageSelect = document.getElementById("languageSelect");
const codeEditor = document.getElementById("codeEditor");
if (!languageSelect || !codeEditor) {
return;
}
const projectName = document.getElementById("projectName");
const lineNumbers = document.getElementById("lineNumbers");
const outputConsole = document.getElementById("outputConsole");
const statusBadge = document.getElementById("statusBadge");
const saveMeta = document.getElementById("saveMeta");
const projectCount = document.getElementById("projectCount");
const projectList = document.getElementById("projectList");
const previewFrame = document.getElementById("previewFrame");
const circuitEditor = document.getElementById("circuitEditor");
const circuitWorkspace = document.getElementById("circuitWorkspace");
const circuitWireLayer = document.getElementById("circuitWireLayer");
const circuitNodeLayer = document.getElementById("circuitNodeLayer");
const circuitStatus = document.getElementById("circuitStatus");
const addComponentButton = document.getElementById("addComponentButton");
const connectComponentButton = document.getElementById("connectComponentButton");
const resetCircuitButton = document.getElementById("resetCircuitButton");
const applyCircuitNodeButton = document.getElementById("applyCircuitNodeButton");
const deleteCircuitNodeButton = document.getElementById("deleteCircuitNodeButton");
const circuitNameInput = document.getElementById("circuitNameInput");
const circuitPinInput = document.getElementById("circuitPinInput");
const circuitXInput = document.getElementById("circuitXInput");
const circuitYInput = document.getElementById("circuitYInput");
const toggleRuntimeConfigButton = document.getElementById("toggleRuntimeConfigButton");
const runtimeConfigBody = document.getElementById("runtimeConfigBody");
const runtimeEndpointInput = document.getElementById("runtimeEndpointInput");
const runtimeHeadersInput = document.getElementById("runtimeHeadersInput");
const saveRuntimeConfigButton = document.getElementById("saveRuntimeConfigButton");
const resetRuntimeConfigButton = document.getElementById("resetRuntimeConfigButton");
const runtimeConfigStatus = document.getElementById("runtimeConfigStatus");
const importFileInput = document.getElementById("importFileInput");
const runButton = document.getElementById("runButton");
const saveButton = document.getElementById("saveButton");
const newProjectButton = document.getElementById("newProjectButton");
const downloadButton = document.getElementById("downloadButton");
const runModePill = document.getElementById("runModePill");
const terminalConsole = document.getElementById("terminalConsole");
const terminalInput = document.getElementById("terminalInput");
const terminalRunButton = document.getElementById("terminalRunButton");
const terminalClearButton = document.getElementById("terminalClearButton");
const guiHintPanel = document.getElementById("guiHintPanel");
const circuitComponentType = document.getElementById("circuitComponentType");
const removeWireButton = document.getElementById("removeWireButton");