@tomako/tools-runtime 0.1.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 (531) hide show
  1. package/README.md +12 -0
  2. package/package.json +629 -0
  3. package/src/assets/icons/brands/social/bluesky-icon.tsx +9 -0
  4. package/src/assets/icons/brands/social/facebook-icon.tsx +9 -0
  5. package/src/assets/icons/brands/social/index.ts +8 -0
  6. package/src/assets/icons/brands/social/instagram-icon.tsx +9 -0
  7. package/src/assets/icons/brands/social/linkedin-icon.tsx +9 -0
  8. package/src/assets/icons/brands/social/threads-icon.tsx +9 -0
  9. package/src/assets/icons/brands/social/tiktok-icon.tsx +10 -0
  10. package/src/assets/icons/brands/social/x-icon.tsx +9 -0
  11. package/src/assets/icons/brands/social/youtube-icon.tsx +9 -0
  12. package/src/assets/icons/social-profile/facebook-icon.tsx +12 -0
  13. package/src/assets/icons/social-profile/index.ts +29 -0
  14. package/src/assets/icons/social-profile/instagram-icon.tsx +18 -0
  15. package/src/assets/icons/social-profile/linkedin-icon.tsx +12 -0
  16. package/src/assets/icons/social-profile/tiktok-icon.tsx +12 -0
  17. package/src/assets/icons/social-profile/x-icon.tsx +13 -0
  18. package/src/assets/icons/social-profile/youtube-icon.tsx +12 -0
  19. package/src/components/tools/engine/form-builder.tsx +219 -0
  20. package/src/components/tools/engine/package-widget-loader.tsx +38 -0
  21. package/src/components/tools/engine/package-widget-runtime.tsx +409 -0
  22. package/src/components/tools/engine/package-widget.tsx +77 -0
  23. package/src/components/tools/engine/result-view.tsx +596 -0
  24. package/src/components/tools/engine/use-legacy-llm-task.ts +64 -0
  25. package/src/components/tools/engine/use-tool-task.ts +195 -0
  26. package/src/components/tools/market-forecast-report.tsx +1150 -0
  27. package/src/components/tools/registry.ts +13 -0
  28. package/src/components/tools/workspace/index.ts +39 -0
  29. package/src/components/tools/workspace/tool-action-bar.tsx +99 -0
  30. package/src/components/tools/workspace/tool-empty-panel.tsx +32 -0
  31. package/src/components/tools/workspace/tool-error-panel.tsx +40 -0
  32. package/src/components/tools/workspace/tool-field.tsx +124 -0
  33. package/src/components/tools/workspace/tool-file-upload.tsx +112 -0
  34. package/src/components/tools/workspace/tool-form-section.tsx +21 -0
  35. package/src/components/tools/workspace/tool-form-shell.tsx +36 -0
  36. package/src/components/tools/workspace/tool-loading-panel.tsx +269 -0
  37. package/src/components/tools/workspace/tool-option-row.tsx +97 -0
  38. package/src/components/tools/workspace/tool-preview-panel.tsx +31 -0
  39. package/src/components/tools/workspace/tool-result-actions.tsx +67 -0
  40. package/src/components/tools/workspace/tool-result-file-actions.ts +31 -0
  41. package/src/components/tools/workspace/tool-result-header.tsx +39 -0
  42. package/src/components/tools/workspace/tool-status.tsx +13 -0
  43. package/src/components/tools/workspace/tool-workspace-layout.tsx +226 -0
  44. package/src/constants/LKS.ts +29 -0
  45. package/src/constants/related-tools.ts +18 -0
  46. package/src/constants/social-profile-kit.ts +284 -0
  47. package/src/constants/tool-assets.ts +7 -0
  48. package/src/env.d.ts +3 -0
  49. package/src/features/tools/_templates/standard-tool.container.tsx +90 -0
  50. package/src/features/tools/app-icon-resizer/app-icon-resizer.container.tsx +11 -0
  51. package/src/features/tools/app-icon-resizer/app-icon-resizer.spec.ts +15 -0
  52. package/src/features/tools/app-icon-resizer/widget/app-icon-resizer.tsx +444 -0
  53. package/src/features/tools/app-icon-resizer/widget/constants.ts +19 -0
  54. package/src/features/tools/app-icon-resizer/widget/helpers.ts +314 -0
  55. package/src/features/tools/app-icon-resizer/widget/index.ts +7 -0
  56. package/src/features/tools/app-icon-resizer/widget/platform-icons.tsx +59 -0
  57. package/src/features/tools/app-icon-resizer/widget/preview-dialogs.tsx +139 -0
  58. package/src/features/tools/app-icon-resizer/widget/result-shell.tsx +165 -0
  59. package/src/features/tools/app-icon-resizer/widget/types.ts +36 -0
  60. package/src/features/tools/app-store-screenshot-generator/app-store-screenshot-generator.container.tsx +108 -0
  61. package/src/features/tools/app-store-screenshot-generator/app-store-screenshot-generator.spec.ts +14 -0
  62. package/src/features/tools/app-store-screenshot-generator/config.ts +3 -0
  63. package/src/features/tools/app-store-screenshot-generator/widget/app-store-screenshot-generator.tsx +1250 -0
  64. package/src/features/tools/app-store-screenshot-generator/widget/asset-slots.ts +76 -0
  65. package/src/features/tools/app-store-screenshot-generator/widget/constants.ts +43 -0
  66. package/src/features/tools/app-store-screenshot-generator/widget/form-controls.tsx +214 -0
  67. package/src/features/tools/app-store-screenshot-generator/widget/generation.ts +528 -0
  68. package/src/features/tools/app-store-screenshot-generator/widget/index.ts +7 -0
  69. package/src/features/tools/app-store-screenshot-generator/widget/input-media.ts +175 -0
  70. package/src/features/tools/app-store-screenshot-generator/widget/types.ts +60 -0
  71. package/src/features/tools/app-store-screenshot-generator/widget/zip.ts +119 -0
  72. package/src/features/tools/build-in-public-story-generator/build-in-public-story-generator.container.tsx +135 -0
  73. package/src/features/tools/build-in-public-story-generator/build-in-public-story-generator.spec.ts +15 -0
  74. package/src/features/tools/build-in-public-story-generator/widget/build-in-public-story-generator.tsx +314 -0
  75. package/src/features/tools/build-in-public-story-generator/widget/constants.ts +3 -0
  76. package/src/features/tools/build-in-public-story-generator/widget/helpers.ts +25 -0
  77. package/src/features/tools/build-in-public-story-generator/widget/index.ts +7 -0
  78. package/src/features/tools/build-in-public-story-generator/widget/preview-panels.tsx +58 -0
  79. package/src/features/tools/build-in-public-story-generator/widget/result-shell.tsx +164 -0
  80. package/src/features/tools/build-in-public-story-generator/widget/types.ts +6 -0
  81. package/src/features/tools/cold-start-channel-selector/cold-start-channel-selector.container.tsx +30 -0
  82. package/src/features/tools/cold-start-channel-selector/cold-start-channel-selector.spec.ts +15 -0
  83. package/src/features/tools/cold-start-channel-selector/widget/cold-start-channel-selector.tsx +417 -0
  84. package/src/features/tools/cold-start-channel-selector/widget/constants.ts +46 -0
  85. package/src/features/tools/cold-start-channel-selector/widget/form-controls.tsx +41 -0
  86. package/src/features/tools/cold-start-channel-selector/widget/helpers.ts +75 -0
  87. package/src/features/tools/cold-start-channel-selector/widget/index.ts +7 -0
  88. package/src/features/tools/cold-start-channel-selector/widget/preview-panels.tsx +250 -0
  89. package/src/features/tools/cold-start-channel-selector/widget/result-shell.tsx +199 -0
  90. package/src/features/tools/cold-start-channel-selector/widget/types.ts +21 -0
  91. package/src/features/tools/commercial-bp-generator/commercial-bp-generator.container.tsx +102 -0
  92. package/src/features/tools/commercial-bp-generator/commercial-bp-generator.spec.ts +14 -0
  93. package/src/features/tools/commercial-bp-generator/widget/commercial-bp-generator.tsx +676 -0
  94. package/src/features/tools/commercial-bp-generator/widget/constants.ts +32 -0
  95. package/src/features/tools/commercial-bp-generator/widget/helpers.ts +147 -0
  96. package/src/features/tools/commercial-bp-generator/widget/index.ts +7 -0
  97. package/src/features/tools/commercial-bp-generator/widget/result-shell.tsx +271 -0
  98. package/src/features/tools/commercial-bp-generator/widget/style-option.tsx +54 -0
  99. package/src/features/tools/commercial-bp-generator/widget/types.ts +33 -0
  100. package/src/features/tools/community-feedback-inbox/community-feedback-inbox.container.tsx +126 -0
  101. package/src/features/tools/community-feedback-inbox/community-feedback-inbox.spec.ts +15 -0
  102. package/src/features/tools/community-feedback-inbox/widget/community-feedback-inbox.tsx +271 -0
  103. package/src/features/tools/community-feedback-inbox/widget/constants.ts +3 -0
  104. package/src/features/tools/community-feedback-inbox/widget/helpers.ts +28 -0
  105. package/src/features/tools/community-feedback-inbox/widget/index.ts +7 -0
  106. package/src/features/tools/community-feedback-inbox/widget/preview-panels.tsx +92 -0
  107. package/src/features/tools/community-feedback-inbox/widget/result-shell.tsx +266 -0
  108. package/src/features/tools/community-feedback-inbox/widget/types.ts +6 -0
  109. package/src/features/tools/community-pain-point-extractor/community-pain-point-extractor.container.tsx +124 -0
  110. package/src/features/tools/community-pain-point-extractor/community-pain-point-extractor.spec.ts +15 -0
  111. package/src/features/tools/community-pain-point-extractor/widget/community-pain-point-extractor.tsx +404 -0
  112. package/src/features/tools/community-pain-point-extractor/widget/constants.ts +41 -0
  113. package/src/features/tools/community-pain-point-extractor/widget/helpers.ts +76 -0
  114. package/src/features/tools/community-pain-point-extractor/widget/index.ts +8 -0
  115. package/src/features/tools/community-pain-point-extractor/widget/progress-panels.tsx +251 -0
  116. package/src/features/tools/community-pain-point-extractor/widget/result-shell.tsx +478 -0
  117. package/src/features/tools/community-pain-point-extractor/widget/types.ts +33 -0
  118. package/src/features/tools/community-post-risk-checker/community-post-risk-checker.package.ts +792 -0
  119. package/src/features/tools/competitor-analysis/competitor-analysis.container.tsx +136 -0
  120. package/src/features/tools/competitor-analysis/competitor-analysis.spec.ts +15 -0
  121. package/src/features/tools/competitor-analysis/widget/competitor-analysis.tsx +241 -0
  122. package/src/features/tools/competitor-analysis/widget/constants.ts +9 -0
  123. package/src/features/tools/competitor-analysis/widget/index.ts +7 -0
  124. package/src/features/tools/competitor-analysis/widget/preview-panels.tsx +60 -0
  125. package/src/features/tools/competitor-analysis/widget/result-shell.tsx +418 -0
  126. package/src/features/tools/competitor-analysis/widget/types.ts +13 -0
  127. package/src/features/tools/disclaimer-generator/disclaimer-generator.package.ts +339 -0
  128. package/src/features/tools/email-template-generator/email-template-generator.container.tsx +224 -0
  129. package/src/features/tools/email-template-generator/email-template-generator.spec.ts +14 -0
  130. package/src/features/tools/email-template-generator/widget/email-template-generator.tsx +358 -0
  131. package/src/features/tools/email-template-generator/widget/helpers.ts +39 -0
  132. package/src/features/tools/email-template-generator/widget/index.ts +7 -0
  133. package/src/features/tools/email-template-generator/widget/preview-panel.tsx +311 -0
  134. package/src/features/tools/email-template-generator/widget/result-text-block.tsx +7 -0
  135. package/src/features/tools/email-template-generator/widget/types.ts +24 -0
  136. package/src/features/tools/gtm-readiness-checklist/gtm-readiness-checklist.container.tsx +75 -0
  137. package/src/features/tools/gtm-readiness-checklist/gtm-readiness-checklist.spec.ts +15 -0
  138. package/src/features/tools/gtm-readiness-checklist/widget/gtm-readiness-checklist.tsx +122 -0
  139. package/src/features/tools/gtm-readiness-checklist/widget/index.ts +7 -0
  140. package/src/features/tools/investor-matching-planner/investor-matching-planner.package.ts +869 -0
  141. package/src/features/tools/koko-codex-assistant/koko-codex-assistant.container.tsx +151 -0
  142. package/src/features/tools/koko-codex-assistant/koko-codex-assistant.spec.ts +14 -0
  143. package/src/features/tools/koko-codex-assistant/widget/index.ts +7 -0
  144. package/src/features/tools/koko-codex-assistant/widget/koko-codex-assistant-download.tsx +192 -0
  145. package/src/features/tools/logo-generator/logo-generator.container.tsx +112 -0
  146. package/src/features/tools/logo-generator/logo-generator.spec.ts +15 -0
  147. package/src/features/tools/logo-generator/widget/index.ts +7 -0
  148. package/src/features/tools/logo-generator/widget/logo-generator.tsx +1244 -0
  149. package/src/features/tools/market-forecast-report/market-forecast-report.container.tsx +73 -0
  150. package/src/features/tools/market-forecast-report/market-forecast-report.spec.ts +15 -0
  151. package/src/features/tools/million-user-growth-test/million-user-growth-test.container.tsx +123 -0
  152. package/src/features/tools/million-user-growth-test/million-user-growth-test.spec.ts +14 -0
  153. package/src/features/tools/million-user-growth-test/widget/index.ts +7 -0
  154. package/src/features/tools/million-user-growth-test/widget/million-user-growth-test.tsx +433 -0
  155. package/src/features/tools/module-loader.ts +107 -0
  156. package/src/features/tools/og-image-generator/og-image-generator.container.tsx +86 -0
  157. package/src/features/tools/og-image-generator/og-image-generator.spec.ts +14 -0
  158. package/src/features/tools/og-image-generator/widget/constants.ts +38 -0
  159. package/src/features/tools/og-image-generator/widget/form-fields.tsx +240 -0
  160. package/src/features/tools/og-image-generator/widget/index.ts +7 -0
  161. package/src/features/tools/og-image-generator/widget/og-image-generator.tsx +742 -0
  162. package/src/features/tools/og-image-generator/widget/preview-panel.tsx +305 -0
  163. package/src/features/tools/og-image-generator/widget/types.ts +1 -0
  164. package/src/features/tools/package/direct-models.ts +20 -0
  165. package/src/features/tools/package/form.ts +38 -0
  166. package/src/features/tools/package/l10n.ts +22 -0
  167. package/src/features/tools/package/package-container.tsx +285 -0
  168. package/src/features/tools/package/package-loaders.ts +22 -0
  169. package/src/features/tools/package/package-registry.ts +36 -0
  170. package/src/features/tools/package/result-markdown.ts +142 -0
  171. package/src/features/tools/package/schema.ts +379 -0
  172. package/src/features/tools/package/to-tool-module.ts +20 -0
  173. package/src/features/tools/package/to-tool-spec.ts +52 -0
  174. package/src/features/tools/package/types.ts +381 -0
  175. package/src/features/tools/pitch-deck-generator/pitch-deck-generator.container.tsx +175 -0
  176. package/src/features/tools/pitch-deck-generator/pitch-deck-generator.spec.ts +14 -0
  177. package/src/features/tools/pitch-deck-generator/pitch-deck-library-data.ts +476 -0
  178. package/src/features/tools/pitch-deck-generator/pitch-deck-library.tsx +159 -0
  179. package/src/features/tools/pitch-deck-generator/widget/ceremony-line.tsx +27 -0
  180. package/src/features/tools/pitch-deck-generator/widget/constants.ts +27 -0
  181. package/src/features/tools/pitch-deck-generator/widget/index.ts +7 -0
  182. package/src/features/tools/pitch-deck-generator/widget/pitch-deck-generator.tsx +763 -0
  183. package/src/features/tools/pitch-deck-generator/widget/result-helpers.ts +102 -0
  184. package/src/features/tools/pitch-deck-generator/widget/result-shell.tsx +263 -0
  185. package/src/features/tools/pitch-deck-generator/widget/slide-frame.tsx +106 -0
  186. package/src/features/tools/pitch-deck-generator/widget/slide-lightbox.tsx +149 -0
  187. package/src/features/tools/pitch-deck-generator/widget/style-option.tsx +54 -0
  188. package/src/features/tools/pitch-deck-generator/widget/types.ts +44 -0
  189. package/src/features/tools/product-agreement-generator/product-agreement-generator.container.tsx +137 -0
  190. package/src/features/tools/product-agreement-generator/product-agreement-generator.spec.ts +14 -0
  191. package/src/features/tools/product-agreement-generator/widget/constants.ts +27 -0
  192. package/src/features/tools/product-agreement-generator/widget/form-controls.tsx +218 -0
  193. package/src/features/tools/product-agreement-generator/widget/helpers.ts +68 -0
  194. package/src/features/tools/product-agreement-generator/widget/index.ts +7 -0
  195. package/src/features/tools/product-agreement-generator/widget/product-agreement-generator.tsx +671 -0
  196. package/src/features/tools/product-agreement-generator/widget/result-shell.tsx +473 -0
  197. package/src/features/tools/product-agreement-generator/widget/types.ts +89 -0
  198. package/src/features/tools/product-hunt-launch-planner/product-hunt-launch-planner.container.tsx +122 -0
  199. package/src/features/tools/product-hunt-launch-planner/product-hunt-launch-planner.spec.ts +15 -0
  200. package/src/features/tools/product-hunt-launch-planner/widget/constants.ts +5 -0
  201. package/src/features/tools/product-hunt-launch-planner/widget/helpers.ts +44 -0
  202. package/src/features/tools/product-hunt-launch-planner/widget/index.ts +7 -0
  203. package/src/features/tools/product-hunt-launch-planner/widget/preview-panel.tsx +143 -0
  204. package/src/features/tools/product-hunt-launch-planner/widget/product-hunt-launch-planner.tsx +504 -0
  205. package/src/features/tools/product-hunt-launch-planner/widget/result-shell.tsx +176 -0
  206. package/src/features/tools/product-hunt-launch-planner/widget/types.ts +20 -0
  207. package/src/features/tools/product-name-generator/product-name-generator.container.tsx +14 -0
  208. package/src/features/tools/product-name-generator/product-name-generator.spec.ts +15 -0
  209. package/src/features/tools/product-name-generator/widget/candidate-card.tsx +273 -0
  210. package/src/features/tools/product-name-generator/widget/constants.ts +21 -0
  211. package/src/features/tools/product-name-generator/widget/form-controls.tsx +136 -0
  212. package/src/features/tools/product-name-generator/widget/helpers.ts +41 -0
  213. package/src/features/tools/product-name-generator/widget/index.ts +7 -0
  214. package/src/features/tools/product-name-generator/widget/product-name-generator.tsx +776 -0
  215. package/src/features/tools/product-name-generator/widget/types.ts +36 -0
  216. package/src/features/tools/product-positioning-analyzer/product-positioning-analyzer.package.ts +801 -0
  217. package/src/features/tools/product-poster-generator/product-poster-generator.container.tsx +14 -0
  218. package/src/features/tools/product-poster-generator/product-poster-generator.spec.ts +15 -0
  219. package/src/features/tools/product-poster-generator/widget/constants.ts +125 -0
  220. package/src/features/tools/product-poster-generator/widget/form-controls.tsx +204 -0
  221. package/src/features/tools/product-poster-generator/widget/helpers.ts +155 -0
  222. package/src/features/tools/product-poster-generator/widget/index.ts +7 -0
  223. package/src/features/tools/product-poster-generator/widget/product-poster-generator.tsx +663 -0
  224. package/src/features/tools/product-poster-generator/widget/result-shell.tsx +12 -0
  225. package/src/features/tools/product-poster-generator/widget/types.ts +83 -0
  226. package/src/features/tools/product-ui-generator/product-ui-generator.container.tsx +116 -0
  227. package/src/features/tools/product-ui-generator/product-ui-generator.spec.ts +14 -0
  228. package/src/features/tools/product-ui-generator/widget/constants.ts +18 -0
  229. package/src/features/tools/product-ui-generator/widget/helpers.ts +11 -0
  230. package/src/features/tools/product-ui-generator/widget/index.ts +7 -0
  231. package/src/features/tools/product-ui-generator/widget/preview-panels.tsx +73 -0
  232. package/src/features/tools/product-ui-generator/widget/product-ui-generator.tsx +304 -0
  233. package/src/features/tools/product-ui-generator/widget/result-shell.tsx +118 -0
  234. package/src/features/tools/product-ui-generator/widget/types.ts +9 -0
  235. package/src/features/tools/reddit-pain-point-finder/reddit-pain-point-finder.container.tsx +113 -0
  236. package/src/features/tools/reddit-pain-point-finder/reddit-pain-point-finder.spec.ts +15 -0
  237. package/src/features/tools/reddit-pain-point-finder/widget/constants.ts +5 -0
  238. package/src/features/tools/reddit-pain-point-finder/widget/index.ts +7 -0
  239. package/src/features/tools/reddit-pain-point-finder/widget/preview-panels.tsx +73 -0
  240. package/src/features/tools/reddit-pain-point-finder/widget/reddit-pain-point-finder.tsx +281 -0
  241. package/src/features/tools/reddit-pain-point-finder/widget/result-shell.tsx +203 -0
  242. package/src/features/tools/reddit-pain-point-finder/widget/types.ts +15 -0
  243. package/src/features/tools/registry.ts +288 -0
  244. package/src/features/tools/runtime-registry.ts +186 -0
  245. package/src/features/tools/shared/create-guide-stack-container.tsx +176 -0
  246. package/src/features/tools/shared/create-standard-landing-container.tsx +163 -0
  247. package/src/features/tools/shared/tool-content-sections.tsx +659 -0
  248. package/src/features/tools/shared/tool-cta.tsx +66 -0
  249. package/src/features/tools/shared/tool-draft-view.tsx +53 -0
  250. package/src/features/tools/shared/tool-guide-section.tsx +191 -0
  251. package/src/features/tools/shared/tool-handoff.tsx +38 -0
  252. package/src/features/tools/shared/tool-mcp-badge.tsx +23 -0
  253. package/src/features/tools/shared/tool-message-translator.ts +37 -0
  254. package/src/features/tools/shared/tool-page-shell.tsx +348 -0
  255. package/src/features/tools/shared/tool-standard-generation-steps.tsx +52 -0
  256. package/src/features/tools/shared/tool-standard-landing-sections.tsx +447 -0
  257. package/src/features/tools/shared/tool-ui-registry.ts +237 -0
  258. package/src/features/tools/shared/tool-widget-slot.tsx +29 -0
  259. package/src/features/tools/slogan-generator/slogan-generator.package.ts +641 -0
  260. package/src/features/tools/social-listening-research-report/social-listening-research-report.container.tsx +124 -0
  261. package/src/features/tools/social-listening-research-report/social-listening-research-report.spec.ts +15 -0
  262. package/src/features/tools/social-listening-research-report/widget/constants.ts +4 -0
  263. package/src/features/tools/social-listening-research-report/widget/index.ts +7 -0
  264. package/src/features/tools/social-listening-research-report/widget/preview-panels.tsx +73 -0
  265. package/src/features/tools/social-listening-research-report/widget/result-shell.tsx +216 -0
  266. package/src/features/tools/social-listening-research-report/widget/social-listening-research-report.tsx +303 -0
  267. package/src/features/tools/social-listening-research-report/widget/types.ts +15 -0
  268. package/src/features/tools/social-platform-asset-adapter/social-platform-asset-adapter.container.tsx +123 -0
  269. package/src/features/tools/social-platform-asset-adapter/social-platform-asset-adapter.spec.ts +15 -0
  270. package/src/features/tools/social-platform-asset-adapter/widget/constants.ts +43 -0
  271. package/src/features/tools/social-platform-asset-adapter/widget/helpers.ts +249 -0
  272. package/src/features/tools/social-platform-asset-adapter/widget/index.ts +7 -0
  273. package/src/features/tools/social-platform-asset-adapter/widget/result-shell.tsx +350 -0
  274. package/src/features/tools/social-platform-asset-adapter/widget/section-heading.tsx +18 -0
  275. package/src/features/tools/social-platform-asset-adapter/widget/social-platform-asset-adapter.tsx +641 -0
  276. package/src/features/tools/social-platform-asset-adapter/widget/types.ts +40 -0
  277. package/src/features/tools/social-profile-kit/social-profile-kit.container.tsx +177 -0
  278. package/src/features/tools/social-profile-kit/social-profile-kit.spec.ts +15 -0
  279. package/src/features/tools/social-profile-kit/widget/context-editor.tsx +171 -0
  280. package/src/features/tools/social-profile-kit/widget/generation-runtime.ts +281 -0
  281. package/src/features/tools/social-profile-kit/widget/index.ts +7 -0
  282. package/src/features/tools/social-profile-kit/widget/input-panel.tsx +294 -0
  283. package/src/features/tools/social-profile-kit/widget/platform-dock.tsx +115 -0
  284. package/src/features/tools/social-profile-kit/widget/profile-canvas.tsx +318 -0
  285. package/src/features/tools/social-profile-kit/widget/profile-preview/bluesky-preview.tsx +188 -0
  286. package/src/features/tools/social-profile-kit/widget/profile-preview/facebook-preview.tsx +168 -0
  287. package/src/features/tools/social-profile-kit/widget/profile-preview/index.ts +1 -0
  288. package/src/features/tools/social-profile-kit/widget/profile-preview/instagram-preview.tsx +278 -0
  289. package/src/features/tools/social-profile-kit/widget/profile-preview/linkedin-preview.tsx +358 -0
  290. package/src/features/tools/social-profile-kit/widget/profile-preview/profile-preview.tsx +348 -0
  291. package/src/features/tools/social-profile-kit/widget/profile-preview/shared.tsx +391 -0
  292. package/src/features/tools/social-profile-kit/widget/profile-preview/threads-preview.tsx +148 -0
  293. package/src/features/tools/social-profile-kit/widget/profile-preview/tiktok-preview.tsx +319 -0
  294. package/src/features/tools/social-profile-kit/widget/profile-preview/x-preview.tsx +341 -0
  295. package/src/features/tools/social-profile-kit/widget/profile-preview/youtube-preview.tsx +297 -0
  296. package/src/features/tools/social-profile-kit/widget/social-profile-kit-generator.tsx +1059 -0
  297. package/src/features/tools/social-profile-kit/widget/types.ts +104 -0
  298. package/src/features/tools/spec-registry.ts +158 -0
  299. package/src/features/tools/startup-idea-research/startup-idea-research.container.tsx +111 -0
  300. package/src/features/tools/startup-idea-research/startup-idea-research.spec.ts +15 -0
  301. package/src/features/tools/startup-idea-research/widget/constants.ts +11 -0
  302. package/src/features/tools/startup-idea-research/widget/index.ts +7 -0
  303. package/src/features/tools/startup-idea-research/widget/preview-panels.tsx +67 -0
  304. package/src/features/tools/startup-idea-research/widget/result-shell.tsx +323 -0
  305. package/src/features/tools/startup-idea-research/widget/startup-idea-research.tsx +272 -0
  306. package/src/features/tools/startup-idea-research/widget/types.ts +21 -0
  307. package/src/features/tools/tarot-reading/tarot-reading.container.tsx +412 -0
  308. package/src/features/tools/tarot-reading/tarot-reading.spec.ts +15 -0
  309. package/src/features/tools/tarot-reading/widget/cards.tsx +152 -0
  310. package/src/features/tools/tarot-reading/widget/constants.ts +69 -0
  311. package/src/features/tools/tarot-reading/widget/deck-view.tsx +86 -0
  312. package/src/features/tools/tarot-reading/widget/deck.ts +86 -0
  313. package/src/features/tools/tarot-reading/widget/index.ts +7 -0
  314. package/src/features/tools/tarot-reading/widget/landing-view.tsx +209 -0
  315. package/src/features/tools/tarot-reading/widget/reading-copy.tsx +138 -0
  316. package/src/features/tools/tarot-reading/widget/reading-view.tsx +189 -0
  317. package/src/features/tools/tarot-reading/widget/reveal-view.tsx +50 -0
  318. package/src/features/tools/tarot-reading/widget/ritual-view.tsx +276 -0
  319. package/src/features/tools/tarot-reading/widget/tarot-reading.tsx +262 -0
  320. package/src/features/tools/tarot-reading/widget/types.ts +22 -0
  321. package/src/features/tools/twitter-gif-downloader/twitter-gif-downloader.container.tsx +114 -0
  322. package/src/features/tools/twitter-gif-downloader/twitter-gif-downloader.spec.ts +14 -0
  323. package/src/features/tools/twitter-gif-downloader/widget/index.ts +7 -0
  324. package/src/features/tools/twitter-gif-downloader/widget/twitter-gif-downloader.tsx +351 -0
  325. package/src/features/tools/types.ts +68 -0
  326. package/src/features/tools/user-interview-guide-generator/user-interview-guide-generator.package.ts +774 -0
  327. package/src/features/tools/user-interview-insight-report-generator/user-interview-insight-report-generator.container.tsx +23 -0
  328. package/src/features/tools/user-interview-insight-report-generator/user-interview-insight-report-generator.spec.ts +14 -0
  329. package/src/features/tools/user-interview-insight-report-generator/widget/constants.ts +8 -0
  330. package/src/features/tools/user-interview-insight-report-generator/widget/helpers.ts +4 -0
  331. package/src/features/tools/user-interview-insight-report-generator/widget/index.ts +7 -0
  332. package/src/features/tools/user-interview-insight-report-generator/widget/preview-panels.tsx +55 -0
  333. package/src/features/tools/user-interview-insight-report-generator/widget/result-shell.tsx +122 -0
  334. package/src/features/tools/user-interview-insight-report-generator/widget/types.ts +16 -0
  335. package/src/features/tools/user-interview-insight-report-generator/widget/user-interview-insight-report-generator.tsx +293 -0
  336. package/src/features/tools/user-interview-record-generator/user-interview-record-generator.container.tsx +18 -0
  337. package/src/features/tools/user-interview-record-generator/user-interview-record-generator.spec.ts +14 -0
  338. package/src/features/tools/user-interview-record-generator/widget/constants.ts +9 -0
  339. package/src/features/tools/user-interview-record-generator/widget/helpers.ts +4 -0
  340. package/src/features/tools/user-interview-record-generator/widget/index.ts +7 -0
  341. package/src/features/tools/user-interview-record-generator/widget/preview-panels.tsx +48 -0
  342. package/src/features/tools/user-interview-record-generator/widget/result-shell.tsx +128 -0
  343. package/src/features/tools/user-interview-record-generator/widget/types.ts +16 -0
  344. package/src/features/tools/user-interview-record-generator/widget/user-interview-record-generator.tsx +305 -0
  345. package/src/features/tools/utm-builder/utm-builder.package.ts +846 -0
  346. package/src/features/tools/widget-registry.ts +73 -0
  347. package/src/generated/mcp-tools-registry.json +17 -0
  348. package/src/host/client-proxy.test.ts +33 -0
  349. package/src/host/client.ts +678 -0
  350. package/src/host/server.ts +119 -0
  351. package/src/i18n/client.ts +11 -0
  352. package/src/i18n/locale-fallback.ts +38 -0
  353. package/src/i18n/locale.ts +18 -0
  354. package/src/i18n/messages/en/page/tools.ts +124 -0
  355. package/src/i18n/messages/en/tools/app-icon-resizer.ts +244 -0
  356. package/src/i18n/messages/en/tools/app-store-screenshot-generator.ts +145 -0
  357. package/src/i18n/messages/en/tools/build-in-public-story-generator.ts +229 -0
  358. package/src/i18n/messages/en/tools/cold-start-channel-selector.ts +423 -0
  359. package/src/i18n/messages/en/tools/commercial-bp-generator.ts +304 -0
  360. package/src/i18n/messages/en/tools/community-feedback-inbox.ts +218 -0
  361. package/src/i18n/messages/en/tools/community-pain-point-extractor.ts +230 -0
  362. package/src/i18n/messages/en/tools/community-post-risk-checker.ts +209 -0
  363. package/src/i18n/messages/en/tools/competitor-analysis.ts +197 -0
  364. package/src/i18n/messages/en/tools/email-template-generator.ts +302 -0
  365. package/src/i18n/messages/en/tools/engine.ts +69 -0
  366. package/src/i18n/messages/en/tools/gtm-readiness-checklist.ts +166 -0
  367. package/src/i18n/messages/en/tools/index.ts +73 -0
  368. package/src/i18n/messages/en/tools/koko-codex-assistant.ts +171 -0
  369. package/src/i18n/messages/en/tools/logo-generator.ts +173 -0
  370. package/src/i18n/messages/en/tools/market-forecast-report.ts +228 -0
  371. package/src/i18n/messages/en/tools/million-user-growth-test.ts +386 -0
  372. package/src/i18n/messages/en/tools/og-image-generator.ts +172 -0
  373. package/src/i18n/messages/en/tools/pitch-deck-generator.ts +358 -0
  374. package/src/i18n/messages/en/tools/product-agreement-generator.ts +411 -0
  375. package/src/i18n/messages/en/tools/product-hunt-launch-planner.ts +272 -0
  376. package/src/i18n/messages/en/tools/product-name-generator.ts +383 -0
  377. package/src/i18n/messages/en/tools/product-poster-generator.ts +294 -0
  378. package/src/i18n/messages/en/tools/product-ui-generator.ts +234 -0
  379. package/src/i18n/messages/en/tools/reddit-pain-point-finder.ts +163 -0
  380. package/src/i18n/messages/en/tools/shared.ts +24 -0
  381. package/src/i18n/messages/en/tools/social-listening-research-report.ts +185 -0
  382. package/src/i18n/messages/en/tools/social-media-profile-generator.ts +22 -0
  383. package/src/i18n/messages/en/tools/social-platform-asset-adapter.ts +287 -0
  384. package/src/i18n/messages/en/tools/social-profile-kit.ts +150 -0
  385. package/src/i18n/messages/en/tools/startup-idea-research.ts +170 -0
  386. package/src/i18n/messages/en/tools/tarot-reading.ts +230 -0
  387. package/src/i18n/messages/en/tools/twitter-gif-downloader.ts +145 -0
  388. package/src/i18n/messages/en/tools/user-interview-insight-report-generator.ts +242 -0
  389. package/src/i18n/messages/en/tools/user-interview-record-generator.ts +244 -0
  390. package/src/i18n/messages/en/tools-ui.ts +704 -0
  391. package/src/i18n/messages/zh/page/tools.ts +124 -0
  392. package/src/i18n/messages/zh/tools/app-icon-resizer.ts +242 -0
  393. package/src/i18n/messages/zh/tools/app-store-screenshot-generator.ts +145 -0
  394. package/src/i18n/messages/zh/tools/build-in-public-story-generator.ts +230 -0
  395. package/src/i18n/messages/zh/tools/cold-start-channel-selector.ts +422 -0
  396. package/src/i18n/messages/zh/tools/commercial-bp-generator.ts +302 -0
  397. package/src/i18n/messages/zh/tools/community-feedback-inbox.ts +217 -0
  398. package/src/i18n/messages/zh/tools/community-pain-point-extractor.ts +230 -0
  399. package/src/i18n/messages/zh/tools/community-post-risk-checker.ts +200 -0
  400. package/src/i18n/messages/zh/tools/competitor-analysis.ts +192 -0
  401. package/src/i18n/messages/zh/tools/email-template-generator.ts +302 -0
  402. package/src/i18n/messages/zh/tools/engine.ts +68 -0
  403. package/src/i18n/messages/zh/tools/gtm-readiness-checklist.ts +166 -0
  404. package/src/i18n/messages/zh/tools/index.ts +73 -0
  405. package/src/i18n/messages/zh/tools/koko-codex-assistant.ts +170 -0
  406. package/src/i18n/messages/zh/tools/logo-generator.ts +172 -0
  407. package/src/i18n/messages/zh/tools/market-forecast-report.ts +228 -0
  408. package/src/i18n/messages/zh/tools/million-user-growth-test.ts +385 -0
  409. package/src/i18n/messages/zh/tools/og-image-generator.ts +173 -0
  410. package/src/i18n/messages/zh/tools/pitch-deck-generator.ts +358 -0
  411. package/src/i18n/messages/zh/tools/product-agreement-generator.ts +401 -0
  412. package/src/i18n/messages/zh/tools/product-hunt-launch-planner.ts +267 -0
  413. package/src/i18n/messages/zh/tools/product-name-generator.ts +383 -0
  414. package/src/i18n/messages/zh/tools/product-poster-generator.ts +282 -0
  415. package/src/i18n/messages/zh/tools/product-ui-generator.ts +234 -0
  416. package/src/i18n/messages/zh/tools/reddit-pain-point-finder.ts +162 -0
  417. package/src/i18n/messages/zh/tools/shared.ts +24 -0
  418. package/src/i18n/messages/zh/tools/social-listening-research-report.ts +181 -0
  419. package/src/i18n/messages/zh/tools/social-media-profile-generator.ts +22 -0
  420. package/src/i18n/messages/zh/tools/social-platform-asset-adapter.ts +284 -0
  421. package/src/i18n/messages/zh/tools/social-profile-kit.ts +150 -0
  422. package/src/i18n/messages/zh/tools/startup-idea-research.ts +165 -0
  423. package/src/i18n/messages/zh/tools/tarot-reading.ts +228 -0
  424. package/src/i18n/messages/zh/tools/twitter-gif-downloader.ts +138 -0
  425. package/src/i18n/messages/zh/tools/user-interview-insight-report-generator.ts +224 -0
  426. package/src/i18n/messages/zh/tools/user-interview-record-generator.ts +226 -0
  427. package/src/i18n/messages/zh/tools-ui.ts +684 -0
  428. package/src/i18n/messages/zh-tw/page/tools.ts +115 -0
  429. package/src/i18n/messages/zh-tw/tools/app-icon-resizer.ts +242 -0
  430. package/src/i18n/messages/zh-tw/tools/app-store-screenshot-generator.ts +145 -0
  431. package/src/i18n/messages/zh-tw/tools/build-in-public-story-generator.ts +230 -0
  432. package/src/i18n/messages/zh-tw/tools/cold-start-channel-selector.ts +440 -0
  433. package/src/i18n/messages/zh-tw/tools/commercial-bp-generator.ts +302 -0
  434. package/src/i18n/messages/zh-tw/tools/community-feedback-inbox.ts +217 -0
  435. package/src/i18n/messages/zh-tw/tools/community-pain-point-extractor.ts +230 -0
  436. package/src/i18n/messages/zh-tw/tools/community-post-risk-checker.ts +200 -0
  437. package/src/i18n/messages/zh-tw/tools/competitor-analysis.ts +192 -0
  438. package/src/i18n/messages/zh-tw/tools/email-template-generator.ts +302 -0
  439. package/src/i18n/messages/zh-tw/tools/engine.ts +68 -0
  440. package/src/i18n/messages/zh-tw/tools/gtm-readiness-checklist.ts +166 -0
  441. package/src/i18n/messages/zh-tw/tools/index.ts +73 -0
  442. package/src/i18n/messages/zh-tw/tools/koko-codex-assistant.ts +170 -0
  443. package/src/i18n/messages/zh-tw/tools/logo-generator.ts +71 -0
  444. package/src/i18n/messages/zh-tw/tools/market-forecast-report.ts +228 -0
  445. package/src/i18n/messages/zh-tw/tools/million-user-growth-test.ts +385 -0
  446. package/src/i18n/messages/zh-tw/tools/og-image-generator.ts +173 -0
  447. package/src/i18n/messages/zh-tw/tools/pitch-deck-generator.ts +358 -0
  448. package/src/i18n/messages/zh-tw/tools/product-agreement-generator.ts +401 -0
  449. package/src/i18n/messages/zh-tw/tools/product-hunt-launch-planner.ts +267 -0
  450. package/src/i18n/messages/zh-tw/tools/product-name-generator.ts +383 -0
  451. package/src/i18n/messages/zh-tw/tools/product-poster-generator.ts +282 -0
  452. package/src/i18n/messages/zh-tw/tools/product-ui-generator.ts +234 -0
  453. package/src/i18n/messages/zh-tw/tools/reddit-pain-point-finder.ts +162 -0
  454. package/src/i18n/messages/zh-tw/tools/shared.ts +24 -0
  455. package/src/i18n/messages/zh-tw/tools/social-listening-research-report.ts +181 -0
  456. package/src/i18n/messages/zh-tw/tools/social-media-profile-generator.ts +22 -0
  457. package/src/i18n/messages/zh-tw/tools/social-platform-asset-adapter.ts +284 -0
  458. package/src/i18n/messages/zh-tw/tools/social-profile-kit.ts +150 -0
  459. package/src/i18n/messages/zh-tw/tools/startup-idea-research.ts +165 -0
  460. package/src/i18n/messages/zh-tw/tools/tarot-reading.ts +228 -0
  461. package/src/i18n/messages/zh-tw/tools/twitter-gif-downloader.ts +138 -0
  462. package/src/i18n/messages/zh-tw/tools/user-interview-insight-report-generator.ts +224 -0
  463. package/src/i18n/messages/zh-tw/tools/user-interview-record-generator.ts +226 -0
  464. package/src/i18n/messages/zh-tw/tools-ui.ts +525 -0
  465. package/src/index.ts +15 -0
  466. package/src/lib/activity-task-handoff.ts +404 -0
  467. package/src/lib/api/errors.ts +39 -0
  468. package/src/lib/app-origin.ts +30 -0
  469. package/src/lib/fact-claim-contract.ts +68 -0
  470. package/src/lib/fake-delay.ts +3 -0
  471. package/src/lib/file/download.ts +10 -0
  472. package/src/lib/routing/locale-paths.ts +27 -0
  473. package/src/lib/seo-meta.ts +3 -0
  474. package/src/lib/tools/app-icon-resizer-schema.ts +191 -0
  475. package/src/lib/tools/app-store-screenshot-generator-schema.ts +185 -0
  476. package/src/lib/tools/build-in-public-story-generator-schema.ts +67 -0
  477. package/src/lib/tools/build-in-public-story-generator.format.ts +507 -0
  478. package/src/lib/tools/cold-start-channel-selector-schema.ts +147 -0
  479. package/src/lib/tools/commercial-bp-generator-schema.ts +222 -0
  480. package/src/lib/tools/community-feedback-inbox-schema.ts +125 -0
  481. package/src/lib/tools/community-feedback-inbox.format.ts +764 -0
  482. package/src/lib/tools/community-pain-point-extractor-schema.ts +166 -0
  483. package/src/lib/tools/community-post-risk-checker-schema.ts +99 -0
  484. package/src/lib/tools/community-post-risk-checker.format.ts +557 -0
  485. package/src/lib/tools/competitor-analysis-schema.ts +144 -0
  486. package/src/lib/tools/draft-preview-access.ts +3 -0
  487. package/src/lib/tools/draft-store.ts +69 -0
  488. package/src/lib/tools/email-template-generator-schema.ts +124 -0
  489. package/src/lib/tools/gtm-checklist-copy.ts +57 -0
  490. package/src/lib/tools/gtm-checklist-structure.ts +37 -0
  491. package/src/lib/tools/image-download.ts +68 -0
  492. package/src/lib/tools/input-quality.ts +97 -0
  493. package/src/lib/tools/logo-generator-schema.ts +224 -0
  494. package/src/lib/tools/market-forecast-report-schema.ts +299 -0
  495. package/src/lib/tools/mcp-registry.ts +16 -0
  496. package/src/lib/tools/mcp-types.ts +18 -0
  497. package/src/lib/tools/million-user-growth-test-schema.ts +49 -0
  498. package/src/lib/tools/million-user-growth-test.ts +181 -0
  499. package/src/lib/tools/og-image-generator-render.ts +448 -0
  500. package/src/lib/tools/og-image-generator-schema.ts +140 -0
  501. package/src/lib/tools/ops-catalog.ts +102 -0
  502. package/src/lib/tools/pitch-deck-generator-schema.ts +182 -0
  503. package/src/lib/tools/pitch-deck-package.ts +0 -0
  504. package/src/lib/tools/product-agreement-generator-schema.ts +168 -0
  505. package/src/lib/tools/product-hunt-launch-planner-schema.ts +136 -0
  506. package/src/lib/tools/product-hunt-launch-planner.format.ts +72 -0
  507. package/src/lib/tools/product-name-generator-schema.ts +563 -0
  508. package/src/lib/tools/product-poster-generator-schema.ts +215 -0
  509. package/src/lib/tools/product-ui-generator-schema.ts +157 -0
  510. package/src/lib/tools/reddit-pain-point-finder-schema.ts +88 -0
  511. package/src/lib/tools/resolve-tool-locale-copy.ts +75 -0
  512. package/src/lib/tools/slug.ts +60 -0
  513. package/src/lib/tools/social-listening-research-report-schema.ts +105 -0
  514. package/src/lib/tools/social-platform-asset-adapter-schema.ts +152 -0
  515. package/src/lib/tools/social-profile-kit.constants.ts +12 -0
  516. package/src/lib/tools/social-profile-kit.generation.ts +257 -0
  517. package/src/lib/tools/social-profile-kit.input.ts +290 -0
  518. package/src/lib/tools/social-profile-kit.manual.ts +481 -0
  519. package/src/lib/tools/social-profile-kit.package.ts +220 -0
  520. package/src/lib/tools/social-profile-kit.schema.ts +328 -0
  521. package/src/lib/tools/startup-idea-research-schema.ts +107 -0
  522. package/src/lib/tools/tarot-imagery.ts +89 -0
  523. package/src/lib/tools/tarot-reading.ts +1044 -0
  524. package/src/lib/tools/tool-message-keys.ts +18 -0
  525. package/src/lib/tools/types.ts +32 -0
  526. package/src/lib/tools/user-interview.format.ts +147 -0
  527. package/src/lib/tools/user-interview.generation.ts +670 -0
  528. package/src/lib/tools/user-interview.schema.ts +192 -0
  529. package/src/lib/zip.ts +106 -0
  530. package/src/services/tools/handlers/gtm-readiness-checklist.ts +71 -0
  531. package/src/services/tools/handlers/types.ts +7 -0
