botrun-horse 2.31.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (664) hide show
  1. package/bin/bh.mjs +42 -170
  2. package/bin/commands/help.mjs +30 -30
  3. package/{lib/flows → modules/_core}/draft-writing.mjs +1 -1
  4. package/{lib/flows → modules/_core}/gemini-ask.mjs +1 -1
  5. package/{lib/flows → modules/_core}/legal-ask.mjs +4 -4
  6. package/{lib/flows → modules/_core}/openai-agent.mjs +2 -2
  7. package/{lib/flows → modules/_core}/openrouter-ask.mjs +1 -1
  8. package/{bin/commands/dag-cmd.mjs → modules/dag/command.mjs} +2 -2
  9. package/modules/dag/index.mjs +30 -0
  10. package/{bin/commands/db-cmd.mjs → modules/db/command.mjs} +3 -3
  11. package/modules/db/index.mjs +26 -0
  12. package/modules/discover.mjs +43 -0
  13. package/{bin/commands/doc.mjs → modules/doc/command.mjs} +5 -5
  14. package/modules/doc/index.mjs +55 -0
  15. package/{lib/doc/index.mjs → modules/doc/ingest.mjs} +1 -1
  16. package/{lib → modules}/doc/office2text.mjs +1 -1
  17. package/{lib → modules}/doc/pdf2text.mjs +1 -1
  18. package/{lib → modules}/doc/split.mjs +1 -1
  19. package/{bin/commands/gemini.mjs → modules/gemini/command.mjs} +3 -3
  20. package/modules/gemini/index.mjs +41 -0
  21. package/modules/imagen/command.mjs +117 -0
  22. package/modules/imagen/imagen-client.mjs +239 -0
  23. package/modules/imagen/index.mjs +60 -0
  24. package/modules/imagen/server.mjs +133 -0
  25. package/{bin/commands/legal.mjs → modules/legal/command.mjs} +5 -5
  26. package/modules/legal/index.mjs +56 -0
  27. package/{bin/commands/nchc.mjs → modules/nchc/command.mjs} +4 -4
  28. package/modules/nchc/index.mjs +48 -0
  29. package/modules/ocr/index.mjs +17 -0
  30. package/{bin/commands/openrouter.mjs → modules/openrouter/command.mjs} +5 -5
  31. package/modules/openrouter/index.mjs +49 -0
  32. package/modules/portal/index.mjs +17 -0
  33. package/{bin/commands/search.mjs → modules/search/command.mjs} +3 -3
  34. package/modules/search/index.mjs +23 -0
  35. package/{lib/search/index.mjs → modules/search/search-lib.mjs} +1 -1
  36. package/{bin/commands/skill.mjs → modules/skill/command.mjs} +8 -8
  37. package/modules/skill/index.mjs +35 -0
  38. package/{lib/prompt → modules/skill}/prompt-search.mjs +1 -1
  39. package/{lib/prompt → modules/skill}/prompt-store.mjs +7 -5
  40. package/modules/skill/prompts/zero-framework/address-matching.md +22 -0
  41. package/modules/skill/prompts/zero-framework/data-integrity.md +15 -0
  42. package/modules/skill/prompts/zero-framework/data-reasonability.md +17 -0
  43. package/modules/skill/prompts/zero-framework/gemini-image-gen.md +20 -0
  44. package/modules/skill/prompts/zero-framework/html-report.md +15 -0
  45. package/{bin/commands/writing.mjs → modules/writing/command.mjs} +3 -3
  46. package/{lib → modules}/writing/generate.mjs +2 -2
  47. package/modules/writing/index.mjs +38 -0
  48. package/package.json +3 -1
  49. package/parallel-dag-todo-list/imagen-module.md +51 -0
  50. package/bin/commands/schema.mjs +0 -254
  51. package/botrun-c/.claude/skills/DOCX/351/226/261/350/256/200/346/212/200/350/203/275/SKILL.md +0 -103
  52. package/botrun-c/.claude/skills/DOCX/351/226/261/350/256/200/346/212/200/350/203/275/scripts/docx-to-markdown.mjs +0 -206
  53. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/SKILL.md +0 -1093
  54. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-branch.mjs +0 -73
  55. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-branches.mjs +0 -77
  56. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-checkout.mjs +0 -72
  57. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-clone.mjs +0 -72
  58. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-commit.mjs +0 -75
  59. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-delete-branch.mjs +0 -75
  60. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-delete-file.mjs +0 -72
  61. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-diff.mjs +0 -80
  62. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-list-tree.mjs +0 -336
  63. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-list.mjs +0 -199
  64. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-merge.mjs +0 -86
  65. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-move.mjs +0 -75
  66. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-pr-create.mjs +0 -81
  67. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-pr-list.mjs +0 -74
  68. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-pr-merge.mjs +0 -83
  69. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-pr-view.mjs +0 -71
  70. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-push.mjs +0 -71
  71. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-read.mjs +0 -277
  72. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-search.mjs +0 -370
  73. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-stash.mjs +0 -116
  74. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-sync.mjs +0 -71
  75. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-write.mjs +0 -74
  76. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/lib/path-validator.mjs +0 -167
  77. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/SKILL.md +0 -605
  78. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-create.mjs +0 -127
  79. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-delete.mjs +0 -77
  80. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-diff.mjs +0 -87
  81. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-history.mjs +0 -99
  82. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-list.mjs +0 -174
  83. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-read.mjs +0 -214
  84. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-restore.mjs +0 -75
  85. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-search.mjs +0 -270
  86. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-sync-back.mjs +0 -155
  87. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-update.mjs +0 -100
  88. package/botrun-c/.claude/skills/HTML/347/224/237/346/210/220/346/212/200/350/203/275/SKILL.md +0 -414
  89. package/botrun-c/.claude/skills/HTML/347/224/237/346/210/220/346/212/200/350/203/275/scripts/create-project.mjs +0 -91
  90. package/botrun-c/.claude/skills/HTML/347/224/237/346/210/220/346/212/200/350/203/275/scripts/finalize-project.mjs +0 -162
  91. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/SKILL.md +0 -206
  92. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/package.json +0 -19
  93. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/pdf-analyze.mjs +0 -309
  94. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/pdf-compress.mjs +0 -315
  95. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/pdf-split.mjs +0 -275
  96. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/pdf-to-text.mjs +0 -336
  97. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/test-pdf-scripts.mjs +0 -491
  98. package/botrun-c/.claude/skills/docx-to-markdown/SKILL.md +0 -76
  99. package/botrun-c/.claude/skills/docx-to-markdown/scripts/convert_docx.py +0 -183
  100. package/botrun-c/.claude/skills/gcp-coord-ml-ocr/SKILL.md +0 -133
  101. package/botrun-c/.claude/skills/gcp-coord-ml-ocr/scripts/ocr_processor.py +0 -381
  102. package/botrun-c/.claude/skills/gemini-transcribe/SKILL.md +0 -115
  103. package/botrun-c/.claude/skills/gemini-transcribe/scripts/transcribe.py +0 -499
  104. package/botrun-c/.claude/skills/nchc-transcribe/SKILL.md +0 -131
  105. package/botrun-c/.claude/skills/nchc-transcribe/scripts/transcribe.py +0 -522
  106. package/botrun-c/.claude/skills/p320-moj-review/SKILL.md +0 -200
  107. package/botrun-c/.claude/skills/p320-moj-review/references/guideline.md +0 -118
  108. package/botrun-c/.claude/skills/pdf-multimodal-processor/SKILL.md +0 -186
  109. package/botrun-c/.claude/skills/pdf-multimodal-processor/scripts/process_pdf.py +0 -515
  110. package/botrun-c/.claude/skills/ripgrep/346/220/234/345/260/213/346/212/200/350/203/275/SKILL.md +0 -81
  111. package/botrun-c/.claude/skills/ripgrep/346/220/234/345/260/213/346/212/200/350/203/275/scripts/package.json +0 -13
  112. package/botrun-c/.claude/skills/ripgrep/346/220/234/345/260/213/346/212/200/350/203/275/scripts/secure-search.mjs +0 -232
  113. package/botrun-c/.claude/skills/top100/351/240/230/350/242/226/351/240/220/346/270/254/346/212/200/350/203/275/SKILL.md +0 -518
  114. package/botrun-c/.claude/skills//345/216/273/350/255/230/345/210/245/346/212/200/350/203/275/SKILL.md +0 -125
  115. package/botrun-c/.claude/skills//345/233/236/346/206/266/346/212/200/350/203/275/SKILL.md +0 -123
  116. package/botrun-c/.claude/skills//345/233/236/346/206/266/346/212/200/350/203/275/scripts/recall.mjs +0 -339
  117. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/SKILL.md +0 -156
  118. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/__tests__/utils.test.mjs +0 -139
  119. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/constants.mjs +0 -40
  120. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/gcs-uploader.mjs +0 -195
  121. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/gemini-image-client.mjs +0 -307
  122. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/generate-image.mjs +0 -103
  123. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/image-session-manager.mjs +0 -219
  124. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/output-formatter.mjs +0 -209
  125. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/package.json +0 -20
  126. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/utils.mjs +0 -115
  127. package/botrun-c/.claude/skills//345/244/232/346/250/241/346/205/213/351/226/261/350/256/200/346/212/200/350/203/275/SKILL.md +0 -86
  128. package/botrun-c/.claude/skills//345/244/232/346/250/241/346/205/213/351/226/261/350/256/200/346/212/200/350/203/275/scripts/multimodal-read.mjs +0 -304
  129. package/botrun-c/.claude/skills//345/244/232/346/250/241/346/205/213/351/226/261/350/256/200/346/212/200/350/203/275/scripts/package.json +0 -17
  130. package/botrun-c/.claude/skills//345/255/265/345/214/226/346/212/200/350/203/275/SKILL.md +0 -131
  131. package/botrun-c/.claude/skills//345/255/265/345/214/226/346/212/200/350/203/275/scripts/skill-manager.mjs +0 -542
  132. package/botrun-c/.claude/skills//346/234/203/350/255/260/350/250/230/351/214/204/346/212/200/350/203/275/SKILL.md +0 -127
  133. package/botrun-c/.claude/skills//347/233/256/351/214/204/345/210/227/350/241/250/346/212/200/350/203/275/SKILL.md +0 -53
  134. package/botrun-c/.claude/skills//347/233/256/351/214/204/345/210/227/350/241/250/346/212/200/350/203/275/scripts/package.json +0 -13
  135. package/botrun-c/.claude/skills//347/233/256/351/214/204/345/210/227/350/241/250/346/212/200/350/203/275/scripts/tree-view.mjs +0 -149
  136. package/botrun-c/.claude/skills//347/264/224/346/226/207/345/255/227/345/244/232/346/252/224/346/241/210/350/256/200/345/217/226/346/212/200/350/203/275/SKILL.md +0 -82
  137. package/botrun-c/.claude/skills//347/264/224/346/226/207/345/255/227/345/244/232/346/252/224/346/241/210/350/256/200/345/217/226/346/212/200/350/203/275/scripts/package.json +0 -16
  138. package/botrun-c/.claude/skills//347/264/224/346/226/207/345/255/227/345/244/232/346/252/224/346/241/210/350/256/200/345/217/226/346/212/200/350/203/275/scripts/secure-read.mjs +0 -260
  139. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/SKILL.md +0 -156
  140. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/scripts/package.json +0 -16
  141. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/scripts/search.js +0 -139
  142. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/scripts/search.mjs +0 -272
  143. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/scripts/search.ts +0 -166
  144. package/botrun-c/.claude/skills//350/250/230/346/206/266/346/212/200/350/203/275/SKILL.md +0 -114
  145. package/botrun-c/.claude/skills//350/250/230/346/206/266/346/212/200/350/203/275/scripts/remember.mjs +0 -159
  146. package/botrun-c/.claude/skills//350/250/230/346/206/266/346/212/200/350/203/275/scripts/test-advanced.mjs +0 -342
  147. package/botrun-c/.claude/skills//350/250/230/346/206/266/346/212/200/350/203/275/scripts/test.mjs +0 -430
  148. package/botrun-c/.claude/skills//350/252/236/351/237/263/350/275/211/346/226/207/345/255/227/346/212/200/350/203/275/SKILL.md +0 -30
  149. package/botrun-c/.claude/skills//350/252/236/351/237/263/350/275/211/346/226/207/345/255/227/346/212/200/350/203/275/scripts/package.json +0 -13
  150. package/botrun-c/.claude/skills//350/252/236/351/237/263/350/275/211/346/226/207/345/255/227/346/212/200/350/203/275/scripts/transcribe.mjs +0 -294
  151. package/botrun-c/.claude/skills//351/233/266/345/271/273/350/246/272/350/255/211/346/230/216/346/212/200/350/203/275/SKILL.md +0 -338
  152. package/botrun-c/.dockerignore +0 -131
  153. package/botrun-c/.dockeroptimize +0 -13
  154. package/botrun-c/.firebase/hosting.b3V0.cache +0 -30
  155. package/botrun-c/.firebaserc +0 -5
  156. package/botrun-c/.gcloudignore +0 -56
  157. package/botrun-c/.ruby-version +0 -1
  158. package/botrun-c/CHANGELOG.md +0 -224
  159. package/botrun-c/Dockerfile.gcp +0 -365
  160. package/botrun-c/Dockerfile.slim +0 -195
  161. package/botrun-c/Gemfile +0 -8
  162. package/botrun-c/Gemfile.lock +0 -233
  163. package/botrun-c/LOCAL-DEVELOPMENT-GUIDE.md +0 -393
  164. package/botrun-c/PLAN.md +0 -131
  165. package/botrun-c/README.md +0 -129
  166. package/botrun-c/TODO-CAPACITOR-INTEGRATION.md +0 -482
  167. package/botrun-c/VERIFICATION.md +0 -121
  168. package/botrun-c/admin/RATE_LIMIT_MANAGEMENT.md +0 -312
  169. package/botrun-c/admin/README.md +0 -338
  170. package/botrun-c/admin/botrun-billing-report-1765235244038.csv +0 -22
  171. package/botrun-c/admin/botrun-billing-report-1765235323089.csv +0 -22
  172. package/botrun-c/admin/botrun-dashboard-1765235244039.html +0 -278
  173. package/botrun-c/admin/botrun-dashboard-1765235323089.html +0 -278
  174. package/botrun-c/admin/botrun-timeseries-074xGzIKWyfKTBt1NMIj9lxi5mO2-1765235244039.html +0 -161
  175. package/botrun-c/admin/botrun-timeseries-074xGzIKWyfKTBt1NMIj9lxi5mO2-1765235323090.html +0 -161
  176. package/botrun-c/admin/check-billing.ts +0 -48
  177. package/botrun-c/admin/count-all-users.ts +0 -65
  178. package/botrun-c/admin/generate-analytics-report.ts +0 -188
  179. package/botrun-c/admin/jest.config.ts +0 -43
  180. package/botrun-c/admin/lib/__tests__/formatters.test.ts +0 -264
  181. package/botrun-c/admin/lib/__tests__/mocks/firebase.mock.ts +0 -112
  182. package/botrun-c/admin/lib/__tests__/services/UserService.test.ts +0 -83
  183. package/botrun-c/admin/lib/__tests__/user-resolver.test.ts +0 -439
  184. package/botrun-c/admin/lib/analyzers/BillingAnalyticsService.ts +0 -242
  185. package/botrun-c/admin/lib/analyzers/CsvExporter.ts +0 -263
  186. package/botrun-c/admin/lib/analyzers/HtmlChartGenerator.ts +0 -530
  187. package/botrun-c/admin/lib/analyzers/SpikeAnalyzer.ts +0 -608
  188. package/botrun-c/admin/lib/analyzers/TimeSeriesAnalyzer.ts +0 -344
  189. package/botrun-c/admin/lib/cli/CLIBootstrapper.ts +0 -40
  190. package/botrun-c/admin/lib/commands/AbstractCommand.ts +0 -69
  191. package/botrun-c/admin/lib/commands/AnalyticsCommand.ts +0 -215
  192. package/botrun-c/admin/lib/commands/ListCommand.ts +0 -85
  193. package/botrun-c/admin/lib/commands/QueryCommand.ts +0 -81
  194. package/botrun-c/admin/lib/commands/SpikeCommand.ts +0 -83
  195. package/botrun-c/admin/lib/domain/User.ts +0 -38
  196. package/botrun-c/admin/lib/firebase.ts +0 -92
  197. package/botrun-c/admin/lib/firestore-safe.ts +0 -105
  198. package/botrun-c/admin/lib/formatters.ts +0 -154
  199. package/botrun-c/admin/lib/rate-limit-detailed.ts +0 -355
  200. package/botrun-c/admin/lib/rate-limit.ts +0 -286
  201. package/botrun-c/admin/lib/repositories/UserRepository.ts +0 -104
  202. package/botrun-c/admin/lib/services/UserService.ts +0 -108
  203. package/botrun-c/admin/lib/types.ts +0 -56
  204. package/botrun-c/admin/lib/user-resolver.ts +0 -275
  205. package/botrun-c/admin/manage-auth-domains.js +0 -178
  206. package/botrun-c/admin/migrate-plan-free.sh +0 -64
  207. package/botrun-c/admin/package-lock.json +0 -5656
  208. package/botrun-c/admin/package.json +0 -28
  209. package/botrun-c/admin/rate-limit.sh +0 -55
  210. package/botrun-c/admin/remove-custom-limits.ts +0 -75
  211. package/botrun-c/admin/reset-rate.sh +0 -70
  212. package/botrun-c/admin/reset-trial.sh +0 -68
  213. package/botrun-c/admin/seed-trial-data.sh +0 -38
  214. package/botrun-c/admin/trial.sh +0 -55
  215. package/botrun-c/admin/tsconfig.json +0 -20
  216. package/botrun-c/admin/users-cli.py +0 -298
  217. package/botrun-c/admin/users.sh +0 -12
  218. package/botrun-c/admin/users.ts +0 -1321
  219. package/botrun-c/api/admin-tools/check-whitelist.mjs.migration-bak +0 -74
  220. package/botrun-c/api/admin-tools/create-user.mjs +0 -92
  221. package/botrun-c/api/admin-tools/create-user.mjs.migration-bak +0 -92
  222. package/botrun-c/api/admin-tools/firebase-auth-domains-v2.py +0 -287
  223. package/botrun-c/api/admin-tools/firebase-auth-domains-v2.py.migration-bak +0 -287
  224. package/botrun-c/api/admin-tools/init-firestore.mjs +0 -75
  225. package/botrun-c/api/admin-tools/init-firestore.mjs.migration-bak +0 -101
  226. package/botrun-c/api/admin-tools/list-users.mjs +0 -43
  227. package/botrun-c/api/admin-tools/list-users.mjs.migration-bak +0 -43
  228. package/botrun-c/api/admin-tools/manage-authorized-domains.ts +0 -255
  229. package/botrun-c/api/admin-tools/manage-authorized-domains.ts.migration-bak +0 -255
  230. package/botrun-c/api/admin-tools/setup-custom-claims.ts.migration-bak +0 -155
  231. package/botrun-c/api/admin-tools/test-claims.mjs +0 -69
  232. package/botrun-c/api/admin-tools/test-claims.mjs.migration-bak +0 -69
  233. package/botrun-c/api/admin-tools/update-service-urls-gcloud.mjs +0 -72
  234. package/botrun-c/api/admin-tools/update-service-urls-gcloud.mjs.migration-bak +0 -72
  235. package/botrun-c/api/admin-tools/update-service-urls-to-lb.mjs +0 -159
  236. package/botrun-c/api/admin-tools/update-service-urls-to-lb.mjs.migration-bak +0 -159
  237. package/botrun-c/api/admin-tools/update-service-urls.mjs +0 -82
  238. package/botrun-c/api/admin-tools/update-service-urls.mjs.migration-bak +0 -82
  239. package/botrun-c/api/package-lock.json +0 -5031
  240. package/botrun-c/api/package.json +0 -63
  241. package/botrun-c/api/pnpm-lock.yaml +0 -4157
  242. package/botrun-c/api/rate-limit-export-1763620678737.csv +0 -79
  243. package/botrun-c/api/scripts/README-migrate-plan-free.md +0 -197
  244. package/botrun-c/api/scripts/check-users.ts +0 -45
  245. package/botrun-c/api/scripts/delete-firestore-docs.js +0 -43
  246. package/botrun-c/api/scripts/dump-subscriptions.ts +0 -395
  247. package/botrun-c/api/scripts/list-all-users.ts +0 -65
  248. package/botrun-c/api/scripts/migrate-plan-free-rpd-16to32.ts +0 -262
  249. package/botrun-c/api/scripts/migrate-plan-names-to-lite-max.ts +0 -269
  250. package/botrun-c/api/scripts/query-user-info.ts +0 -304
  251. package/botrun-c/api/scripts/reset-rate-limit.ts +0 -206
  252. package/botrun-c/api/scripts/reset-trial-subscription.ts +0 -166
  253. package/botrun-c/api/scripts/rollback-plan-names-from-lite-max.ts +0 -139
  254. package/botrun-c/api/scripts/seed-rate-limit-test-data.ts +0 -114
  255. package/botrun-c/api/scripts/seed-subscription-test-data.ts +0 -173
  256. package/botrun-c/api/scripts/test-rate-limit-message.ts +0 -108
  257. package/botrun-c/api/test-billing-write.ts +0 -103
  258. package/botrun-c/api/test-billing.ts +0 -81
  259. package/botrun-c/api/test-chat-upload.sh +0 -162
  260. package/botrun-c/api/test-nchc-whisper.ts +0 -87
  261. package/botrun-c/api/test-skills-loading.js +0 -77
  262. package/botrun-c/api/test-skills-symlink.js +0 -118
  263. package/botrun-c/api/test-upload-multimodal.sh +0 -167
  264. package/botrun-c/api/tsconfig.json +0 -27
  265. package/botrun-c/api/verify-form-2-1.mjs +0 -229
  266. package/botrun-c/api/vitest.config.ts +0 -9
  267. package/botrun-c/appium-capabilities.json +0 -20
  268. package/botrun-c/cors.json +0 -26
  269. package/botrun-c/delete-old.sh +0 -44
  270. package/botrun-c/design/google-drive/01-architecture.md +0 -186
  271. package/botrun-c/design/google-drive/02-api-design.md +0 -314
  272. package/botrun-c/design/google-drive/03-frontend-design.md +0 -278
  273. package/botrun-c/design/google-drive/04-skill-design.md +0 -384
  274. package/botrun-c/design/google-drive/05-test-plan.md +0 -507
  275. package/botrun-c/design/google-drive/README.md +0 -32
  276. package/botrun-c/dev/mcp-token-calculator/README.md +0 -92
  277. package/botrun-c/dev/mcp-token-calculator/RESULTS.md +0 -188
  278. package/botrun-c/dev/mcp-token-calculator/calculate-current-tokens.ts +0 -369
  279. package/botrun-c/dev/mcp-token-calculator/calculate-mcp-tokens.ts +0 -464
  280. package/botrun-c/dev/mcp-token-calculator/optimization-proposals.ts +0 -316
  281. package/botrun-c/dev/mcp-token-calculator/package-lock.json +0 -599
  282. package/botrun-c/dev/mcp-token-calculator/package.json +0 -22
  283. package/botrun-c/dev/mcp-token-calculator/test-all-schemas.ts +0 -314
  284. package/botrun-c/dev/mcp-token-calculator/test-schema-correctness.ts +0 -221
  285. package/botrun-c/dev/mcp-token-calculator/verify-optimization.ts +0 -159
  286. package/botrun-c/fastlane/ANDROID-DEPLOYMENT-SETUP.md +0 -825
  287. package/botrun-c/fastlane/IOS-DEPLOYMENT-SETUP.md +0 -431
  288. package/botrun-c/fastlane/Pluginfile +0 -5
  289. package/botrun-c/fastlane/QUICK-START.md +0 -133
  290. package/botrun-c/fastlane/README.md +0 -424
  291. package/botrun-c/fastlane/RUBY-SETUP.md +0 -449
  292. package/botrun-c/fastlane/android/Appfile +0 -11
  293. package/botrun-c/fastlane/android/Fastfile +0 -240
  294. package/botrun-c/firebase.json +0 -46
  295. package/botrun-c/firestore-init-data.json +0 -40
  296. package/botrun-c/firestore.indexes.json +0 -55
  297. package/botrun-c/firestore.rules +0 -53
  298. package/botrun-c/fix-codex-permissions.sh +0 -56
  299. package/botrun-c/gcs-lifecycle-30day-retention.json +0 -12
  300. package/botrun-c/html-architecture/architecture/deployment.html +0 -664
  301. package/botrun-c/html-architecture/architecture/index.html +0 -309
  302. package/botrun-c/html-architecture/architecture/styles.css +0 -500
  303. package/botrun-c/html-architecture/architecture/system-components.html +0 -538
  304. package/botrun-c/html-architecture/architecture/user-flow.html +0 -370
  305. package/botrun-c/mobile-android/FIREBASE-AUTH-SETUP.md +0 -302
  306. package/botrun-c/mobile-android/WSL-ANDROID-SETUP.md +0 -252
  307. package/botrun-c/mobile-android/android/app/build.gradle +0 -75
  308. package/botrun-c/mobile-android/android/app/capacitor.build.gradle +0 -31
  309. package/botrun-c/mobile-android/android/app/proguard-rules.pro +0 -21
  310. package/botrun-c/mobile-android/android/build.gradle +0 -29
  311. package/botrun-c/mobile-android/android/capacitor.settings.gradle +0 -42
  312. package/botrun-c/mobile-android/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  313. package/botrun-c/mobile-android/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  314. package/botrun-c/mobile-android/android/gradle.properties +0 -22
  315. package/botrun-c/mobile-android/android/gradlew +0 -252
  316. package/botrun-c/mobile-android/android/gradlew.bat +0 -94
  317. package/botrun-c/mobile-android/android/settings.gradle +0 -5
  318. package/botrun-c/mobile-android/android/variables.gradle +0 -16
  319. package/botrun-c/mobile-android/capacitor.config.ts +0 -44
  320. package/botrun-c/mobile-android/fastlane/Appfile +0 -11
  321. package/botrun-c/mobile-android/fastlane/Fastfile +0 -202
  322. package/botrun-c/mobile-android/fastlane/README.md +0 -96
  323. package/botrun-c/mobile-android/google-services.json.template +0 -39
  324. package/botrun-c/mobile-android/package.json +0 -32
  325. package/botrun-c/mobile-android/resources/icon.png +0 -0
  326. package/botrun-c/mobile-android/tsconfig.json +0 -15
  327. package/botrun-c/mobile-ios/.ruby-version +0 -1
  328. package/botrun-c/mobile-ios/BUILD-LOG.md +0 -82
  329. package/botrun-c/mobile-ios/Gemfile +0 -4
  330. package/botrun-c/mobile-ios/Gemfile.lock +0 -299
  331. package/botrun-c/mobile-ios/GoogleService-Info.plist.template +0 -34
  332. package/botrun-c/mobile-ios/IOS-PERMISSIONS.md +0 -80
  333. package/botrun-c/mobile-ios/QUICK-START.md +0 -189
  334. package/botrun-c/mobile-ios/README.md +0 -231
  335. package/botrun-c/mobile-ios/capacitor.config.ts +0 -46
  336. package/botrun-c/mobile-ios/fastlane/Fastfile +0 -326
  337. package/botrun-c/mobile-ios/fastlane/README.md +0 -88
  338. package/botrun-c/mobile-ios/ios/App/App/App.entitlements +0 -10
  339. package/botrun-c/mobile-ios/ios/App/App/AppDelegate.swift +0 -49
  340. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png +0 -0
  341. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -14
  342. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Contents.json +0 -6
  343. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json +0 -56
  344. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.png +0 -0
  345. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.png +0 -0
  346. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.png +0 -0
  347. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.png +0 -0
  348. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.png +0 -0
  349. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.png +0 -0
  350. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png +0 -0
  351. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png +0 -0
  352. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png +0 -0
  353. package/botrun-c/mobile-ios/ios/App/App/Base.lproj/LaunchScreen.storyboard +0 -32
  354. package/botrun-c/mobile-ios/ios/App/App/Base.lproj/Main.storyboard +0 -19
  355. package/botrun-c/mobile-ios/ios/App/App/Info.plist +0 -72
  356. package/botrun-c/mobile-ios/ios/App/App.xcodeproj/project.pbxproj +0 -446
  357. package/botrun-c/mobile-ios/ios/App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme +0 -78
  358. package/botrun-c/mobile-ios/ios/App/App.xcworkspace/contents.xcworkspacedata +0 -10
  359. package/botrun-c/mobile-ios/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  360. package/botrun-c/mobile-ios/ios/App/Podfile +0 -52
  361. package/botrun-c/mobile-ios/ios/App/Podfile.lock +0 -205
  362. package/botrun-c/mobile-ios/ios/App/add_plist.rb +0 -30
  363. package/botrun-c/mobile-ios/ios/App/fix_plist_path.rb +0 -22
  364. package/botrun-c/mobile-ios/package.json +0 -29
  365. package/botrun-c/mobile-ios/resources/icon.png +0 -0
  366. package/botrun-c/mobile-ios/run-simulator.sh +0 -34
  367. package/botrun-c/mobile-ios/screenshot-after-clean-rebuild.png +0 -0
  368. package/botrun-c/mobile-ios/screenshot-final-version.png +0 -0
  369. package/botrun-c/mobile-ios/screenshot-simulator.png +0 -0
  370. package/botrun-c/mobile-ios/screenshot-version-1009.png +0 -0
  371. package/botrun-c/mobile-ios/screenshot-with-version.png +0 -0
  372. package/botrun-c/mobile-ios/screenshot-working.png +0 -0
  373. package/botrun-c/mobile-ios/setup-ios.sh +0 -106
  374. package/botrun-c/mobile-ios/tsconfig.json +0 -16
  375. package/botrun-c/org-policy-allow-all-users.yaml +0 -3
  376. package/botrun-c/package.json +0 -68
  377. package/botrun-c/pnpm-lock.yaml +0 -10198
  378. package/botrun-c/pnpm-workspace.yaml +0 -6
  379. package/botrun-c/prompts/BOTRUN-1.md +0 -13
  380. package/botrun-c/prompts/BOTRUN-2.md +0 -70
  381. package/botrun-c/prompts/BOTRUN-3.md +0 -139
  382. package/botrun-c/prototypes/firestore-gax-debug/package-lock.json +0 -1204
  383. package/botrun-c/prototypes/firestore-gax-debug/package.json +0 -12
  384. package/botrun-c/prototypes/firestore-gax-debug/pnpm-workspace.yaml +0 -2
  385. package/botrun-c/prototypes/firestore-gax-debug/test.js +0 -98
  386. package/botrun-c/prototypes/taiwan-haiku/README.md +0 -93
  387. package/botrun-c/prototypes/taiwan-haiku/index.ts +0 -139
  388. package/botrun-c/prototypes/taiwan-haiku/package.json +0 -23
  389. package/botrun-c/prototypes/taiwan-haiku/pnpm-lock.yaml +0 -730
  390. package/botrun-c/prototypes/taiwan-haiku/test-vertex-direct.ts +0 -83
  391. package/botrun-c/prototypes/taiwan-haiku/tsconfig.json +0 -18
  392. package/botrun-c/prototypes/web-search/FINDINGS-STAGE-1.md +0 -48
  393. package/botrun-c/prototypes/web-search/README.md +0 -46
  394. package/botrun-c/prototypes/web-search/VERIFICATION-REPORT.md +0 -310
  395. package/botrun-c/prototypes/web-search/package-lock.json +0 -595
  396. package/botrun-c/prototypes/web-search/package.json +0 -18
  397. package/botrun-c/prototypes/web-search/stage-1.1-basic-connection.js +0 -123
  398. package/botrun-c/prototypes/web-search/stage-1.2-with-websearch.js +0 -154
  399. package/botrun-c/prototypes/web-search/stage-1.3-search-query.js +0 -192
  400. package/botrun-c/prototypes/web-search/stage-2-agent-sdk-websearch.js +0 -265
  401. package/botrun-c/runtime-scripts/env-detect.sh +0 -64
  402. package/botrun-c/scripts/github-collaborator-manager.sh +0 -297
  403. package/botrun-c/scripts/github-collaborator-manager.ts +0 -325
  404. package/botrun-c/scripts/manage-collaborators.sh +0 -91
  405. package/botrun-c/scripts/setup-secrets.sh +0 -202
  406. package/botrun-c/scripts/stress-test-17-users.sh +0 -113
  407. package/botrun-c/scripts/stress-test-api.sh +0 -119
  408. package/botrun-c/scripts/stress-test-full-report.sh +0 -385
  409. package/botrun-c/specs/infra-vm/design.md +0 -0
  410. package/botrun-c/specs/share-skill/design.md +0 -1146
  411. package/botrun-c/specs/share-skill/tasks.md +0 -444
  412. package/botrun-c/stress-test-results/20260121-162630/config.json +0 -7
  413. package/botrun-c/stress-test-results/20260121-162630/user-1-status.txt +0 -1
  414. package/botrun-c/stress-test-results/20260121-162630/user-1-timing.txt +0 -1
  415. package/botrun-c/stress-test-results/20260121-162630/user-1.json +0 -2
  416. package/botrun-c/stress-test-results/20260121-162646/config.json +0 -7
  417. package/botrun-c/stress-test-results/20260121-162646/user-1-status.txt +0 -1
  418. package/botrun-c/stress-test-results/20260121-162646/user-1-timing.txt +0 -1
  419. package/botrun-c/stress-test-results/20260121-162646/user-1.json +0 -2
  420. package/botrun-c/stress-test-results/20260121-162646/user-10-status.txt +0 -1
  421. package/botrun-c/stress-test-results/20260121-162646/user-10-timing.txt +0 -1
  422. package/botrun-c/stress-test-results/20260121-162646/user-10.json +0 -2
  423. package/botrun-c/stress-test-results/20260121-162646/user-11-status.txt +0 -1
  424. package/botrun-c/stress-test-results/20260121-162646/user-11-timing.txt +0 -1
  425. package/botrun-c/stress-test-results/20260121-162646/user-11.json +0 -2
  426. package/botrun-c/stress-test-results/20260121-162646/user-12-status.txt +0 -1
  427. package/botrun-c/stress-test-results/20260121-162646/user-12-timing.txt +0 -1
  428. package/botrun-c/stress-test-results/20260121-162646/user-12.json +0 -2
  429. package/botrun-c/stress-test-results/20260121-162646/user-13-status.txt +0 -1
  430. package/botrun-c/stress-test-results/20260121-162646/user-13-timing.txt +0 -1
  431. package/botrun-c/stress-test-results/20260121-162646/user-13.json +0 -2
  432. package/botrun-c/stress-test-results/20260121-162646/user-14-status.txt +0 -1
  433. package/botrun-c/stress-test-results/20260121-162646/user-14-timing.txt +0 -1
  434. package/botrun-c/stress-test-results/20260121-162646/user-14.json +0 -2
  435. package/botrun-c/stress-test-results/20260121-162646/user-15-status.txt +0 -1
  436. package/botrun-c/stress-test-results/20260121-162646/user-15-timing.txt +0 -1
  437. package/botrun-c/stress-test-results/20260121-162646/user-15.json +0 -2
  438. package/botrun-c/stress-test-results/20260121-162646/user-16-status.txt +0 -1
  439. package/botrun-c/stress-test-results/20260121-162646/user-16-timing.txt +0 -1
  440. package/botrun-c/stress-test-results/20260121-162646/user-16.json +0 -2
  441. package/botrun-c/stress-test-results/20260121-162646/user-17-status.txt +0 -1
  442. package/botrun-c/stress-test-results/20260121-162646/user-17-timing.txt +0 -1
  443. package/botrun-c/stress-test-results/20260121-162646/user-17.json +0 -2
  444. package/botrun-c/stress-test-results/20260121-162646/user-2-status.txt +0 -1
  445. package/botrun-c/stress-test-results/20260121-162646/user-2-timing.txt +0 -1
  446. package/botrun-c/stress-test-results/20260121-162646/user-2.json +0 -2
  447. package/botrun-c/stress-test-results/20260121-162646/user-3-status.txt +0 -1
  448. package/botrun-c/stress-test-results/20260121-162646/user-3-timing.txt +0 -1
  449. package/botrun-c/stress-test-results/20260121-162646/user-3.json +0 -2
  450. package/botrun-c/stress-test-results/20260121-162646/user-4-status.txt +0 -1
  451. package/botrun-c/stress-test-results/20260121-162646/user-4-timing.txt +0 -1
  452. package/botrun-c/stress-test-results/20260121-162646/user-4.json +0 -2
  453. package/botrun-c/stress-test-results/20260121-162646/user-5-status.txt +0 -1
  454. package/botrun-c/stress-test-results/20260121-162646/user-5-timing.txt +0 -1
  455. package/botrun-c/stress-test-results/20260121-162646/user-5.json +0 -2
  456. package/botrun-c/stress-test-results/20260121-162646/user-6-status.txt +0 -1
  457. package/botrun-c/stress-test-results/20260121-162646/user-6-timing.txt +0 -1
  458. package/botrun-c/stress-test-results/20260121-162646/user-6.json +0 -2
  459. package/botrun-c/stress-test-results/20260121-162646/user-7-status.txt +0 -1
  460. package/botrun-c/stress-test-results/20260121-162646/user-7-timing.txt +0 -1
  461. package/botrun-c/stress-test-results/20260121-162646/user-7.json +0 -2
  462. package/botrun-c/stress-test-results/20260121-162646/user-8-status.txt +0 -1
  463. package/botrun-c/stress-test-results/20260121-162646/user-8-timing.txt +0 -1
  464. package/botrun-c/stress-test-results/20260121-162646/user-8.json +0 -2
  465. package/botrun-c/stress-test-results/20260121-162646/user-9-status.txt +0 -1
  466. package/botrun-c/stress-test-results/20260121-162646/user-9-timing.txt +0 -1
  467. package/botrun-c/stress-test-results/20260121-162646/user-9.json +0 -2
  468. package/botrun-c/stress-test-results/20260121-163148/REPORT.html +0 -344
  469. package/botrun-c/stress-test-results/20260121-163148/REPORT.md +0 -93
  470. package/botrun-c/stress-test-results/20260121-163148/REPORT.pdf +0 -0
  471. package/botrun-c/stress-test-results/20260121-163148/config.json +0 -7
  472. package/botrun-c/stress-test-results/20260121-163148/summary.json +0 -26
  473. package/botrun-c/stress-test-results/20260121-163148/user-1-status.txt +0 -1
  474. package/botrun-c/stress-test-results/20260121-163148/user-1-timing.txt +0 -1
  475. package/botrun-c/stress-test-results/20260121-163148/user-1.json +0 -206
  476. package/botrun-c/stress-test-results/20260121-163148/user-10-status.txt +0 -1
  477. package/botrun-c/stress-test-results/20260121-163148/user-10-timing.txt +0 -1
  478. package/botrun-c/stress-test-results/20260121-163148/user-10.json +0 -110
  479. package/botrun-c/stress-test-results/20260121-163148/user-11-status.txt +0 -1
  480. package/botrun-c/stress-test-results/20260121-163148/user-11-timing.txt +0 -1
  481. package/botrun-c/stress-test-results/20260121-163148/user-11.json +0 -104
  482. package/botrun-c/stress-test-results/20260121-163148/user-12-status.txt +0 -1
  483. package/botrun-c/stress-test-results/20260121-163148/user-12-timing.txt +0 -1
  484. package/botrun-c/stress-test-results/20260121-163148/user-12.json +0 -98
  485. package/botrun-c/stress-test-results/20260121-163148/user-13-status.txt +0 -1
  486. package/botrun-c/stress-test-results/20260121-163148/user-13-timing.txt +0 -1
  487. package/botrun-c/stress-test-results/20260121-163148/user-13.json +0 -68
  488. package/botrun-c/stress-test-results/20260121-163148/user-14-status.txt +0 -1
  489. package/botrun-c/stress-test-results/20260121-163148/user-14-timing.txt +0 -1
  490. package/botrun-c/stress-test-results/20260121-163148/user-14.json +0 -116
  491. package/botrun-c/stress-test-results/20260121-163148/user-15-status.txt +0 -1
  492. package/botrun-c/stress-test-results/20260121-163148/user-15-timing.txt +0 -1
  493. package/botrun-c/stress-test-results/20260121-163148/user-15.json +0 -131
  494. package/botrun-c/stress-test-results/20260121-163148/user-16-status.txt +0 -1
  495. package/botrun-c/stress-test-results/20260121-163148/user-16-timing.txt +0 -1
  496. package/botrun-c/stress-test-results/20260121-163148/user-16.json +0 -110
  497. package/botrun-c/stress-test-results/20260121-163148/user-17-status.txt +0 -1
  498. package/botrun-c/stress-test-results/20260121-163148/user-17-timing.txt +0 -1
  499. package/botrun-c/stress-test-results/20260121-163148/user-17.json +0 -116
  500. package/botrun-c/stress-test-results/20260121-163148/user-2-status.txt +0 -1
  501. package/botrun-c/stress-test-results/20260121-163148/user-2-timing.txt +0 -1
  502. package/botrun-c/stress-test-results/20260121-163148/user-2.json +0 -98
  503. package/botrun-c/stress-test-results/20260121-163148/user-3-status.txt +0 -1
  504. package/botrun-c/stress-test-results/20260121-163148/user-3-timing.txt +0 -1
  505. package/botrun-c/stress-test-results/20260121-163148/user-3.json +0 -62
  506. package/botrun-c/stress-test-results/20260121-163148/user-4-status.txt +0 -1
  507. package/botrun-c/stress-test-results/20260121-163148/user-4-timing.txt +0 -1
  508. package/botrun-c/stress-test-results/20260121-163148/user-4.json +0 -113
  509. package/botrun-c/stress-test-results/20260121-163148/user-5-status.txt +0 -1
  510. package/botrun-c/stress-test-results/20260121-163148/user-5-timing.txt +0 -1
  511. package/botrun-c/stress-test-results/20260121-163148/user-5.json +0 -83
  512. package/botrun-c/stress-test-results/20260121-163148/user-6-status.txt +0 -1
  513. package/botrun-c/stress-test-results/20260121-163148/user-6-timing.txt +0 -1
  514. package/botrun-c/stress-test-results/20260121-163148/user-6.json +0 -125
  515. package/botrun-c/stress-test-results/20260121-163148/user-7-status.txt +0 -1
  516. package/botrun-c/stress-test-results/20260121-163148/user-7-timing.txt +0 -1
  517. package/botrun-c/stress-test-results/20260121-163148/user-7.json +0 -62
  518. package/botrun-c/stress-test-results/20260121-163148/user-8-status.txt +0 -1
  519. package/botrun-c/stress-test-results/20260121-163148/user-8-timing.txt +0 -1
  520. package/botrun-c/stress-test-results/20260121-163148/user-8.json +0 -41
  521. package/botrun-c/stress-test-results/20260121-163148/user-9-status.txt +0 -1
  522. package/botrun-c/stress-test-results/20260121-163148/user-9-timing.txt +0 -1
  523. package/botrun-c/stress-test-results/20260121-163148/user-9.json +0 -53
  524. package/botrun-c/test-merge.md +0 -1
  525. package/botrun-c/version.txt +0 -1
  526. package/botrun-c/web/.version +0 -1
  527. package/botrun-c/web/ALLOW-PUBLIC-ACCESS.md +0 -57
  528. package/botrun-c/web/FINAL-SETUP-STEPS.md +0 -71
  529. package/botrun-c/web/FIREBASE-MANUAL-STEPS.md +0 -41
  530. package/botrun-c/web/QUICK-SETUP-HELPER.md +0 -79
  531. package/botrun-c/web/README.md +0 -162
  532. package/botrun-c/web/__tests__/image-extractor.test.ts +0 -147
  533. package/botrun-c/web/__tests__/useGitHub.test.ts +0 -245
  534. package/botrun-c/web/app/favicon.ico +0 -0
  535. package/botrun-c/web/app/globals.css +0 -215
  536. package/botrun-c/web/app/image-preview/page.tsx +0 -213
  537. package/botrun-c/web/app/layout.tsx +0 -46
  538. package/botrun-c/web/app/page.tsx +0 -45
  539. package/botrun-c/web/components/CapacitorProvider.tsx +0 -43
  540. package/botrun-c/web/components/ChatPage.tsx +0 -1736
  541. package/botrun-c/web/components/ConversationList.tsx +0 -237
  542. package/botrun-c/web/components/ConversationListItem.tsx +0 -121
  543. package/botrun-c/web/components/LoginPage.tsx +0 -212
  544. package/botrun-c/web/components/Providers.tsx +0 -12
  545. package/botrun-c/web/components/StatusBarProvider.tsx +0 -16
  546. package/botrun-c/web/components/attachment-preview.tsx +0 -193
  547. package/botrun-c/web/components/botrun-incubator/BotrunIncubator.tsx +0 -266
  548. package/botrun-c/web/components/botrun-incubator/index.ts +0 -1
  549. package/botrun-c/web/components/chat-input.tsx +0 -1251
  550. package/botrun-c/web/components/chat-message.tsx +0 -598
  551. package/botrun-c/web/components/drop-zone.tsx +0 -143
  552. package/botrun-c/web/components/gdrive/gdrive-add-dialog.tsx +0 -254
  553. package/botrun-c/web/components/gdrive/gdrive-drawer.tsx +0 -236
  554. package/botrun-c/web/components/gdrive/gdrive-selector.tsx +0 -226
  555. package/botrun-c/web/components/gdrive/index.ts +0 -7
  556. package/botrun-c/web/components/generated-image-card.tsx +0 -363
  557. package/botrun-c/web/components/github/GitHubConnect.tsx +0 -366
  558. package/botrun-c/web/components/github/index.ts +0 -2
  559. package/botrun-c/web/components/import-skill-dialog.tsx +0 -208
  560. package/botrun-c/web/components/scroll-to-bottom-button.tsx +0 -90
  561. package/botrun-c/web/components/share-skill-dialog.tsx +0 -175
  562. package/botrun-c/web/components/skills-editor.tsx +0 -443
  563. package/botrun-c/web/components/skills-manager-drawer.tsx +0 -586
  564. package/botrun-c/web/components/smart-button-group.tsx +0 -132
  565. package/botrun-c/web/config/smart-buttons.ts +0 -295
  566. package/botrun-c/web/config/welcome-messages.ts +0 -29
  567. package/botrun-c/web/contexts/AuthContext.tsx +0 -220
  568. package/botrun-c/web/hooks/use-toast.ts +0 -187
  569. package/botrun-c/web/hooks/useAppStoreUpdate.ts +0 -356
  570. package/botrun-c/web/hooks/useAppVersion.ts +0 -113
  571. package/botrun-c/web/hooks/useAttachments.ts +0 -269
  572. package/botrun-c/web/hooks/useAuthToken.ts +0 -59
  573. package/botrun-c/web/hooks/useAutoWelcome.ts +0 -89
  574. package/botrun-c/web/hooks/useCapacitorPlatform.ts +0 -113
  575. package/botrun-c/web/hooks/useCapawesomeLiveUpdate.ts +0 -149
  576. package/botrun-c/web/hooks/useClaudeModel.ts +0 -118
  577. package/botrun-c/web/hooks/useConversation.ts +0 -115
  578. package/botrun-c/web/hooks/useConversations.ts +0 -235
  579. package/botrun-c/web/hooks/useGdriveFolders.ts +0 -199
  580. package/botrun-c/web/hooks/useGdriveSync.ts +0 -107
  581. package/botrun-c/web/hooks/useGitHub.ts +0 -409
  582. package/botrun-c/web/hooks/useScrollToBottom.ts +0 -150
  583. package/botrun-c/web/hooks/useStatusBar.ts +0 -42
  584. package/botrun-c/web/hooks/useUserPlan.ts +0 -153
  585. package/botrun-c/web/lib/api-config.ts +0 -162
  586. package/botrun-c/web/lib/api-url.ts +0 -62
  587. package/botrun-c/web/lib/api.ts +0 -105
  588. package/botrun-c/web/lib/attachment-utils.ts +0 -168
  589. package/botrun-c/web/lib/build-version.ts +0 -11
  590. package/botrun-c/web/lib/clipboard-utils.ts +0 -199
  591. package/botrun-c/web/lib/constants.ts +0 -62
  592. package/botrun-c/web/lib/conversation-storage.ts +0 -324
  593. package/botrun-c/web/lib/firebase-capacitor.ts +0 -135
  594. package/botrun-c/web/lib/firebase.ts +0 -137
  595. package/botrun-c/web/lib/firestore-auth.ts +0 -61
  596. package/botrun-c/web/lib/firestore-auth.ts.migration-bak +0 -319
  597. package/botrun-c/web/lib/image-extractor.ts +0 -156
  598. package/botrun-c/web/lib/path-utils.ts +0 -48
  599. package/botrun-c/web/lib/rehype-del-to-mark.ts +0 -19
  600. package/botrun-c/web/lib/upload-service.ts +0 -326
  601. package/botrun-c/web/lib/utils.ts +0 -6
  602. package/botrun-c/web/next.config.js +0 -49
  603. package/botrun-c/web/package-lock.json +0 -6711
  604. package/botrun-c/web/package.json +0 -63
  605. package/botrun-c/web/postcss.config.js +0 -6
  606. package/botrun-c/web/public/apple_icon.png +0 -0
  607. package/botrun-c/web/public/google_icon.png +0 -0
  608. package/botrun-c/web/public/logo-48-small.png +0 -0
  609. package/botrun-c/web/public/logo-48.png +0 -0
  610. package/botrun-c/web/scripts/generate-version.js +0 -36
  611. package/botrun-c/web/scripts/update-build-version.js +0 -41
  612. package/botrun-c/web/tailwind.config.ts +0 -80
  613. package/botrun-c/web/test-welcome-message.mjs +0 -115
  614. package/botrun-c/web/tsconfig.json +0 -42
  615. package/botrun-c/web/types/agent.ts +0 -6
  616. package/botrun-c/web/types/attachment.ts +0 -39
  617. package/botrun-c/web/types/conversation.ts +0 -68
  618. package/botrun-c/web/types/flutter.d.ts +0 -6
  619. package/botrun-c/web/types/project.ts +0 -41
  620. package/botrun-c/web/types/skills.ts +0 -13
  621. package/botrun-c/web//350/250/272/346/226/267-Skills-/345/211/215/347/253/257/351/241/257/347/244/272/345/225/217/351/241/214.md +0 -83
  622. package/lib/writing/index.mjs +0 -5
  623. /package/{lib/core → modules/_core}/adapters/base.mjs +0 -0
  624. /package/{lib/core → modules/_core}/adapters/claude.mjs +0 -0
  625. /package/{lib/core → modules/_core}/adapters/gemini-api.mjs +0 -0
  626. /package/{lib/core → modules/_core}/adapters/gemini-shared.mjs +0 -0
  627. /package/{lib/core → modules/_core}/adapters/gemini-vertex.mjs +0 -0
  628. /package/{lib/core → modules/_core}/adapters/local.mjs +0 -0
  629. /package/{lib/core → modules/_core}/adapters/nchc.mjs +0 -0
  630. /package/{lib/core → modules/_core}/adapters/openai-shared.mjs +0 -0
  631. /package/{lib/core → modules/_core}/adapters/openrouter.mjs +0 -0
  632. /package/{lib/core → modules/_core}/ai-cache.mjs +0 -0
  633. /package/{lib/core → modules/_core}/ai-router.mjs +0 -0
  634. /package/{lib/core → modules/_core}/cli-utils.mjs +0 -0
  635. /package/{lib/core → modules/_core}/dag.mjs +0 -0
  636. /package/{lib/core → modules/_core}/db.mjs +0 -0
  637. /package/{lib/core → modules/_core}/env.mjs +0 -0
  638. /package/{lib/flows → modules/_core}/hatch-portal.mjs +0 -0
  639. /package/{lib/core → modules/_core}/llm.mjs +0 -0
  640. /package/{lib/flows → modules/_core}/opencode-agent.mjs +0 -0
  641. /package/{lib/core → modules/_core}/paths.mjs +0 -0
  642. /package/{lib/core → modules/_core}/proxy.mjs +0 -0
  643. /package/{lib/flows → modules/_core}/review-doc.mjs +0 -0
  644. /package/{lib → modules/_core}/tools/fs-tools.mjs +0 -0
  645. /package/{lib → modules/_core}/tools/index.mjs +0 -0
  646. /package/{lib/core → modules/_core}/watermelon.mjs +0 -0
  647. /package/{lib/ocr/index.mjs → modules/ocr/ocr-lib.mjs} +0 -0
  648. /package/{lib → modules}/portal/hatch.mjs +0 -0
  649. /package/{lib/portal/index.mjs → modules/portal/portal-lib.mjs} +0 -0
  650. /package/{lib → modules}/search/crawler.mjs +0 -0
  651. /package/{lib/prompt → modules/skill}/prompts/zero-framework/coding.md +0 -0
  652. /package/{lib/prompt → modules/skill}/prompts/zero-framework/fullstack.md +0 -0
  653. /package/{lib/prompt → modules/skill}/prompts/zero-framework/gemini-search-cli.md +0 -0
  654. /package/{lib/prompt → modules/skill}/prompts/zero-framework/search.md +0 -0
  655. /package/{lib/prompt → modules/skill}/prompts/zero-framework/segment.md +0 -0
  656. /package/{lib/prompt → modules/skill}/prompts/zero-framework/slice.md +0 -0
  657. /package/{lib/prompt → modules/skill}/prompts/zero-framework/smart-cache.md +0 -0
  658. /package/{lib/prompt → modules/skill}/prompts/zero-framework/trigram-search.md +0 -0
  659. /package/{lib/prompt → modules/skill}/prompts/zero-framework/tts-format.md +0 -0
  660. /package/{lib/prompt → modules/skill}/prompts/zero-framework/watermelon-sqlite.md +0 -0
  661. /package/{lib → modules}/writing/generators/nstc-generators.mjs +0 -0
  662. /package/{lib → modules}/writing/generators/nstc-top5.mjs +0 -0
  663. /package/{lib → modules}/writing/layouts/nstc-layout.mjs +0 -0
  664. /package/{lib → modules}/writing/renderer.mjs +0 -0
