@recombine-ai/bosun 0.13.0 → 0.15.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 (224) hide show
  1. package/bin/bosun.js +7 -0
  2. package/build/client/_app/immutable/assets/0.b0jhAoSC.css +1 -0
  3. package/build/client/_app/immutable/assets/0.b0jhAoSC.css.br +0 -0
  4. package/build/client/_app/immutable/assets/0.b0jhAoSC.css.gz +0 -0
  5. package/build/client/_app/immutable/chunks/BTX1tKMW.js +1 -0
  6. package/build/client/_app/immutable/chunks/BTX1tKMW.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/BTX1tKMW.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/BVxLHCs8.js +1 -0
  9. package/build/client/_app/immutable/chunks/BVxLHCs8.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/BVxLHCs8.js.gz +0 -0
  11. package/build/client/_app/immutable/chunks/{De8YgKS3.js → ChVBNPUb.js} +1 -1
  12. package/build/client/_app/immutable/chunks/ChVBNPUb.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/ChVBNPUb.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/E9rdsnHb.js +1 -0
  15. package/build/client/_app/immutable/chunks/E9rdsnHb.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/E9rdsnHb.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/Z1_c4us_.js +3 -0
  18. package/build/client/_app/immutable/chunks/Z1_c4us_.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/Z1_c4us_.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/{Bj8jfBTi.js → lmnC3bvv.js} +1 -1
  21. package/build/client/_app/immutable/chunks/lmnC3bvv.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/lmnC3bvv.js.gz +0 -0
  23. package/build/client/_app/immutable/entry/{app.DK_wVDeR.js → app.BY5DP2K_.js} +2 -2
  24. package/build/client/_app/immutable/entry/app.BY5DP2K_.js.br +0 -0
  25. package/build/client/_app/immutable/entry/app.BY5DP2K_.js.gz +0 -0
  26. package/build/client/_app/immutable/entry/start.BeIIwvvV.js +1 -0
  27. package/build/client/_app/immutable/entry/start.BeIIwvvV.js.br +0 -0
  28. package/build/client/_app/immutable/entry/start.BeIIwvvV.js.gz +0 -0
  29. package/build/client/_app/immutable/nodes/{0.5fEbn14Q.js → 0.Cao0LJwW.js} +1 -1
  30. package/build/client/_app/immutable/nodes/0.Cao0LJwW.js.br +0 -0
  31. package/build/client/_app/immutable/nodes/0.Cao0LJwW.js.gz +0 -0
  32. package/build/client/_app/immutable/nodes/{1.CJrSN8zL.js → 1.Kjgu6yxL.js} +1 -1
  33. package/build/client/_app/immutable/nodes/1.Kjgu6yxL.js.br +0 -0
  34. package/build/client/_app/immutable/nodes/1.Kjgu6yxL.js.gz +0 -0
  35. package/build/client/_app/immutable/nodes/{10.SZwTlOEA.js → 10.yW2mHq85.js} +1 -1
  36. package/build/client/_app/immutable/nodes/10.yW2mHq85.js.br +0 -0
  37. package/build/client/_app/immutable/nodes/10.yW2mHq85.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/{17.D-KFl7Vk.js → 17.COHkD98p.js} +3 -3
  39. package/build/client/_app/immutable/nodes/17.COHkD98p.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/17.COHkD98p.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/18.DQVit7Md.js +11 -0
  42. package/build/client/_app/immutable/nodes/18.DQVit7Md.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/18.DQVit7Md.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/2.DVwYttKv.js +35 -0
  45. package/build/client/_app/immutable/nodes/2.DVwYttKv.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/2.DVwYttKv.js.gz +0 -0
  47. package/build/client/_app/immutable/nodes/{3.B1RGTOJ8.js → 3.BhmyTb3R.js} +1 -1
  48. package/build/client/_app/immutable/nodes/3.BhmyTb3R.js.br +0 -0
  49. package/build/client/_app/immutable/nodes/3.BhmyTb3R.js.gz +0 -0
  50. package/build/client/_app/immutable/nodes/{4.D2jcIUpD.js → 4.CxATX0ci.js} +1 -1
  51. package/build/client/_app/immutable/nodes/4.CxATX0ci.js.br +0 -0
  52. package/build/client/_app/immutable/nodes/4.CxATX0ci.js.gz +0 -0
  53. package/build/client/_app/immutable/nodes/{5.CUyCIyRd.js → 5.Dal1qxB1.js} +1 -1
  54. package/build/client/_app/immutable/nodes/5.Dal1qxB1.js.br +0 -0
  55. package/build/client/_app/immutable/nodes/5.Dal1qxB1.js.gz +0 -0
  56. package/build/client/_app/immutable/nodes/{6.B8HsGA2V.js → 6.iAOxBhJw.js} +1 -1
  57. package/build/client/_app/immutable/nodes/6.iAOxBhJw.js.br +0 -0
  58. package/build/client/_app/immutable/nodes/6.iAOxBhJw.js.gz +0 -0
  59. package/build/client/_app/immutable/nodes/{8.jY56-_OU.js → 8.DFhKu3dp.js} +1 -1
  60. package/build/client/_app/immutable/nodes/8.DFhKu3dp.js.br +0 -0
  61. package/build/client/_app/immutable/nodes/8.DFhKu3dp.js.gz +0 -0
  62. package/build/client/_app/immutable/nodes/9.DRIEv5m9.js +2 -0
  63. package/build/client/_app/immutable/nodes/9.DRIEv5m9.js.br +0 -0
  64. package/build/client/_app/immutable/nodes/9.DRIEv5m9.js.gz +0 -0
  65. package/build/client/_app/version.json +1 -1
  66. package/build/client/_app/version.json.br +0 -0
  67. package/build/client/_app/version.json.gz +0 -0
  68. package/build/server/chunks/{0-4QCErf9j.js → 0-CIzAMtA3.js} +3 -3
  69. package/build/server/chunks/{0-4QCErf9j.js.map → 0-CIzAMtA3.js.map} +1 -1
  70. package/build/server/chunks/{1-Dsjy8Hzc.js → 1-D1heuNbf.js} +2 -2
  71. package/build/server/chunks/{1-Dsjy8Hzc.js.map → 1-D1heuNbf.js.map} +1 -1
  72. package/build/server/chunks/{10-8061c8ui.js → 10-CjLtjXyZ.js} +2 -2
  73. package/build/server/chunks/{10-8061c8ui.js.map → 10-CjLtjXyZ.js.map} +1 -1
  74. package/build/server/chunks/{17-3_5wsIEX.js → 17-DklEmmOx.js} +4 -4
  75. package/build/server/chunks/{17-3_5wsIEX.js.map → 17-DklEmmOx.js.map} +1 -1
  76. package/build/server/chunks/{18-D73Mw140.js → 18-BJS-eRcz.js} +4 -4
  77. package/build/server/chunks/18-BJS-eRcz.js.map +1 -0
  78. package/build/server/chunks/{2-DM0VjzFi.js → 2-DCrAVYXs.js} +4 -4
  79. package/build/server/chunks/2-DCrAVYXs.js.map +1 -0
  80. package/build/server/chunks/{3-B3zlFGkL.js → 3-BpTqa4DU.js} +2 -2
  81. package/build/server/chunks/{3-B3zlFGkL.js.map → 3-BpTqa4DU.js.map} +1 -1
  82. package/build/server/chunks/{4-tESdMZpI.js → 4-B7GjwQzI.js} +2 -2
  83. package/build/server/chunks/{4-tESdMZpI.js.map → 4-B7GjwQzI.js.map} +1 -1
  84. package/build/server/chunks/{5-BJMTlq4U.js → 5-XjS__bcp.js} +2 -2
  85. package/build/server/chunks/{5-BJMTlq4U.js.map → 5-XjS__bcp.js.map} +1 -1
  86. package/build/server/chunks/{6-CDK2Aia6.js → 6-DWkWptmB.js} +2 -2
  87. package/build/server/chunks/{6-CDK2Aia6.js.map → 6-DWkWptmB.js.map} +1 -1
  88. package/build/server/chunks/{8-BJbDytoU.js → 8-_swAx6gL.js} +2 -2
  89. package/build/server/chunks/{8-BJbDytoU.js.map → 8-_swAx6gL.js.map} +1 -1
  90. package/build/server/chunks/{9-BNfIeSqk.js → 9-NEe24O4u.js} +4 -4
  91. package/build/server/chunks/9-NEe24O4u.js.map +1 -0
  92. package/build/server/chunks/{ChatStepTrace-DQL3qlna.js → ChatStepTrace-CX2Zolmp.js} +21 -8
  93. package/build/server/chunks/ChatStepTrace-CX2Zolmp.js.map +1 -0
  94. package/build/server/chunks/{_layout.svelte-DK7yl8vI.js → _layout.svelte-PtdpfgwJ.js} +66 -4
  95. package/build/server/chunks/_layout.svelte-PtdpfgwJ.js.map +1 -0
  96. package/build/server/chunks/{_page.svelte-ZWNBigwS.js → _page.svelte-C3gizoSD.js} +7 -4
  97. package/build/server/chunks/_page.svelte-C3gizoSD.js.map +1 -0
  98. package/build/server/chunks/{_page.svelte-D6bSXGHX.js → _page.svelte-CoCBnE2l.js} +8 -5
  99. package/build/server/chunks/_page.svelte-CoCBnE2l.js.map +1 -0
  100. package/build/server/chunks/{_page.svelte-ej3vdYeF.js → _page.svelte-DZvcjON6.js} +3 -2
  101. package/build/server/chunks/_page.svelte-DZvcjON6.js.map +1 -0
  102. package/build/server/chunks/steps-DzZCg39h.js +8 -0
  103. package/build/server/chunks/steps-DzZCg39h.js.map +1 -0
  104. package/build/server/index.js +1 -1
  105. package/build/server/index.js.map +1 -1
  106. package/build/server/manifest.js +13 -13
  107. package/build/server/manifest.js.map +1 -1
  108. package/package.json +4 -4
  109. package/src/backend/lib/backend-restart.ts +15 -0
  110. package/src/backend/roleplay/router.ts +42 -5
  111. package/src/backend/roleplay/schema-file.spec.ts +38 -0
  112. package/src/backend/roleplay/schema-file.ts +33 -0
  113. package/src/backend/tests/academy.ts +4 -2
  114. package/src/backend/tests/analyze.ts +2 -1
  115. package/src/backend/tests/emulate-agents.ts +13 -21
  116. package/src/backend/tests/pii-sanitized-test-drafts.spec.ts +162 -0
  117. package/src/backend/tests/pii-sanitized-test-drafts.ts +106 -0
  118. package/src/backend/tests/pii-sanitizer.spec.ts +145 -0
  119. package/src/backend/tests/pii-sanitizer.ts +171 -0
  120. package/src/backend/tests/router.ts +37 -0
  121. package/src/lib/agent-catalog.ts +4 -0
  122. package/src/lib/agents-context.ts +8 -0
  123. package/src/lib/api-telescope/client.ts +174 -0
  124. package/src/lib/api-telescope/index.ts +4 -0
  125. package/src/lib/api-telescope/latency-timeline-view-state.spec.ts +25 -0
  126. package/src/lib/api-telescope/latency-timeline-view-state.ts +86 -0
  127. package/src/lib/api-telescope/latency.ts +252 -0
  128. package/src/lib/api-telescope/source-selection.ts +20 -0
  129. package/src/lib/api-telescope/sources.server.ts +94 -0
  130. package/src/lib/api-telescope/types.ts +143 -0
  131. package/src/lib/components/Button.svelte +62 -0
  132. package/src/lib/components/CollapseExpand.svelte +425 -0
  133. package/src/lib/components/CopyTracesDropdown.svelte +118 -0
  134. package/src/lib/components/Dialog.svelte +69 -0
  135. package/src/lib/components/Dropdown.svelte +111 -0
  136. package/src/lib/components/History.svelte +174 -0
  137. package/src/lib/components/Icon.svelte +94 -0
  138. package/src/lib/components/Input.svelte +43 -0
  139. package/src/lib/components/Link.svelte +45 -0
  140. package/src/lib/components/Modal.svelte +45 -0
  141. package/src/lib/components/Panel.svelte +12 -0
  142. package/src/lib/components/PopoverModal.svelte +34 -0
  143. package/src/lib/components/Popper.svelte +109 -0
  144. package/src/lib/components/Tab.svelte +91 -0
  145. package/src/lib/components/Tabs.svelte +140 -0
  146. package/src/lib/components/agent-colors.ts +70 -0
  147. package/src/lib/dom/text-selection.ts +97 -0
  148. package/src/lib/edit/edit-vm.spec.ts +115 -0
  149. package/src/lib/edit/edit-vm.ts +169 -0
  150. package/src/lib/edit/edit-workspace-context.ts +56 -0
  151. package/src/lib/edit/highlight-styles.spec.ts +14 -0
  152. package/src/lib/edit/prompt-highlighter.spec.ts +80 -0
  153. package/src/lib/edit/prompt-highlighter.ts +155 -0
  154. package/src/lib/edit/quote-matching.ts +97 -0
  155. package/src/lib/monaco/bosun-theme.ts +125 -0
  156. package/src/lib/monaco/font.ts +2 -0
  157. package/src/lib/monaco-workers.ts +28 -0
  158. package/src/lib/steps.ts +19 -0
  159. package/src/lib/tests/pii-sanitized-test-draft.ts +50 -0
  160. package/src/lib/trpc-client.ts +16 -0
  161. package/src/lib/ws-client.ts +144 -0
  162. package/src/lib/ws-message.ts +150 -0
  163. package/build/client/_app/immutable/assets/0.B-Br6U4N.css +0 -1
  164. package/build/client/_app/immutable/assets/0.B-Br6U4N.css.br +0 -0
  165. package/build/client/_app/immutable/assets/0.B-Br6U4N.css.gz +0 -0
  166. package/build/client/_app/immutable/chunks/BOuo-4gP.js +0 -1
  167. package/build/client/_app/immutable/chunks/BOuo-4gP.js.br +0 -0
  168. package/build/client/_app/immutable/chunks/BOuo-4gP.js.gz +0 -0
  169. package/build/client/_app/immutable/chunks/Bj8jfBTi.js.br +0 -0
  170. package/build/client/_app/immutable/chunks/Bj8jfBTi.js.gz +0 -0
  171. package/build/client/_app/immutable/chunks/CX0Ekq8t.js +0 -1
  172. package/build/client/_app/immutable/chunks/CX0Ekq8t.js.br +0 -0
  173. package/build/client/_app/immutable/chunks/CX0Ekq8t.js.gz +0 -0
  174. package/build/client/_app/immutable/chunks/Cyj62X2W.js +0 -2
  175. package/build/client/_app/immutable/chunks/Cyj62X2W.js.br +0 -0
  176. package/build/client/_app/immutable/chunks/Cyj62X2W.js.gz +0 -0
  177. package/build/client/_app/immutable/chunks/DSDSeO7L.js +0 -1
  178. package/build/client/_app/immutable/chunks/DSDSeO7L.js.br +0 -0
  179. package/build/client/_app/immutable/chunks/DSDSeO7L.js.gz +0 -0
  180. package/build/client/_app/immutable/chunks/De8YgKS3.js.br +0 -1
  181. package/build/client/_app/immutable/chunks/De8YgKS3.js.gz +0 -0
  182. package/build/client/_app/immutable/entry/app.DK_wVDeR.js.br +0 -0
  183. package/build/client/_app/immutable/entry/app.DK_wVDeR.js.gz +0 -0
  184. package/build/client/_app/immutable/entry/start.xQy-2aGo.js +0 -1
  185. package/build/client/_app/immutable/entry/start.xQy-2aGo.js.br +0 -2
  186. package/build/client/_app/immutable/entry/start.xQy-2aGo.js.gz +0 -0
  187. package/build/client/_app/immutable/nodes/0.5fEbn14Q.js.br +0 -0
  188. package/build/client/_app/immutable/nodes/0.5fEbn14Q.js.gz +0 -0
  189. package/build/client/_app/immutable/nodes/1.CJrSN8zL.js.br +0 -0
  190. package/build/client/_app/immutable/nodes/1.CJrSN8zL.js.gz +0 -0
  191. package/build/client/_app/immutable/nodes/10.SZwTlOEA.js.br +0 -0
  192. package/build/client/_app/immutable/nodes/10.SZwTlOEA.js.gz +0 -0
  193. package/build/client/_app/immutable/nodes/17.D-KFl7Vk.js.br +0 -0
  194. package/build/client/_app/immutable/nodes/17.D-KFl7Vk.js.gz +0 -0
  195. package/build/client/_app/immutable/nodes/18.DqRdJY9A.js +0 -11
  196. package/build/client/_app/immutable/nodes/18.DqRdJY9A.js.br +0 -0
  197. package/build/client/_app/immutable/nodes/18.DqRdJY9A.js.gz +0 -0
  198. package/build/client/_app/immutable/nodes/2.B3Uuk25v.js +0 -35
  199. package/build/client/_app/immutable/nodes/2.B3Uuk25v.js.br +0 -0
  200. package/build/client/_app/immutable/nodes/2.B3Uuk25v.js.gz +0 -0
  201. package/build/client/_app/immutable/nodes/3.B1RGTOJ8.js.br +0 -0
  202. package/build/client/_app/immutable/nodes/3.B1RGTOJ8.js.gz +0 -0
  203. package/build/client/_app/immutable/nodes/4.D2jcIUpD.js.br +0 -0
  204. package/build/client/_app/immutable/nodes/4.D2jcIUpD.js.gz +0 -0
  205. package/build/client/_app/immutable/nodes/5.CUyCIyRd.js.br +0 -0
  206. package/build/client/_app/immutable/nodes/5.CUyCIyRd.js.gz +0 -0
  207. package/build/client/_app/immutable/nodes/6.B8HsGA2V.js.br +0 -0
  208. package/build/client/_app/immutable/nodes/6.B8HsGA2V.js.gz +0 -0
  209. package/build/client/_app/immutable/nodes/8.jY56-_OU.js.br +0 -0
  210. package/build/client/_app/immutable/nodes/8.jY56-_OU.js.gz +0 -0
  211. package/build/client/_app/immutable/nodes/9.v5dX6L9n.js +0 -2
  212. package/build/client/_app/immutable/nodes/9.v5dX6L9n.js.br +0 -0
  213. package/build/client/_app/immutable/nodes/9.v5dX6L9n.js.gz +0 -0
  214. package/build/server/chunks/18-D73Mw140.js.map +0 -1
  215. package/build/server/chunks/2-DM0VjzFi.js.map +0 -1
  216. package/build/server/chunks/9-BNfIeSqk.js.map +0 -1
  217. package/build/server/chunks/ChatStepTrace-DQL3qlna.js.map +0 -1
  218. package/build/server/chunks/_layout.svelte-DK7yl8vI.js.map +0 -1
  219. package/build/server/chunks/_page.svelte-D6bSXGHX.js.map +0 -1
  220. package/build/server/chunks/_page.svelte-ZWNBigwS.js.map +0 -1
  221. package/build/server/chunks/_page.svelte-ej3vdYeF.js.map +0 -1
  222. /package/build/client/_app/immutable/assets/{Popper.CLTyHc88.css → steps.CLTyHc88.css} +0 -0
  223. /package/build/client/_app/immutable/assets/{Popper.CLTyHc88.css.br → steps.CLTyHc88.css.br} +0 -0
  224. /package/build/client/_app/immutable/assets/{Popper.CLTyHc88.css.gz → steps.CLTyHc88.css.gz} +0 -0