@@ -0,0 +1,670 @@
1
+ import type {
2
+ InterviewGuide,
3
+ InterviewRecord,
4
+ InsightReport,
5
+ ResearchBrief,
6
+ UserInterviewGoal,
7
+ } from "./user-interview.schema";
8
+
9
+ export type InterviewGuideTemplate = {
10
+ interviewTypes: Record<UserInterviewGoal, string>;
11
+ objectives: Record<UserInterviewGoal, string[]>;
12
+ participantCriteria: string[];
13
+ exclusionCriteria: string[];
14
+ sessionFlow: Record<ResearchBrief["sessionLength"], InterviewGuide["sessionFlow"]>;
15
+ questions: Record<UserInterviewGoal, InterviewGuide["questions"]>;
16
+ defaultQuestions: InterviewGuide["questions"];
17
+ followUpTrees: InterviewGuide["followUpTrees"];
18
+ strongSignals: string[];
19
+ weakSignals: string[];
20
+ riskSignals: string[];
21
+ postInterviewHandoff: string[];
22
+ };
23
+
24
+ type OutputLanguage = "zh" | "en";
25
+
26
+ export function resolveInterviewType(goal: UserInterviewGoal): string {
27
+ const fallback: Record<UserInterviewGoal, string> = {
28
+ churn: "Churn interview",
29
+ discovery: "Exploratory discovery interview",
30
+ positioning: "Messaging and positioning interview",
31
+ pricing: "Pricing interview",
32
+ problemValidation: "Problem validation interview",
33
+ solutionValidation: "Solution validation interview",
34
+ usability: "Usability interview",
35
+ };
36
+
37
+ return fallback[goal];
38
+ }
39
+
40
+ function compactText(text?: string): string {
41
+ return (text ?? "").replace(/\s+/g, " ").trim();
42
+ }
43
+
44
+ function hasCjk(text: string): boolean {
45
+ return /[\u3400-\u9fff]/.test(text);
46
+ }
47
+
48
+ function inferLanguage(text: string, fallback: OutputLanguage = "en"): OutputLanguage {
49
+ return hasCjk(text) ? "zh" : fallback;
50
+ }
51
+
52
+ function languageFromBrief(brief: Pick<ResearchBrief, "language">): OutputLanguage {
53
+ return brief.language === "zh" ? "zh" : "en";
54
+ }
55
+
56
+ function label(language: OutputLanguage, zh: string, en: string): string {
57
+ return language === "zh" ? zh : en;
58
+ }
59
+
60
+ function takeUnique(items: readonly string[], max: number): string[] {
61
+ const seen = new Set<string>();
62
+ const result: string[] = [];
63
+
64
+ for (const item of items) {
65
+ const normalized = compactText(item);
66
+ const key = normalized.toLowerCase();
67
+ if (!normalized || seen.has(key)) continue;
68
+ seen.add(key);
69
+ result.push(normalized);
70
+ if (result.length >= max) break;
71
+ }
72
+
73
+ return result;
74
+ }
75
+
76
+ function compactLines(text: string): string[] {
77
+ return text
78
+ .split(/\r?\n+/)
79
+ .flatMap((line) => line.split(/(?<=[。!?.!?])\s+/))
80
+ .map((line) => line.trim())
81
+ .filter((line) => line.length >= 8);
82
+ }
83
+
84
+ function includesAny(text: string, terms: readonly string[]): boolean {
85
+ const lower = text.toLowerCase();
86
+ return terms.some((term) => lower.includes(term.toLowerCase()));
87
+ }
88
+
89
+ function pickEvidenceLines(
90
+ lines: readonly string[],
91
+ terms: readonly string[],
92
+ fallbackCount = 2,
93
+ ): string[] {
94
+ const matches = lines.filter((line) => includesAny(line, terms));
95
+ return takeUnique(matches.length ? matches : lines, fallbackCount);
96
+ }
97
+
98
+ function contextualizeList(
99
+ items: readonly string[],
100
+ context: string,
101
+ language: OutputLanguage,
102
+ max = 4,
103
+ ): string[] {
104
+ const suffix = context
105
+ ? label(language, `结合:${context}`, `Context: ${context}`)
106
+ : "";
107
+
108
+ return takeUnique(
109
+ items.map((item, index) =>
110
+ index < 2 && suffix ? `${item} ${suffix}` : item,
111
+ ),
112
+ max,
113
+ );
114
+ }
115
+
116
+ export function createInterviewGuideFromTemplate(
117
+ brief: ResearchBrief,
118
+ template: InterviewGuideTemplate,
119
+ ): InterviewGuide {
120
+ const language = languageFromBrief(brief);
121
+ const productName = compactText(brief.productName);
122
+ const targetParticipants = compactText(brief.targetParticipants);
123
+ const decision = compactText(brief.decisionToMake);
124
+ const hypotheses = compactText(brief.knownHypotheses);
125
+ const context = [
126
+ productName,
127
+ decision
128
+ ? label(language, `本轮要支持的决策:${decision}`, `decision to support: ${decision}`)
129
+ : "",
130
+ hypotheses
131
+ ? label(language, `待验证假设:${hypotheses}`, `hypothesis to test: ${hypotheses}`)
132
+ : "",
133
+ ]
134
+ .filter(Boolean)
135
+ .join(";");
136
+ const goalQuestions =
137
+ template.questions[brief.interviewGoal] ?? template.defaultQuestions;
138
+ const productStageLine = label(
139
+ language,
140
+ `当前阶段是「${brief.productStage}」,问题需要贴近这一步的真实风险。`,
141
+ `Current stage is "${brief.productStage}", so questions should focus on the risks of this stage.`,
142
+ );
143
+
144
+ return {
145
+ recommendedInterviewType:
146
+ template.interviewTypes[brief.interviewGoal] ??
147
+ resolveInterviewType(brief.interviewGoal),
148
+ researchObjectives: takeUnique(
149
+ [
150
+ ...contextualizeList(template.objectives[brief.interviewGoal] ?? template.objectives.discovery, context, language, 3),
151
+ decision
152
+ ? label(language, `把访谈证据映射到决策:${decision}`, `Map interview evidence back to the decision: ${decision}`)
153
+ : productStageLine,
154
+ hypotheses
155
+ ? label(language, `验证或推翻假设:${hypotheses}`, `Confirm or reject this hypothesis: ${hypotheses}`)
156
+ : "",
157
+ ],
158
+ 5,
159
+ ),
160
+ participantCriteria: takeUnique(
161
+ [
162
+ targetParticipants
163
+ ? label(language, `优先招募:${targetParticipants}`, `Prioritize participants: ${targetParticipants}`)
164
+ : "",
165
+ label(
166
+ language,
167
+ `能讲出和「${productName}」相关的最近一次真实经历。`,
168
+ `Can describe a recent real situation related to "${productName}".`,
169
+ ),
170
+ ...template.participantCriteria,
171
+ ],
172
+ 5,
173
+ ),
174
+ exclusionCriteria: takeUnique(
175
+ [
176
+ label(
177
+ language,
178
+ "只有抽象兴趣、不能回忆具体案例的人。",
179
+ "People with abstract interest but no concrete recent example.",
180
+ ),
181
+ ...template.exclusionCriteria,
182
+ ],
183
+ 5,
184
+ ),
185
+ sessionFlow: template.sessionFlow[brief.sessionLength],
186
+ questions: goalQuestions.map((item) => ({
187
+ ...item,
188
+ purpose: context
189
+ ? `${item.purpose} ${label(language, `关联到 ${productName} 的当前决策。`, `Tie it back to the current decision for ${productName}.`)}`
190
+ : item.purpose,
191
+ followUps: takeUnique(
192
+ [
193
+ ...item.followUps,
194
+ decision
195
+ ? label(language, `这个回答会如何改变「${decision}」?`, `How would this answer change "${decision}"?`)
196
+ : "",
197
+ targetParticipants
198
+ ? label(language, `这在「${targetParticipants}」中是否常见?`, `Is this common among "${targetParticipants}"?`)
199
+ : "",
200
+ ],
201
+ 5,
202
+ ),
203
+ })),
204
+ followUpTrees: template.followUpTrees.map((tree) => ({
205
+ ...tree,
206
+ probes: takeUnique(
207
+ [
208
+ ...tree.probes,
209
+ label(language, "请给出最近一次具体例子。", "Ask for the most recent concrete example."),
210
+ ],
211
+ 4,
212
+ ),
213
+ })),
214
+ strongSignals: takeUnique(
215
+ [
216
+ ...template.strongSignals,
217
+ decision
218
+ ? label(language, `回答能直接影响「${decision}」。`, `The answer directly changes "${decision}".`)
219
+ : "",
220
+ ],
221
+ 6,
222
+ ),
223
+ weakSignals: template.weakSignals,
224
+ riskSignals: takeUnique(
225
+ [
226
+ ...template.riskSignals,
227
+ label(
228
+ language,
229
+ "受访者描述的真实任务和当前产品假设不匹配。",
230
+ "The participant's real task does not match the current product assumption.",
231
+ ),
232
+ ],
233
+ 6,
234
+ ),
235
+ postInterviewHandoff: template.postInterviewHandoff,
236
+ };
237
+ }
238
+
239
+ export function stringifyInterviewGuide(guide: InterviewGuide): string {
240
+ const lines = [
241
+ `# ${guide.recommendedInterviewType}`,
242
+ "",
243
+ "## Research objectives",
244
+ ...guide.researchObjectives.map((item) => `- ${item}`),
245
+ "",
246
+ "## Participant criteria",
247
+ ...guide.participantCriteria.map((item) => `- ${item}`),
248
+ "",
249
+ "## Interview questions",
250
+ ...guide.questions.flatMap((item, index) => [
251
+ `${index + 1}. ${item.question}`,
252
+ ` - Purpose: ${item.purpose}`,
253
+ ` - Listen for: ${item.listenFor.join("; ")}`,
254
+ ` - Follow-ups: ${item.followUps.join("; ")}`,
255
+ ` - Avoid: ${item.avoid}`,
256
+ ]),
257
+ "",
258
+ "## Strong signals",
259
+ ...guide.strongSignals.map((item) => `- ${item}`),
260
+ "",
261
+ "## Weak signals",
262
+ ...guide.weakSignals.map((item) => `- ${item}`),
263
+ "",
264
+ "## Risk signals",
265
+ ...guide.riskSignals.map((item) => `- ${item}`),
266
+ ];
267
+
268
+ return lines.join("\n");
269
+ }
270
+
271
+ export type InterviewRecordInput = {
272
+ transcript: string;
273
+ productContext?: string;
274
+ researchGoal?: string;
275
+ participantContext?: string;
276
+ language?: OutputLanguage;
277
+ };
278
+
279
+ function extractQuotedEvidence(lines: readonly string[]): string[] {
280
+ return takeUnique(
281
+ lines
282
+ .filter((line) => !/^interviewer\s*:/i.test(line))
283
+ .filter((line) => !/^主持人\s*[::]/.test(line))
284
+ .filter((line) => line.length > 24)
285
+ .map((line) => line.replace(/^(participant|user|受访者|用户)\s*[::]\s*/i, "")),
286
+ 5,
287
+ );
288
+ }
289
+
290
+ function painLabel(evidence: string, language: OutputLanguage): string {
291
+ if (includesAny(evidence, ["hour", "hours", "day", "spent", "花", "耗时", "天"])) {
292
+ return label(language, "时间成本或返工", "Time cost or rework");
293
+ }
294
+ if (includesAny(evidence, ["$", "pay", "paid", "budget", "cost", "付费", "预算", "成本"])) {
295
+ return label(language, "金钱或预算压力", "Money or budget pressure");
296
+ }
297
+ if (includesAny(evidence, ["delay", "blocked", "stuck", "延期", "卡住", "阻塞"])) {
298
+ return label(language, "延期或流程阻塞", "Delay or workflow blocker");
299
+ }
300
+
301
+ return label(language, "转写中提到的痛点或阻力", "Pain or friction mentioned in the transcript");
302
+ }
303
+
304
+ function emptyEvidence(language: OutputLanguage, topic: string): string[] {
305
+ return [label(language, `转写中没有明确找到「${topic}」,下一场需要追问。`, `No clear "${topic}" evidence found; ask a follow-up next time.`)];
306
+ }
307
+
308
+ export function createInterviewRecordFromTranscript(
309
+ input: InterviewRecordInput,
310
+ ): InterviewRecord {
311
+ const language = input.language ?? inferLanguage(input.transcript, "en");
312
+ const lines = compactLines(input.transcript);
313
+ const painEvidence = pickEvidenceLines(lines, [
314
+ "annoying",
315
+ "hard",
316
+ "difficult",
317
+ "delay",
318
+ "delayed",
319
+ "stuck",
320
+ "spent",
321
+ "hours",
322
+ "cost",
323
+ "麻烦",
324
+ "卡",
325
+ "延期",
326
+ "花了",
327
+ "耗时",
328
+ "成本",
329
+ ], 4);
330
+ const workflowEvidence = pickEvidenceLines(lines, [
331
+ "first",
332
+ "then",
333
+ "used",
334
+ "tool",
335
+ "manual",
336
+ "workflow",
337
+ "process",
338
+ "先",
339
+ "然后",
340
+ "工具",
341
+ "手动",
342
+ "流程",
343
+ ], 4);
344
+ const buyingEvidence = pickEvidenceLines(lines, [
345
+ "pay",
346
+ "paid",
347
+ "dollars",
348
+ "$",
349
+ "subscribe",
350
+ "budget",
351
+ "付费",
352
+ "多少钱",
353
+ "预算",
354
+ "订阅",
355
+ ], 4);
356
+ const alternativeEvidence = pickEvidenceLines(lines, [
357
+ "alternative",
358
+ "instead",
359
+ "tool",
360
+ "template",
361
+ "agency",
362
+ "manual",
363
+ "spreadsheet",
364
+ "替代",
365
+ "工具",
366
+ "模板",
367
+ "自己",
368
+ "表格",
369
+ ], 4);
370
+ const recentBehavior = pickEvidenceLines(lines, [
371
+ "last time",
372
+ "recent",
373
+ "yesterday",
374
+ "last week",
375
+ "two months",
376
+ "上一次",
377
+ "最近",
378
+ "昨天",
379
+ "上周",
380
+ ], 4);
381
+
382
+ return {
383
+ participantProfile:
384
+ compactText(input.participantContext) ||
385
+ label(language, "未提供受访者背景;仅根据转写谨慎整理。", "Participant profile was not provided; infer cautiously from transcript only."),
386
+ interviewContext:
387
+ compactText(input.researchGoal) ||
388
+ compactText(input.productContext) ||
389
+ label(language, "未提供访谈背景;建议补充研究目标后重新整理。", "Interview context was not provided; add the research goal before relying on this record."),
390
+ recentBehavior: recentBehavior.length ? recentBehavior : emptyEvidence(language, label(language, "近期真实行为", "recent behavior")),
391
+ workflow: workflowEvidence.length ? workflowEvidence : emptyEvidence(language, label(language, "工具和流程", "workflow")),
392
+ painPoints: (painEvidence.length ? painEvidence : lines.slice(0, 2)).map((evidence) => ({
393
+ pain: painLabel(evidence, language),
394
+ evidence,
395
+ cost: includesAny(evidence, ["hour", "hours", "day", "花", "天", "$", "dollar", "cost", "预算"])
396
+ ? label(language, "转写中包含明确或部分成本线索。", "Transcript contains explicit or partial cost evidence.")
397
+ : label(language, "成本仍需下一场追问确认。", "Cost needs a follow-up question."),
398
+ confidence: includesAny(evidence, ["hour", "hours", "delay", "延期", "花了", "$", "budget"])
399
+ ? "medium"
400
+ : "low",
401
+ })),
402
+ alternatives: alternativeEvidence.length ? alternativeEvidence : emptyEvidence(language, label(language, "替代方案", "alternatives")),
403
+ buyingSignals: buyingEvidence.length ? buyingEvidence : emptyEvidence(language, label(language, "付费或采用信号", "buying or adoption signals")),
404
+ quotes: extractQuotedEvidence(lines),
405
+ strongSignals: takeUnique(
406
+ [
407
+ label(
408
+ language,
409
+ "强信号优先看:近期真实行为、具名工具、明确时间/金钱成本、已付费或强替代证据。",
410
+ "Strong signals: recent behavior, named tools, explicit time/money cost, paid workaround or strong alternative evidence.",
411
+ ),
412
+ ...buyingEvidence.slice(0, 2),
413
+ ],
414
+ 4,
415
+ ),
416
+ weakSignals: [
417
+ label(
418
+ language,
419
+ "礼貌性认可、未来可能会用、只愿意免费试试,都应视为弱信号。",
420
+ "Compliments, future-tense interest, and free-only interest should be treated as weak evidence.",
421
+ ),
422
+ ],
423
+ missedFollowUps: [
424
+ label(language, "追问最耗时或最不确定的一步。", "Ask for the exact step that consumed the most time or created most uncertainty."),
425
+ label(language, "追问是否造成延期、额外支出、质量下降或机会损失。", "Ask whether this caused delay, extra spend, quality loss, or missed opportunity."),
426
+ label(language, "追问当前替代工具为什么还不够好。", "Ask why the current alternative is still not good enough."),
427
+ label(language, "要求对方提供下一步可测试的真实材料或场景。", "Ask for a concrete next-step test using real materials or a real scenario."),
428
+ ],
429
+ openQuestions: [
430
+ label(language, "这个场景多久重复一次?", "How often does this scenario repeat?"),
431
+ label(language, "谁拥有预算或最终决策权?", "Who owns the budget or final decision?"),
432
+ label(language, "结果达到什么标准才足够可用?", "What result quality would be good enough to use?"),
433
+ ],
434
+ };
435
+ }
436
+
437
+ export function stringifyInterviewRecord(record: InterviewRecord): string {
438
+ const lines = [
439
+ "# Interview Record",
440
+ "",
441
+ "## Participant snapshot",
442
+ record.participantProfile,
443
+ "",
444
+ "## Interview context",
445
+ record.interviewContext,
446
+ "",
447
+ "## Recent behavior",
448
+ ...record.recentBehavior.map((item) => `- ${item}`),
449
+ "",
450
+ "## Workflow",
451
+ ...record.workflow.map((item) => `- ${item}`),
452
+ "",
453
+ "## Pain points",
454
+ ...record.painPoints.map(
455
+ (item) =>
456
+ `- ${item.pain}: ${item.evidence} (cost: ${item.cost}; confidence: ${item.confidence})`,
457
+ ),
458
+ "",
459
+ "## Alternatives",
460
+ ...record.alternatives.map((item) => `- ${item}`),
461
+ "",
462
+ "## Buying signals",
463
+ ...record.buyingSignals.map((item) => `- ${item}`),
464
+ "",
465
+ "## Quotes",
466
+ ...record.quotes.map((item) => `- "${item}"`),
467
+ "",
468
+ "## Missed follow-ups",
469
+ ...record.missedFollowUps.map((item) => `- ${item}`),
470
+ ];
471
+
472
+ return lines.join("\n");
473
+ }
474
+
475
+ export type InsightReportInput = {
476
+ recordsText: string;
477
+ researchGoal?: string;
478
+ decisionToMake?: string;
479
+ language?: OutputLanguage;
480
+ };
481
+
482
+ function splitRecordBlocks(text: string): string[] {
483
+ const blocks = text
484
+ .split(/\n\s*(?:---|##\s+Record\s+\d+|#\s+Record\s+\d+|记录\s*\d+|访谈\s*\d+)\s*\n/i)
485
+ .map((block) => block.trim())
486
+ .filter((block) => block.length > 40);
487
+
488
+ if (blocks.length > 1) return blocks;
489
+
490
+ return text
491
+ .split(/\n\s*\n(?=(?:Participant|受访者|Record|记录)\s*[::])/i)
492
+ .map((block) => block.trim())
493
+ .filter((block) => block.length > 40);
494
+ }
495
+
496
+ function countMatches(text: string, terms: readonly string[]): number {
497
+ const lower = text.toLowerCase();
498
+ return terms.reduce(
499
+ (count, term) => count + (lower.includes(term.toLowerCase()) ? 1 : 0),
500
+ 0,
501
+ );
502
+ }
503
+
504
+ function confidenceFrom(count: number, recordCount: number): "high" | "medium" | "low" {
505
+ if (recordCount >= 6 && count >= 4) return "high";
506
+ if (recordCount >= 3 && count >= 2) return "medium";
507
+ return "low";
508
+ }
509
+
510
+ function buildTheme(
511
+ language: OutputLanguage,
512
+ sourceLines: readonly string[],
513
+ sourceText: string,
514
+ recordCount: number,
515
+ theme: { zh: string; en: string; terms: string[] },
516
+ ): InsightReport["themeClusters"][number] | null {
517
+ const evidence = pickEvidenceLines(sourceLines, theme.terms, 4);
518
+ const count = countMatches(sourceText, theme.terms);
519
+ if (!count && evidence.length === 0) return null;
520
+
521
+ return {
522
+ theme: label(language, theme.zh, theme.en),
523
+ evidence,
524
+ confidence: confidenceFrom(count, recordCount),
525
+ };
526
+ }
527
+
528
+ export function createInsightReportFromRecords(
529
+ input: InsightReportInput,
530
+ ): InsightReport {
531
+ const language = input.language ?? inferLanguage(input.recordsText, "en");
532
+ const records = splitRecordBlocks(input.recordsText);
533
+ const sourceText = input.recordsText;
534
+ const sourceLines = compactLines(sourceText);
535
+ const recordCount = Math.max(records.length, 1);
536
+ const globalConfidence = recordCount >= 6 ? "high" : recordCount >= 3 ? "medium" : "low";
537
+ const researchGoal = compactText(input.researchGoal);
538
+ const decision = compactText(input.decisionToMake);
539
+ const themes = [
540
+ buildTheme(language, sourceLines, sourceText, recordCount, {
541
+ zh: "用户反复提到同一类流程阻力",
542
+ en: "Participants repeatedly mention workflow friction",
543
+ terms: ["workflow", "process", "manual", "stuck", "delay", "流程", "手动", "卡", "延期"],
544
+ }),
545
+ buildTheme(language, sourceLines, sourceText, recordCount, {
546
+ zh: "当前替代方案存在明显缺口",
547
+ en: "Current alternatives leave clear gaps",
548
+ terms: ["alternative", "tool", "template", "spreadsheet", "instead", "替代", "工具", "模板", "表格", "自己"],
549
+ }),
550
+ buildTheme(language, sourceLines, sourceText, recordCount, {
551
+ zh: "时间、金钱或质量成本正在累积",
552
+ en: "Time, money, or quality cost is accumulating",
553
+ terms: ["hour", "hours", "cost", "pay", "budget", "quality", "花", "耗时", "成本", "付费", "预算", "质量"],
554
+ }),
555
+ buildTheme(language, sourceLines, sourceText, recordCount, {
556
+ zh: "使用门槛和信任边界仍需验证",
557
+ en: "Adoption threshold and trust boundary still need validation",
558
+ terms: ["trust", "risk", "accurate", "edit", "review", "privacy", "信任", "风险", "准确", "编辑", "隐私", "复核"],
559
+ }),
560
+ ].filter((theme): theme is InsightReport["themeClusters"][number] => Boolean(theme));
561
+
562
+ const fallbackThemes = themes.length
563
+ ? themes
564
+ : [
565
+ {
566
+ theme: label(language, "访谈材料需要进一步结构化后再综合", "Interview material needs more structure before synthesis"),
567
+ evidence: takeUnique(sourceLines, 4),
568
+ confidence: "low" as const,
569
+ },
570
+ ];
571
+
572
+ return {
573
+ executiveSummary: [
574
+ label(
575
+ language,
576
+ `已识别 ${recordCount} 份记录;整体置信度为 ${globalConfidence}。`,
577
+ `${recordCount} record(s) identified; overall confidence is ${globalConfidence}.`,
578
+ ),
579
+ researchGoal
580
+ ? label(language, `本轮研究目标:${researchGoal}`, `Research goal: ${researchGoal}`)
581
+ : label(language, "未提供研究目标,报告只按文本证据做方向性整理。", "No research goal was provided, so the report remains directional."),
582
+ decision
583
+ ? label(language, `建议只把高证据发现用于支持决策:${decision}`, `Use only well-evidenced findings to support this decision: ${decision}`)
584
+ : label(language, "重复行为和明确代价应优先于口头兴趣。", "Repeated behavior and explicit cost should outweigh stated interest."),
585
+ ],
586
+ participantSegments: [
587
+ label(language, "高证据用户:能描述近期真实行为、具名工具和明确代价。", "High-evidence users: describe recent behavior, named tools, and clear cost."),
588
+ label(language, "待验证用户:表达兴趣但缺少具体场景、预算或频率。", "Needs-validation users: express interest but lack concrete scenario, budget, or frequency."),
589
+ label(language, "不匹配用户:问题由其他角色负责,或现有方案已经足够。", "Poor-fit users: the problem is owned by another role or current solutions are good enough."),
590
+ ],
591
+ themeClusters: fallbackThemes,
592
+ painPointRanking: fallbackThemes.map((theme, index) =>
593
+ label(
594
+ language,
595
+ `${index + 1}. ${theme.theme}:置信度 ${theme.confidence},优先复核证据是否来自多份记录。`,
596
+ `${index + 1}. ${theme.theme}: confidence ${theme.confidence}; verify whether evidence appears across multiple records.`,
597
+ ),
598
+ ),
599
+ alternativeMap: [
600
+ label(language, "当前工具或手工流程:从记录里提取具名工具、模板、表格、人工服务或 workaround。", "Current tools/manual flow: extract named tools, templates, spreadsheets, services, or workarounds from records."),
601
+ label(language, "继续不用的原因:若用户能忍受现状,说明痛点强度可能不够。", "Reason to keep doing nothing: if users tolerate the status quo, pain intensity may be weak."),
602
+ label(language, "替代成本:关注学习成本、迁移成本、质量风险和协作阻力。", "Switching cost: watch learning cost, migration cost, quality risk, and collaboration friction."),
603
+ ],
604
+ evidenceStrength: [
605
+ label(language, `样本量:${recordCount};整体置信度:${globalConfidence}。`, `Sample size: ${recordCount}; overall confidence: ${globalConfidence}.`),
606
+ label(language, "强证据:近期行为、具名替代方案、明确时间/金钱/风险代价。", "Strong evidence: recent behavior, named alternatives, explicit time/money/risk cost."),
607
+ label(language, "弱证据:礼貌性认可、未来可能会用、没有具体案例的主观判断。", "Weak evidence: compliments, future-tense interest, and subjective judgment without examples."),
608
+ ],
609
+ opportunityMap: fallbackThemes.slice(0, 3).map((theme) =>
610
+ decision
611
+ ? label(language, `围绕「${theme.theme}」设计一个能验证「${decision}」的小实验。`, `Design a small experiment around "${theme.theme}" to validate "${decision}".`)
612
+ : label(language, `围绕「${theme.theme}」设计一个低成本原型或访谈追问。`, `Design a low-cost prototype or follow-up question around "${theme.theme}".`),
613
+ ),
614
+ mvpRecommendation: [
615
+ label(language, "先选择证据最集中、使用场景最窄的一步作为 MVP。", "Start with the narrowest workflow step with the densest evidence."),
616
+ label(language, "MVP 输出必须能直接替代或改善现有 workaround 的关键部分。", "The MVP output must replace or improve a key part of the existing workaround."),
617
+ label(language, "低置信主题进入下一轮访谈,不直接进入开发排期。", "Low-confidence themes should go into the next interview round, not directly into the roadmap."),
618
+ ],
619
+ pricingSignals: [
620
+ label(language, "优先寻找过去付费、预算归属、替代成本和时间节省证据。", "Look first for past payment, budget owner, switching cost, and time-saved evidence."),
621
+ label(language, "口头愿付价格只作为假设,需要通过预售、试点或人工服务验证。", "Stated willingness to pay is only a hypothesis; validate with pre-sale, pilot, or concierge test."),
622
+ label(language, "若记录中没有成本证据,先不要把定价作为结论。", "If records contain no cost evidence, do not treat pricing as a conclusion."),
623
+ ],
624
+ messagingSignals: [
625
+ label(language, "直接保留用户描述问题和替代方案的原话。", "Preserve the user's own language for the problem and alternatives."),
626
+ label(language, "把高频词用于 landing page 和下一轮访谈筛选问题。", "Use repeated terms in landing page copy and next-round screening questions."),
627
+ label(language, "避免把团队内部术语当成用户自然语言。", "Avoid treating internal terminology as natural customer language."),
628
+ ],
629
+ nextExperiments: [
630
+ label(language, "用最高置信主题设计 3-5 个可观察任务或原型测试。", "Turn the highest-confidence theme into 3-5 observable task or prototype tests."),
631
+ label(language, "邀请提到强痛点的用户参与下一步真实材料测试。", "Invite users who described strong pain to test with real materials."),
632
+ label(language, "为低置信或矛盾主题补访谈,而不是用平均结论抹平。", "Run more interviews for low-confidence or contradictory themes instead of averaging them away."),
633
+ ],
634
+ risksAndUnknowns: [
635
+ label(language, "访谈样本不能单独推断市场规模或比例。", "Interviews alone cannot estimate market size or prevalence."),
636
+ label(language, "若记录格式混乱,主题聚类可能遗漏上下文,需要人工复核原文。", "If records are messy, theme clustering may miss context; review the source text."),
637
+ label(language, "建议和 MVP 方向都应再用行为数据、支付或原型实验验证。", "Recommendations and MVP direction should be validated with behavior data, payment, or prototype tests."),
638
+ ],
639
+ };
640
+ }
641
+
642
+ export function stringifyInsightReport(report: InsightReport): string {
643
+ const lines = [
644
+ "# User Interview Insight Report",
645
+ "",
646
+ "## Executive summary",
647
+ ...report.executiveSummary.map((item) => `- ${item}`),
648
+ "",
649
+ "## Theme clusters",
650
+ ...report.themeClusters.flatMap((theme) => [
651
+ `### ${theme.theme}`,
652
+ `Confidence: ${theme.confidence}`,
653
+ ...theme.evidence.map((item) => `- ${item}`),
654
+ "",
655
+ ]),
656
+ "## Evidence strength",
657
+ ...report.evidenceStrength.map((item) => `- ${item}`),
658
+ "",
659
+ "## Pain point ranking",
660
+ ...report.painPointRanking.map((item) => `- ${item}`),
661
+ "",
662
+ "## MVP recommendation",
663
+ ...report.mvpRecommendation.map((item) => `- ${item}`),
664
+ "",
665
+ "## Next experiments",
666
+ ...report.nextExperiments.map((item) => `- ${item}`),
667
+ ];
668
+
669
+ return lines.join("\n");
670
+ }