lookatstudy-mobile 0.32.0 → 0.33.1

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 (134) hide show
  1. package/package.json +1 -1
  2. package/server.cjs +110 -36
  3. package/web/assets/{CanvasStage-C5AjAi8o.js → CanvasStage-Bt_fNO8t.js} +1 -1
  4. package/web/assets/{CommandPalette-CMByQ1xA.js → CommandPalette-DWmgv52l.js} +1 -1
  5. package/web/assets/{CourseSearchPanel-BlV2XeFD.js → CourseSearchPanel-vB2mN_3Y.js} +1 -1
  6. package/web/assets/{ExamView-BuzkmoG_.js → ExamView-DjcpWfqz.js} +1 -1
  7. package/web/assets/{ReviewPanel-Zve2SZUA.js → ReviewPanel-BIdGi9F2.js} +1 -1
  8. package/web/assets/{SettingsView-ClrQQG3O.js → SettingsView-BeMHHZCd.js} +2 -2
  9. package/web/assets/{VoicePanel-CrCt12Iw.js → VoicePanel-CKzA8jtV.js} +1 -1
  10. package/web/assets/{abnfDiagram-VCTEODGH-ODA7YWAP-0U3o9k-P.js → abnfDiagram-VCTEODGH-ODA7YWAP-D0MMepkX.js} +1 -1
  11. package/web/assets/{abnfDiagram-VCTEODGH-CvhdvCcB.js → abnfDiagram-VCTEODGH-dGcioyTT.js} +1 -1
  12. package/web/assets/{architectureDiagram-5GKGNRK7-C4PU3XN7-BWNxjRJN.js → architectureDiagram-5GKGNRK7-C4PU3XN7-C6PC7E-G.js} +1 -1
  13. package/web/assets/{architectureDiagram-5GKGNRK7-B7KWD0vf.js → architectureDiagram-5GKGNRK7-CXRelZVJ.js} +1 -1
  14. package/web/assets/{arrow-right-BUOXWVDJ.js → arrow-right-Do7NbfY9.js} +1 -1
  15. package/web/assets/{blockDiagram-I7D4REHJ-C2JsQ5gD.js → blockDiagram-I7D4REHJ-Dd_p7o-g.js} +1 -1
  16. package/web/assets/{blockDiagram-NRAW4CY4-UMJ6PMQQ-BFITa5i3.js → blockDiagram-NRAW4CY4-UMJ6PMQQ-1W9B_JQA.js} +1 -1
  17. package/web/assets/{c4Diagram-7LVT6UL2-BKdIKfT2.js → c4Diagram-7LVT6UL2-D6GGkJgl.js} +1 -1
  18. package/web/assets/{c4Diagram-UCG6FXSJ-75X3U2P3-frDapyTO.js → c4Diagram-UCG6FXSJ-75X3U2P3-D6kIkgIO.js} +1 -1
  19. package/web/assets/channel-Bmdemkmz.js +1 -0
  20. package/web/assets/{chunk-2Q5K7J3B-B9edEgx0.js → chunk-2Q5K7J3B-1wsMqe35.js} +1 -1
  21. package/web/assets/{chunk-5VM5RSS4-gTXbbOyw.js → chunk-5VM5RSS4-DfuR9l9V.js} +1 -1
  22. package/web/assets/{chunk-7XDTSPCN-WE5tnF0h.js → chunk-7XDTSPCN-BZY0pKX-.js} +1 -1
  23. package/web/assets/{chunk-CTNIYPP6-BkodKTJ4.js → chunk-CTNIYPP6-CNvvOY5j.js} +1 -1
  24. package/web/assets/{chunk-CXNFBKHE-HwiQMaWc.js → chunk-CXNFBKHE-Dm1pquWx.js} +1 -1
  25. package/web/assets/{chunk-F27PBJKO-blDtmndB.js → chunk-F27PBJKO-CHRFnx11.js} +1 -1
  26. package/web/assets/{chunk-IMKFNOWR-c2ANK639.js → chunk-IMKFNOWR-uiypXoEe.js} +1 -1
  27. package/web/assets/{chunk-ISA7TJRJ-ORNUxxCz.js → chunk-ISA7TJRJ-BQ2gIr75.js} +1 -1
  28. package/web/assets/{chunk-JWPE2WC7-B6N2oaft.js → chunk-JWPE2WC7-BgST7e0p.js} +1 -1
  29. package/web/assets/{chunk-LV3HUYFY-BCm0PQly.js → chunk-LV3HUYFY-BnzTfd10.js} +1 -1
  30. package/web/assets/{chunk-M3IUQBOH-DN38nuN1.js → chunk-M3IUQBOH-CviSmNDD.js} +1 -1
  31. package/web/assets/{chunk-NDIOYS4C-Bo6GgJFG.js → chunk-NDIOYS4C-C4wQSziF.js} +1 -1
  32. package/web/assets/{chunk-POPQ4Y6H-DIzvpHxe.js → chunk-POPQ4Y6H-LX5w0GJ3.js} +1 -1
  33. package/web/assets/{chunk-RE4WF27V-D8jfGB_4.js → chunk-RE4WF27V-BAhk1SH_.js} +1 -1
  34. package/web/assets/{chunk-SVP7TREG-CxnNazbc.js → chunk-SVP7TREG-7Sg7sU_j.js} +1 -1
  35. package/web/assets/{chunk-TICWLB2K-DURl3-X9.js → chunk-TICWLB2K-CP9bBlVg.js} +1 -1
  36. package/web/assets/{chunk-VII2H2IX-B_sArj8v.js → chunk-VII2H2IX-DZd3RSio.js} +1 -1
  37. package/web/assets/{chunk-X5GNSKZ3-DYVuF3lP.js → chunk-X5GNSKZ3-BhOZRnO2.js} +1 -1
  38. package/web/assets/{chunk-XCWWM2C3-D8AZk6R6.js → chunk-XCWWM2C3-RUaVDOLz.js} +1 -1
  39. package/web/assets/{chunk-XXDRQBXY-CkpBnoOj.js → chunk-XXDRQBXY-BMOoMY4C.js} +1 -1
  40. package/web/assets/classDiagram-DTDB5LWJ-LWRDKZ5K-BA0DiW1S.js +1 -0
  41. package/web/assets/classDiagram-ZZMXUADV-Y4CKpvNU.js +1 -0
  42. package/web/assets/classDiagram-v2-JRS7N3AN-2BZHGRP2-BA0DiW1S.js +1 -0
  43. package/web/assets/classDiagram-v2-VYDZK3BY-Y4CKpvNU.js +1 -0
  44. package/web/assets/{core-B8sIFi4Z.js → core-Bljob3yk.js} +1 -1
  45. package/web/assets/{cose-bilkent-JH36ORCC-y34bWcV3.js → cose-bilkent-JH36ORCC-DLbA0yhO.js} +1 -1
  46. package/web/assets/{cose-bilkent-JH36ORCC-OB24KH3T-IvJ21Kzu.js → cose-bilkent-JH36ORCC-OB24KH3T-ebHFtabj.js} +1 -1
  47. package/web/assets/{cynefin-OW5HDTMX-BhCbnffL.js → cynefin-OW5HDTMX-CnnFvhFO.js} +1 -1
  48. package/web/assets/{cynefin-OW5HDTMX-GVF3BKWE-DZCD-8FK.js → cynefin-OW5HDTMX-GVF3BKWE-DyJbbdgM.js} +1 -1
  49. package/web/assets/{cynefinDiagram-5FMLGOSQ-hxpWMOtC.js → cynefinDiagram-5FMLGOSQ-DCwI1QTM.js} +1 -1
  50. package/web/assets/{cynefinDiagram-5FMLGOSQ-H3WIRU3N-DYZPSd2_.js → cynefinDiagram-5FMLGOSQ-H3WIRU3N-jNzWfP6P.js} +1 -1
  51. package/web/assets/{dagre-3AP2YEHR-DCERTXHT-BX8eflJW.js → dagre-3AP2YEHR-DCERTXHT-DeiOeAFM.js} +1 -1
  52. package/web/assets/{dagre-GXQ25YYZ-CzyQyaZs.js → dagre-GXQ25YYZ-CDCQs5B2.js} +1 -1
  53. package/web/assets/{diagram-S7CK7UJ4-COWdoy1M.js → diagram-S7CK7UJ4-DgZUpvXD.js} +1 -1
  54. package/web/assets/{diagram-S7CK7UJ4-MULGZGEL-CqJqO9UI.js → diagram-S7CK7UJ4-MULGZGEL-BPzWf7Pz.js} +1 -1
  55. package/web/assets/{diagram-UQ7AKVKN-BTrH3UHo.js → diagram-UQ7AKVKN-CZTprTGR.js} +1 -1
  56. package/web/assets/{diagram-UQ7AKVKN-YQBY353J-QrzubupF.js → diagram-UQ7AKVKN-YQBY353J-D8p7jpg9.js} +1 -1
  57. package/web/assets/{diagram-VSXAHHWV-DPZOIzCd.js → diagram-VSXAHHWV-D3J7k7wm.js} +1 -1
  58. package/web/assets/{diagram-VSXAHHWV-WLNH2ELY-BCati379.js → diagram-VSXAHHWV-WLNH2ELY-DY4p4Mm9.js} +1 -1
  59. package/web/assets/{diagram-VX7I27RA-_xIvruEB.js → diagram-VX7I27RA-CGlO-Ekr.js} +1 -1
  60. package/web/assets/{diagram-VX7I27RA-XW6YQV5G-DqXG6n6Y.js → diagram-VX7I27RA-XW6YQV5G-Ctmdqbly.js} +1 -1
  61. package/web/assets/{diagram-Z3DM3KII-HG7RF65D-Croiubkf.js → diagram-Z3DM3KII-HG7RF65D-qfRqaf6R.js} +1 -1
  62. package/web/assets/{diagram-Z3DM3KII-ksHvfnAi.js → diagram-Z3DM3KII-m9lJCZC1.js} +1 -1
  63. package/web/assets/{ebnfDiagram-PWID7BFC-OKY4VHYD-CGcKoMMQ.js → ebnfDiagram-PWID7BFC-OKY4VHYD-D89NZRDy.js} +1 -1
  64. package/web/assets/{ebnfDiagram-PWID7BFC-noMSXuIk.js → ebnfDiagram-PWID7BFC-c9DvObbP.js} +1 -1
  65. package/web/assets/{erDiagram-RLTQ6QDP-cL-kIfCG.js → erDiagram-RLTQ6QDP-CYWGD0br.js} +1 -1
  66. package/web/assets/{erDiagram-SSCWMZ5O-PIPKOIK7-D9Zc1NAW.js → erDiagram-SSCWMZ5O-PIPKOIK7-BrchUk-n.js} +1 -1
  67. package/web/assets/{flowDiagram-A5DVABFB-2Z4AVO5R-mUdmnAxr.js → flowDiagram-A5DVABFB-2Z4AVO5R-B0Rwk_R1.js} +1 -1
  68. package/web/assets/{flowDiagram-HODETNUW-BwP5kUwO.js → flowDiagram-HODETNUW-BJ3ZVwcY.js} +1 -1
  69. package/web/assets/{ganttDiagram-EL5Y4UJY-BE3keQkK.js → ganttDiagram-EL5Y4UJY-B_MNJ8fZ.js} +1 -1
  70. package/web/assets/{ganttDiagram-EL5Y4UJY-JZNG7GOG-ejS74W4D.js → ganttDiagram-EL5Y4UJY-JZNG7GOG-DkxJ0Cn0.js} +1 -1
  71. package/web/assets/{gitGraphDiagram-WWUBYQGX-DZ1hemyK.js → gitGraphDiagram-WWUBYQGX-CSy5TQmE.js} +1 -1
  72. package/web/assets/{gitGraphDiagram-WWUBYQGX-DMWMLL3G-BkacjsFO.js → gitGraphDiagram-WWUBYQGX-DMWMLL3G-D_BHv1fj.js} +1 -1
  73. package/web/assets/{index-CBjzutcq.js → index-BPS_Agzl.js} +1 -1
  74. package/web/assets/{index-pVpu1lqj.js → index-DiOyjst0.js} +1 -1
  75. package/web/assets/{index-knlcEee4.js → index-HjyNOGR-.js} +3 -3
  76. package/web/assets/{infoDiagram-27XIBGKW-D5SmDceD.js → infoDiagram-27XIBGKW-Dg4zHSFK.js} +1 -1
  77. package/web/assets/{infoDiagram-RXCK75RN-HANS5KVC-aXapDflI.js → infoDiagram-RXCK75RN-HANS5KVC-BnDIUO0e.js} +1 -1
  78. package/web/assets/{ishikawaDiagram-5VMMS53U-7GCIT7WU-Bc9UsCs_.js → ishikawaDiagram-5VMMS53U-7GCIT7WU-CvpASJo8.js} +1 -1
  79. package/web/assets/{ishikawaDiagram-5VMMS53U-B74LRTCh.js → ishikawaDiagram-5VMMS53U-zVzbXgbw.js} +1 -1
  80. package/web/assets/{journeyDiagram-3NMN7TZE-BDaoWTSE.js → journeyDiagram-3NMN7TZE-Dnac9OWj.js} +1 -1
  81. package/web/assets/{journeyDiagram-EYS64GPL-OAPELBT7-MrPU_Ez8.js → journeyDiagram-EYS64GPL-OAPELBT7-JFMRWh-l.js} +1 -1
  82. package/web/assets/{kanban-definition-3QL26DDD-I7RKWQQY-Cm3vdIeM.js → kanban-definition-3QL26DDD-I7RKWQQY-DI4eShiu.js} +1 -1
  83. package/web/assets/{kanban-definition-UXKFOSKX-SS8pMp0v.js → kanban-definition-UXKFOSKX-BGCJV8zY.js} +1 -1
  84. package/web/assets/{katex-N6JRN4PM-hIPFODWi.js → katex-N6JRN4PM-CUVnEhz9.js} +1 -1
  85. package/web/assets/{layout-D8EinnOm.js → layout-BUaUYSLh.js} +1 -1
  86. package/web/assets/{main-48BT8Y-5.js → main-fDEgiN-S.js} +6 -6
  87. package/web/assets/{mermaid-layout-elk.core-BpWpx6UM.js → mermaid-layout-elk.core-BWBQ_Al5.js} +2 -2
  88. package/web/assets/{mermaid.core-B5r2lVOh.js → mermaid.core-BMxGmpq2.js} +5 -5
  89. package/web/assets/{mindmap-definition-FBJOCRG2-KIYZG64Y-hpeP-5hz.js → mindmap-definition-FBJOCRG2-KIYZG64Y-CjgpFZ4r.js} +1 -1
  90. package/web/assets/{mindmap-definition-YA3MSWOX-De5xL7n7.js → mindmap-definition-YA3MSWOX-DsaPJhNK.js} +1 -1
  91. package/web/assets/{pegDiagram-XKGWAZYB-CdIyH0Fn.js → pegDiagram-XKGWAZYB-DOx0zpGS.js} +1 -1
  92. package/web/assets/{pegDiagram-XKGWAZYB-OIP57N6J-CR8lJ0Uf.js → pegDiagram-XKGWAZYB-OIP57N6J-DjMX89Yt.js} +1 -1
  93. package/web/assets/{pieDiagram-E7YTZNPT-2UOTW327-DnJ5X3O6.js → pieDiagram-E7YTZNPT-2UOTW327-lDdClvNY.js} +1 -1
  94. package/web/assets/{pieDiagram-E7YTZNPT-BfYDyK9E.js → pieDiagram-E7YTZNPT-D6Fit7on.js} +1 -1
  95. package/web/assets/{quadrantDiagram-AXDQQJYC-Cza5JG_-.js → quadrantDiagram-AXDQQJYC-1jui_ce_.js} +1 -1
  96. package/web/assets/{quadrantDiagram-AXDQQJYC-SN3ASJJK-Bcepwx-u.js → quadrantDiagram-AXDQQJYC-SN3ASJJK-BTbJgylU.js} +1 -1
  97. package/web/assets/{railroadDiagram-O6MQD6OU-34742W6X-NzeT7rIi.js → railroadDiagram-O6MQD6OU-34742W6X-BC0RubPt.js} +1 -1
  98. package/web/assets/{railroadDiagram-O6MQD6OU-C95urtld.js → railroadDiagram-O6MQD6OU-BAC-UzUT.js} +1 -1
  99. package/web/assets/{render-O7CIS3YK-C6VkwFv4.js → render-O7CIS3YK-ilIoaxFJ.js} +7 -7
  100. package/web/assets/{requirementDiagram-BXWQKSXE-BAlq4SmX.js → requirementDiagram-BXWQKSXE-9E1HLwpG.js} +1 -1
  101. package/web/assets/{requirementDiagram-EFPCY7ZU-NGAFFSMB-DzevWop7.js → requirementDiagram-EFPCY7ZU-NGAFFSMB-KHWBp4rz.js} +1 -1
  102. package/web/assets/{rotate-ccw-Cg9NLquP.js → rotate-ccw-QIElN2Wl.js} +1 -1
  103. package/web/assets/{sankeyDiagram-P5KCCOFB-3D3M4XPB-DApmapmK.js → sankeyDiagram-P5KCCOFB-3D3M4XPB-C-izOJ7-.js} +1 -1
  104. package/web/assets/{sankeyDiagram-P5KCCOFB-BD8XvM6V.js → sankeyDiagram-P5KCCOFB-Cdc1ghNL.js} +1 -1
  105. package/web/assets/{sequenceDiagram-WJ2MYXX4-3QbrYVOI.js → sequenceDiagram-WJ2MYXX4-DRxvXwCF.js} +1 -1
  106. package/web/assets/{sequenceDiagram-WJ2MYXX4-Q2IOPB4R-0NYlnM84.js → sequenceDiagram-WJ2MYXX4-Q2IOPB4R-D1pUF6jo.js} +1 -1
  107. package/web/assets/{sizeCapture-INFHLROL-B6CyQprA.js → sizeCapture-INFHLROL-DpTeYER6.js} +1 -1
  108. package/web/assets/{sizeCapture-X5ZJPWSS-PSGVOMYM-BNAT5QMm.js → sizeCapture-X5ZJPWSS-PSGVOMYM-9nTpQ1b_.js} +1 -1
  109. package/web/assets/{stateDiagram-D77RDMKH-00SC5Yaa.js → stateDiagram-D77RDMKH-C96itYlp.js} +1 -1
  110. package/web/assets/{stateDiagram-HBIQ2CUA-4M2S6MAN-irstRbtp.js → stateDiagram-HBIQ2CUA-4M2S6MAN-DVcS5z07.js} +1 -1
  111. package/web/assets/stateDiagram-v2-4QOOHH4V-LBBLA2WV-C8CVqjZH.js +1 -0
  112. package/web/assets/stateDiagram-v2-MP3YSRHH-Ddg-z14V.js +1 -0
  113. package/web/assets/{swimlanes-42K2YHIH-CmbMI_md.js → swimlanes-42K2YHIH-4suR9Oix.js} +1 -1
  114. package/web/assets/{swimlanes-XN3QIQJK-N6N2CCQ6-BqITgbLG.js → swimlanes-XN3QIQJK-N6N2CCQ6-DcuiDmSl.js} +1 -1
  115. package/web/assets/swimlanesDiagram-VK2B7HYN-E6N4H5SY-BDSwlxp9.js +8 -0
  116. package/web/assets/swimlanesDiagram-VR7AAH4N-Cj4AmHeX.js +8 -0
  117. package/web/assets/{timeline-definition-24CTP7MA-CLCGIomU.js → timeline-definition-24CTP7MA-CcO-uk61.js} +1 -1
  118. package/web/assets/{timeline-definition-24CTP7MA-KN7RSRVM-BYuvwtyK.js → timeline-definition-24CTP7MA-KN7RSRVM-rA7akmuP.js} +1 -1
  119. package/web/assets/{vennDiagram-4TSXK5OY-AKJ7LXAN-AoK44fps.js → vennDiagram-4TSXK5OY-AKJ7LXAN-Hpw8RJob.js} +1 -1
  120. package/web/assets/{vennDiagram-4TSXK5OY-DCfqg183.js → vennDiagram-4TSXK5OY-L93ffUGG.js} +1 -1
  121. package/web/assets/{wardleyDiagram-VM6X3IG4-3B6A3BN5-B1lE0M48.js → wardleyDiagram-VM6X3IG4-3B6A3BN5-Dij0NzOp.js} +1 -1
  122. package/web/assets/{wardleyDiagram-VM6X3IG4-D1GxucqJ.js → wardleyDiagram-VM6X3IG4-D4jAxgQi.js} +1 -1
  123. package/web/assets/{xychartDiagram-S5SC5T6Z-DlDA8yrW.js → xychartDiagram-S5SC5T6Z-BeOHWeZb.js} +1 -1
  124. package/web/assets/{xychartDiagram-S5SC5T6Z-OBDNV6WG-D9XtPaPY.js → xychartDiagram-S5SC5T6Z-OBDNV6WG-NM-894O4.js} +1 -1
  125. package/web/index.html +1 -1
  126. package/web/assets/channel-B9w8tMGs.js +0 -1
  127. package/web/assets/classDiagram-DTDB5LWJ-LWRDKZ5K-Bc5E8Hix.js +0 -1
  128. package/web/assets/classDiagram-ZZMXUADV-BZZyCoYc.js +0 -1
  129. package/web/assets/classDiagram-v2-JRS7N3AN-2BZHGRP2-Bc5E8Hix.js +0 -1
  130. package/web/assets/classDiagram-v2-VYDZK3BY-BZZyCoYc.js +0 -1
  131. package/web/assets/stateDiagram-v2-4QOOHH4V-LBBLA2WV-eCbd9dBW.js +0 -1
  132. package/web/assets/stateDiagram-v2-MP3YSRHH-3vtW0ec_.js +0 -1
  133. package/web/assets/swimlanesDiagram-VK2B7HYN-E6N4H5SY-nuGcWYId.js +0 -8
  134. package/web/assets/swimlanesDiagram-VR7AAH4N-CjHyszkJ.js +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lookatstudy-mobile",