@@ -0,0 +1,425 @@
1
+ <script lang="ts">
2
+ export let value: string | any[] = ''
3
+ export let limit = 200
4
+ export let maxLines: number | null = null
5
+ export let className = ''
6
+ // export let expandedLabel = 'collapse'
7
+ export let collapsedLabel = '...more'
8
+ export let initiallyExpanded = false
9
+ export let stopPropagation = true
10
+ export let expanded = initiallyExpanded
11
+ export let treatPlainText = false
12
+ export let highlightPrompt = false
13
+
14
+ type Segment = {
15
+ text: string
16
+ role?: string
17
+ time?: string
18
+ duration?: number
19
+ }
20
+
21
+ function escapeHtml(text: string): string {
22
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
23
+ }
24
+
25
+ function highlightPromptText(raw: string): string {
26
+ const s = normalizeNewlines(raw)
27
+ let html = escapeHtml(s)
28
+
29
+ const curlyBlocks: string[] = []
30
+ html = html.replace(/(\{+[\s\S]*?\}+)/g, (match) => {
31
+ const id = curlyBlocks.length
32
+ curlyBlocks.push(match)
33
+ return `__CURLY${id}__`
34
+ })
35
+
36
+ // Rows beginnig with # --> .mtk8 (blue)
37
+ html = html.replace(/^(\s*#.*)$/gm, '<span class="prompt-heading">$1</span>')
38
+
39
+ // <> --> .mtk8 (blue)
40
+ html = html.replace(/(&lt;[^&]*?&gt;)/g, '<span class="mtk8">$1</span>')
41
+
42
+ // "1. ", "2) ", "10." --> .mtk8 (blue)
43
+ html = html.replace(
44
+ /^(\s*)(\d+)([.)])(\s+)/gm,
45
+ (_match, p1, p2, p3, p4) => `${p1}<span class="mtk8">${p2}${p3}</span>${p4}`
46
+ )
47
+
48
+ // {} --> .mtk5 (orange)
49
+ curlyBlocks.forEach((block, idx) => {
50
+ const highlighted = `<span class="mtk5">${block}</span>`
51
+ html = html.replace(`__CURLY${idx}__`, highlighted)
52
+ })
53
+
54
+ // \n --> <br/>
55
+ html = html.replace(/\n/g, '<br/>')
56
+
57
+ return html
58
+ }
59
+
60
+ function normalizeNewlines(s: string): string {
61
+ let result = s.replace(/\r\n/g, '\n')
62
+ result = result.replace(/\\n/g, '\n')
63
+ return result
64
+ }
65
+
66
+ function getPlain(v: string | any[]): string {
67
+ try {
68
+ if (Array.isArray(v)) {
69
+ const s = v.map((s) => s?.text ?? s?.content ?? JSON.stringify(s)).join(' ')
70
+ return normalizeNewlines(s)
71
+ }
72
+ if (typeof v === 'string') {
73
+ try {
74
+ const parsed = JSON.parse(v)
75
+ if (Array.isArray(parsed)) {
76
+ const s = parsed.map((s: any) => s?.text ?? s?.content ?? JSON.stringify(s)).join(' ')
77
+ return normalizeNewlines(s)
78
+ }
79
+ return normalizeNewlines(v)
80
+ } catch {
81
+ return normalizeNewlines(v)
82
+ }
83
+ }
84
+ return normalizeNewlines(JSON.stringify(v))
85
+ } catch {
86
+ return normalizeNewlines(String(v ?? ''))
87
+ }
88
+ }
89
+
90
+ function getFull(v: string | any[]): string {
91
+ try {
92
+ if (Array.isArray(v)) {
93
+ const s = v.map((s) => s?.text ?? s?.content ?? JSON.stringify(s)).join('\n')
94
+ return normalizeNewlines(s)
95
+ }
96
+ if (typeof v === 'string') {
97
+ try {
98
+ const parsed = JSON.parse(v)
99
+ if (Array.isArray(parsed)) {
100
+ const s = parsed.map((s: any) => s?.text ?? s?.content ?? JSON.stringify(s)).join('\n')
101
+ return normalizeNewlines(s)
102
+ }
103
+ return normalizeNewlines(v)
104
+ } catch {
105
+ return normalizeNewlines(v)
106
+ }
107
+ }
108
+ return normalizeNewlines(JSON.stringify(v, null, 2))
109
+ } catch {
110
+ return normalizeNewlines(String(v ?? ''))
111
+ }
112
+ }
113
+
114
+ function parseLine(raw: string): Segment[] {
115
+ const s = normalizeNewlines(raw)
116
+
117
+ const lines = s
118
+ .split(/\n+/)
119
+ .map((l) => l.trim())
120
+ .filter((l) => l.length > 0)
121
+
122
+ const segments: Segment[] = []
123
+ let currentRole: string | undefined = undefined
124
+ let currentText = ''
125
+
126
+ for (const line of lines) {
127
+ const m = line.match(
128
+ /^(user|assistant|system|customer|caller|tool|model|agent)\s*[:\-]\s*(.*)$/i
129
+ )
130
+
131
+ if (m) {
132
+ if (currentText) {
133
+ segments.push({
134
+ text: currentText,
135
+ role: currentRole
136
+ })
137
+ }
138
+
139
+ currentRole = m[1].toLowerCase()
140
+ currentText = m[2] ?? ''
141
+ } else {
142
+ if (currentText) {
143
+ currentText += '\n' + line
144
+ } else {
145
+ currentText = line
146
+ }
147
+ }
148
+ }
149
+
150
+ if (currentText) {
151
+ segments.push({
152
+ text: currentText,
153
+ role: currentRole
154
+ })
155
+ }
156
+
157
+ return segments
158
+ }
159
+
160
+ function asSegments(v: string | any[]): Segment[] {
161
+ try {
162
+ let raw: any = v
163
+
164
+ if (typeof v === 'string') {
165
+ try {
166
+ raw = JSON.parse(v)
167
+ } catch {
168
+ if (!treatPlainText) return []
169
+
170
+ return parseLine(v)
171
+ }
172
+ }
173
+
174
+ // Support format { messages: [...] }
175
+ if (!Array.isArray(raw)) {
176
+ if (Array.isArray(raw?.messages)) {
177
+ raw = raw.messages
178
+ } else {
179
+ return []
180
+ }
181
+ }
182
+
183
+ return raw
184
+ .map((s: any) => {
185
+ let text: string
186
+
187
+ if (s?.text != null) {
188
+ text = String(s.text)
189
+ } else if (s?.content != null) {
190
+ if (Array.isArray(s.content)) {
191
+ text = s.content
192
+ .map((part: any) => {
193
+ if (typeof part === 'string') return part
194
+ if (typeof part?.text === 'string') return part.text
195
+ if (typeof part?.text?.value === 'string') return part.text.value
196
+ return JSON.stringify(part)
197
+ })
198
+ .join(' ')
199
+ } else if (typeof s.content === 'string') {
200
+ text = s.content
201
+ } else {
202
+ text = JSON.stringify(s.content)
203
+ }
204
+ } else if (typeof s === 'string') {
205
+ text = s
206
+ } else {
207
+ text = JSON.stringify(s)
208
+ }
209
+
210
+ return {
211
+ text: normalizeNewlines(text),
212
+ role: s?.role,
213
+ time: s?.time,
214
+ duration: s?.duration
215
+ } as Segment
216
+ })
217
+ .filter((s: Segment) => s.text && String(s.text).trim().length > 0)
218
+ } catch {
219
+ return []
220
+ }
221
+ }
222
+
223
+ const plain = () => getPlain(value)
224
+ const full = () => getFull(value)
225
+ const plainLines = () => plain().split('\n')
226
+ const segments = () => asSegments(value)
227
+ const hasStructuredTranscript = () => segments().length > 0
228
+
229
+ const isLong = () => {
230
+ if (maxLines != null) {
231
+ return plainLines().length > maxLines
232
+ }
233
+ return plain().length > limit
234
+ }
235
+
236
+ const collapsedText = () => {
237
+ const p = plain()
238
+ if (!isLong()) return p
239
+
240
+ //Max Lines
241
+ if (maxLines != null) {
242
+ return plainLines().slice(0, maxLines).join('\n')
243
+ }
244
+
245
+ // Symbols
246
+ return p.slice(0, limit)
247
+ }
248
+
249
+ // --- time / duration / role helpers ---
250
+ function formatTime(raw?: string): string | null {
251
+ if (!raw) return null
252
+ try {
253
+ const d = new Date(raw)
254
+ if (Number.isNaN(d.getTime())) return raw
255
+ return d.toLocaleTimeString(undefined, {
256
+ hour: '2-digit',
257
+ minute: '2-digit',
258
+ second: '2-digit'
259
+ })
260
+ } catch {
261
+ return raw
262
+ }
263
+ }
264
+
265
+ function formatDuration(raw?: number): string | null {
266
+ if (raw == null || !Number.isFinite(raw as any)) return null
267
+ const total = Number(raw)
268
+ const totalSec = Math.floor(total) // remove miliseconds
269
+ const m = Math.floor(totalSec / 60)
270
+ const s = totalSec % 60
271
+ const mm = String(m).padStart(2, '0')
272
+ const ss = String(s).padStart(2, '0')
273
+ return `${mm}:${ss}`
274
+ }
275
+
276
+ function normalizeRole(role?: string): string {
277
+ if (!role) return 'Assistant'
278
+ const r = role.trim()
279
+ if (!r) return 'Assistant'
280
+ return r.charAt(0).toUpperCase() + r.slice(1)
281
+ }
282
+
283
+ function isUserRole(role?: string): boolean {
284
+ const r = role?.toLowerCase() ?? ''
285
+ return r === 'user' || r === 'customer' || r === 'caller'
286
+ }
287
+
288
+ // System messages in <>
289
+ function splitSystem(text: string): { main: string; system: string[] } {
290
+ const system: string[] = []
291
+ const main = text
292
+ .replace(/<([^>]+)>/g, (_m, inner) => {
293
+ const cleaned = String(inner).trim()
294
+ if (cleaned) system.push(cleaned)
295
+ return ''
296
+ })
297
+ .trim()
298
+
299
+ return { main, system }
300
+ }
301
+
302
+ function toggle(e: Event) {
303
+ if (stopPropagation) e.stopPropagation()
304
+ expanded = !expanded
305
+ }
306
+
307
+ function onContainerClick(e: Event) {
308
+ if (stopPropagation) e.stopPropagation()
309
+ }
310
+ </script>
311
+
312
+ {#if expanded}
313
+ <!-- <div
314
+ class={`mt-1 max-h-96 cursor-text select-text overflow-y-auto rounded border-thin border-rc-gray-600 bg-rc-gray-900 p-2 pr-4 text-sm text-rc-gray-200 ${className}`}
315
+ onclick={onContainerClick}
316
+ > -->
317
+ {#if hasStructuredTranscript()}
318
+ <div class="space-y-3">
319
+ {#each segments() as seg}
320
+ {@const userSide = isUserRole(seg.role)}
321
+ {@const isSystem = (seg.role ?? '').toLowerCase() === 'system'}
322
+ {@const parts = splitSystem(seg.text)}
323
+ {@const t = formatTime(seg.time)}
324
+ {@const d = formatDuration(seg.duration)}
325
+
326
+ <div class={`flex ${userSide ? 'justify-end' : 'justify-start'}`}>
327
+ <div
328
+ class={`max-w-[70%] break-words rounded-md px-3 py-2 text-sm shadow-sm ${
329
+ userSide ? 'bg-rc-gray-600 text-rc-gray-100' : 'bg-rc-gray-700 text-rc-gray-100'
330
+ }`}
331
+ >
332
+ <!-- Role -->
333
+ <div
334
+ class={`my-1 text-xs font-semibold uppercase ${
335
+ isSystem
336
+ ? 'text-rc-blue ' + (userSide ? 'text-right' : 'text-left')
337
+ : 'text-rc-green-200 ' + (userSide ? 'text-right' : 'text-left')
338
+ }`}
339
+ >
340
+ {normalizeRole(seg.role)}
341
+ </div>
342
+
343
+ <!-- Text & system messages -->
344
+ <div
345
+ class={`whitespace-pre-wrap break-words py-1 text-sm ${userSide ? 'text-justify' : 'text-left'} ${
346
+ isSystem ? 'italic text-rc-gray-200' : ''
347
+ }`}
348
+ >
349
+ {#if parts.main}
350
+ <div>{parts.main}</div>
351
+ {/if}
352
+
353
+ {#each parts.system as sys}
354
+ <div class="mt-1 whitespace-pre-wrap break-words text-xs italic text-rc-gray-200">
355
+ &lt;{sys}&gt;
356
+ </div>
357
+ {/each}
358
+ </div>
359
+
360
+ <!-- Time / duration -->
361
+ {#if t || d}
362
+ <div class={`mt-1 text-xs text-rc-gray-300 ${userSide ? 'text-right' : 'text-left'}`}>
363
+ {#if t}{t}{/if}
364
+ {#if t && d}
365
+ ·
366
+ {/if}
367
+ {#if d}{d}{/if}
368
+ </div>
369
+ {/if}
370
+ </div>
371
+ </div>
372
+ {/each}
373
+ </div>
374
+ {:else}
375
+ <div class="whitespace-pre-wrap break-words">
376
+ {#if highlightPrompt}
377
+ {@html highlightPromptText(full())}
378
+ {:else}
379
+ {full()}
380
+ {/if}
381
+ </div>
382
+ {/if}
383
+ <!-- </div> -->
384
+ <!-- <div class="mt-2 pr-2 text-right">
385
+ <button class="text-rc-green-200 hover:text-rc-green-100" onclick={toggle}>
386
+ {expandedLabel}
387
+ </button>
388
+ </div> -->
389
+ {:else}
390
+ <div
391
+ class={`mt-1 cursor-text select-text rounded bg-rc-gray-900 p-2 text-sm text-rc-gray-200 ${className}`}
392
+ onclick={onContainerClick}
393
+ >
394
+ <span class="whitespace-pre-wrap break-words">
395
+ {#if highlightPrompt}
396
+ {@html highlightPromptText(collapsedText())}
397
+ {:else}
398
+ {collapsedText()}
399
+ {/if}
400
+ </span>
401
+
402
+ {#if isLong()}
403
+ <button
404
+ class="inline-flex text-sm text-rc-green-200 hover:text-rc-green-100"
405
+ onclick={toggle}
406
+ >
407
+ {collapsedLabel}
408
+ </button>
409
+ {/if}
410
+ </div>
411
+ {/if}
412
+
413
+ <style lang="postcss">
414
+ :global(.mtk8) {
415
+ @apply text-rc-blue;
416
+ }
417
+
418
+ :global(.mtk5) {
419
+ @apply text-rc-orange;
420
+ }
421
+
422
+ :global(.prompt-heading) {
423
+ @apply font-semibold text-rc-blue;
424
+ }
425
+ </style>
@@ -0,0 +1,118 @@
1
+ <script lang="ts">
2
+ import Button from '$lib/components/Button.svelte'
3
+ import Dropdown from '$lib/components/Dropdown.svelte'
4
+ import Icon from '$lib/components/Icon.svelte'
5
+
6
+ type CopiedTracesMode = 'with-prompts' | 'without-prompts'
7
+
8
+ interface Props {
9
+ disabled?: boolean
10
+ getWithPromptsText: () => string
11
+ getWithoutPromptsText: () => string
12
+ dropdownId?: string
13
+ }
14
+
15
+ let {
16
+ disabled = false,
17
+ getWithPromptsText,
18
+ getWithoutPromptsText,
19
+ dropdownId = 'copy-traces-dropdown'
20
+ }: Props = $props()
21
+
22
+ let open = $state(false)
23
+ let anchorEl = $state<HTMLButtonElement | null>(null)
24
+
25
+ let copied: CopiedTracesMode | null = $state(null)
26
+ let timer: number | null = $state(null)
27
+
28
+ $effect(() => {
29
+ if (disabled && open) open = false
30
+ })
31
+
32
+ async function copy(mode: CopiedTracesMode) {
33
+ try {
34
+ const text = mode === 'with-prompts' ? getWithPromptsText() : getWithoutPromptsText()
35
+ await navigator.clipboard.writeText(text)
36
+
37
+ copied = mode
38
+ if (timer) clearTimeout(timer)
39
+ timer = window.setTimeout(() => {
40
+ copied = null
41
+ timer = null
42
+ }, 2000)
43
+ } catch (e) {
44
+ console.error('Failed to copy traces', e)
45
+ }
46
+ }
47
+ </script>
48
+
49
+ <div>
50
+ <Button
51
+ variant="secondary"
52
+ size="small"
53
+ {disabled}
54
+ aria-expanded={open}
55
+ aria-haspopup="listbox"
56
+ aria-controls={dropdownId}
57
+ bind:el={anchorEl}
58
+ onclick={(e) => {
59
+ e.stopPropagation()
60
+ open = !open
61
+ }}
62
+ class="flex items-center gap-1"
63
+ >
64
+ {#if copied === 'without-prompts'}
65
+ Copied traces (without prompts)
66
+ {:else if copied === 'with-prompts'}
67
+ Copied traces (with prompts)
68
+ {:else}
69
+ Copy traces
70
+ {/if}
71
+ <Icon
72
+ name="chevron-down"
73
+ class={'size-5 text-rc-gray-400 transition-transform duration-200 ' +
74
+ (open ? 'rotate-180' : '')}
75
+ />
76
+ </Button>
77
+
78
+ <Dropdown
79
+ id={dropdownId}
80
+ {open}
81
+ onClose={() => (open = false)}
82
+ {anchorEl}
83
+ placement="bottom-end"
84
+ offset={[0, 1]}
85
+ >{#snippet children()}
86
+ <div
87
+ class="mt-1 overflow-hidden rounded-lg border border-rc-gray-110 bg-rc-gray-930 shadow-inner-white-soft backdrop-blur-2xl"
88
+ role="listbox"
89
+ >
90
+ <Button
91
+ variant="ghost"
92
+ size="small"
93
+ role="option"
94
+ onclick={(e) => {
95
+ e.stopPropagation()
96
+ open = false
97
+ copy('without-prompts')
98
+ }}
99
+ class="w-full rounded-none border-b border-rc-gray-110 py-2 pr-3 text-left backdrop-blur-2xl hover:bg-rc-gray-700 focus:bg-rc-gray-400"
100
+ >
101
+ Without prompts
102
+ </Button>
103
+ <Button
104
+ variant="ghost"
105
+ size="small"
106
+ role="option"
107
+ onclick={(e) => {
108
+ e.stopPropagation()
109
+ open = false
110
+ copy('with-prompts')
111
+ }}
112
+ class="w-full rounded-none py-2 pr-3 text-left backdrop-blur-2xl hover:bg-rc-gray-700 focus:bg-rc-gray-400"
113
+ >
114
+ With prompts
115
+ </Button>
116
+ </div>{/snippet}
117
+ </Dropdown>
118
+ </div>
@@ -0,0 +1,69 @@
1
+ <script lang="ts">
2
+ import { onMount, type Snippet } from 'svelte'
3
+ import Button from './Button.svelte'
4
+
5
+ let dialog: HTMLDialogElement
6
+
7
+ type DialogProps = {
8
+ showCloseButton?: boolean
9
+ onClose?: () => void
10
+ onOpen?: () => void
11
+ isOpen?: boolean
12
+ children: Snippet
13
+ }
14
+
15
+ let {
16
+ showCloseButton = false,
17
+ onClose = () => {},
18
+ onOpen = () => {},
19
+ isOpen = false,
20
+ children
21
+ }: DialogProps = $props()
22
+
23
+ $effect(() => {
24
+ if (isOpen) {
25
+ console.log('show')
26
+ dialog?.showModal()
27
+ onOpen()
28
+ } else if (dialog?.open) {
29
+ console.log('close')
30
+ dialog.close()
31
+ }
32
+ })
33
+
34
+ function handleClose(): void {
35
+ onClose()
36
+ }
37
+
38
+ onMount(() => {
39
+ dialog.addEventListener('close', handleClose)
40
+
41
+ return () => {
42
+ dialog.removeEventListener('close', handleClose)
43
+ }
44
+ })
45
+
46
+ function handleBackdropClick(e: MouseEvent): void {
47
+ if (e.target === dialog) {
48
+ dialog.close()
49
+ }
50
+ }
51
+ </script>
52
+
53
+ <dialog
54
+ bind:this={dialog}
55
+ onmousedown={handleBackdropClick}
56
+ class="dialog m-auto bg-transparent p-0 text-rc-gray-100 shadow-md"
57
+ >
58
+ {@render children()}
59
+
60
+ {#if showCloseButton}
61
+ <Button variant="ghost" onclick={() => dialog.close()} class="absolute right-2 top-2">✕</Button>
62
+ {/if}
63
+ </dialog>
64
+
65
+ <style lang="postcss">
66
+ .dialog::backdrop {
67
+ @apply bg-black/50 backdrop-blur-sm backdrop-filter;
68
+ }
69
+ </style>
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte'
3
+ import type { Snippet } from 'svelte'
4
+ import type { Placement } from '@popperjs/core'
5
+ import Popper from './Popper.svelte'
6
+
7
+ interface Props {
8
+ open: boolean
9
+ anchorEl: HTMLElement | null
10
+ id: string
11
+ placement?: Placement
12
+ offset?: [number, number]
13
+ flip?: boolean
14
+ preventOverflow?: boolean
15
+ onClose: () => void
16
+ children: Snippet
17
+ }
18
+
19
+ let {
20
+ open = false,
21
+ anchorEl = null,
22
+ id = '',
23
+ placement = 'bottom-start',
24
+ offset = [0, 2],
25
+ flip = true,
26
+ preventOverflow = true,
27
+ onClose = () => {},
28
+ children
29
+ }: Props = $props()
30
+
31
+ let popoverEl: HTMLDivElement | null = $state(null)
32
+ let popperComponent: Popper | null = $state(null)
33
+
34
+ function handleClose() {
35
+ onClose()
36
+ }
37
+
38
+ function handleClickOutside(event: MouseEvent) {
39
+ if (open && popoverEl && anchorEl) {
40
+ const target = event.target as Node
41
+ if (!popoverEl.contains(target) && !anchorEl.contains(target)) {
42
+ handleClose()
43
+ }
44
+ }
45
+ }
46
+
47
+ function handleKeydown(event: KeyboardEvent) {
48
+ if (event.key === 'Escape' && open) {
49
+ handleClose()
50
+ }
51
+ }
52
+
53
+ onMount(() => {
54
+ document.addEventListener('click', handleClickOutside, true)
55
+ document.addEventListener('keydown', handleKeydown)
56
+
57
+ const handleResize = () => {
58
+ if (popperComponent) {
59
+ popperComponent.update()
60
+ }
61
+ }
62
+
63
+ const handleScroll = () => {
64
+ if (popperComponent) {
65
+ popperComponent.update()
66
+ }
67
+ }
68
+
69
+ window.addEventListener('resize', handleResize)
70
+ window.addEventListener('scroll', handleScroll, true)
71
+
72
+ return () => {
73
+ document.removeEventListener('click', handleClickOutside, true)
74
+ document.removeEventListener('keydown', handleKeydown)
75
+ window.removeEventListener('resize', handleResize)
76
+ window.removeEventListener('scroll', handleScroll, true)
77
+ }
78
+ })
79
+ </script>
80
+
81
+ {#if open && anchorEl}
82
+ <Popper
83
+ bind:this={popperComponent}
84
+ referenceEl={anchorEl}
85
+ {placement}
86
+ {offset}
87
+ {flip}
88
+ {preventOverflow}
89
+ >
90
+ {#snippet children()}
91
+ <div bind:this={popoverEl} {id} class="popover" role="dialog" aria-modal="true">
92
+ <div
93
+ bind:this={popoverEl}
94
+ {id}
95
+ class="popover"
96
+ role="dialog"
97
+ aria-modal="true"
98
+ style={`min-width:${anchorEl?.offsetWidth ?? 0}px`}
99
+ >
100
+ {@render children()}
101
+ </div>
102
+ </div>
103
+ {/snippet}
104
+ </Popper>
105
+ {/if}
106
+
107
+ <style lang="postcss">
108
+ .popover {
109
+ @apply z-popover w-fit rounded-lg bg-rc-gray-800 font-sans shadow-xl;
110
+ }
111
+ </style>