@@ -1,1251 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useRef, useEffect, useCallback } from "react";
4
- import { Button } from "@/components/ui/button";
5
- import { Textarea } from "@/components/ui/textarea";
6
- import { Send, Loader2, Paperclip, StopCircle, Mic, Square, Download, AlertCircle, RefreshCw, X, Settings, Shield, User, Camera as CameraIcon, Folder, Video, Info } from "lucide-react";
7
- import {
8
- DropdownMenu,
9
- DropdownMenuContent,
10
- DropdownMenuItem,
11
- DropdownMenuLabel,
12
- DropdownMenuSeparator,
13
- DropdownMenuTrigger,
14
- } from "@/components/ui/dropdown-menu";
15
- import { useAuth } from "@/contexts/AuthContext";
16
- import { useCapacitorPlatform } from "@/hooks/useCapacitorPlatform";
17
- import { SkillsManagerDrawer } from "./skills-manager-drawer";
18
- import { SkillsEditor } from "./skills-editor";
19
- import { getApiEndpoint } from "@/lib/api-url";
20
- import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder';
21
- import { SystemLimits, DEFAULT_LIMITS, formatFileSize } from "@/lib/constants";
22
- import { Filesystem, Directory } from '@capacitor/filesystem';
23
- import { Share } from '@capacitor/share';
24
- import { ForegroundService } from '@capawesome-team/capacitor-android-foreground-service';
25
- import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
26
- import { uploadFile, uploadFiles } from "@/lib/upload-service";
27
- import { convertToLocalPath } from "@/lib/path-utils";
28
- import type { Skill } from "@/types/skills";
29
- import { useAttachments } from "@/hooks/useAttachments";
30
- import { AttachmentPreview } from "./attachment-preview";
31
- import { DropZone } from "./drop-zone";
32
-
33
- // API URL - 根據環境和當前路徑動態計算
34
- const API_URL = (() => {
35
- if (typeof window === 'undefined') return '';
36
-
37
- // Capacitor (iOS/Android App)
38
- if ((window as any).Capacitor) {
39
- return process.env.NEXT_PUBLIC_API_URL || 'https://botrun-c-257949799705.asia-east1.run.app';
40
- }
41
-
42
- // Load Balancer 架構:檢查是否在個人後端 (/u/{hash}/)
43
- const pathname = window.location.pathname;
44
- if (pathname.startsWith('/u/')) {
45
- const match = pathname.match(/^\/u\/[0-9a-f]{8}/);
46
- if (match) {
47
- return match[0];
48
- }
49
- }
50
-
51
- // 使用環境變數設定的 API URL(如果有的話)
52
- if (process.env.NEXT_PUBLIC_API_URL) {
53
- return process.env.NEXT_PUBLIC_API_URL;
54
- }
55
-
56
- // 統一前端或其他情況:使用相對路徑(空字串)
57
- return '';
58
- })();
59
-
60
- interface ChatInputProps {
61
- onSend: (message: string) => void;
62
- onStop?: () => void;
63
- disabled?: boolean;
64
- isLoading?: boolean;
65
- isStopping?: boolean;
66
- placeholder?: string;
67
- transcriptionModel?: string;
68
- }
69
-
70
- export function ChatInput({
71
- onSend,
72
- onStop,
73
- disabled = false,
74
- isLoading = false,
75
- isStopping = false,
76
- placeholder = "Type your message...",
77
- transcriptionModel = 'whisper-Breeze-ASR-25',
78
- }: ChatInputProps) {
79
- const { user } = useAuth();
80
- const { isCapacitor, isAndroid, isIOS } = useCapacitorPlatform();
81
-
82
- // 🔑 判斷是否為桌面 Web(非 App + 非行動裝置 UserAgent)
83
- const isDesktopWeb = !isCapacitor &&
84
- typeof window !== 'undefined' &&
85
- !/iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
86
-
87
-
88
- // 🔄 測試:改回受控組件 + composition 保護
89
- const [message, setMessage] = useState("");
90
- const [uploading, setUploading] = useState(false);
91
- const [skills, setSkills] = useState<Skill[]>([]);
92
- const [systemLimits, setSystemLimits] = useState<SystemLimits>(DEFAULT_LIMITS);
93
- const [isRecording, setIsRecording] = useState(false);
94
- const [transcribing, setTranscribing] = useState(false);
95
- const [transcriptionStatus, setTranscriptionStatus] = useState("");
96
- const [recordedAudio, setRecordedAudio] = useState<Blob | null>(null);
97
- const [transcriptionError, setTranscriptionError] = useState<string | null>(null);
98
- const [managerDrawerOpen, setManagerDrawerOpen] = useState(false);
99
- const [editorOpen, setEditorOpen] = useState(false);
100
- const [skillToEdit, setSkillToEdit] = useState<Skill | null>(null);
101
- const [attachmentError, setAttachmentError] = useState<string | null>(null);
102
- const isComposingRef = useRef(false); // 追蹤輸入法狀態
103
-
104
- // 統一附件系統
105
- const {
106
- attachments,
107
- addFiles,
108
- removeAttachment,
109
- clearAll: clearAllAttachments,
110
- retryUpload,
111
- uploadAll,
112
- isUploading: isUploadingAttachments,
113
- hasAttachments,
114
- canAddMore,
115
- count: attachmentCount,
116
- } = useAttachments({
117
- maxCount: 20,
118
- maxFileSize: systemLimits.http.maxFileSize,
119
- onError: (error) => setAttachmentError(error),
120
- });
121
- const inputRef = useRef<HTMLTextAreaElement>(null);
122
- const fileInputRef = useRef<HTMLInputElement>(null);
123
- const videoInputRef = useRef<HTMLInputElement>(null); // 錄影專用
124
-
125
- // Load system limits
126
- const loadSystemLimits = async () => {
127
- try {
128
- const url = getApiEndpoint("/api/config/limits");
129
- const response = await fetch(url);
130
- if (response.ok) {
131
- const limits = await response.json();
132
- setSystemLimits(limits);
133
- // Removed: console.log('[ChatInput] System limits loaded:', limits);
134
- }
135
- } catch (error) {
136
- console.error('[ChatInput] Failed to load system limits, using defaults:', error);
137
- }
138
- };
139
-
140
- // Load skills
141
- const loadSkills = async () => {
142
- const url = getApiEndpoint("/api/skills");
143
- // Removed: console.log('[ChatInput] Loading skills from:', url);
144
-
145
- try {
146
- // 如果已登入,帶上認證 token
147
- const headers: HeadersInit = {};
148
- if (user) {
149
- const idToken = await user.getIdToken();
150
- headers.Authorization = `Bearer ${idToken}`;
151
- // Removed: console.log('[ChatInput] Loading with auth token');
152
- }
153
- // Removed: else { console.log('[ChatInput] Loading without auth (guest mode)'); }
154
-
155
- const res = await fetch(url, { headers });
156
- const data = await res.json();
157
- // Removed: console.log('[ChatInput] Skills loaded:', data);
158
-
159
- if (data.success && Array.isArray(data.skills)) {
160
- setSkills(data.skills);
161
- }
162
- } catch (error) {
163
- console.error("Failed to load skills:", error);
164
- }
165
- };
166
-
167
- // 當 user 狀態變化時重新載入 skills(登入/登出時)
168
- useEffect(() => {
169
- loadSystemLimits();
170
- loadSkills();
171
- }, [user]);
172
-
173
- useEffect(() => {
174
- // 只在 Web 環境下,LLM 回傳完畢後自動 focus
175
- // Mobile App 不 auto-focus,避免鍵盤自動彈出干擾閱讀
176
- if (!disabled && !isCapacitor) {
177
- inputRef.current?.focus();
178
- }
179
- }, [disabled, isCapacitor]);
180
-
181
- // 🔄 受控組件版本:自動調整高度(只在非 composition 期間)
182
- useEffect(() => {
183
- // 在 composition 期間不調整高度,避免干擾 IME
184
- if (isComposingRef.current) return;
185
-
186
- const textarea = inputRef.current;
187
- if (!textarea) return;
188
-
189
- // 調整高度
190
- textarea.style.height = 'auto';
191
- const newHeight = Math.min(textarea.scrollHeight, 200);
192
- textarea.style.height = `${newHeight}px`;
193
- textarea.style.overflowY = textarea.scrollHeight > 200 ? 'auto' : 'hidden';
194
- }, [message]); // message 改變時調整高度
195
-
196
- const handleSubmit = async (e: React.FormEvent) => {
197
- e.preventDefault();
198
-
199
- const messageText = message.trim();
200
-
201
- // 需要有訊息或附件才能送出
202
- if (!messageText && !hasAttachments) return;
203
- if (disabled) return;
204
-
205
- // 如果有附件,需要先上傳
206
- if (hasAttachments) {
207
- if (!user) {
208
- alert("請先登入");
209
- return;
210
- }
211
-
212
- setUploading(true);
213
- try {
214
- const idToken = await user.getIdToken();
215
- const results = await uploadAll(idToken, user.uid, 'default');
216
-
217
- // 取得成功上傳的檔案路徑
218
- const attachmentPaths = results
219
- .filter(r => r.success)
220
- .map(r => convertToLocalPath(r.displayPath));
221
-
222
- // 組合最終訊息
223
- const finalMessage = attachmentPaths.length > 0
224
- ? `${attachmentPaths.join('\n')}\n\n${messageText}`
225
- : messageText;
226
-
227
- // 發送訊息
228
- if (finalMessage.trim()) {
229
- onSend(finalMessage);
230
- }
231
-
232
- // 清空
233
- setMessage('');
234
- clearAllAttachments();
235
- setAttachmentError(null);
236
-
237
- } catch (error) {
238
- console.error('Send error:', error);
239
- setAttachmentError('發送失敗,請重試');
240
- } finally {
241
- setUploading(false);
242
- }
243
- } else {
244
- // 沒有附件,直接發送訊息
245
- onSend(messageText);
246
- setMessage('');
247
- }
248
- };
249
-
250
- const handleSkillSelect = (skill: Skill) => {
251
- // Send skill name directly to chat
252
- onSend(skill.name);
253
- };
254
-
255
- // 開啟編輯器(編輯模式)
256
- const handleEditSkill = (skill: Skill) => {
257
- setSkillToEdit(skill);
258
- setManagerDrawerOpen(false);
259
- setEditorOpen(true);
260
- };
261
-
262
- // 開啟編輯器(新增模式)
263
- const handleCreateSkill = () => {
264
- setSkillToEdit(null);
265
- setManagerDrawerOpen(false);
266
- setEditorOpen(true);
267
- };
268
-
269
- // Skills 變更後重新載入
270
- const handleSkillsChange = () => {
271
- loadSkills();
272
- };
273
-
274
- const handleKeyDown = (e: React.KeyboardEvent) => {
275
- // 忽略輸入法組合中的 Enter
276
- if (e.nativeEvent.isComposing) return;
277
-
278
- if (e.key === "Enter") {
279
- if (isDesktopWeb) {
280
- // 桌面 Web:Shift+Enter 換行、Enter 送出
281
- if (!e.shiftKey) {
282
- e.preventDefault();
283
- handleSubmit(e);
284
- }
285
- // Shift+Enter 時不阻止,允許換行
286
- }
287
- // 其他情況(手機 Web、iOS App、Android App):Enter 換行,不處理
288
- }
289
- };
290
-
291
- // 處理輸入法 composition events(中文、日文、韓文等)
292
- const handleCompositionStart = () => {
293
- isComposingRef.current = true;
294
- };
295
-
296
- const handleCompositionEnd = (e: React.CompositionEvent<HTMLTextAreaElement>) => {
297
- isComposingRef.current = false;
298
-
299
- // 確保最終值被更新
300
- setMessage(e.currentTarget.value);
301
- };
302
-
303
- // 處理一般輸入
304
- const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
305
- setMessage(e.target.value);
306
- };
307
-
308
- // 處理檔案選擇(統一附件系統:只加入預覽,送出時才上傳)
309
- const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
310
- const selectedFiles = e.target.files;
311
- if (!selectedFiles || selectedFiles.length === 0) return;
312
-
313
- // 加入附件列表(延遲上傳)
314
- addFiles(Array.from(selectedFiles));
315
-
316
- // Reset input value to allow selecting the same file again
317
- if (fileInputRef.current) {
318
- fileInputRef.current.value = "";
319
- }
320
- }, [addFiles]);
321
-
322
- // 處理貼上事件(支援截圖和圖片)
323
- const handlePaste = useCallback((e: React.ClipboardEvent<HTMLTextAreaElement>) => {
324
- const items = e.clipboardData?.items;
325
- if (!items) return;
326
-
327
- const files: File[] = [];
328
- for (const item of items) {
329
- if (item.kind === 'file') {
330
- const file = item.getAsFile();
331
- if (file) {
332
- files.push(file);
333
- }
334
- }
335
- }
336
-
337
- // 如果有檔案,加入附件列表並阻止預設的文字貼上
338
- if (files.length > 0) {
339
- e.preventDefault();
340
- addFiles(files);
341
- }
342
- // 如果沒有檔案(純文字),讓預設行為處理
343
- }, [addFiles]);
344
-
345
- // 處理拖放事件
346
- const handleDrop = useCallback((files: File[]) => {
347
- addFiles(files);
348
- }, [addFiles]);
349
-
350
- const handleFileButtonClick = () => {
351
- fileInputRef.current?.click();
352
- };
353
-
354
- const handleVideoCaptureClick = () => {
355
- videoInputRef.current?.click();
356
- };
357
-
358
- // 使用 Capacitor Camera API 處理拍照/錄影/相簿選取(統一附件系統)
359
- const handleMediaCapture = async () => {
360
- try {
361
- // 使用 Camera.getPhoto 搭配 Prompt 模式
362
- // 會顯示原生選單:拍照、從相簿選擇
363
- const media = await Camera.getPhoto({
364
- quality: 90,
365
- allowEditing: false,
366
- resultType: CameraResultType.Uri,
367
- source: CameraSource.Camera, // 關鍵:不跳出選項,直接進入相機
368
- saveToGallery: false, // 拍照後不自動存到相簿(避免重複)
369
- });
370
-
371
- if (!media.webPath) {
372
- throw new Error("無法取得媒體檔案");
373
- }
374
-
375
- // 將 webPath 轉換為 Blob
376
- const response = await fetch(media.webPath);
377
- const blob = await response.blob();
378
-
379
- // 根據 format 判斷檔案類型和副檔名
380
- const format = media.format || 'jpg';
381
- const mimeType = blob.type || (format.includes('mp4') || format.includes('mov') ? 'video/mp4' : 'image/jpeg');
382
- const fileName = `capture_${Date.now()}.${format}`;
383
- const file = new File([blob], fileName, { type: mimeType });
384
-
385
- // 加入附件列表(延遲上傳)
386
- addFiles([file]);
387
-
388
- } catch (error: any) {
389
- // 使用者取消操作不顯示錯誤
390
- if (error.message === 'User cancelled photos app') {
391
- console.log('📸 [Media Capture] 使用者取消操作');
392
- } else {
393
- console.error('❌ [Media Capture] 失敗:', error);
394
- alert("媒體操作失敗: " + (error instanceof Error ? error.message : "未知錯誤"));
395
- }
396
- }
397
- };
398
-
399
- // Helper: 將 base64 字串轉換為 Blob
400
- const base64ToBlob = (base64: string, mimeType: string): Blob => {
401
- const byteCharacters = atob(base64);
402
- const byteNumbers = new Array(byteCharacters.length);
403
- for (let i = 0; i < byteCharacters.length; i++) {
404
- byteNumbers[i] = byteCharacters.charCodeAt(i);
405
- }
406
- const byteArray = new Uint8Array(byteNumbers);
407
- return new Blob([byteArray], { type: mimeType });
408
- };
409
-
410
- // Helper: 記憶體監控
411
- const logMemoryUsage = (label: string) => {
412
- if ((performance as any).memory) {
413
- const m = (performance as any).memory;
414
- console.log(`💾 [Memory] ${label}:`, {
415
- used: `${(m.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
416
- total: `${(m.totalJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
417
- percent: `${((m.usedJSHeapSize / m.jsHeapSizeLimit) * 100).toFixed(1)}%`,
418
- });
419
- }
420
- };
421
-
422
- // 儲存錄音到 IndexedDB
423
- const saveAudioToIndexedDB = async (audioBlob: Blob, timestamp: number) => {
424
- try {
425
- const dbName = 'AudioRecordings';
426
- const storeName = 'recordings';
427
-
428
- const request = indexedDB.open(dbName, 1);
429
-
430
- request.onupgradeneeded = (event) => {
431
- const db = (event.target as IDBOpenDBRequest).result;
432
- if (!db.objectStoreNames.contains(storeName)) {
433
- db.createObjectStore(storeName, { keyPath: 'timestamp' });
434
- }
435
- };
436
-
437
- return new Promise<void>((resolve, reject) => {
438
- request.onsuccess = () => {
439
- const db = request.result;
440
- const transaction = db.transaction([storeName], 'readwrite');
441
- const store = transaction.objectStore(storeName);
442
- store.put({ timestamp, audioBlob, createdAt: new Date().toISOString() });
443
-
444
- transaction.oncomplete = () => {
445
- console.log('✅ 錄音已儲存到 IndexedDB');
446
- resolve();
447
- };
448
-
449
- transaction.onerror = () => reject(transaction.error);
450
- };
451
-
452
- request.onerror = () => reject(request.error);
453
- });
454
- } catch (error) {
455
- console.error('儲存錄音失敗:', error);
456
- }
457
- };
458
-
459
- // 從 IndexedDB 讀取錄音
460
- const getAudioFromIndexedDB = async (timestamp: number): Promise<Blob | null> => {
461
- try {
462
- const dbName = 'AudioRecordings';
463
- const storeName = 'recordings';
464
-
465
- const request = indexedDB.open(dbName, 1);
466
-
467
- return new Promise((resolve, reject) => {
468
- request.onsuccess = () => {
469
- const db = request.result;
470
- const transaction = db.transaction([storeName], 'readonly');
471
- const store = transaction.objectStore(storeName);
472
- const getRequest = store.get(timestamp);
473
-
474
- getRequest.onsuccess = () => {
475
- const result = getRequest.result;
476
- resolve(result ? result.audioBlob : null);
477
- };
478
-
479
- getRequest.onerror = () => reject(getRequest.error);
480
- };
481
-
482
- request.onerror = () => reject(request.error);
483
- });
484
- } catch (error) {
485
- console.error('讀取錄音失敗:', error);
486
- return null;
487
- }
488
- };
489
-
490
- // 轉錄音訊(帶 retry 機制)- 使用 presigned URL 避免 413 錯誤
491
- const transcribeAudio = async (audioBlob: Blob, timestamp: number, retryCount = 0): Promise<boolean> => {
492
- const maxRetries = 3;
493
-
494
- try {
495
- setTranscribing(true);
496
- setTranscriptionError(null);
497
- setTranscriptionStatus(`🎤 轉錄中... ${retryCount > 0 ? `(重試 ${retryCount}/${maxRetries})` : ''}`);
498
-
499
- if (!user) {
500
- throw new Error("請先登入");
501
- }
502
-
503
- const idToken = await user.getIdToken();
504
-
505
- const mimeType = audioBlob.type || 'audio/webm';
506
- const extension = mimeType.includes('mp4') || mimeType.includes('m4a') ? 'm4a' :
507
- mimeType.includes('aac') ? 'aac' :
508
- mimeType.includes('mpeg') || mimeType.includes('mp3') ? 'mp3' : 'webm';
509
- const fileName = `recording-${timestamp}.${extension}`;
510
-
511
- const audioFile = new File([audioBlob], fileName, { type: mimeType });
512
-
513
- // 🔍 記錄音訊檔案資訊
514
- const fileSizeMB = (audioFile.size / 1024 / 1024).toFixed(2);
515
- console.log(`🎤 [Transcribe] Audio file: ${fileSizeMB} MB (${audioFile.size} bytes), ${mimeType}`);
516
-
517
- // 🚀 Step 1: 上傳音訊到 GCS(使用統一上傳服務)
518
- setTranscriptionStatus(`📤 上傳音訊中...`);
519
- const uploadStartTime = Date.now();
520
- console.log(`📤 [Transcribe] Upload started at ${new Date(uploadStartTime).toLocaleTimeString()}`);
521
-
522
- const uploadResult = await uploadFile({
523
- file: audioFile,
524
- idToken,
525
- userId: user.uid,
526
- projectId: 'default'
527
- });
528
-
529
- const uploadEndTime = Date.now();
530
- const uploadDuration = ((uploadEndTime - uploadStartTime) / 1000).toFixed(2);
531
-
532
- if (!uploadResult.success) {
533
- console.error(`❌ [Transcribe] Upload failed after ${uploadDuration}s:`, uploadResult.error);
534
- throw new Error(uploadResult.error || "音訊上傳失敗");
535
- }
536
-
537
- const gcsPath = uploadResult.displayPath;
538
- const audioUploadId = uploadResult.audioUploadId;
539
- console.log(`✅ [Transcribe] Upload completed in ${uploadDuration}s → ${gcsPath}`);
540
- console.log(`🎫 [Transcribe] audioUploadId: ${audioUploadId}`);
541
- logMemoryUsage('After upload completed');
542
-
543
- // 🚀 Step 2: 呼叫轉錄 API(傳遞 GCS URI 和 audioUploadId)
544
- setTranscriptionStatus(`🎤 轉錄中... ${retryCount > 0 ? `(重試 ${retryCount}/${maxRetries})` : ''}`);
545
- const transcribeStartTime = Date.now();
546
- console.log(`🎤 [Transcribe] Transcription started at ${new Date(transcribeStartTime).toLocaleTimeString()}`);
547
-
548
- const response = await fetch(getApiEndpoint("/api/transcribe-audio"), {
549
- method: "POST",
550
- headers: {
551
- "Content-Type": "application/json",
552
- "Authorization": `Bearer ${idToken}`,
553
- ...(audioUploadId && { "X-Audio-Upload-Id": audioUploadId }),
554
- },
555
- body: JSON.stringify({
556
- gcsUri: gcsPath,
557
- model: transcriptionModel,
558
- }),
559
- });
560
-
561
- const transcribeEndTime = Date.now();
562
- const transcribeDuration = ((transcribeEndTime - transcribeStartTime) / 1000).toFixed(2);
563
-
564
- if (!response.ok) {
565
- console.error(`❌ [Transcribe] API failed after ${transcribeDuration}s: HTTP ${response.status}`);
566
- throw new Error(`HTTP error! status: ${response.status}`);
567
- }
568
-
569
- const result = await response.json();
570
- console.log(`🔍 [Transcribe] API response received in ${transcribeDuration}s:`, {
571
- success: result.success,
572
- resultsCount: result.results?.length || 0,
573
- hasResults: !!result.results,
574
- firstResultKeys: result.results?.[0] ? Object.keys(result.results[0]) : [],
575
- textLength: result.results?.[0]?.text?.length || 0
576
- });
577
-
578
- // 🔍 Debug: 詳細檢查條件
579
- console.log(`🔍 [Transcribe] Condition check:`, {
580
- 'response.ok': response.ok,
581
- 'result.success': result.success,
582
- 'result.results': !!result.results,
583
- 'result.results.length': result.results?.length || 0,
584
- 'condition met': response.ok && result.success && result.results && result.results.length > 0
585
- });
586
-
587
- if (response.ok && result.success && result.results && result.results.length > 0) {
588
- // 轉錄成功
589
- const transcripts = result.results.map((r: any) => r.text).join('\n\n');
590
- const textLength = transcripts.length;
591
- const totalDuration = ((transcribeEndTime - uploadStartTime) / 1000).toFixed(2);
592
-
593
- console.log(`✅ [Transcribe] Success! Transcription completed in ${transcribeDuration}s`);
594
- console.log(`📊 [Transcribe] Summary: Upload ${uploadDuration}s + Transcribe ${transcribeDuration}s = Total ${totalDuration}s`);
595
- console.log(`📝 [Transcribe] Transcribed ${textLength} characters`);
596
-
597
- // 檢查是否為空字串(未偵測到語音)
598
- if (transcripts === '' || transcripts.trim() === '') {
599
- console.log('ℹ️ [Transcribe] No speech detected in audio');
600
- setTranscriptionStatus('no-speech');
601
- setRecordedAudio(null);
602
- setTranscriptionError(null);
603
-
604
- // 5秒後清除狀態
605
- setTimeout(() => {
606
- setTranscriptionStatus("");
607
- setTranscribing(false);
608
- }, 5000);
609
-
610
- return true;
611
- }
612
-
613
- // 有內容的正常轉錄,自動送出
614
- onSend(transcripts);
615
-
616
- setTranscriptionStatus(`✅ 轉錄成功!`);
617
- setRecordedAudio(null);
618
- setTranscriptionError(null);
619
-
620
- // 3秒後清除狀態
621
- setTimeout(() => {
622
- setTranscriptionStatus("");
623
- setTranscribing(false);
624
- }, 3000);
625
-
626
- return true;
627
- } else {
628
- console.error('❌ [Transcribe] Failed:', result.error || "轉錄失敗");
629
- throw new Error(result.error || "轉錄失敗");
630
- }
631
- } catch (error) {
632
- console.error(`轉錄錯誤 (嘗試 ${retryCount + 1}/${maxRetries + 1}):`, error);
633
-
634
- // 如果還有重試機會,自動重試
635
- if (retryCount < maxRetries) {
636
- console.log(`⚠️ 將在 2 秒後重試...`);
637
- await new Promise(resolve => setTimeout(resolve, 2000));
638
- return transcribeAudio(audioBlob, timestamp, retryCount + 1);
639
- } else {
640
- // 已達最大重試次數
641
- const errorMessage = error instanceof Error ? error.message : "未知錯誤";
642
- setTranscriptionError(`轉錄失敗(已重試 ${maxRetries} 次): ${errorMessage}`);
643
- setTranscriptionStatus("");
644
- setTranscribing(false);
645
- return false;
646
- }
647
- }
648
- };
649
-
650
- // 開始/停止錄音(使用 Capacitor Audio Recorder,跨平台支援)
651
- const handleRecordingToggle = async () => {
652
- if (isRecording) {
653
- // 停止錄音
654
- try {
655
- const result = await AudioRecorder.stopRecording();
656
- console.log('🎤 Recording stopped:', result);
657
-
658
- // 🔥 Android: 關閉前景服務
659
- if ((window as any).Capacitor?.getPlatform() === 'android') {
660
- try {
661
- await ForegroundService.stopForegroundService();
662
- console.log('🔥 Foreground service stopped');
663
- } catch (fgError) {
664
- console.warn('⚠️ Failed to stop foreground service:', fgError);
665
- }
666
- }
667
-
668
- const timestamp = Date.now();
669
- let audioBlob: Blob | null = null;
670
-
671
- // Web 平台返回 blob,Native 平台返回 uri
672
- if (result.blob) {
673
- // Web: 直接使用 blob
674
- console.log('🎤 [Web] Using blob');
675
- audioBlob = result.blob;
676
- } else if (result.uri) {
677
- // Native (iOS/Android): 從 uri 讀取檔案並轉換為 blob
678
- try {
679
- const fileData = await Filesystem.readFile({
680
- path: result.uri
681
- });
682
-
683
- const base64Data = fileData.data as string;
684
- const mimeType = 'audio/mp4';
685
- audioBlob = base64ToBlob(base64Data, mimeType);
686
-
687
- if (audioBlob.size === 0) {
688
- throw new Error('Blob size is 0 - conversion failed');
689
- }
690
- } catch (fileError) {
691
- console.error('Failed to read/convert file:', fileError);
692
- throw new Error('無法讀取錄音檔案: ' + (fileError instanceof Error ? fileError.message : '未知錯誤'));
693
- }
694
- }
695
-
696
- if (audioBlob) {
697
- // 儲存到 IndexedDB
698
- await saveAudioToIndexedDB(audioBlob, timestamp);
699
-
700
- // 儲存到 state(用於 retry 和下載)
701
- setRecordedAudio(audioBlob);
702
-
703
- // 開始轉錄
704
- await transcribeAudio(audioBlob, timestamp);
705
- }
706
-
707
- setIsRecording(false);
708
- } catch (error) {
709
- console.error('❌ Failed to stop recording:', error);
710
- alert('停止錄音失敗: ' + (error instanceof Error ? error.message : '未知錯誤'));
711
- setIsRecording(false);
712
- }
713
- } else {
714
- // 開始錄音
715
- try {
716
- // 請求麥克風權限
717
- const permissions = await AudioRecorder.requestPermissions();
718
- console.log('🎤 Audio Permissions:', permissions);
719
-
720
- if (permissions.recordAudio !== 'granted') {
721
- alert('需要麥克風權限才能錄音');
722
- return;
723
- }
724
-
725
- // 🔥 Android: 請求通知權限並啟動前景服務(讓錄音在背景/螢幕關閉時繼續)
726
- if ((window as any).Capacitor?.getPlatform() === 'android') {
727
- try {
728
- // Android 13+ (API 33+) 需要明確請求通知權限
729
- const notificationPermission = await ForegroundService.checkPermissions();
730
- console.log('🔔 Notification Permission:', notificationPermission);
731
-
732
- if (notificationPermission.display !== 'granted') {
733
- console.log('🔔 Requesting notification permission...');
734
- const result = await ForegroundService.requestPermissions();
735
- console.log('🔔 Notification Permission Result:', result);
736
-
737
- if (result.display !== 'granted') {
738
- console.warn('⚠️ Notification permission denied, foreground service may not show notification');
739
- // 繼續執行,但前景服務可能無法顯示通知
740
- }
741
- }
742
-
743
- // 啟動前景服務
744
- await ForegroundService.startForegroundService({
745
- id: 1,
746
- title: '正在錄音',
747
- body: '點擊返回 App',
748
- smallIcon: 'ic_launcher',
749
- });
750
- console.log('🔥 Foreground service started');
751
- } catch (fgError) {
752
- console.warn('⚠️ Failed to start foreground service:', fgError);
753
- // 繼續錄音,即使前景服務啟動失敗
754
- }
755
- }
756
-
757
- // 開始錄音(優化設定:極致壓縮 - 方案 1)
758
- // 32 kbps + 16 kHz = ~240 KB/分鐘(相較原本 1.4 MB 減少 83%)
759
- // 上傳速度從 ~1.1 秒加速到 ~0.2 秒(5.5 倍加速)
760
- await AudioRecorder.startRecording({
761
- bitRate: 32000, // 32 kbps(語音辨識足夠)
762
- sampleRate: 16000, // 16 kHz(Whisper 最低支援,覆蓋所有語音頻率)
763
- });
764
- console.log('🎤 Recording started');
765
-
766
- setIsRecording(true);
767
- setTranscriptionStatus("🎤 錄音中...");
768
- } catch (error) {
769
- console.error('❌ Failed to start recording:', error);
770
- alert('無法啟動錄音: ' + (error instanceof Error ? error.message : '未知錯誤'));
771
- }
772
- }
773
- };
774
-
775
- // 下載錄音檔案
776
- const handleDownloadRecording = async () => {
777
- if (!recordedAudio) return;
778
-
779
- try {
780
- // 檢測平台
781
- const isNative = (window as any).Capacitor?.isNativePlatform() || false;
782
-
783
- if (isNative) {
784
- // Native 平台(iOS/Android):使用 Share API 觸發分享面板
785
- console.log('🔽 [Download] Native platform detected, using Share API');
786
-
787
- // 轉換 Blob 為 base64
788
- const reader = new FileReader();
789
- reader.onloadend = async () => {
790
- try {
791
- const base64Data = reader.result as string;
792
- const base64String = base64Data.split(',')[1]; // 移除 data URL prefix
793
-
794
- const fileName = `recording-${Date.now()}.${recordedAudio.type.includes('mp4') ? 'm4a' : 'webm'}`;
795
-
796
- // 寫入檔案到 Cache 目錄(暫存)
797
- const result = await Filesystem.writeFile({
798
- path: fileName,
799
- data: base64String,
800
- directory: Directory.Cache,
801
- });
802
-
803
- console.log('✅ [Download] Temp file created:', result.uri);
804
-
805
- // 使用 Share API 觸發系統分享面板
806
- await Share.share({
807
- title: '儲存錄音檔案',
808
- text: `錄音檔案:${fileName}`,
809
- url: result.uri, // iOS/Android 支援 file:// URLs
810
- dialogTitle: '選擇儲存位置',
811
- });
812
-
813
- console.log('✅ [Download] Share dialog completed');
814
- } catch (error) {
815
- const errorMessage = error instanceof Error ? error.message : String(error);
816
-
817
- // 如果使用者取消分享,不顯示錯誤訊息
818
- if (errorMessage.toLowerCase().includes('cancel')) {
819
- console.log('ℹ️ [Download] Share cancelled by user');
820
- return;
821
- }
822
-
823
- // 其他錯誤顯示為下載失敗
824
- console.error('❌ [Download] Failed to save file:', error);
825
- alert('下載失敗: ' + errorMessage);
826
- }
827
- };
828
- reader.readAsDataURL(recordedAudio);
829
- } else {
830
- // Web 平台:使用傳統下載方式
831
- console.log('🔽 [Download] Web platform detected, using blob URL');
832
-
833
- const url = URL.createObjectURL(recordedAudio);
834
- const a = document.createElement('a');
835
- a.href = url;
836
- a.download = `recording-${Date.now()}.webm`;
837
- document.body.appendChild(a);
838
- a.click();
839
- document.body.removeChild(a);
840
- URL.revokeObjectURL(url);
841
-
842
- console.log('✅ [Download] File downloaded');
843
- }
844
- } catch (error) {
845
- console.error('❌ [Download] Error:', error);
846
- alert('下載失敗: ' + (error instanceof Error ? error.message : '未知錯誤'));
847
- }
848
- };
849
-
850
- // 重試轉錄
851
- const handleRetryTranscription = async () => {
852
- if (!recordedAudio) return;
853
-
854
- const timestamp = Date.now();
855
- await transcribeAudio(recordedAudio, timestamp);
856
- };
857
-
858
-
859
- return (
860
- <DropZone onDrop={handleDrop} disabled={disabled || uploading || !canAddMore}>
861
- <div className="w-full border-t bg-background">
862
- {/* 錄音/轉錄狀態:只有異常時才顯示,正常狀態不凸顯(按鈕本身已表示狀態) */}
863
-
864
- {/* 轉錄錯誤處理 */}
865
- {transcriptionError && recordedAudio && (
866
- <div className="relative flex items-start gap-3 p-4 pb-3 bg-red-50/50 dark:bg-red-950/30">
867
- <div className="flex-shrink-0">
868
- <AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400" />
869
- </div>
870
- {/* [D-2] 加入 pr-8 預留 X 按鈕空間 */}
871
- <div className="flex-1 pr-8">
872
- <p className="text-sm font-medium text-red-900 dark:text-red-200 mb-2">
873
- {transcriptionError}
874
- </p>
875
- {/* [D-1] 加入 flex-col sm:flex-row 實現 responsive */}
876
- <div className="flex flex-col sm:flex-row gap-2">
877
- <Button
878
- type="button"
879
- size="sm"
880
- onClick={handleRetryTranscription}
881
- disabled={transcribing}
882
- variant="outline"
883
- className="border-red-300 hover:bg-red-100 dark:border-red-700 dark:hover:bg-red-900"
884
- >
885
- {transcribing ? (
886
- <>
887
- <Loader2 className="mr-2 h-3 w-3 animate-spin" />
888
- 重試中...
889
- </>
890
- ) : (
891
- <>
892
- <RefreshCw className="mr-2 h-3 w-3" />
893
- 重試轉錄
894
- </>
895
- )}
896
- </Button>
897
- <Button
898
- type="button"
899
- size="sm"
900
- onClick={handleDownloadRecording}
901
- variant="outline"
902
- className="border-red-300 hover:bg-red-100 dark:border-red-700 dark:hover:bg-red-900"
903
- >
904
- <Download className="mr-2 h-3 w-3" />
905
- 下載錄音
906
- </Button>
907
- </div>
908
- </div>
909
- <div className="absolute top-2 right-2">
910
- <Button
911
- type="button"
912
- size="icon"
913
- variant="ghost"
914
- onClick={() => setTranscriptionError(null)}
915
- className="h-6 w-6 text-red-600 hover:bg-red-100 dark:text-red-400 dark:hover:bg-red-900"
916
- aria-label="關閉錯誤訊息"
917
- >
918
- <X className="h-4 w-4" />
919
- </Button>
920
- </div>
921
- </div>
922
- )}
923
-
924
- {/* 未偵測到語音訊息 */}
925
- {transcriptionStatus === 'no-speech' && !transcriptionError && (
926
- <div className="relative">
927
- <div className="flex gap-3 p-4 pb-2 bg-blue-50 dark:bg-blue-950/50 border-l-4 border-blue-500">
928
- <Info className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
929
- <div className="flex-1 min-w-0">
930
- <p className="text-sm font-medium text-blue-900 dark:text-blue-200">
931
- 未偵測到語音內容
932
- </p>
933
- <p className="text-xs text-blue-700 dark:text-blue-400 mt-1">
934
- 請確認麥克風權限已開啟,或嘗試重新錄音
935
- </p>
936
- </div>
937
- </div>
938
- <div className="absolute top-2 right-2">
939
- <Button
940
- type="button"
941
- size="icon"
942
- variant="ghost"
943
- onClick={() => setTranscriptionStatus("")}
944
- className="h-6 w-6 text-blue-600 hover:bg-blue-100 dark:text-blue-400 dark:hover:bg-blue-900"
945
- aria-label="關閉訊息"
946
- >
947
- <X className="h-4 w-4" />
948
- </Button>
949
- </div>
950
- </div>
951
- )}
952
-
953
- {/* 錄音/轉錄狀態 */}
954
- {(transcribing || transcriptionStatus) && !transcriptionError && transcriptionStatus !== 'no-speech' && (
955
- <div className="flex items-center gap-2 p-4 pb-2 bg-amber-50/50 dark:bg-amber-950/30">
956
- <Loader2 className="h-4 w-4 animate-spin text-amber-600 dark:text-amber-500" />
957
- <div className="flex-1">
958
- <p className="text-sm font-medium text-amber-900 dark:text-amber-200">
959
- {transcriptionStatus}
960
- </p>
961
- <p className="text-xs text-amber-700 dark:text-amber-400">
962
- 轉錄完成後會自動送出(錄音已安全儲存)
963
- </p>
964
- </div>
965
- </div>
966
- )}
967
-
968
- {/* 上傳中狀態 */}
969
- {uploading && (
970
- <div className="flex items-center gap-2 p-4 pb-2 bg-blue-50/50 dark:bg-blue-950/30">
971
- <Loader2 className="h-4 w-4 animate-spin text-blue-600 dark:text-blue-500" />
972
- <p className="text-sm font-medium text-blue-900 dark:text-blue-200">
973
- 📤 正在自動上傳檔案...
974
- </p>
975
- </div>
976
- )}
977
-
978
- {/* 附件錯誤提示 */}
979
- {attachmentError && (
980
- <div className="relative flex items-start gap-3 p-4 pb-3 bg-red-50/50 dark:bg-red-950/30">
981
- <AlertCircle className="h-5 w-5 flex-shrink-0 text-red-600 dark:text-red-400" />
982
- <p className="text-sm font-medium text-red-900 dark:text-red-200 flex-1">
983
- {attachmentError}
984
- </p>
985
- <Button
986
- type="button"
987
- size="icon"
988
- variant="ghost"
989
- onClick={() => setAttachmentError(null)}
990
- className="h-6 w-6 text-red-600 hover:bg-red-100 dark:text-red-400 dark:hover:bg-red-900 flex-shrink-0"
991
- aria-label="關閉錯誤訊息"
992
- >
993
- <X className="h-4 w-4" />
994
- </Button>
995
- </div>
996
- )}
997
-
998
- {/* 附件預覽區 */}
999
- <AttachmentPreview
1000
- attachments={attachments}
1001
- maxCount={20}
1002
- onRemove={removeAttachment}
1003
- onRetry={retryUpload}
1004
- onClearAll={clearAllAttachments}
1005
- disabled={disabled || uploading}
1006
- />
1007
-
1008
- <form
1009
- onSubmit={handleSubmit}
1010
- className="flex w-full gap-2 p-3 sm:p-4"
1011
- >
1012
- {/* 檔案上傳 input (用於選擇本機檔案、多選等) */}
1013
- <input
1014
- ref={fileInputRef}
1015
- type="file"
1016
- multiple
1017
- onChange={handleFileSelect}
1018
- className="hidden"
1019
- />
1020
-
1021
- {/* 錄影專用 input */}
1022
- <input
1023
- ref={videoInputRef}
1024
- type="file"
1025
- capture="environment"
1026
- onChange={handleFileSelect}
1027
- className="hidden"
1028
- />
1029
-
1030
- {/* 上傳按鈕 - Web 直接上傳,App 顯示下拉選單 */}
1031
- {isCapacitor ? (
1032
- // Capacitor App:保留下拉選單(拍照、錄影)
1033
- <DropdownMenu>
1034
- <DropdownMenuTrigger asChild>
1035
- <Button
1036
- type="button"
1037
- size="icon"
1038
- variant="outline"
1039
- disabled={disabled}
1040
- className="shrink-0"
1041
- title="上傳檔案"
1042
- >
1043
- <Paperclip className="h-4 w-4" />
1044
- </Button>
1045
- </DropdownMenuTrigger>
1046
- <DropdownMenuContent align="start" className="w-64">
1047
- {/* Capacitor App:拍照和錄影分離 */}
1048
- {isCapacitor && (
1049
- <>
1050
- <DropdownMenuItem
1051
- onClick={handleFileButtonClick}
1052
- className="cursor-pointer hover:bg-accent font-medium"
1053
- >
1054
- <Folder className="mr-2 h-4 w-4" />
1055
- <span>上傳檔案</span>
1056
- </DropdownMenuItem>
1057
- {/* 拍照和錄影功能僅在 Android 平台顯示 */}
1058
- {isAndroid && (
1059
- <>
1060
- <DropdownMenuItem
1061
- onClick={handleMediaCapture}
1062
- className="cursor-pointer hover:bg-accent font-medium"
1063
- >
1064
- <CameraIcon className="mr-2 h-4 w-4" />
1065
- <span>拍照</span>
1066
- </DropdownMenuItem>
1067
- <DropdownMenuItem
1068
- onClick={handleVideoCaptureClick}
1069
- className="cursor-pointer hover:bg-accent font-medium"
1070
- >
1071
- <Video className="mr-2 h-4 w-4" />
1072
- <span>錄影</span>
1073
- </DropdownMenuItem>
1074
- </>
1075
- )}
1076
- </>
1077
- )}
1078
-
1079
- </DropdownMenuContent>
1080
- </DropdownMenu>
1081
- ) : (
1082
- // Web 環境:直接點擊上傳
1083
- <Button
1084
- type="button"
1085
- size="icon"
1086
- variant="outline"
1087
- disabled={disabled}
1088
- className="shrink-0"
1089
- onClick={handleFileButtonClick}
1090
- title="上傳檔案"
1091
- >
1092
- <Paperclip className="h-4 w-4" />
1093
- </Button>
1094
- )}
1095
-
1096
- {/* Input Field - 受控組件 + composition 保護 */}
1097
- <Textarea
1098
- ref={inputRef}
1099
- value={message}
1100
- onChange={handleChange}
1101
- onKeyDown={handleKeyDown}
1102
- onPaste={handlePaste}
1103
- onCompositionStart={handleCompositionStart}
1104
- onCompositionEnd={handleCompositionEnd}
1105
- placeholder={hasAttachments ? '新增說明文字...' : placeholder}
1106
- disabled={disabled}
1107
- className="flex-1 text-base resize-none overflow-hidden min-h-[2.25rem] max-h-[200px] leading-normal py-2"
1108
- autoComplete="off"
1109
- autoFocus
1110
- rows={1}
1111
- inputMode="text"
1112
- />
1113
-
1114
- {/* 動態右側按鈕:Stop (AI) > Stop (錄音) > Send > Mic */}
1115
- {isLoading ? (
1116
- // 1. Stop AI response (最高優先級)
1117
- // 旋轉圓環 + 停止方塊:表示 AI 思考中,可點擊停止
1118
- <Button
1119
- type="button"
1120
- size="icon"
1121
- variant="ghost"
1122
- onClick={onStop}
1123
- disabled={isStopping}
1124
- className="shrink-0 relative hover:bg-destructive/10"
1125
- title="AI 思考中,點擊停止"
1126
- >
1127
- {isStopping ? (
1128
- <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
1129
- ) : (
1130
- <div className="relative flex items-center justify-center w-6 h-6">
1131
- {/* 外圈旋轉動畫 - 漸層弧線 */}
1132
- <svg className="absolute w-6 h-6 animate-spin" viewBox="0 0 24 24">
1133
- <circle
1134
- className="opacity-20"
1135
- cx="12"
1136
- cy="12"
1137
- r="10"
1138
- stroke="currentColor"
1139
- strokeWidth="2"
1140
- fill="none"
1141
- />
1142
- <circle
1143
- className="text-primary"
1144
- cx="12"
1145
- cy="12"
1146
- r="10"
1147
- stroke="currentColor"
1148
- strokeWidth="2"
1149
- fill="none"
1150
- strokeDasharray="32"
1151
- strokeDashoffset="12"
1152
- strokeLinecap="round"
1153
- />
1154
- </svg>
1155
- {/* 中心停止方塊 */}
1156
- <Square className="h-2.5 w-2.5 text-primary fill-primary" />
1157
- </div>
1158
- )}
1159
- </Button>
1160
- ) : isRecording ? (
1161
- // 2. Stop recording
1162
- <Button
1163
- type="button"
1164
- size="icon"
1165
- variant="destructive"
1166
- onClick={handleRecordingToggle}
1167
- disabled={disabled || transcribing}
1168
- className="shrink-0"
1169
- title="停止錄音"
1170
- >
1171
- <Square className="h-4 w-4" />
1172
- </Button>
1173
- ) : message.trim() ? (
1174
- // 3. Send message (有文字訊息)
1175
- <Button
1176
- type="submit"
1177
- size="icon"
1178
- disabled={disabled || uploading}
1179
- className="shrink-0"
1180
- title="送出訊息"
1181
- >
1182
- {uploading ? (
1183
- <Loader2 className="h-4 w-4 animate-spin" />
1184
- ) : (
1185
- <Send className="h-4 w-4" />
1186
- )}
1187
- </Button>
1188
- ) : hasAttachments ? (
1189
- // 4. 有附件但無文字:顯示錄音 + 發送雙按鈕
1190
- <div className="flex items-center gap-1">
1191
- <Button
1192
- type="button"
1193
- size="icon"
1194
- variant="outline"
1195
- onClick={handleRecordingToggle}
1196
- disabled={disabled || transcribing}
1197
- className="shrink-0"
1198
- title="開始錄音"
1199
- >
1200
- <Mic className="h-4 w-4" />
1201
- </Button>
1202
- <Button
1203
- type="submit"
1204
- size="icon"
1205
- disabled={disabled || uploading}
1206
- className="shrink-0"
1207
- title="送出附件"
1208
- >
1209
- {uploading ? (
1210
- <Loader2 className="h-4 w-4 animate-spin" />
1211
- ) : (
1212
- <Send className="h-4 w-4" />
1213
- )}
1214
- </Button>
1215
- </div>
1216
- ) : (
1217
- // 5. Start recording (預設,無附件無文字)
1218
- <Button
1219
- type="button"
1220
- size="icon"
1221
- variant="outline"
1222
- onClick={handleRecordingToggle}
1223
- disabled={disabled || transcribing}
1224
- className="shrink-0"
1225
- title="開始錄音"
1226
- >
1227
- <Mic className="h-4 w-4" />
1228
- </Button>
1229
- )}
1230
- </form>
1231
-
1232
- {/* Skills Manager Drawer */}
1233
- <SkillsManagerDrawer
1234
- open={managerDrawerOpen}
1235
- onOpenChange={setManagerDrawerOpen}
1236
- onEdit={handleEditSkill}
1237
- onCreate={handleCreateSkill}
1238
- onSkillsChange={handleSkillsChange}
1239
- />
1240
-
1241
- {/* Skills Editor */}
1242
- <SkillsEditor
1243
- open={editorOpen}
1244
- onOpenChange={setEditorOpen}
1245
- skill={skillToEdit}
1246
- onSaved={handleSkillsChange}
1247
- />
1248
- </div>
1249
- </DropZone>
1250
- );
1251
- }