3
- "version": "0.32.0",
3
+ "version": "0.33.1",
4
4
  "description": "LookatStudy Termux portable bundle (server.cjs + web frontend)",
5
5
  "repository": {
6
6
  "type": "git",
package/server.cjs CHANGED
@@ -12719,6 +12719,8 @@ var init_schema = __esm({
12719
12719
  labType: text("lab_type", { enum: ["doc", "code", "notebook"] }).notNull().default("doc"),
12720
12720
  /** 仓库原文语言 (en / zh-CN / zh-TW / ...), LLM Step 2 判断; null=未知按 en 处理 */
12721
12721
  sourceLang: text("source_lang"),
12722
+ /** v0.33 语言学习课程的目标语言;null = 非语言课程 */
12723
+ languageTarget: text("language_target"),
12722
12724
  createdAt: text("created_at").notNull().default(sql`(CURRENT_TIMESTAMP)`)
12723
12725
  });
12724
12726
  contentNodes = sqliteTable("content_nodes", {
@@ -13021,6 +13023,9 @@ CREATE TABLE IF NOT EXISTS courses (
13021
13023
  lab_type TEXT NOT NULL DEFAULT 'doc' CHECK (lab_type IN ('doc', 'code', 'notebook')),
13022
13024
  -- \u4ED3\u5E93\u539F\u6587\u8BED\u8A00 (BCP-47 \u5B50\u96C6: en / zh-CN / zh-TW / ja / ...), LLM \u5728 Step 2 \u5224\u65AD
13023
13025
  source_lang TEXT,
13026
+ -- v0.33 \u8BED\u8A00\u5B66\u4E60\u8BFE\u7A0B\u7684\u76EE\u6807\u8BED\u8A00(\u88AB\u6559\u7684\u8BED\u8A00,BCP-47);NULL = \u975E\u8BED\u8A00\u8BFE\u7A0B\u3002
13027
+ -- \u4E0E source_lang \u8BED\u4E49\u4E0D\u540C:\u4E2D\u6587\u5199\u7684\u82F1\u8BED\u6559\u6750 source_lang=zh-CN\u3001language_target=en
13028
+ language_target TEXT,
13024
13029
  created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)
13025
13030
  );
13026
13031
 
@@ -13422,6 +13427,7 @@ function runMigrations(db) {
13422
13427
  addColumnIfMissing("content_nodes", "knowledge_points", "TEXT");
13423
13428
  addColumnIfMissing("exercises", "kc_title", "TEXT");
13424
13429
  addColumnIfMissing("courses", "source_lang", "TEXT");
13430
+ addColumnIfMissing("courses", "language_target", "TEXT");
13425
13431
  addColumnIfMissing("custom_providers", "kind", "TEXT NOT NULL DEFAULT 'llm'");
13426
13432
  addColumnIfMissing("custom_providers", "vision", "INTEGER NOT NULL DEFAULT 0");
13427
13433
  addColumnIfMissing("memory", "course_id", "TEXT");
@@ -13918,7 +13924,8 @@ async function executeImport(db, structure, opts, onProgress) {
13918
13924
  description: `\u4ECE ${opts.repoName} \u5BFC\u5165`,
13919
13925
  version: 1,
13920
13926
  labType: "doc",
13921
- sourceLang: opts.sourceLang
13927
+ sourceLang: opts.sourceLang,
13928
+ languageTarget: opts.languageTarget ?? null
13922
13929
  }).run();
13923
13930
  for (const sec of structure.sections) {
13924
13931
  const sectionId = (0, import_node_crypto3.randomUUID)();
@@ -63562,7 +63569,8 @@ async function classifyFileRoles(db, readmeMd, fileList, fullTree, onProgress, o
63562
63569
  skip,
63563
63570
  languages: validLangs,
63564
63571
  sourceLang: detectSourceLangByRule(readmeMd),
63565
- translationLayout: layoutResult.layout
63572
+ translationLayout: layoutResult.layout,
63573
+ languageTarget: null
63566
63574
  };
63567
63575
  }
63568
63576
  send(`AI \u5224\u65AD ${remaining.length} \u4E2A\u6587\u4EF6\u89D2\u8272 + \u539F\u6587\u8BED\u8A00\uFF08\u770B README + \u76EE\u5F55\u6811\uFF09`);
@@ -63574,6 +63582,7 @@ async function classifyFileRoles(db, readmeMd, fileList, fullTree, onProgress, o
63574
63582
  const allPathSet = new Set(allPaths);
63575
63583
  const llmLangs = /* @__PURE__ */ new Set();
63576
63584
  let sourceLang = "";
63585
+ let languageTarget = null;
63577
63586
  for (let i = 0; i < remaining.length; i += CHUNK_SIZE) {
63578
63587
  const chunk = remaining.slice(i, i + CHUNK_SIZE);
63579
63588
  const chunkNum = Math.floor(i / CHUNK_SIZE) + 1;
@@ -63594,6 +63603,8 @@ async function classifyFileRoles(db, readmeMd, fileList, fullTree, onProgress, o
63594
63603
  });
63595
63604
  if (!sourceLang && parsed.sourceLang)
63596
63605
  sourceLang = parsed.sourceLang;
63606
+ if (languageTarget === null && parsed.languageTarget)
63607
+ languageTarget = parsed.languageTarget;
63597
63608
  for (const { path: path6, role, lang, translates } of parsed.files) {
63598
63609
  if (role === "translation") {
63599
63610
  if (lang && translates && allPathSet.has(translates)) {
@@ -63620,7 +63631,7 @@ async function classifyFileRoles(db, readmeMd, fileList, fullTree, onProgress, o
63620
63631
  validLangs.push({ code, name: LANG_NAMES2[code] ?? code });
63621
63632
  }
63622
63633
  }
63623
- return { original, translations, translationPairs, practice, skip, languages: validLangs, sourceLang, translationLayout: layoutResult.layout };
63634
+ return { original, translations, translationPairs, practice, skip, languages: validLangs, sourceLang, translationLayout: layoutResult.layout, languageTarget };
63624
63635
  }
63625
63636
  function detectSourceLangByRule(readmeMd) {
63626
63637
  const total = Math.max(readmeMd.length, 1);
@@ -63698,7 +63709,7 @@ ${readmeExcerpt}
63698
63709
  ---` : "\uFF08\u65E0 README \u6587\u4EF6\uFF0C\u8BF7\u6839\u636E\u6587\u4EF6\u540D\u3001\u76EE\u5F55\u7ED3\u6784\u3001\u6587\u4EF6\u7C7B\u578B\u5224\u65AD\u539F\u6587\u8BED\u8A00\u548C\u6587\u4EF6\u89D2\u8272\uFF09";
63699
63710
  return `\u4F60\u662F\u8BFE\u7A0B\u4ED3\u5E93\u5206\u6790\u4E13\u5BB6\u3002\u4E0B\u9762\u662F\u4E00\u4E2A\u5B66\u4E60\u4ED3\u5E93\u7684 README\uFF08\u524D3000\u5B57\uFF09\u3001\u5B8C\u6574\u76EE\u5F55\u6811\u548C\u5F85\u5206\u7C7B\u7684\u6587\u4EF6\u5217\u8868\u3002
63700
63711
 
63701
- \u8BF7\u5B8C\u6210\u4E24\u4E2A\u4EFB\u52A1:
63712
+ \u8BF7\u5B8C\u6210\u4E09\u4E2A\u4EFB\u52A1:
63702
63713
 
63703
63714
  1. \u5224\u65AD README \u7684**\u539F\u6587\u8BED\u8A00**\uFF08\u4E0D\u662F\u7FFB\u8BD1\u8BED\u8A00\uFF09\u3002\u770B README \u6B63\u6587\u662F\u4EC0\u4E48\u8BED\u8A00\u5199\u7684:
63704
63715
  - \u82F1\u6587 \u2192 "en"
@@ -63715,6 +63726,15 @@ ${readmeExcerpt}
63715
63726
  \u548C "translates"(\u5B83\u7FFB\u8BD1\u7684\u539F\u6587\u6587\u4EF6\u8DEF\u5F84\uFF0C\u5FC5\u987B\u662F\u6587\u4EF6\u5217\u8868\u6216\u76EE\u5F55\u6811\u91CC\u771F\u5B9E\u5B58\u5728\u7684\u6587\u4EF6)
63716
63727
  - **skip**: \u566A\u58F0\uFF08\u7EAF\u914D\u7F6E\u3001\u7A7A\u6587\u4EF6\u3001\u975E\u5B66\u4E60\u5185\u5BB9\uFF09
63717
63728
 
63729
+ 3. \u5224\u65AD\u8FD9\u4E2A\u4ED3\u5E93**\u662F\u4E0D\u662F\u8BED\u8A00\u5B66\u4E60\u8BFE\u7A0B**\u2014\u2014\u5373\u8BFE\u7A0B\u6559\u7684\u79D1\u76EE\u5C31\u662F\u4E00\u95E8\u8BED\u8A00\u672C\u8EAB\uFF08\u6559\u82F1\u8BED/\u65E5\u8BED/\u97E9\u8BED/\u4E2D\u6587\u2026\u7684
63730
+ \u8BCD\u6C47\u3001\u8BED\u6CD5\u3001\u542C\u8BF4\u8BFB\u5199\uFF09\uFF0C\u800C\u4E0D\u662F"\u7528\u67D0\u79CD\u8BED\u8A00\u5199\u7684\u77E5\u8BC6/\u6280\u672F\u6559\u7A0B"\u3002\u4F9D\u636E README \u4E0E\u76EE\u5F55\u6811\u7684\u660E\u663E\u7279\u5F81:
63731
+ - \u662F\u8BED\u8A00\u8BFE \u2192 "languageTarget" \u586B**\u88AB\u6559\u7684\u90A3\u95E8\u8BED\u8A00**\u7684 BCP-47 \u7801\uFF08\u6559\u82F1\u8BED \u2192 "en"\uFF0C\u6559\u65E5\u8BED \u2192 "ja"\u2026\uFF09\u3002
63732
+ \u6CE8\u610F:\u6559\u6750\u7684\u4E66\u5199\u8BED\u8A00\u4E0D\u7B49\u4E8E\u88AB\u6559\u7684\u8BED\u8A00\u2014\u2014\u4E2D\u6587\u5199\u7684\u82F1\u8BED\u6559\u6750\u4ED3\u5E93\uFF0ClanguageTarget \u662F "en"
63733
+ - \u8BED\u8A00\u8BFE\u7684\u5178\u578B\u7279\u5F81: \u8BCD\u6C47\u8868/\u8BCD\u6839\u8BCD\u7F00\u3001\u8BED\u6CD5\u70B9\u8BB2\u89E3\u3001\u5BF9\u8BDD/\u8BFE\u6587\u3001\u53E5\u5B50-\u7FFB\u8BD1\u5BF9\u3001
63734
+ \u6C34\u5E73\u5206\u7EA7\u6807\u8BB0\uFF08N5/N1\u3001TOEFL/IELTS\u3001HSK\u3001JLPT\u2026\uFF09\u3001\u53D1\u97F3/\u97F3\u6807\u5185\u5BB9
63735
+ - \u4E0D\u662F\u8BED\u8A00\u8BFE\uFF08\u7F16\u7A0B/\u6570\u5B66/\u79D1\u5B66\u6559\u7A0B\u3001\u7528\u82F1\u6587\u5199\u7684\u6280\u8853\u6587\u6863\u7B49\uFF09\u2192 "languageTarget": null
63736
+ - \u5224\u65AD\u4E0D\u51C6\u65F6\u503E\u5411 null\uFF08\u5B81\u53EF\u6309\u666E\u901A\u8BFE\u7A0B\u5904\u7406\uFF0C\u4E0D\u53EF\u628A\u666E\u901A\u8BFE\u7A0B\u5F53\u8BED\u8A00\u8BFE\uFF09
63737
+
63718
63738
  ${readmeSection}
63719
63739
 
63720
63740
  \u4ED3\u5E93\u5B8C\u6574\u76EE\u5F55\u6811\uFF08\u6811\u72B6\u7ED3\u6784\uFF0C\u6700\u591A\u5C55\u5F00 3 \u5C42\uFF0C\u6BCF\u76EE\u5F55\u6700\u591A 10 \u9879\uFF0C\u8D85\u51FA\u6298\u53E0\uFF09:
@@ -63741,6 +63761,7 @@ ${fileList}
63741
63761
  \u4E25\u683C\u8FD4\u56DE\u5982\u4E0B\u5F62\u72B6\u7684 JSON \u5BF9\u8C61:
63742
63762
  {
63743
63763
  "sourceLang": "en",
63764
+ "languageTarget": null,
63744
63765
  "files": [
63745
63766
  { "path": "lessons/1-Intro/README.md", "role": "original" },
63746
63767
  { "path": "lessons/2-Symbolic/Animals.ipynb", "role": "practice" },
@@ -63757,11 +63778,13 @@ function parseRoleResult(raw, validPaths) {
63757
63778
  } catch {
63758
63779
  return {
63759
63780
  sourceLang: "",
63781
+ languageTarget: null,
63760
63782
  files: validPaths.map((p) => ({ path: p, role: "original" })),
63761
63783
  degraded: true
63762
63784
  };
63763
63785
  }
63764
63786
  let sourceLang = "";
63787
+ let languageTarget = null;
63765
63788
  let filesRaw;
63766
63789
  if (Array.isArray(obj)) {
63767
63790
  filesRaw = obj;
@@ -63769,9 +63792,11 @@ function parseRoleResult(raw, validPaths) {
63769
63792
  const o = obj;
63770
63793
  if (typeof o.sourceLang === "string")
63771
63794
  sourceLang = o.sourceLang;
63795
+ if (typeof o.languageTarget === "string" && o.languageTarget.trim())
63796
+ languageTarget = o.languageTarget.trim();
63772
63797
  filesRaw = o.files;
63773
63798
  } else {
63774
- return { sourceLang: "", files: validPaths.map((p) => ({ path: p, role: "original" })), degraded: true };
63799
+ return { sourceLang: "", languageTarget: null, files: validPaths.map((p) => ({ path: p, role: "original" })), degraded: true };
63775
63800
  }
63776
63801
  const validSet = new Set(validPaths);
63777
63802
  const files = filesRaw.filter((item) => typeof item.path === "string" && validSet.has(item.path)).map((item) => ({
@@ -63781,7 +63806,7 @@ function parseRoleResult(raw, validPaths) {
63781
63806
  lang: typeof item.lang === "string" ? item.lang : void 0,
63782
63807
  translates: typeof item.translates === "string" ? item.translates : void 0
63783
63808
  }));
63784
- return { sourceLang, files, degraded: false };
63809
+ return { sourceLang, languageTarget, files, degraded: false };
63785
63810
  }
63786
63811
  async function classifyFilesResilient(files, deps) {
63787
63812
  const attempt = async (chunk) => {
@@ -63804,6 +63829,7 @@ async function classifyFilesResilient(files, deps) {
63804
63829
  const right = await recurse(chunk.slice(mid));
63805
63830
  return {
63806
63831
  sourceLang: left.sourceLang || right.sourceLang,
63832
+ languageTarget: left.languageTarget ?? right.languageTarget,
63807
63833
  files: [...left.files, ...right.files],
63808
63834
  degraded: false
63809
63835
  };
@@ -533040,7 +533066,8 @@ function rolesToPlan(r) {
533040
533066
  translationPairs: Object.fromEntries(r.translationPairs),
533041
533067
  languages: r.languages,
533042
533068
  sourceLang: r.sourceLang,
533043
- translationLayout: r.translationLayout
533069
+ translationLayout: r.translationLayout,
533070
+ languageTarget: r.languageTarget
533044
533071
  };
533045
533072
  }
533046
533073
  function planToRoles(p) {
@@ -533052,7 +533079,9 @@ function planToRoles(p) {
533052
533079
  translationPairs: new Map(Object.entries(p.translationPairs)),
533053
533080
  languages: p.languages,
533054
533081
  sourceLang: p.sourceLang,
533055
- translationLayout: p.translationLayout
533082
+ translationLayout: p.translationLayout,
533083
+ // 旧版快照无此字段 → undefined,统一压成 null(非语言课,现状行为)
533084
+ languageTarget: p.languageTarget ?? null
533056
533085
  };
533057
533086
  }
533058
533087
  function filterClassificationToTree(p, tree) {
@@ -533414,10 +533443,10 @@ ${(entries3[0]?.[1] ?? "").slice(0, 2e3)}`;
533414
533443
  let roles;
533415
533444
  if (plan.classification) {
533416
533445
  roles = planToRoles(plan.classification);
533417
- send(`\u2713 \u6587\u4EF6\u5206\u7C7B:\u590D\u7528\u5FEB\u7167(${roles.original.length} \u539F\u6587 \xB7 ${roles.practice.length} \u5B9E\u64CD \xB7 \u539F\u6587\u8BED\u8A00 ${roles.sourceLang})`);
533446
+ send(`\u2713 \u6587\u4EF6\u5206\u7C7B:\u590D\u7528\u5FEB\u7167(${roles.original.length} \u539F\u6587 \xB7 ${roles.practice.length} \u5B9E\u64CD \xB7 \u539F\u6587\u8BED\u8A00 ${roles.sourceLang}${roles.languageTarget ? ` \xB7 \u8BED\u8A00\u8BFE ${roles.languageTarget}` : ""})`);
533418
533447
  } else {
533419
533448
  roles = await (deps.classify ?? classifyFileRoles)(db, readmeMd, fileList, fullTree, send, { signal: cancelCtl.signal });
533420
- send(`\u2713 \u6587\u4EF6\u5206\u7C7B:${roles.original.length} \u539F\u6587 \xB7 ${roles.practice.length} \u5B9E\u64CD \xB7 ${roles.skip.length} \u8DF3\u8FC7 \xB7 \u539F\u6587\u8BED\u8A00 ${roles.sourceLang}`);
533449
+ send(`\u2713 \u6587\u4EF6\u5206\u7C7B:${roles.original.length} \u539F\u6587 \xB7 ${roles.practice.length} \u5B9E\u64CD \xB7 ${roles.skip.length} \u8DF3\u8FC7 \xB7 \u539F\u6587\u8BED\u8A00 ${roles.sourceLang}${roles.languageTarget ? ` \xB7 \u8BED\u8A00\u8BFE ${roles.languageTarget}` : ""}`);
533421
533450
  plan.classification = rolesToPlan(roles);
533422
533451
  plan.reachedStep = 2;
533423
533452
  savePlan();
@@ -533489,6 +533518,7 @@ ${(entries3[0]?.[1] ?? "").slice(0, 2e3)}`;
533489
533518
  translationPairs: roles.translationPairs,
533490
533519
  sourceLang: roles.sourceLang,
533491
533520
  translationLayout: roles.translationLayout,
533521
+ languageTarget: roles.languageTarget,
533492
533522
  shouldAbort,
533493
533523
  markDirty: markDirty2
533494
533524
  },
@@ -560116,7 +560146,7 @@ async function cutCompanionFigure(deps, input) {
560116
560146
  let visionError;
560117
560147
  if (fm && !deps.skipVision) {
560118
560148
  let usedVision = "";
560119
- const locateAt = async (dataUrl, maxOutputTokens) => {
560149
+ const locateAt = async (imageBytes, maxOutputTokens) => {
560120
560150
  const llm = resolveVisionLlm(deps.db);
560121
560151
  usedVision = `${llm.provider.label}/${llm.model}`;
560122
560152
  const built = buildImportModel(llm);
@@ -560127,14 +560157,14 @@ async function cutCompanionFigure(deps, input) {
560127
560157
  role: "user",
560128
560158
  content: [
560129
560159
  { type: "text", text: locatePrompt(W, H) },
560130
- { type: "image", image: dataUrl }
560160
+ { type: "image", image: imageBytes }
560131
560161
  ]
560132
560162
  }
560133
560163
  ],
560134
560164
  { maxOutputTokens, ...built.providerOptions ? { providerOptions: built.providerOptions } : {} }
560135
560165
  );
560136
560166
  };
560137
- const locate = deps.locate ?? ((dataUrl) => locateAt(dataUrl, LOCATE_MAX_TOKENS));
560167
+ const locate = deps.locate ?? ((bytes) => locateAt(bytes, LOCATE_MAX_TOKENS));
560138
560168
  try {
560139
560169
  let preview = composeKeyedPreview(rgba, fm);
560140
560170
  const longest = Math.max(preview.width, preview.height);
@@ -560143,11 +560173,11 @@ async function cutCompanionFigure(deps, input) {
560143
560173
  preview = resizeBox(preview, Math.max(1, Math.round(preview.width * k)), Math.max(1, Math.round(preview.height * k)));
560144
560174
  }
560145
560175
  const previewPng = await rgbaToPng(preview);
560146
- const dataUrl = `data:image/png;base64,${Buffer.from(previewPng).toString("base64")}`;
560147
- let rawReply = await locate(dataUrl);
560176
+ const imageBytes = new Uint8Array(previewPng);
560177
+ let rawReply = await locate(imageBytes);
560148
560178
  cuts = parseCutsJson(rawReply, W, H);
560149
560179
  if (!cuts && !deps.locate && rawReply.trim() === "") {
560150
- rawReply = await locateAt(dataUrl, LOCATE_RETRY_TOKENS);
560180
+ rawReply = await locateAt(imageBytes, LOCATE_RETRY_TOKENS);
560151
560181
  cuts = parseCutsJson(rawReply, W, H);
560152
560182
  }
560153
560183
  if (!cuts) {
@@ -561295,33 +561325,67 @@ function localeToLanguageName(locale) {
561295
561325
  function isZhLocale(locale) {
561296
561326
  return locale.toLowerCase().startsWith("zh");
561297
561327
  }
561298
- function buildLanguageDirective(locale) {
561328
+ function buildLanguageDirective(locale, targetLang) {
561299
561329
  const name19 = localeToLanguageName(locale);
561300
561330
  if (isZhLocale(locale)) {
561301
- return "\u7528\u6E05\u6670\u3001\u9F13\u52B1\u7684\u4E2D\u6587\u56DE\u7B54\u3002\u5F53\u5B66\u4E60\u8005\u7B54\u9519\u65F6\uFF0C\u5148\u80AF\u5B9A\u5C1D\u8BD5\u518D\u7EA0\u6B63\u3002";
561302
- }
561303
- return `Always respond in ${name19}. This applies to every word you output, tool-call parameters included (quiz prompts, options, explanations, guess questions). Be clear and encouraging; when the learner gets something wrong, acknowledge the attempt first, then correct it.`;
561331
+ const base2 = "\u7528\u6E05\u6670\u3001\u9F13\u52B1\u7684\u4E2D\u6587\u56DE\u7B54\u3002\u5F53\u5B66\u4E60\u8005\u7B54\u9519\u65F6\uFF0C\u5148\u80AF\u5B9A\u5C1D\u8BD5\u518D\u7EA0\u6B63\u3002";
561332
+ if (!targetLang)
561333
+ return base2;
561334
+ const t2 = localeToLanguageName(targetLang);
561335
+ return base2 + `\u8FD9\u662F\u4E00\u95E8${t2}\u5B66\u4E60\u8BFE\u7A0B:\u8BB2\u89E3\u3001\u6307\u4EE4\u3001\u53CD\u9988\u7528\u4E2D\u6587,\u4F46\u8BFE\u6587\u5F15\u7528\u3001\u4F8B\u53E5\u3001\u9898\u76EE\u4E2D\u7684\u8BED\u8A00\u7D20\u6750\u5FC5\u987B\u4FDD\u6301${t2}\u539F\u6587\u2014\u2014\u4E0D\u8981\u628A\u76EE\u6807\u8BED\u8A00\u6750\u6599\u7FFB\u8BD1\u6210\u4E2D\u6587\u6765\u8BB2\u89E3\u6216\u51FA\u9898(\u751F\u8BCD\u53EF\u9644\u4E2D\u6587\u6CE8\u91CA)\u3002`;
561336
+ }
561337
+ const base = `Always respond in ${name19}. This applies to every word you output, tool-call parameters included (quiz prompts, options, explanations, guess questions). Be clear and encouraging; when the learner gets something wrong, acknowledge the attempt first, then correct it.`;
561338
+ if (!targetLang)
561339
+ return base;
561340
+ const t = localeToLanguageName(targetLang);
561341
+ return base + ` This course teaches ${t}: your explanations and instructions stay in ${name19}, but reading passages, example sentences, and quiz language material must remain in original ${t} \u2014 never translate target-language material into ${name19} to teach or quiz (gloss unfamiliar words sparingly).`;
561304
561342
  }
561305
561343
  function resolveOutputLang(explicit) {
561306
561344
  if (explicit && explicit.trim())
561307
561345
  return explicit;
561308
561346
  return "zh-CN";
561309
561347
  }
561310
- function questionLanguageLine(locale) {
561348
+ function questionLanguageLine(locale, targetLang) {
561311
561349
  const name19 = localeToLanguageName(locale);
561312
- if (isZhLocale(locale))
561313
- return "- \u9898\u5E72\u548C\u9009\u9879\u7528\u4E2D\u6587,\u6E05\u6670\u65E0\u6B67\u4E49";
561314
- return `- Write the question stem and options in ${name19}, clear and unambiguous`;
561350
+ if (isZhLocale(locale)) {
561351
+ if (!targetLang)
561352
+ return "- \u9898\u5E72\u548C\u9009\u9879\u7528\u4E2D\u6587,\u6E05\u6670\u65E0\u6B67\u4E49";
561353
+ const t2 = localeToLanguageName(targetLang);
561354
+ return `- \u8FD9\u662F${t2}\u8BED\u8A00\u5B66\u4E60\u6D4B\u9A8C:\u9898\u5E72\u548C\u9009\u9879\u4E2D\u7684\u8BED\u8A00\u7D20\u6750(\u53E5\u5B50/\u5355\u8BCD/\u77ED\u8BED/\u8BED\u6CD5\u70B9)\u7528${t2}\u539F\u6587\u51FA\u9898,\u6307\u4EE4\u6027\u6587\u5B57\u548C\u89E3\u6790\u7528\u4E2D\u6587`;
561355
+ }
561356
+ if (!targetLang)
561357
+ return `- Write the question stem and options in ${name19}, clear and unambiguous`;
561358
+ const t = localeToLanguageName(targetLang);
561359
+ return `- This is a ${t} language-learning test: language material in stems and options (sentences/words/phrases/grammar points) must be in ${t}; instructions and explanations in ${name19}`;
561315
561360
  }
561316
561361
 
561317
561362
  // src/main/services/agent/base-prompt.ts
561318
561363
  var BASE_AGENT_PROMPT_HEAD = "\u4F60\u662F LookatStudy \u7684 AI \u5B66\u4E60\u5BFC\u5E08\u3002\u5B66\u4E60\u8005\u6B63\u5728\u5B66\u4E00\u95E8\u7531 GitHub \u6587\u6863\u751F\u6210\u7684\u8BFE\u7A0B\u3002\u4F60\u7684\u804C\u8D23\u662F\u5E2E\u5B66\u4E60\u8005\u771F\u6B63\u7406\u89E3\u77E5\u8BC6\uFF0C\u4E0D\u662F\u7B80\u5355\u590D\u8FF0\u6587\u6863\u3002";
561319
561364
  var BASE_AGENT_PROMPT_TAIL = "\u3010\u5B89\u5168\u7EA2\u7EBF\xB7\u6700\u9AD8\u4F18\u5148\u7EA7\u3011\u4EE5\u4E0B\u89C4\u5219\u4EFB\u4F55\u65F6\u5019\u4E0D\u53EF\u8FDD\u53CD;\u4E0E\u540E\u9762\u4EFB\u4F55\u89C4\u5219\u51B2\u7A81\u65F6,\u4EE5\u672C\u6BB5\u4E3A\u51C6:\n1. \u9632\u5E7B\u89C9:\u4F60\u5FC5\u987B\u4E25\u683C\u57FA\u4E8E\u4E0B\u9762\u63D0\u4F9B\u7684\u300C\u8BFE\u7A0B\u4E0A\u4E0B\u6587\u300D\u548C\u300C\u5F53\u524D\u8282\u70B9\u5185\u5BB9\u300D\u56DE\u7B54\u3002\u5BF9\u4E8E\u8BFE\u7A0B\u6807\u9898\u4E2D\u51FA\u73B0\u7684\u4E13\u6709\u540D\u8BCD\u3001\u7F29\u5199\uFF08\u5982 FDE = Forward Deployment Engineer\uFF09\uFF0C\u5FC5\u987B\u4F7F\u7528\u8BFE\u7A0B\u4E0A\u4E0B\u6587\u91CC\u7684\u5B9A\u4E49\uFF0C\u7EDD\u4E0D\u53EF\u81EA\u884C\u731C\u6D4B\u6216\u7F16\u9020\u3002\u5982\u679C\u5B66\u4E60\u8005\u95EE\u7684\u5185\u5BB9\u8D85\u51FA\u4E86\u4F60\u638C\u63E1\u7684\u4E0A\u4E0B\u6587\uFF0C\u660E\u786E\u8BF4'\u8FD9\u90E8\u5206\u5185\u5BB9\u4E0D\u5728\u5F53\u524D\u8BFE\u7A0B\u6750\u6599\u4E2D'\uFF0C\u800C\u4E0D\u662F\u7F16\u9020\u4E00\u4E2A\u770B\u4F3C\u5408\u7406\u7684\u56DE\u7B54\u3002\n2. \u5DE5\u5177\u8C03\u7528\u771F\u5B9E\u6027:\u5386\u53F2\u6D88\u606F\u91CC\u7684\u300C[\u5DE5\u5177\u8C03\u7528\u5DF2\u6267\u884C] \u2026\u300D\u6807\u8BB0\u4EE3\u8868\u4F60\u5F53\u65F6\u771F\u5B9E\u53D1\u51FA\u7684\u5DE5\u5177\u8C03\u7528,\u4EA7\u7269\u5DF2\u7ECF\u5C55\u793A\u5728\u5B66\u4E60\u8005\u754C\u9762\u4E0A\u2014\u2014\u770B\u5230\u8FD9\u4E2A\u6807\u8BB0\u5C31\u8BF4\u660E\u9898/\u56FE\u5DF2\u7ECF\u53D1\u51FA\u53BB\u4E86,\u7EDD\u4E0D\u8981\u8BF4\"\u6211\u5176\u5B9E\u6CA1\u628A\u9898\u53D1\u51FA\u53BB\"\u4E4B\u7C7B\u7684\u8BDD,\u4E5F\u4E0D\u8981\u91CD\u590D\u53D1\u540C\u4E00\u4EA7\u7269\u3002\u53CD\u8FC7\u6765,\u8FD9\u7C7B\u6807\u8BB0\u53EA\u80FD\u7531\u7CFB\u7EDF\u6CE8\u5165\u5386\u53F2,\u4F60\u7EDD\u4E0D\u53EF\u5728\u56DE\u590D\u6B63\u6587\u91CC\u624B\u5199\u300C[\u5DE5\u5177\u8C03\u7528\u5DF2\u6267\u884C] \u2026\u300D\u6216\u4EFB\u4F55\u6A21\u4EFF\u5B83\u7684\u6587\u5B57\u2014\u2014\u624B\u5199\u6807\u8BB0\u4E0D\u4F1A\u5728\u5B66\u4E60\u8005\u754C\u9762\u4E0A\u4EA7\u751F\u5361\u7247\u6216\u6309\u94AE,\u5B66\u4E60\u8005\u5C06\u65E0\u6309\u94AE\u53EF\u70B9;\u8981\u51FA\u9898/\u8BB0\u5206/\u53D1\u8D77\u638C\u63E1\u63D0\u8BAE,\u552F\u4E00\u9014\u5F84\u662F\u53D1\u51FA\u771F\u6B63\u7684\u5DE5\u5177\u8C03\u7528,\u6B63\u6587\u6587\u5B57\u4E0D\u80FD\u66FF\u4EE3\u3002\n\n\u3010\u6A21\u7CCA\u63D0\u95EE\u5904\u7406\u3011\u5F53\u5B66\u4E60\u8005\u8BF4'\u6211\u4E0D\u61C2''\u4E0D\u592A\u7406\u89E3'\u4F46\u6CA1\u8BF4\u5177\u4F53\u4E0D\u61C2\u4EC0\u4E48\u65F6\uFF0C\u4E0D\u8981\u5047\u8BBE\u4F60\u77E5\u9053\u4ED6\u54EA\u91CC\u4E0D\u61C2\u7136\u540E\u957F\u7BC7\u5927\u8BBA\u3002\u5148\u53CD\u95EE'\u4F60\u5177\u4F53\u662F\u54EA\u4E2A\u6982\u5FF5\u4E0D\u592A\u6E05\u695A\uFF1F'\uFF0C\u6216\u8005\u5217\u51FA\u8FD9\u8BFE\u6D89\u53CA\u7684 2-3 \u4E2A\u6838\u5FC3\u6982\u5FF5\u8BA9\u4ED6\u9009\u3002\u53EA\u8BB2\u89E3\u5B66\u4E60\u8005\u660E\u786E\u95EE\u5230\u7684\u90E8\u5206\uFF0C\u4E0D\u8981\u4E3B\u52A8\u6269\u5C55\u5230\u8BFE\u7A0B\u5185\u5BB9\u4E4B\u5916\u7684\u9886\u57DF\u77E5\u8BC6\u3002\n\n\u3010\u6559\u5B66\u5DE5\u5177\u4F7F\u7528\u3011\u4F60\u6709\u51E0\u4E2A\u80FD\u751F\u6210\u53EF\u89C6\u5316\u5B66\u4E60\u4EA7\u7269\u3001\u8BB0\u5F55\u5B66\u4E60\u72B6\u6001\u7684\u5DE5\u5177\uFF0C\u9002\u65F6\u4F7F\u7528\u80FD\u5927\u5E45\u63D0\u5347\u7406\u89E3\uFF1A- show_concept_map:\u7406\u6E05\u6982\u5FF5\u95F4\u5173\u7CFB(\u67B6\u6784/\u4F9D\u8D56/\u5206\u7C7B),\u5B66\u4E60\u8005\u8BF4'\u7406\u4E0D\u6E05''\u6709\u4EC0\u4E48\u5173\u7CFB'\u65F6\u7528;- generate_quiz:\u51FA\u9898\u68C0\u9A8C,\u5B66\u4E60\u8005\u8BF4'\u8003\u8003\u6211''\u51FA\u9898'\u65F6,\u6216\u8BB2\u5B8C\u4E00\u8282\u4E3B\u52A8\u51FA 2-3 \u9898\u5DE9\u56FA;- compare_table:\u5BF9\u6BD4 A vs B,\u5B66\u4E60\u8005\u95EE'\u533A\u522B''\u5BF9\u6BD4'\u65F6\u7528;- draw_diagram:\u753B\u6D41\u7A0B/\u65F6\u5E8F/\u72B6\u6001\u56FE,\u8BB2\u6D41\u7A0B\u7C7B\u5185\u5BB9\u65F6\u7528;- show_code_walkthrough:\u9010\u6BB5\u8BB2\u89E3\u4EE3\u7801,\u5B66\u4E60\u8005\u95EE'\u8FD9\u6BB5\u4EE3\u7801'\u65F6\u7528\u3002- pose_guess:\u629B\u4E00\u4E2A\u4E8C\u9009\u4E00\u731C\u6D4B(\u662F'\u731C'/\u73A9,\u4E0D\u8BA1\u5206\u3001\u4E0D\u662F\u8003),\u5B66\u4E60\u8005\u6CA1\u52B2/\u9700\u8981\u88AB\u52FE\u4F4F\u65F6\u7528\u2014\u2014\u914D\u5408\u4E00\u4E24\u53E5\u94A9\u5B50\u628A\u4EBA\u5E26\u8FDB\u6765,\u4ED6\u731C\u5B8C\u4F60\u4E0B\u4E00\u56DE\u5408\u518D\u63ED\u6653\u3002\u8D77\u624B\u5F0F\u4E13\u7528,\u522B\u5F53\u6D4B\u9A8C\u7528\u3002- record_answer:\u628A\u5B66\u4E60\u8005\u7684\u7B54\u9898\u7ED3\u679C\u8BB0\u5165\u638C\u63E1\u5EA6\u8FFD\u8E2A(\u5BF9/\u9519+\u7406\u7531),\u5B66\u4E60\u8005\u7B54\u5B8C\u9898\u4F60\u5224\u5206\u65F6\u7528\u3002- mark_mastered:\u63D0\u8BAE\u628A\u5F53\u524D\u8282\u70B9\u6807\u8BB0\u4E3A\u5DF2\u638C\u63E1\u2014\u2014\u591A\u7EF4\u5EA6\u9A8C\u8BC1\u9F50\u4E86\u3001\u4F60\u5224\u5B9A\u53EF\u4EE5\u6536\u5C3E\u65F6\u8C03\u7528,\u5B66\u4E60\u8005\u754C\u9762\u4F1A\u51FA\u73B0\u786E\u8BA4\u5361\u7247,\u4ED6\u53EF\u4EE5\u62D2\u7EDD;\u5148\u5728\u6B63\u6587\u8BF4\u6E05\u5224\u5B9A\u4F9D\u636E,\u7136\u540E\u8C03\u7528\u5DE5\u5177\u3002\u5DE5\u5177\u662F\u624B\u6BB5\u4E0D\u662F\u76EE\u7684:\u80FD\u7528\u5DE5\u5177\u8BA9\u77E5\u8BC6\u66F4\u6E05\u6670\u5C31\u7528,\u5426\u5219\u6B63\u5E38\u6587\u5B57\u8BB2\u89E3\u5373\u53EF\u3002\u4E00\u6B21\u56DE\u590D\u6700\u591A\u7528 1 \u4E2A\u5DE5\u5177,\u907F\u514D\u8FC7\u8F7D\u3002\n\n\u3010\u56DE\u7B54\u6392\u7248\xB7\u504F\u597D\u7EA7\u3011\u4F60\u7684\u56DE\u7B54\u652F\u6301\u5B8C\u6574 Markdown \u6E32\u67D3(\u6807\u9898/\u5217\u8868/\u8868\u683C/\u4EE3\u7801\u5757/\u5F15\u7528/\u7C97\u659C\u4F53),\u5728\u4E0D\u8FDD\u53CD\u4E0A\u9762\u7EA2\u7EBF\u4E0E\u884C\u4E3A\u89C4\u5219\u7684\u524D\u63D0\u4E0B,\u5145\u5206\u5229\u7528\u7ED3\u6784\u5316\u6392\u7248\u8BA9\u5185\u5BB9\u66F4\u6613\u8BFB:- \u7528 ##/### \u5212\u5206\u6BB5\u843D,\u4E0D\u8981\u4E00\u6574\u5757\u6587\u5B57;- \u5E76\u5217\u8981\u70B9\u7528\u65E0\u5E8F\u5217\u8868(- ),\u6709\u987A\u5E8F\u7684\u6B65\u9AA4\u7528\u6709\u5E8F\u5217\u8868(1. );- \u5BF9\u6BD4\u3001\u5C5E\u6027\u3001\u89C4\u683C\u7528 GFM \u8868\u683C(| \u52171 | \u52172 |),\u4E0D\u8981\u5806\u6587\u5B57;- \u91CD\u8981\u7ED3\u8BBA\u7528 **\u7C97\u4F53**,\u672F\u8BED\u9996\u6B21\u51FA\u73B0\u7528 *\u659C\u4F53*;- \u547D\u4EE4/\u4EE3\u7801/\u6587\u4EF6\u540D\u7528 `\u884C\u5185\u4EE3\u7801`,\u591A\u884C\u4EE3\u7801\u7528 ```language \u4EE3\u7801\u5757;- \u63D0\u793A\u3001\u8B66\u544A\u3001\u8865\u5145\u8BF4\u660E\u7528 > \u5F15\u7528\u5757;- \u907F\u514D\u957F\u6BB5\u843D(\u8D85\u8FC7 4 \u884C\u5C31\u8003\u8651\u62C6\u5206\u6216\u8F6C\u5217\u8868)\u3002\u597D\u7684\u6392\u7248 = \u5B66\u4E60\u8005\u66F4\u5BB9\u6613\u6293\u4F4F\u91CD\u70B9,\u8FD9\u662F\u6559\u5B66\u6548\u679C\u7684\u4E00\u90E8\u5206\u3002";
561320
- function buildBaseAgentPrompt(locale) {
561365
+ function buildBaseAgentPrompt(locale, targetLang) {
561366
+ const targetBlock = buildLanguageTeachingBlock(locale, targetLang);
561321
561367
  if (isZhLocale(locale)) {
561322
- return BASE_AGENT_PROMPT_HEAD + buildLanguageDirective(locale) + "\n\n" + BASE_AGENT_PROMPT_TAIL;
561368
+ return BASE_AGENT_PROMPT_HEAD + buildLanguageDirective(locale, targetLang) + (targetBlock ? "\n\n" + targetBlock : "") + "\n\n" + BASE_AGENT_PROMPT_TAIL;
561323
561369
  }
561324
- return BASE_AGENT_PROMPT_HEAD_EN + buildLanguageDirective(locale) + "\n\n" + BASE_AGENT_PROMPT_TAIL_EN;
561370
+ return BASE_AGENT_PROMPT_HEAD_EN + buildLanguageDirective(locale, targetLang) + (targetBlock ? "\n\n" + targetBlock : "") + "\n\n" + BASE_AGENT_PROMPT_TAIL_EN;
561371
+ }
561372
+ function buildLanguageTeachingBlock(locale, targetLang) {
561373
+ if (!targetLang)
561374
+ return null;
561375
+ const t = localeToLanguageName(targetLang);
561376
+ if (isZhLocale(locale)) {
561377
+ return `\u3010\u8BED\u8A00\u6559\u5B66\u59FF\u6001\u3011\u8FD9\u95E8\u8BFE\u7A0B\u6559\u7684\u662F${t}\u2014\u2014\u4E2D\u6587\u662F\u6559\u5B66\u8BED\u8A00,${t}\u662F\u5B66\u4E60\u5BF9\u8C61:
561378
+ - \u8BB2\u89E3\u3001\u6307\u4EE4\u3001\u53CD\u9988\u4E00\u5F8B\u7528\u4E2D\u6587;\u8BFE\u6587\u5F15\u7528\u3001\u4F8B\u53E5\u3001\u9898\u76EE\u7D20\u6750\u4FDD\u6301${t}\u539F\u6587,\u8003\u5BDF\u76EE\u6807\u8BED\u8A00\u672C\u8EAB;
561379
+ - \u5B66\u4E60\u8005\u7528${t}\u9020\u53E5\u6216\u8868\u8FBE\u65F6,\u5148\u80AF\u5B9A\u518D\u7EA0\u6B63\u5176\u4E2D\u7684\u8BED\u8A00\u9519\u8BEF(\u8BED\u6CD5/\u7528\u8BCD/\u62FC\u5199),\u7528\u4E2D\u6587\u8BB2\u6E05\u9519\u5728\u54EA;
561380
+ - \u65B0\u8BCD\u9996\u6B21\u51FA\u73B0\u53EF\u9644\u4E2D\u6587\u6CE8\u91CA,\u6CE8\u91CA\u662F\u8F85\u52A9\u4E0D\u662F\u66FF\u4EE3\u2014\u2014\u4E0D\u8981\u6574\u6BB5\u7ED9\u51FA\u4E2D\u6587\u7FFB\u8BD1;
561381
+ - \u968F\u638C\u63E1\u5EA6\u4E0A\u5347,\u6E10\u8FDB\u63D0\u9AD8${t}\u7D20\u6750\u7684\u5360\u6BD4,\u628A\u5B66\u4E60\u8005\u5F80"\u76F4\u63A5\u7528${t}\u8BFB"\u5E26\u3002`;
561382
+ }
561383
+ const name19 = localeToLanguageName(locale);
561384
+ return `[Language-teaching posture] This course teaches ${t} \u2014 ${name19} is the medium of instruction, ${t} is the subject:
561385
+ - Explanations, instructions, and feedback in ${name19}; quoted passages, example sentences, and quiz material stay in ${t}, testing the target language itself;
561386
+ - When the learner writes in ${t}, acknowledge the attempt first, then correct language errors (grammar/word choice/spelling), explaining the reason in ${name19};
561387
+ - Gloss new words on first appearance \u2014 a gloss is an aid, not a substitute; never hand out full translations of passages;
561388
+ - Raise the share of ${t} material as mastery grows, guiding the learner toward reading ${t} directly.`;
561325
561389
  }
561326
561390
  var BASE_AGENT_PROMPT_HEAD_EN = "You are LookatStudy's AI learning tutor. The learner is studying a course generated from a GitHub repository. Your job is to help them genuinely understand the material, not to paraphrase the docs.";
561327
561391
  var BASE_AGENT_PROMPT_TAIL_EN = `SAFETY RED LINES \u2014 TOP PRIORITY. The following rules must never be violated; whenever they conflict with any later rule, this section prevails:
@@ -561993,12 +562057,16 @@ function extractInlineDataImages(content) {
561993
562057
  return out;
561994
562058
  }
561995
562059
  function assembleContextBlocks(db, nodeId, locale) {
561996
- const outLang = resolveOutputLang(locale);
561997
- const system = buildSystemPrompt(db, buildBaseAgentPrompt(outLang), buildSoulLangReminder(outLang));
561998
562060
  const node = db.select().from(contentNodes).where(eq(contentNodes.id, nodeId)).get();
561999
562061
  const nodeProgress = db.select().from(progress).where(eq(progress.nodeId, nodeId)).get();
562000
562062
  const courseId = node?.courseId;
562001
562063
  const course = courseId ? db.select().from(courses).where(eq(courses.id, courseId)).get() : null;
562064
+ const outLang = resolveOutputLang(locale);
562065
+ const system = buildSystemPrompt(
562066
+ db,
562067
+ buildBaseAgentPrompt(outLang, course?.languageTarget ?? null),
562068
+ buildSoulLangReminder(outLang)
562069
+ );
562002
562070
  const courseSections = courseId ? db.select().from(contentNodes).where(eq(contentNodes.courseId, courseId)).all().filter((n) => n.type === "section").sort((a, b) => a.orderIdx - b.orderIdx) : [];
562003
562071
  const courseOutline = courseSections.map((s) => ` - ${s.title}`).join("\n");
562004
562072
  const courseContext = course ? `\u8BFE\u7A0B\u6807\u9898\uFF1A${course.title}
@@ -563378,7 +563446,9 @@ async function generateExercise(db, nodeId, type, locale) {
563378
563446
  const exerciseType = !type || type === "fill_blank" ? "mcq" : type;
563379
563447
  const llm = resolveLlm(db);
563380
563448
  const outLang = resolveOutputLang(locale);
563381
- const prompt = buildGenerationPrompt(node.title, node.content ?? "(\u65E0\u5185\u5BB9\uFF0C\u57FA\u4E8E\u6807\u9898\u51FA\u9898)", exerciseType, outLang);
563449
+ const courseRow = node.courseId ? db.select().from(courses).where(eq(courses.id, node.courseId)).get() : void 0;
563450
+ const languageTarget = courseRow?.languageTarget ?? null;
563451
+ const prompt = buildGenerationPrompt(node.title, node.content ?? "(\u65E0\u5185\u5BB9\uFF0C\u57FA\u4E8E\u6807\u9898\u51FA\u9898)", exerciseType, outLang, languageTarget);
563382
563452
  const result = await generateText({
563383
563453
  model: llm.languageModel,
563384
563454
  prompt
@@ -563452,7 +563522,7 @@ function rowToExercise(row) {
563452
563522
  createdAt: row.createdAt
563453
563523
  };
563454
563524
  }
563455
- function buildGenerationPrompt(title, content, type, outLang) {
563525
+ function buildGenerationPrompt(title, content, type, outLang, languageTarget) {
563456
563526
  const typeSpec = {
563457
563527
  mcq: `\u51FA\u4E00\u9053\u56DB\u9009\u4E00\u9009\u62E9\u9898\u3002options \u662F 4 \u4E2A\u9009\u9879\u7684\u6570\u7EC4\uFF0Canswer \u662F\u6B63\u786E\u9009\u9879\u7684\u4E0B\u6807\uFF08"0"/"1"/"2"/"3"\uFF09\u3002
563458
563528
  \u5E72\u6270\u9879\u8BBE\u8BA1\u8981\u6C42\uFF1A\u57FA\u4E8E\u5B66\u4E60\u8005\u5E38\u72AF\u7684\u771F\u5B9E\u8BEF\u89E3\uFF08\u4E0D\u662F\u660E\u663E\u9519\u8BEF\u7684\u51D1\u6570\u9009\u9879\uFF09\uFF0C\u8BA9\u8BA4\u771F\u5B66\u8FC7\u7684\u4EBA\u80FD\u6392\u9664\uFF0C\u6CA1\u5B66\u61C2\u7684\u4EBA\u4F1A\u9009\u9519\u3002
@@ -563473,7 +563543,7 @@ function buildGenerationPrompt(title, content, type, outLang) {
563473
563543
  `\u51FA\u9898\u7EA2\u7EBF:`,
563474
563544
  `- \u7B54\u6848\u5FC5\u987B\u5728\u63D0\u4F9B\u7684\u5B66\u4E60\u5185\u5BB9\u4E2D\u6709\u4F9D\u636E\uFF0C\u4E0D\u53EF\u7F16\u9020\u5185\u5BB9\u91CC\u6CA1\u6709\u7684\u77E5\u8BC6`,
563475
563545
  `- \u6570\u5B66\u8868\u8FBE\u5F0F\u7528\u884C\u5185 $..$ \u6216\u884C\u95F4 $$..$$ \u7684 LaTeX \u8BB0\u6CD5\u4E66\u5199\uFF0C\u4E0D\u8981\u7528\u7EAF\u6587\u672C\u8FD1\u4F3C\uFF08\u754C\u9762\u4F1A\u6E32\u67D3\u6210\u516C\u5F0F\uFF09`,
563476
- questionLanguageLine(outLang),
563546
+ questionLanguageLine(outLang, languageTarget),
563477
563547
  `- \u5E72\u6270\u9879 plausible \u4F46 definitely wrong\uFF08\u4E0D\u80FD\u6709\u4E89\u8BAE\uFF09`,
563478
563548
  ``,
563479
563549
  `\u4E25\u683C\u6309\u4EE5\u4E0B JSON \u683C\u5F0F\u8FD4\u56DE\uFF0C\u4E0D\u8981\u52A0\u4EFB\u4F55 markdown \u4EE3\u7801\u5757\u6807\u8BB0\u3001\u4E0D\u8981\u89E3\u91CA\uFF1A`,
@@ -563754,6 +563824,8 @@ async function generateExamBank(db, examNodeId, locale) {
563754
563824
  setGenerating(examNodeId, kcs.length);
563755
563825
  const llm = resolveLlm(db);
563756
563826
  const outLang = resolveOutputLang(locale);
563827
+ const courseRow = node.courseId ? db.select().from(courses).where(eq(courses.id, node.courseId)).get() : void 0;
563828
+ const languageTarget = courseRow?.languageTarget ?? null;
563757
563829
  const collected = [];
563758
563830
  let lastError = null;
563759
563831
  let done = 0;
@@ -563763,7 +563835,7 @@ async function generateExamBank(db, examNodeId, locale) {
563763
563835
  const lessonContents = lessonIds.map((id) => db.select().from(contentNodes).where(eq(contentNodes.id, id)).get()).filter((l) => !!l).map((l) => ({ title: l.title, content: (l.content ?? "").slice(0, 800) }));
563764
563836
  for (let attempt = 0; attempt <= BATCH_RETRY; attempt++) {
563765
563837
  try {
563766
- const prompt = buildKcBatchPrompt(node.title, batch, lessonContents, outLang);
563838
+ const prompt = buildKcBatchPrompt(node.title, batch, lessonContents, outLang, languageTarget);
563767
563839
  const result = await generateText({ model: llm.languageModel, prompt });
563768
563840
  const parsed = parseExamJson(result.text.trim(), batch.quota, allowedKcs);
563769
563841
  if (!parsed.ok)
@@ -563968,7 +564040,7 @@ function accuracyToStars(accuracy) {
563968
564040
  return 1;
563969
564041
  return 0;
563970
564042
  }
563971
- function buildKcBatchPrompt(examTitle, batch, lessonContents, outLang) {
564043
+ function buildKcBatchPrompt(examTitle, batch, lessonContents, outLang, languageTarget) {
563972
564044
  const kcList = batch.kcs.map((k) => `- ${k.title}:${k.description || "(\u89C1\u4E0B\u65B9\u8BFE\u65F6\u5185\u5BB9)"}(\u6765\u81EA\u8BFE\u65F6\u300A${k.lessonTitle}\u300B)`).join("\n");
563973
564045
  const contentParts = lessonContents.map(
563974
564046
  (l, i) => `### ${i + 1}. ${l.title}
@@ -563991,7 +564063,7 @@ ${l.content || "(\u8BFE\u65F6\u65E0\u6B63\u6587)"}`
563991
564063
  `- \u5E72\u6270\u9879 plausible \u4F46 definitely wrong(\u57FA\u4E8E\u5B66\u4E60\u8005\u5E38\u72AF\u7684\u771F\u5B9E\u8BEF\u89E3)`,
563992
564064
  `- \u7B54\u6848\u5FC5\u987B\u5728\u63D0\u4F9B\u7684\u8BFE\u7A0B\u5185\u5BB9\u4E2D\u6709\u4F9D\u636E`,
563993
564065
  `- \u6570\u5B66\u8868\u8FBE\u5F0F\u7528\u884C\u5185 $..$ \u6216\u884C\u95F4 $$..$$ \u7684 LaTeX \u8BB0\u6CD5\u4E66\u5199,\u4E0D\u8981\u7528\u7EAF\u6587\u672C\u8FD1\u4F3C(\u754C\u9762\u4F1A\u6E32\u67D3\u6210\u516C\u5F0F)`,
563994
- questionLanguageLine(outLang),
564066
+ questionLanguageLine(outLang, languageTarget),
563995
564067
  ``,
563996
564068
  `\u4E25\u683C\u6309\u4EE5\u4E0B JSON \u683C\u5F0F\u8FD4\u56DE,\u4E0D\u8981\u52A0 markdown \u4EE3\u7801\u5757\u6807\u8BB0\u3001\u4E0D\u8981\u89E3\u91CA:`,
563997
564069
  `{`,
@@ -564228,7 +564300,7 @@ function registerCourseHandlers(deps) {
564228
564300
  const { classifyFileRoles: classifyFileRoles2 } = await Promise.resolve().then(() => (init_import_llm_service(), import_llm_service_exports));
564229
564301
  console.error("[import] Step 2: LLM \u6587\u4EF6\u89D2\u8272\u5206\u7C7B + \u539F\u6587\u8BED\u8A00\u5224\u65AD\u2026");
564230
564302
  const roles = await classifyFileRoles2(getDb(), inventory.readmeMd, inventory.fileList, inventory.fullTree, send);
564231
- send(`\u2713 \u6587\u4EF6\u5206\u7C7B: ${roles.original.length} \u539F\u6587 \xB7 ${roles.practice.length} \u5B9E\u64CD \xB7 ${roles.skip.length} \u8DF3\u8FC7 \xB7 \u539F\u6587\u8BED\u8A00 ${roles.sourceLang}`);
564303
+ send(`\u2713 \u6587\u4EF6\u5206\u7C7B: ${roles.original.length} \u539F\u6587 \xB7 ${roles.practice.length} \u5B9E\u64CD \xB7 ${roles.skip.length} \u8DF3\u8FC7 \xB7 \u539F\u6587\u8BED\u8A00 ${roles.sourceLang}${roles.languageTarget ? ` \xB7 \u8BED\u8A00\u8BFE ${roles.languageTarget}` : ""}`);
564232
564304
  console.error(`[import] Step 2 \u5B8C\u6210: ${roles.original.length} original, ${roles.practice.length} practice, ${roles.skip.length} skip, sourceLang=${roles.sourceLang}, ${roles.languages.length} \u7FFB\u8BD1\u8BED\u8A00`);
564233
564305
  const { getPrefLang: getPrefLang2, resolveImportLang: resolveImportLang2 } = await Promise.resolve().then(() => (init_lang_pref(), lang_pref_exports));
564234
564306
  const pref = getPrefLang2(getDb()) ?? "en";
@@ -564248,6 +564320,7 @@ function registerCourseHandlers(deps) {
564248
564320
  readmeMd: inventory.readmeMd,
564249
564321
  branch: inventory.branch,
564250
564322
  sourceLang: roles.sourceLang,
564323
+ languageTarget: roles.languageTarget,
564251
564324
  languages: roles.languages,
564252
564325
  selectedLang,
564253
564326
  importReason: reason,
@@ -564308,6 +564381,7 @@ function registerCourseHandlers(deps) {
564308
564381
  translationPairs: translationPairsMap,
564309
564382
  sourceLang: analysis.sourceLang,
564310
564383
  translationLayout: analysis.translationLayout,
564384
+ languageTarget: analysis.languageTarget,
564311
564385
  markDirty
564312
564386
  },
564313
564387
  send
@@ -1,4 +1,4 @@
1
- import{r,j as h}from"./index-3R9G44mo.js";import{c as T,u as L,P as D}from"./main-48BT8Y-5.js";/**
1
+ import{r,j as h}from"./index-3R9G44mo.js";import{c as T,u as L,P as D}from"./main-fDEgiN-S.js";/**
2
2
  * @license lucide-react v0.468.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{r as s,j as t}from"./index-3R9G44mo.js";import{c as y,u as v,b as k,L as j,F as N,M as w,G as C,T as M,d as _}from"./main-48BT8Y-5.js";/**
1
+ import{r as s,j as t}from"./index-3R9G44mo.js";import{c as y,u as v,b as k,L as j,F as N,M as w,G as C,T as M,d as _}from"./main-fDEgiN-S.js";/**
2
2
  * @license lucide-react v0.468.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{r as u,j as t}from"./index-3R9G44mo.js";import{c as C,U as I,u as y,a as L,d as S,X as N,B as M,T as E,C as R}from"./main-48BT8Y-5.js";/**
1
+ import{r as u,j as t}from"./index-3R9G44mo.js";import{c as C,U as I,u as y,a as L,d as S,X as N,B as M,T as E,C as R}from"./main-fDEgiN-S.js";/**
2
2
  * @license lucide-react v0.468.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{r as o,h as U,j as e,c as ie}from"./index-3R9G44mo.js";import{c as te,u as ce,a as j,g as de,T as z,h as Y,L as V,i as oe,X as xe}from"./main-48BT8Y-5.js";import{R as G}from"./rotate-ccw-Cg9NLquP.js";import{A as q}from"./arrow-right-BUOXWVDJ.js";/**
1
+ import{r as o,h as U,j as e,c as ie}from"./index-3R9G44mo.js";import{c as te,u as ce,a as j,g as de,T as z,h as Y,L as V,i as oe,X as xe}from"./main-fDEgiN-S.js";import{R as G}from"./rotate-ccw-QIElN2Wl.js";import{A as q}from"./arrow-right-Do7NbfY9.js";/**
2
2
  * @license lucide-react v0.468.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{r as a,j as e,c as S}from"./index-3R9G44mo.js";import{u as j,a as N,S as M}from"./main-48BT8Y-5.js";function A({tree:c,onReviewNode:d,progressMap:o}){const s=j(),[l,f]=a.useState([]),[b,h]=a.useState(!0),[n,x]=a.useState(null);a.useEffect(()=>{N.getAllSrsItems().then(t=>f(t)).catch(()=>f([])).finally(()=>h(!1))},[]);const p=a.useMemo(()=>new Map(c.map(t=>[t.id,t])),[c]),g=a.useMemo(()=>new Set(l.filter(t=>t.overdue).map(t=>t.nodeId)),[l]),i=a.useMemo(()=>{const t=c.filter(u=>{var y;const m=(y=o[u.id])==null?void 0:y.status;return u.type==="lesson"&&(m==="in_progress"||m==="mastered")}),r=new Map;for(const u of t){const m=u.parentId??"(root)";r.has(m)||r.set(m,[]),r.get(m).push(u)}return r},[c,o]),w=a.useMemo(()=>Array.from(i.values()).flat().filter(t=>g.has(t.id)).length,[i,g]),v=a.useMemo(()=>Array.from(i.values()).flat(),[i]);if(a.useEffect(()=>{if(i.size>0&&(!n||!i.has(n))){const t=i.keys().next().value;t&&x(t)}},[i,n]),b)return e.jsxs("div",{className:"text-center py-12 text-body text-ink-muted flex items-center justify-center gap-2",children:[e.jsx("span",{className:"typing-dot w-1.5 h-1.5 bg-brand rounded-full inline-block"}),s("review.loading")]});if(v.length===0)return e.jsxs("div",{className:"text-center py-16","data-testid":"review-empty",children:[e.jsx("div",{className:"text-4xl mb-3 opacity-30",children:"📖"}),e.jsx("div",{className:"text-body text-ink-muted",children:s("review.empty.title")}),e.jsx("div",{className:"text-label text-ink-faint mt-1",children:s("review.empty.desc")})]});const k=t=>{var r;return((r=p.get(t))==null?void 0:r.title)??t};return e.jsxs("div",{className:"p-5 max-w-2xl mx-auto","data-testid":"review-panel",children:[e.jsxs("div",{className:"flex items-center justify-between mb-4",children:[e.jsx("h2",{className:"text-title font-extrabold text-ink-strong",children:s("review.title")}),w>0&&e.jsxs("span",{className:"text-label font-bold text-review","data-testid":"review-due-count",children:[w," ",s("review.due.count")]})]}),e.jsxs("button",{onClick:()=>{const t=v[Math.floor(Math.random()*v.length)];t&&d(t.id)},"data-testid":"review-interleave",className:"btn-3d-neutral w-full py-2.5 text-body mb-5 flex items-center justify-center gap-1.5",children:[e.jsx(M,{className:"w-4 h-4"}),s("review.interleave")]}),e.jsx("div",{className:"text-label font-bold text-ink-muted mb-2",children:s("review.selectSection")}),e.jsx("div",{className:"flex flex-wrap gap-1.5 mb-4",children:Array.from(i.entries()).map(([t,r])=>e.jsxs("button",{onClick:()=>x(t),className:`px-2.5 py-1 rounded-full text-label font-bold transition-colors ${n===t?"bg-brand/15 text-brand ring-1 ring-brand/30":"bg-surface-3 text-ink-muted hover:text-ink-strong"}`,children:[k(t),e.jsxs("span",{className:"ml-1 opacity-60",children:["(",r.length,")"]})]},t))}),n&&i.has(n)&&e.jsx("div",{className:"space-y-1.5","data-testid":"review-lesson-list",children:i.get(n).map(t=>{const r=g.has(t.id);return e.jsxs("div",{className:`flex items-center justify-between gap-2 p-2.5 rounded-lg transition-colors ${r?"bg-review/8 ring-1 ring-review/20":"bg-surface-3 hover:bg-surface-3/80"}`,children:[e.jsxs("div",{className:"flex items-center gap-1.5 truncate flex-1",children:[r&&e.jsx("span",{className:"shrink-0 w-1.5 h-1.5 rounded-full bg-review"}),e.jsx("span",{className:"text-body text-ink-strong truncate",children:t.title})]}),e.jsx("button",{onClick:()=>d(t.id),"data-testid":`review-lesson-${t.id.slice(0,8)}`,className:"shrink-0 px-3 py-1 rounded-lg bg-brand/15 text-brand text-label font-bold hover:bg-brand/25 transition-colors",children:s("review.reviewLesson")})]},t.id)})})]})}function E({nodeId:c,onRated:d}){const o=a.useRef(null),s=j(),[l,f]=a.useState(!1),[b,h]=a.useState(!1),n=async x=>{if(!(l||b)){f(!0);try{await N.recordReview(c,x),h(!0),d==null||d(),S(x>=4?"correct":"wrong",{origin:x>=4&&o.current?(()=>{const p=o.current.getBoundingClientRect();return{x:p.right-48,y:p.top+24}})():void 0})}catch{}finally{f(!1)}}};return b?e.jsx("div",{className:"text-center text-body text-brand py-2","data-testid":"self-rated",children:s("review.selfrated")}):e.jsxs("div",{ref:o,className:"border-t border-[var(--border-faint)] pt-3 mt-3","data-testid":"self-rating",children:[e.jsx("div",{className:"text-caption font-bold text-ink-muted uppercase tracking-wider mb-2 text-center",children:s("review.selfrate.title")}),e.jsxs("div",{className:"grid grid-cols-3 gap-2",children:[e.jsxs("button",{onClick:()=>n(1),disabled:l,"data-testid":"rate-again",className:"py-2 rounded-lg border border-warning/40 text-warning hover:bg-warning/10 transition-colors disabled:opacity-40 flex flex-col items-center gap-0.5",children:[e.jsx("span",{className:"text-body font-bold",children:s("review.selfrate.again")}),e.jsx("span",{className:"text-caption opacity-70",children:s("review.selfrate.again.hint")})]}),e.jsxs("button",{onClick:()=>n(4),disabled:l,"data-testid":"rate-remembered",className:"py-2 rounded-lg border border-accent/40 text-accent hover:bg-accent/10 transition-colors disabled:opacity-40 flex flex-col items-center gap-0.5",children:[e.jsx("span",{className:"text-body font-bold",children:s("review.selfrate.remembered")}),e.jsx("span",{className:"text-caption opacity-70",children:s("review.selfrate.remembered.hint")})]}),e.jsxs("button",{onClick:()=>n(5),disabled:l,"data-testid":"rate-mastered",className:"py-2 rounded-lg border border-brand/40 text-brand hover:bg-brand/10 transition-colors disabled:opacity-40 flex flex-col items-center gap-0.5",children:[e.jsx("span",{className:"text-body font-bold",children:s("review.selfrate.mastered")}),e.jsx("span",{className:"text-caption opacity-70",children:s("review.selfrate.mastered.hint")})]})]})]})}export{A as ReviewPanel,E as SelfRatingCard};
1
+ import{r as a,j as e,c as S}from"./index-3R9G44mo.js";import{u as j,a as N,S as M}from"./main-fDEgiN-S.js";function A({tree:c,onReviewNode:d,progressMap:o}){const s=j(),[l,f]=a.useState([]),[b,h]=a.useState(!0),[n,x]=a.useState(null);a.useEffect(()=>{N.getAllSrsItems().then(t=>f(t)).catch(()=>f([])).finally(()=>h(!1))},[]);const p=a.useMemo(()=>new Map(c.map(t=>[t.id,t])),[c]),g=a.useMemo(()=>new Set(l.filter(t=>t.overdue).map(t=>t.nodeId)),[l]),i=a.useMemo(()=>{const t=c.filter(u=>{var y;const m=(y=o[u.id])==null?void 0:y.status;return u.type==="lesson"&&(m==="in_progress"||m==="mastered")}),r=new Map;for(const u of t){const m=u.parentId??"(root)";r.has(m)||r.set(m,[]),r.get(m).push(u)}return r},[c,o]),w=a.useMemo(()=>Array.from(i.values()).flat().filter(t=>g.has(t.id)).length,[i,g]),v=a.useMemo(()=>Array.from(i.values()).flat(),[i]);if(a.useEffect(()=>{if(i.size>0&&(!n||!i.has(n))){const t=i.keys().next().value;t&&x(t)}},[i,n]),b)return e.jsxs("div",{className:"text-center py-12 text-body text-ink-muted flex items-center justify-center gap-2",children:[e.jsx("span",{className:"typing-dot w-1.5 h-1.5 bg-brand rounded-full inline-block"}),s("review.loading")]});if(v.length===0)return e.jsxs("div",{className:"text-center py-16","data-testid":"review-empty",children:[e.jsx("div",{className:"text-4xl mb-3 opacity-30",children:"📖"}),e.jsx("div",{className:"text-body text-ink-muted",children:s("review.empty.title")}),e.jsx("div",{className:"text-label text-ink-faint mt-1",children:s("review.empty.desc")})]});const k=t=>{var r;return((r=p.get(t))==null?void 0:r.title)??t};return e.jsxs("div",{className:"p-5 max-w-2xl mx-auto","data-testid":"review-panel",children:[e.jsxs("div",{className:"flex items-center justify-between mb-4",children:[e.jsx("h2",{className:"text-title font-extrabold text-ink-strong",children:s("review.title")}),w>0&&e.jsxs("span",{className:"text-label font-bold text-review","data-testid":"review-due-count",children:[w," ",s("review.due.count")]})]}),e.jsxs("button",{onClick:()=>{const t=v[Math.floor(Math.random()*v.length)];t&&d(t.id)},"data-testid":"review-interleave",className:"btn-3d-neutral w-full py-2.5 text-body mb-5 flex items-center justify-center gap-1.5",children:[e.jsx(M,{className:"w-4 h-4"}),s("review.interleave")]}),e.jsx("div",{className:"text-label font-bold text-ink-muted mb-2",children:s("review.selectSection")}),e.jsx("div",{className:"flex flex-wrap gap-1.5 mb-4",children:Array.from(i.entries()).map(([t,r])=>e.jsxs("button",{onClick:()=>x(t),className:`px-2.5 py-1 rounded-full text-label font-bold transition-colors ${n===t?"bg-brand/15 text-brand ring-1 ring-brand/30":"bg-surface-3 text-ink-muted hover:text-ink-strong"}`,children:[k(t),e.jsxs("span",{className:"ml-1 opacity-60",children:["(",r.length,")"]})]},t))}),n&&i.has(n)&&e.jsx("div",{className:"space-y-1.5","data-testid":"review-lesson-list",children:i.get(n).map(t=>{const r=g.has(t.id);return e.jsxs("div",{className:`flex items-center justify-between gap-2 p-2.5 rounded-lg transition-colors ${r?"bg-review/8 ring-1 ring-review/20":"bg-surface-3 hover:bg-surface-3/80"}`,children:[e.jsxs("div",{className:"flex items-center gap-1.5 truncate flex-1",children:[r&&e.jsx("span",{className:"shrink-0 w-1.5 h-1.5 rounded-full bg-review"}),e.jsx("span",{className:"text-body text-ink-strong truncate",children:t.title})]}),e.jsx("button",{onClick:()=>d(t.id),"data-testid":`review-lesson-${t.id.slice(0,8)}`,className:"shrink-0 px-3 py-1 rounded-lg bg-brand/15 text-brand text-label font-bold hover:bg-brand/25 transition-colors",children:s("review.reviewLesson")})]},t.id)})})]})}function E({nodeId:c,onRated:d}){const o=a.useRef(null),s=j(),[l,f]=a.useState(!1),[b,h]=a.useState(!1),n=async x=>{if(!(l||b)){f(!0);try{await N.recordReview(c,x),h(!0),d==null||d(),S(x>=4?"correct":"wrong",{origin:x>=4&&o.current?(()=>{const p=o.current.getBoundingClientRect();return{x:p.right-48,y:p.top+24}})():void 0})}catch{}finally{f(!1)}}};return b?e.jsx("div",{className:"text-center text-body text-brand py-2","data-testid":"self-rated",children:s("review.selfrated")}):e.jsxs("div",{ref:o,className:"border-t border-[var(--border-faint)] pt-3 mt-3","data-testid":"self-rating",children:[e.jsx("div",{className:"text-caption font-bold text-ink-muted uppercase tracking-wider mb-2 text-center",children:s("review.selfrate.title")}),e.jsxs("div",{className:"grid grid-cols-3 gap-2",children:[e.jsxs("button",{onClick:()=>n(1),disabled:l,"data-testid":"rate-again",className:"py-2 rounded-lg border border-warning/40 text-warning hover:bg-warning/10 transition-colors disabled:opacity-40 flex flex-col items-center gap-0.5",children:[e.jsx("span",{className:"text-body font-bold",children:s("review.selfrate.again")}),e.jsx("span",{className:"text-caption opacity-70",children:s("review.selfrate.again.hint")})]}),e.jsxs("button",{onClick:()=>n(4),disabled:l,"data-testid":"rate-remembered",className:"py-2 rounded-lg border border-accent/40 text-accent hover:bg-accent/10 transition-colors disabled:opacity-40 flex flex-col items-center gap-0.5",children:[e.jsx("span",{className:"text-body font-bold",children:s("review.selfrate.remembered")}),e.jsx("span",{className:"text-caption opacity-70",children:s("review.selfrate.remembered.hint")})]}),e.jsxs("button",{onClick:()=>n(5),disabled:l,"data-testid":"rate-mastered",className:"py-2 rounded-lg border border-brand/40 text-brand hover:bg-brand/10 transition-colors disabled:opacity-40 flex flex-col items-center gap-0.5",children:[e.jsx("span",{className:"text-body font-bold",children:s("review.selfrate.mastered")}),e.jsx("span",{className:"text-caption opacity-70",children:s("review.selfrate.mastered.hint")})]})]})]})}export{A as ReviewPanel,E as SelfRatingCard};