arcane-os 0.1.0-dev.5

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 (248) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMMERCIAL-LICENSE.md +13 -0
  3. package/LICENSE +661 -0
  4. package/NOTICE +70 -0
  5. package/README.md +448 -0
  6. package/bin/arcane-test.mjs +741 -0
  7. package/bin/arcane.mjs +5 -0
  8. package/docs/architecture.md +234 -0
  9. package/docs/compatibility.md +36 -0
  10. package/docs/event-manager.md +166 -0
  11. package/docs/platform-targets.md +108 -0
  12. package/docs/publishing.md +203 -0
  13. package/docs/reference/README.md +117 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +123 -0
  16. package/docs/reference/behavioral-testing.md +86 -0
  17. package/docs/reference/cli.md +569 -0
  18. package/docs/reference/core/README.md +62 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +873 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +65 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +957 -0
  32. package/docs/reference/inventory/package-api.json +2632 -0
  33. package/docs/reference/inventory/runtime-components.json +934 -0
  34. package/docs/reference/inventory/runtime-entities.json +26 -0
  35. package/docs/reference/inventory/runtime-modules.json +1249 -0
  36. package/docs/reference/protocols.md +242 -0
  37. package/docs/reference/runtime-components.md +1098 -0
  38. package/docs/reference/runtime-entities.md +303 -0
  39. package/docs/reference/runtime-modules.md +2010 -0
  40. package/docs/reference/sdk-api.md +4901 -0
  41. package/docs/roadmap.md +79 -0
  42. package/docs/work-amplification.md +124 -0
  43. package/node_modules/event-pubsub/CHANGELOG.md +55 -0
  44. package/node_modules/event-pubsub/MIGRATION.md +70 -0
  45. package/node_modules/event-pubsub/README.md +363 -0
  46. package/node_modules/event-pubsub/SECURITY.md +37 -0
  47. package/node_modules/event-pubsub/index.js +141 -0
  48. package/node_modules/event-pubsub/licence +21 -0
  49. package/node_modules/event-pubsub/package.json +59 -0
  50. package/node_modules/strong-type/README.md +408 -0
  51. package/node_modules/strong-type/assets/strong-type-header.png +0 -0
  52. package/node_modules/strong-type/index.js +1151 -0
  53. package/node_modules/strong-type/licence +21 -0
  54. package/node_modules/strong-type/node.js +125 -0
  55. package/node_modules/strong-type/package.json +61 -0
  56. package/package.json +95 -0
  57. package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
  58. package/runtime/arcane/components/app-bar.html +468 -0
  59. package/runtime/arcane/components/assistant-panel.html +715 -0
  60. package/runtime/arcane/components/calculator.html +8 -0
  61. package/runtime/arcane/components/chart.html +655 -0
  62. package/runtime/arcane/components/chat.html +1225 -0
  63. package/runtime/arcane/components/conversation-view.html +13 -0
  64. package/runtime/arcane/components/dashboard-config.html +341 -0
  65. package/runtime/arcane/components/data-maintenance.html +112 -0
  66. package/runtime/arcane/components/data-view.html +92 -0
  67. package/runtime/arcane/components/directory-picker.html +197 -0
  68. package/runtime/arcane/components/document-inspector.html +252 -0
  69. package/runtime/arcane/components/file-drop.html +264 -0
  70. package/runtime/arcane/components/file-inspector.html +293 -0
  71. package/runtime/arcane/components/file-manager.html +1715 -0
  72. package/runtime/arcane/components/header.html +142 -0
  73. package/runtime/arcane/components/integration-settings.html +14 -0
  74. package/runtime/arcane/components/local-ai-status.html +360 -0
  75. package/runtime/arcane/components/markdown-document.html +1048 -0
  76. package/runtime/arcane/components/markdown-editor.html +360 -0
  77. package/runtime/arcane/components/media-embed.html +8 -0
  78. package/runtime/arcane/components/modal.html +402 -0
  79. package/runtime/arcane/components/output-panel.html +259 -0
  80. package/runtime/arcane/components/preferences-form.html +135 -0
  81. package/runtime/arcane/components/record-timeline.html +105 -0
  82. package/runtime/arcane/components/relationship-board.html +116 -0
  83. package/runtime/arcane/components/screen-capture.html +8 -0
  84. package/runtime/arcane/components/source-code-viewer.html +441 -0
  85. package/runtime/arcane/components/source-explanation.html +124 -0
  86. package/runtime/arcane/components/speech.html +365 -0
  87. package/runtime/arcane/components/summary-strip.html +177 -0
  88. package/runtime/arcane/components/table.html +77 -0
  89. package/runtime/arcane/components/task-progress.html +282 -0
  90. package/runtime/arcane/components/terminal-workspace.html +65 -0
  91. package/runtime/arcane/components/theme-editor.html +41 -0
  92. package/runtime/arcane/components/theme-switcher.html +46 -0
  93. package/runtime/arcane/components/unified-inbox.html +20 -0
  94. package/runtime/arcane/components/voice-transcription.html +476 -0
  95. package/runtime/arcane/components/weather-widget.html +8 -0
  96. package/runtime/arcane/components/web-navigator.html +239 -0
  97. package/runtime/arcane/css/communications.css +1 -0
  98. package/runtime/arcane/css/dashboard-config.css +45 -0
  99. package/runtime/arcane/css/document-site.css +981 -0
  100. package/runtime/arcane/css/layout.css +438 -0
  101. package/runtime/arcane/css/primitives.css +321 -0
  102. package/runtime/arcane/css/theme.css +112 -0
  103. package/runtime/arcane/css/utility-workspace.css +1 -0
  104. package/runtime/arcane/entities/ApiModelRecord.js +20 -0
  105. package/runtime/arcane/entities/Calculation.js +13 -0
  106. package/runtime/arcane/entities/Chat.js +581 -0
  107. package/runtime/arcane/entities/CommunicationMessage.js +29 -0
  108. package/runtime/arcane/entities/CommunicationThread.js +21 -0
  109. package/runtime/arcane/entities/Document.js +10 -0
  110. package/runtime/arcane/entities/File.js +143 -0
  111. package/runtime/arcane/entities/Image.js +135 -0
  112. package/runtime/arcane/entities/IntentEnvelope.js +834 -0
  113. package/runtime/arcane/entities/Preference.js +83 -0
  114. package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
  115. package/runtime/arcane/entities/TerminalSession.js +46 -0
  116. package/runtime/arcane/entities/Theme.js +107 -0
  117. package/runtime/arcane/entities/User.js +1046 -0
  118. package/runtime/arcane/entities/Weather.js +23 -0
  119. package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
  120. package/runtime/arcane/img/arrow-left.png +0 -0
  121. package/runtime/arcane/img/arrow-right.png +0 -0
  122. package/runtime/arcane/img/doc.svg +5 -0
  123. package/runtime/arcane/img/folder.svg +4 -0
  124. package/runtime/arcane/img/image.svg +5 -0
  125. package/runtime/arcane/img/refresh.png +0 -0
  126. package/runtime/arcane/img/send.svg +9 -0
  127. package/runtime/arcane/img/trash.svg +5 -0
  128. package/runtime/arcane/img/upload.svg +5 -0
  129. package/runtime/arcane/modules/AI.js +2048 -0
  130. package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
  131. package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
  132. package/runtime/arcane/modules/AIResponseLength.js +42 -0
  133. package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
  134. package/runtime/arcane/modules/AnsiText.js +53 -0
  135. package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
  136. package/runtime/arcane/modules/AppDataScope.js +245 -0
  137. package/runtime/arcane/modules/AppearancePreferences.js +28 -0
  138. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
  139. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
  140. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
  141. package/runtime/arcane/modules/AsyncBoundary.js +161 -0
  142. package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
  143. package/runtime/arcane/modules/CalculatorEngine.js +20 -0
  144. package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
  145. package/runtime/arcane/modules/ChartLibrary.js +35 -0
  146. package/runtime/arcane/modules/ChatRecords.js +13 -0
  147. package/runtime/arcane/modules/CommunicationAppController.js +43 -0
  148. package/runtime/arcane/modules/CommunicationHub.js +16 -0
  149. package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
  150. package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
  151. package/runtime/arcane/modules/ComponentContracts.js +586 -0
  152. package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
  153. package/runtime/arcane/modules/ConversationActionItems.js +488 -0
  154. package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
  155. package/runtime/arcane/modules/ConversationTimebox.js +527 -0
  156. package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
  157. package/runtime/arcane/modules/DBLS.js +171 -0
  158. package/runtime/arcane/modules/DBOPFS.js +1154 -0
  159. package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
  160. package/runtime/arcane/modules/DataMaintenance.js +91 -0
  161. package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
  162. package/runtime/arcane/modules/DirectoryPicker.js +109 -0
  163. package/runtime/arcane/modules/DocumentNavigation.js +223 -0
  164. package/runtime/arcane/modules/Errors.js +1025 -0
  165. package/runtime/arcane/modules/GifEncoder.js +29 -0
  166. package/runtime/arcane/modules/HTMLImport.js +114 -0
  167. package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
  168. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
  169. package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
  170. package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
  171. package/runtime/arcane/modules/MD.js +111 -0
  172. package/runtime/arcane/modules/Mail.js +352 -0
  173. package/runtime/arcane/modules/MailTransport.mjs +180 -0
  174. package/runtime/arcane/modules/Marked.min.js +71 -0
  175. package/runtime/arcane/modules/MemoryRecords.js +44 -0
  176. package/runtime/arcane/modules/MessageAdvisory.js +38 -0
  177. package/runtime/arcane/modules/ModelDefinition.js +189 -0
  178. package/runtime/arcane/modules/Ollama.js +74 -0
  179. package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
  180. package/runtime/arcane/modules/OllamaSettings.js +24 -0
  181. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
  182. package/runtime/arcane/modules/PreferenceStore.js +109 -0
  183. package/runtime/arcane/modules/QRCode.min.js +1 -0
  184. package/runtime/arcane/modules/Questionnaire.js +61 -0
  185. package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
  186. package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
  187. package/runtime/arcane/modules/RecordReviewStore.js +94 -0
  188. package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
  189. package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
  190. package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
  191. package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
  192. package/runtime/arcane/modules/ScreenCapture.js +20 -0
  193. package/runtime/arcane/modules/SpeechPlayback.js +581 -0
  194. package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
  195. package/runtime/arcane/modules/SystemAppearance.js +20 -0
  196. package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
  197. package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
  198. package/runtime/arcane/modules/TerminalClient.js +52 -0
  199. package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
  200. package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
  201. package/runtime/arcane/modules/ThemeManager.js +131 -0
  202. package/runtime/arcane/modules/TimeGuard.js +149 -0
  203. package/runtime/arcane/modules/ToolCallRouter.js +83 -0
  204. package/runtime/arcane/modules/WaitForComponent.js +102 -0
  205. package/runtime/arcane/modules/YouTubeMedia.js +16 -0
  206. package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
  207. package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
  208. package/runtime/arcane/modules/uPlot.min.css +1 -0
  209. package/runtime/arcane/security/arcane-network-policy.json +6 -0
  210. package/runtime/strong-type/index.js +352 -0
  211. package/runtime/strong-type/licence +21 -0
  212. package/runtime/strong-type/package.json +45 -0
  213. package/schemas/arcane-app-bundle.schema.json +125 -0
  214. package/schemas/arcane-app.schema.json +329 -0
  215. package/schemas/arcane-lock.schema.json +86 -0
  216. package/schemas/arcane-package.schema.json +224 -0
  217. package/schemas/cli-event.schema.json +122 -0
  218. package/schemas/event-stack.schema.json +152 -0
  219. package/schemas/native-build-plan.schema.json +176 -0
  220. package/schemas/target-adapter.schema.json +117 -0
  221. package/src/app-descriptor.mjs +500 -0
  222. package/src/cli/main.mjs +561 -0
  223. package/src/constants.mjs +31 -0
  224. package/src/dev-server.mjs +718 -0
  225. package/src/doctor.mjs +315 -0
  226. package/src/dom-event-instrumentation.mjs +594 -0
  227. package/src/errors.mjs +75 -0
  228. package/src/event-manager.mjs +1342 -0
  229. package/src/event-queue.mjs +138 -0
  230. package/src/events.mjs +219 -0
  231. package/src/index.mjs +177 -0
  232. package/src/integrated-provider-loader.mjs +432 -0
  233. package/src/native-plan.mjs +698 -0
  234. package/src/native-provider-loader.mjs +1126 -0
  235. package/src/packager/core.mjs +2691 -0
  236. package/src/process.mjs +353 -0
  237. package/src/release-bundle.mjs +2523 -0
  238. package/src/repository.mjs +90 -0
  239. package/src/runtime.mjs +452 -0
  240. package/src/scaffold.mjs +380 -0
  241. package/src/targets/index.mjs +436 -0
  242. package/src/templates/assets/app-icon.png +0 -0
  243. package/src/templates/workspace-template.mjs +388 -0
  244. package/src/testing-loader.mjs +9 -0
  245. package/src/testing.mjs +427 -0
  246. package/src/toolchain.mjs +1335 -0
  247. package/src/update-check.mjs +307 -0
  248. package/src/workspace.mjs +449 -0
@@ -0,0 +1,1225 @@
1
+ <link rel="stylesheet" href="./arcane/css/layout.css?v=3" />
2
+ <style>
3
+ .chat_area {
4
+ display: flex;
5
+ flex-direction: column;
6
+ justify-content: flex-end;
7
+ padding: 1em;
8
+ height: calc(100% - 2em);
9
+ overflow: hidden;
10
+ }
11
+
12
+ .chat_area .thinking{
13
+ opacity:.36;
14
+ overflow-wrap:anywhere;
15
+ white-space:pre-wrap;
16
+ }
17
+
18
+ .chat_area .thinking.message_progress{
19
+ display:grid;
20
+ gap:.38em;
21
+ width:min(34em,100%);
22
+ opacity:1;
23
+ }
24
+
25
+ .message_progress_label{
26
+ display:flex;
27
+ justify-content:space-between;
28
+ gap:1em;
29
+ font-size:.9em;
30
+ font-variant-numeric:tabular-nums;
31
+ }
32
+
33
+ .message_progress_track{
34
+ display:block;
35
+ height:.58em;
36
+ overflow:hidden;
37
+ border:1px solid var(--border-color);
38
+ border-radius:999px;
39
+ background:var(--background);
40
+ }
41
+
42
+ .message_progress_fill{
43
+ display:block;
44
+ width:0;
45
+ height:100%;
46
+ border-radius:inherit;
47
+ background:var(--primary-color);
48
+ transition:width 240ms ease;
49
+ }
50
+
51
+ .message_progress_content.indeterminate .message_progress_fill{
52
+ width:32%;
53
+ animation:message-progress-indeterminate 1.2s ease-in-out infinite;
54
+ }
55
+
56
+ @keyframes message-progress-indeterminate{
57
+ from{transform:translateX(-120%);}
58
+ to{transform:translateX(320%);}
59
+ }
60
+
61
+ @media (prefers-reduced-motion:reduce){
62
+ .message_progress_fill{
63
+ transition:none;
64
+ }
65
+
66
+ .message_progress_content.indeterminate .message_progress_fill{
67
+ animation:none;
68
+ transform:none;
69
+ }
70
+ }
71
+
72
+ .conversation_timebox{
73
+ display:block;
74
+ flex:0 0 auto;
75
+ align-self:flex-end;
76
+ margin-block-end:.12em;
77
+ margin-inline-end:1em;
78
+ color:var(--text-color);
79
+ font-size:.68em;
80
+ line-height:1;
81
+ pointer-events:none;
82
+ }
83
+
84
+ .conversation_timebox[hidden]{
85
+ display:none;
86
+ }
87
+
88
+ .chat_area[data-conversation-complete="true"] .conversation_timebox,
89
+ .chat_area[data-conversation-complete="true"] .chat_input_area,
90
+ .chat_area[data-conversation-complete="true"] > #speech,
91
+ .chat_area[data-conversation-complete="true"] > .controls{
92
+ display:none;
93
+ }
94
+
95
+ .conversation_timebox[data-error="true"]{
96
+ color:var(--danger-color,var(--text-color));
97
+ }
98
+
99
+ .conversation_timebox output{
100
+ font-variant-numeric:tabular-nums;
101
+ font-weight:600;
102
+ white-space:nowrap;
103
+ }
104
+
105
+ .conversation_timebox_status{
106
+ position:absolute;
107
+ width:1px;
108
+ height:1px;
109
+ padding:0;
110
+ margin:-1px;
111
+ overflow:hidden;
112
+ clip:rect(0,0,0,0);
113
+ white-space:nowrap;
114
+ border:0;
115
+ }
116
+
117
+ .chat_area ul {
118
+ flex:1 1 auto;
119
+ min-height:0;
120
+ list-style: none;
121
+ overflow-y: auto;
122
+ padding: 0 1em;
123
+ }
124
+
125
+ .chat_area li {
126
+ padding: 0.5em;
127
+ margin: 0 0 1em;
128
+ width: auto;
129
+ max-width: 95%;
130
+ clear: both;
131
+ }
132
+
133
+ .chat_area li.ai {
134
+ width: 90%;
135
+ float: left;
136
+ padding: 10px; /* Ensures padding around the content */
137
+ }
138
+
139
+
140
+ .chat_area li.user {
141
+ text-align: right;
142
+ float: right;
143
+ }
144
+
145
+ .chat_input_area {
146
+ display: grid;
147
+ padding-bottom: 2em;
148
+ grid-template-columns: 1fr auto auto;
149
+ column-gap: 0.5em;
150
+ }
151
+
152
+ .chat_input {
153
+ width: calc(100% - 1em);
154
+ min-height: 48px;
155
+ max-height: 160px;
156
+ padding: 1.1em 1em;
157
+ margin: 0;
158
+ border:2px solid var(--border-color);
159
+ border-radius: 0.5em;
160
+ font-family: Arial, sans-serif;
161
+ font-size: 16px;
162
+ line-height: 20px;
163
+ box-sizing: border-box;
164
+ resize: none;
165
+ overflow-y: auto;
166
+ }
167
+
168
+ .chat_input:focus {
169
+ outline:.18em solid var(--focus-color);
170
+ outline-offset:.12em;
171
+ border-color:var(--focus-color);
172
+ }
173
+
174
+ .chat_submit,
175
+ button.chat_upload {
176
+ display:grid;
177
+ place-items:center;
178
+ width:3.75em;
179
+ height:3.75em;
180
+ padding:0;
181
+ margin:0;
182
+ background-color:var(--primary-color);
183
+ border:.2em solid var(--primary-color);
184
+ border-radius:.8em;
185
+ cursor: pointer;
186
+ outline: none;
187
+ box-shadow:0 .18em .45em var(--shadow-color);
188
+ align-self: end;
189
+ transition:background-color 180ms,border-color 180ms,box-shadow 180ms,transform 120ms;
190
+ color:var(--button-text-color,var(--text-color));
191
+ }
192
+
193
+ .chat_submit::before,
194
+ button.chat_upload::before {
195
+ content:'';
196
+ width:1.65em;
197
+ height:1.65em;
198
+ background-color:currentColor;
199
+ mask-position:center;
200
+ mask-repeat:no-repeat;
201
+ mask-size:contain;
202
+ -webkit-mask-position:center;
203
+ -webkit-mask-repeat:no-repeat;
204
+ -webkit-mask-size:contain;
205
+ }
206
+
207
+ .chat_submit::before {
208
+ mask-image:url('./arcane/img/send.svg');
209
+ -webkit-mask-image:url('./arcane/img/send.svg');
210
+ }
211
+
212
+ button.chat_upload::before {
213
+ mask-image:url('./arcane/img/upload.svg');
214
+ -webkit-mask-image:url('./arcane/img/upload.svg');
215
+ }
216
+
217
+ .chat_submit img,
218
+ button.chat_upload img {
219
+ display:none;
220
+ }
221
+
222
+ .chat_submit:hover,
223
+ button.chat_upload:hover {
224
+ background-color:var(--secondary-color);
225
+ border-color:var(--secondary-color);
226
+ color:var(--text-color);
227
+ box-shadow:0 .25em .6em var(--shadow-color);
228
+ }
229
+
230
+
231
+ :host(.journal){
232
+ .chat_output {
233
+ display:none;
234
+ }
235
+ }
236
+
237
+ table {
238
+ width:calc(100% - .5em);
239
+ margin: auto; /* Center the table horizontally */
240
+ border-collapse: collapse;
241
+ overflow-x: auto; /* Enables horizontal scrolling */
242
+ display:table;
243
+ background: var(--modal-background);
244
+ box-shadow: 0 0 0.7em var(--border-color);
245
+ border-radius: 0.5em 0.5em 0.5em 0.5em;
246
+ border-spacing: unset;
247
+ }
248
+
249
+ thead, tbody {
250
+ display: table; /* These elements behave like table rows */
251
+ width: 100%; /* Table rows take full available space */
252
+ table-layout: fixed; /* Ensures the table layout respects the width settings */
253
+ }
254
+
255
+ tbody tr{
256
+ height: 130px;
257
+ }
258
+
259
+ thead tr{
260
+ background: var(--background);
261
+ color: var(--platinum);
262
+ border-radius: .5em .5em 0 0;
263
+ }
264
+
265
+ tr{
266
+ display: grid;
267
+ grid-template-columns:
268
+ 6em 1fr 1fr 1fr;
269
+ }
270
+
271
+ tr:hover{
272
+ background:var(--secondary-color);
273
+ }
274
+
275
+ th, td {
276
+ text-align: left;
277
+ word-wrap: break-word;
278
+ }
279
+
280
+ th{
281
+ border:none;
282
+ padding:.4em;
283
+ }
284
+
285
+ td{
286
+ background: var(--border-color);
287
+ padding:.4em;
288
+ border: 1px solid var(--accent-color);
289
+ max-height: 15em;
290
+ overflow: auto;
291
+ }
292
+ </style>
293
+
294
+ <section class="chat_area">
295
+ <section
296
+ id="conversation_timebox"
297
+ class="conversation_timebox"
298
+ aria-label="Conversation timer"
299
+ hidden
300
+ >
301
+ <output
302
+ id="conversation_timebox_elapsed"
303
+ role="timer"
304
+ aria-live="off"
305
+ aria-label="Conversation elapsed time"
306
+ >00:00</output>
307
+ <span
308
+ id="conversation_timebox_status"
309
+ class="conversation_timebox_status"
310
+ role="status"
311
+ aria-live="polite"
312
+ >Starting the elapsed timer.</span>
313
+ </section>
314
+ <ul type='text' class='chat_output' id='chat_output' tabindex='-1' aria-label='Conversation transcript'></ul>
315
+ <div class="chat_input_area">
316
+ <textarea type='text' class='chat_input' id='chat_input'></textarea>
317
+ <button type="button" id="chat_upload" class="chat_upload" title="Upload files" aria-label="Upload files">
318
+ <img class='upload-icon' src="./arcane/img/upload.svg" />
319
+ </button>
320
+ <input type="file" id="file_upload_input" class="chat_upload" accept=".txt, .md, image/*" hidden multiple />
321
+ <button type="button" id="chat_submit" class="chat_submit" title="Send message" aria-label="Send message">
322
+ <img class='send-icon' src="./arcane/img/send.svg" />
323
+ </button>
324
+ </div>
325
+ <html-import id="speech" href="./arcane/components/speech.html?v=3"></html-import>
326
+ <section class="controls">
327
+ <select id="languages">
328
+ <option value="english" selected>English</option>
329
+
330
+ <!-- American -->
331
+ <optgroup label="American">
332
+ <option value="american-spanish">Español (Estados Unidos)</option>
333
+ <option value="american-southern-dialect">Southern American English</option>
334
+ <option value="american-ebonics">African-American Vernacular English</option>
335
+ </optgroup>
336
+
337
+ <!-- English Variants -->
338
+ <optgroup label="English Variants">
339
+ <option value="british-english">British English</option>
340
+ <option value="cockney">Cockney</option>
341
+ <option value="australian-english">Australian English</option>
342
+ <option value="new-zealand-english">New Zealand English</option>
343
+ <option value="irish-english">Hiberno-English</option>
344
+ <option value="canadian-english">Canadian English</option>
345
+ </optgroup>
346
+
347
+ <!-- European -->
348
+ <optgroup label="European">
349
+ <option value="albanian">Shqip</option>
350
+ <option value="armenian">Հայերեն</option>
351
+ <option value="basque">Euskara</option>
352
+ <option value="belarusian">Беларуская</option>
353
+ <option value="bosnian">Bosanski</option>
354
+ <option value="bulgarian">Български</option>
355
+ <option value="catalan">Català</option>
356
+ <option value="croatian">Hrvatski</option>
357
+ <option value="czech">Čeština</option>
358
+ <option value="danish">Dansk</option>
359
+ <option value="dutch">Nederlands</option>
360
+ <option value="estonian">Eesti</option>
361
+ <option value="finnish">Suomi</option>
362
+ <option value="french">Français</option>
363
+ <option value="galician">Galego</option>
364
+ <option value="german">Deutsch</option>
365
+ <option value="greek">Ελληνικά</option>
366
+ <option value="hungarian">Magyar</option>
367
+ <option value="icelandic">Íslenska</option>
368
+ <option value="irish">Gaeilge</option>
369
+ <option value="italian">Italiano</option>
370
+ <option value="latvian">Latviešu</option>
371
+ <option value="lithuanian">Lietuvių</option>
372
+ <option value="luxembourgish">Lëtzebuergesch</option>
373
+ <option value="macedonian">Македонски</option>
374
+ <option value="maltese">Malti</option>
375
+ <option value="norwegian">Norsk</option>
376
+ <option value="polish">Polski</option>
377
+ <option value="portuguese">Português</option>
378
+ <option value="romanian">Română</option>
379
+ <option value="russian">Русский</option>
380
+ <option value="scottish-gaelic">Gàidhlig</option>
381
+ <option value="serbian">Српски</option>
382
+ <option value="slovak">Slovenčina</option>
383
+ <option value="slovenian">Slovenščina</option>
384
+ <option value="spanish">Español</option>
385
+ <option value="swedish">Svenska</option>
386
+ <option value="ukrainian">Українська</option>
387
+ <option value="welsh">Cymraeg</option>
388
+ </optgroup>
389
+
390
+ <!-- Middle Eastern -->
391
+ <optgroup label="Middle Eastern">
392
+ <option value="arabic">العربية</option>
393
+ <option value="hebrew">עברית</option>
394
+ <option value="persian">فارسی</option>
395
+ <option value="kurdish">Kurdî</option>
396
+ <option value="pashto">پښتو</option>
397
+ <option value="turkish">Türkçe</option>
398
+ </optgroup>
399
+
400
+ <!-- African -->
401
+ <optgroup label="African">
402
+ <option value="afrikaans">Afrikaans</option>
403
+ <option value="amharic">አማርኛ</option>
404
+ <option value="hausa">Hausa</option>
405
+ <option value="igbo">Igbo</option>
406
+ <option value="swahili">Kiswahili</option>
407
+ <option value="yoruba">Yorùbá</option>
408
+ <option value="zulu">IsiZulu</option>
409
+ <option value="xhosa">IsiXhosa</option>
410
+ <option value="somali">Soomaali</option>
411
+ </optgroup>
412
+
413
+ <!-- Indian Subcontinent -->
414
+ <optgroup label="Indian Subcontinent">
415
+ <option value="assamese">অসমীয়া</option>
416
+ <option value="bengali">বাংলা</option>
417
+ <option value="gujarati">ગુજરાતી</option>
418
+ <option value="hindi">हिन्दी</option>
419
+ <option value="kannada">ಕನ್ನಡ</option>
420
+ <option value="malayalam">മലയാളം</option>
421
+ <option value="marathi">मराठी</option>
422
+ <option value="nepali">नेपाली</option>
423
+ <option value="punjabi">ਪੰਜਾਬੀ</option>
424
+ <option value="sinhala">සිංහල</option>
425
+ <option value="tamil">தமிழ்</option>
426
+ <option value="telugu">తెలుగు</option>
427
+ <option value="urdu">اردو</option>
428
+ </optgroup>
429
+
430
+ <!-- East Asian -->
431
+ <optgroup label="East Asian">
432
+ <option value="chinese">中文</option>
433
+ <option value="japanese">日本語</option>
434
+ <option value="栃木弁">栃木弁</option>
435
+ <option value="korean">한국어</option>
436
+ <option value="mongolian">Монгол</option>
437
+ <option value="tibetan">བོད་སྐད</option>
438
+ </optgroup>
439
+
440
+ <!-- Southeast Asian -->
441
+ <optgroup label="Southeast Asian">
442
+ <option value="burmese">မြန်မာ</option>
443
+ <option value="indonesian">Bahasa Indonesia</option>
444
+ <option value="javanese">Basa Jawa</option>
445
+ <option value="khmer">ខ្មែរ</option>
446
+ <option value="lao">ລາວ</option>
447
+ <option value="malay">Bahasa Melayu</option>
448
+ <option value="tagalog">Tagalog</option>
449
+ <option value="thai">ไทย</option>
450
+ <option value="vietnamese">Tiếng Việt</option>
451
+ </optgroup>
452
+
453
+ <!-- Pacific -->
454
+ <optgroup label="Pacific">
455
+ <option value="maori">Te Reo Māori</option>
456
+ <option value="hawaiian">ʻŌlelo Hawaiʻi</option>
457
+ <option value="samoan">Gagana Samoa</option>
458
+ <option value="tongan">Lea Faka-Tonga</option>
459
+ </optgroup>
460
+
461
+ <!-- South American -->
462
+ <optgroup label="South American">
463
+ <option value="brazilian-portuguese">Português do Brasil</option>
464
+ <option value="quechua">Runasimi</option>
465
+ <option value="aymara">Aymar aru</option>
466
+ <option value="guarani">Avañe'ẽ</option>
467
+ <option value="mapudungun">Mapudungun</option>
468
+ </optgroup>
469
+
470
+ <!-- Ancient / Constructed -->
471
+ <optgroup label="Ancient / Constructed">
472
+ <option value="sumerian">𒅴𒂠 (Sumerian)</option>
473
+ <option value="akkadian">𒀝𒅗𒁺𒌑 (Akkadian)</option>
474
+ <option value="latin">Latina</option>
475
+ <option value="ancient-greek">Ἑλληνικὴ</option>
476
+ <option value="old-english">Ænglisc</option>
477
+ <option value="old-norse">Norrœnt mál</option>
478
+ <option value="gothic">𐌲𐌿𐍄𐌹𐍃𐌺</option>
479
+ <option value="aramaic">ܐܪܡܝܐ</option>
480
+ <option value="coptic">Ⲙⲉⲧⲣⲉⲙⲛ̀ⲭⲏⲙⲓ</option>
481
+ <option value="egyptian">𓂋𓐍𓏏𓊖 (Egyptian)</option>
482
+ <option value="etruscan">𐌄𐍄𐍂𐌖𐍃𐌂𐌖𐌌</option>
483
+ <option value="sanskrit">संस्कृतम्</option>
484
+ <option value="pali">𑀧𑀸𑀮𑀺</option>
485
+ <option value="elvish-quenya">Quenya</option>
486
+ <option value="elvish-sindarin">Sindarin</option>
487
+ <option value="klingon">tlhIngan Hol</option>
488
+ <option value="dothraki">Dothraki</option>
489
+ <option value="valyrian">Valyrio</option>
490
+ <option value="esperanto">Esperanto</option>
491
+ <option value="interlingua">Interlingua</option>
492
+ <option value="pirate">Pirate ☠️</option>
493
+ <option value="leetspeak">1337</option>
494
+ <option value="emoji">😀📜</option>
495
+ <option value="binary">01001000 01101001</option>
496
+ <option value="machine">Machine Language</option>
497
+ <option value="assembly">Assembly</option>
498
+ <option value="ai-whisper">AI Whisperer Mode 🤖</option>
499
+ <option value="zen">Zen Koan</option>
500
+ </optgroup>
501
+ </select>
502
+ </section>
503
+ </section>
504
+
505
+ <script type="module">
506
+ const host=this;
507
+ const shadowRoot=host.shadowRoot;
508
+ const chatArea=shadowRoot.querySelector('.chat_area');
509
+ const textArea=shadowRoot.querySelector('#chat_input');
510
+ const chatOutput = shadowRoot.querySelector('#chat_output');
511
+ const send = shadowRoot.querySelector('#chat_submit');
512
+ const speech = shadowRoot.querySelector('#speech');
513
+ const uploadBtn = shadowRoot.querySelector('#chat_upload');
514
+ const fileInput = shadowRoot.querySelector('#file_upload_input');
515
+ const conversationTimeboxPanel=shadowRoot.querySelector('#conversation_timebox');
516
+ const conversationTimeboxElapsed=shadowRoot.querySelector('#conversation_timebox_elapsed');
517
+ const conversationTimeboxStatus=shadowRoot.querySelector('#conversation_timebox_status');
518
+ const [
519
+ {default:MD},
520
+ {default:FileEntity},
521
+ {ConversationSubmissionBarrier,requireConversationTimeboxDelivery}
522
+ ]=await Promise.all([
523
+ import('../modules/MD.js'),
524
+ import('../entities/File.js'),
525
+ import('../modules/ConversationTimebox.js')
526
+ ]);
527
+
528
+ host.language=host.language||'English';
529
+ host.name = host.name||'User';
530
+ host.aiName='AI';
531
+ let requestedInitialSpeechMuted=typeof speech.initialMuted==='boolean'
532
+ ?speech.initialMuted
533
+ :true;
534
+ let initialSpeechMuteListenerInstalled=false;
535
+ const hostSubmissionBarrier=new ConversationSubmissionBarrier();
536
+ let programmaticSubmissionQueue=Promise.resolve(true);
537
+ let conversationTimeboxController=null;
538
+ let conversationTimeboxUnsubscribe=null;
539
+ let conversationTimeboxError='';
540
+ let lastQueuedTimeboxRevision=null;
541
+ let hostSubmissionGeneration=0;
542
+ const conversationTimeboxRetryDelays=Object.freeze([0,500,1_500]);
543
+
544
+ host.sendMessage=host.sendMessage||async function(text=''){
545
+ console.warn('html-import chat.html `host.sendMessage` method not implemented.\n\nThis can also be done by listening for the "chat-send-message" event.\n\nIt is still best practice to implement the `.sendMessage` method atleast with an empty function.\n\n')
546
+ }
547
+
548
+ host.languageChanged=host.languageChanged||async function(text=''){
549
+ console.warn('html-import chat.html `host.languageChanged` method not implemented.\n\nThis can also be done by listening for the "chat-language-changed" event.\n\nIt is still best practice to implement the `.languageChanged` method atleast with an empty function.\n\n')
550
+ }
551
+
552
+ host.streamMessage=streamMessage;
553
+ host.setMessageProgress=setMessageProgress;
554
+ host.setAIAvailability=setAIAvailability;
555
+ host.setInitialSpeechMuted=setInitialSpeechMuted;
556
+ host.setConversationComplete=setConversationComplete;
557
+ host.bindConversationTimebox=bindConversationTimebox;
558
+ host.submitMessage=submitMessage;
559
+ host.aiAvailability={llm:true,stt:true,tts:true};
560
+ host.conversationComplete=false;
561
+
562
+ function setConversationComplete(complete=true){
563
+ if(typeof complete!=='boolean'){
564
+ throw new TypeError('Conversation completion state must be boolean.');
565
+ }
566
+
567
+ host.conversationComplete=complete;
568
+ chatArea.dataset.conversationComplete=String(complete);
569
+ textArea.disabled=complete;
570
+ uploadBtn.disabled=complete;
571
+ send.disabled=complete||!host.aiAvailability.llm;
572
+ if(complete){
573
+ textArea.blur?.();
574
+ chatOutput.focus?.({preventScroll:true});
575
+ chatOutput.scrollTop=chatOutput.scrollHeight;
576
+ }
577
+ return complete;
578
+ }
579
+
580
+ function setInitialSpeechMuted(initialMuted){
581
+ if(typeof initialMuted!=='boolean'){
582
+ throw new TypeError('initialMuted must be boolean');
583
+ }
584
+
585
+ requestedInitialSpeechMuted=initialMuted;
586
+ speech.initialMuted=initialMuted;
587
+
588
+ if(speech.componentReady){
589
+ speech.configure?.({initialMuted:requestedInitialSpeechMuted});
590
+ }else if(!initialSpeechMuteListenerInstalled){
591
+ initialSpeechMuteListenerInstalled=true;
592
+ speech.addEventListener(
593
+ 'speech-ready',
594
+ function applyRequestedInitialSpeechMute(){
595
+ initialSpeechMuteListenerInstalled=false;
596
+ speech.configure?.({initialMuted:requestedInitialSpeechMuted});
597
+ },
598
+ {once:true}
599
+ );
600
+ }
601
+
602
+ return host;
603
+ }
604
+
605
+ function setMessageProgress(
606
+ id='',
607
+ {
608
+ label='Working',
609
+ current=0,
610
+ total=0,
611
+ failed=0,
612
+ indeterminate=false,
613
+ status=''
614
+ }={}
615
+ ){
616
+ const messageId=String(id).trim();
617
+ const normalizedTotal=Number.isFinite(Number(total))
618
+ ?Math.max(0,Math.floor(Number(total)))
619
+ :0;
620
+ const normalizedCurrent=Number.isFinite(Number(current))
621
+ ?Math.min(normalizedTotal,Math.max(0,Math.floor(Number(current))))
622
+ :0;
623
+ const normalizedFailed=Number.isFinite(Number(failed))
624
+ ?Math.max(0,Math.floor(Number(failed)))
625
+ :0;
626
+ const message=messageId
627
+ ?[...chatOutput.children].find(
628
+ candidate=>candidate.id===`message-${messageId}`
629
+ )||null
630
+ :null;
631
+ const thinking=message?.querySelector('.thinking');
632
+
633
+ if(!thinking){
634
+ return false;
635
+ }
636
+
637
+ let progress=thinking.querySelector('.message_progress_content');
638
+ let labelElement;
639
+ let countElement;
640
+ let fill;
641
+
642
+ if(!progress){
643
+ const ownerDocument=thinking.ownerDocument;
644
+ progress=ownerDocument.createElement('span');
645
+ progress.className='message_progress_content';
646
+ labelElement=ownerDocument.createElement('span');
647
+ labelElement.className='message_progress_text';
648
+ countElement=ownerDocument.createElement('span');
649
+ countElement.className='message_progress_count';
650
+ const labelRow=ownerDocument.createElement('span');
651
+ labelRow.className='message_progress_label';
652
+ labelRow.append(labelElement,countElement);
653
+ const track=ownerDocument.createElement('span');
654
+ track.className='message_progress_track';
655
+ fill=ownerDocument.createElement('span');
656
+ fill.className='message_progress_fill';
657
+ track.append(fill);
658
+ progress.append(labelRow,track);
659
+ thinking.replaceChildren(progress);
660
+ thinking.classList.add('message_progress');
661
+ }else{
662
+ labelElement=progress.querySelector('.message_progress_text');
663
+ countElement=progress.querySelector('.message_progress_count');
664
+ fill=progress.querySelector('.message_progress_fill');
665
+ }
666
+
667
+ const isIndeterminate=indeterminate===true;
668
+ const warningLabel=normalizedFailed>0
669
+ ?` · ${normalizedFailed} warning${normalizedFailed===1?'':'s'}`
670
+ :'';
671
+ const progressStatus=isIndeterminate
672
+ ?`${String(status||'Streaming')}${warningLabel}`
673
+ :`${normalizedCurrent} of ${normalizedTotal}${warningLabel}`;
674
+ const percent=normalizedTotal>0
675
+ ?normalizedCurrent/normalizedTotal*100
676
+ :0;
677
+
678
+ labelElement.textContent=String(label||'Working');
679
+ countElement.textContent=progressStatus;
680
+ progress.classList.toggle('indeterminate',isIndeterminate);
681
+ fill.style.width=isIndeterminate?'32%':`${percent}%`;
682
+ progress.setAttribute('role','progressbar');
683
+ progress.setAttribute('aria-label',String(label||'Working'));
684
+ if(isIndeterminate){
685
+ progress.removeAttribute('aria-valuemin');
686
+ progress.removeAttribute('aria-valuemax');
687
+ progress.removeAttribute('aria-valuenow');
688
+ }else{
689
+ progress.setAttribute('aria-valuemin','0');
690
+ progress.setAttribute('aria-valuemax',String(normalizedTotal));
691
+ progress.setAttribute('aria-valuenow',String(normalizedCurrent));
692
+ }
693
+ progress.setAttribute(
694
+ 'aria-valuetext',
695
+ `${String(label||'Working')} ${progressStatus}`
696
+ );
697
+ chatOutput.scrollTop=chatOutput.scrollHeight;
698
+ return true;
699
+ }
700
+
701
+ function setAIAvailability(input={}){
702
+ const availability={
703
+ llm:Object.prototype.hasOwnProperty.call(input,'llm')
704
+ ?Boolean(input.llm)
705
+ :host.aiAvailability.llm,
706
+ stt:Object.prototype.hasOwnProperty.call(input,'stt')
707
+ ?Boolean(input.stt)
708
+ :host.aiAvailability.stt,
709
+ tts:Object.prototype.hasOwnProperty.call(input,'tts')
710
+ ?Boolean(input.tts)
711
+ :host.aiAvailability.tts
712
+ };
713
+
714
+ host.aiAvailability=availability;
715
+ send.disabled=host.conversationComplete||!availability.llm;
716
+ send.title=availability.llm
717
+ ?'Send message'
718
+ :'The selected language model service is unavailable.';
719
+ send.setAttribute('aria-label',send.title);
720
+
721
+ const configureSpeech=()=>speech.setAvailability?.({
722
+ stt:availability.stt,
723
+ tts:availability.tts
724
+ });
725
+
726
+ if(speech.componentReady){
727
+ configureSpeech();
728
+ }else{
729
+ speech.addEventListener('speech-ready',configureSpeech,{once:true});
730
+ }
731
+
732
+ return {...availability};
733
+ }
734
+
735
+ function formatConversationTime(milliseconds=0){
736
+ const totalSeconds=Math.max(0,Math.floor(milliseconds/1000));
737
+ const hours=Math.floor(totalSeconds/3600);
738
+ const minutes=Math.floor((totalSeconds%3600)/60);
739
+ const seconds=totalSeconds%60;
740
+ return hours>0
741
+ ?`${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}:${String(seconds).padStart(2,'0')}`
742
+ :`${String(minutes).padStart(2,'0')}:${String(seconds).padStart(2,'0')}`;
743
+ }
744
+
745
+ function bindConversationTimebox(controller){
746
+ const requiredMethods=[
747
+ 'snapshot',
748
+ 'start',
749
+ 'subscribe'
750
+ ];
751
+ if(
752
+ !controller
753
+ ||requiredMethods.some(function missingConversationTimeboxMethod(name){
754
+ return typeof controller[name]!=='function';
755
+ })
756
+ ||typeof controller.limitReachedMessage!=='string'
757
+ ){
758
+ throw new TypeError('A compatible conversation timebox controller is required.');
759
+ }
760
+ if(conversationTimeboxController===controller){
761
+ return controller;
762
+ }
763
+ conversationTimeboxUnsubscribe?.();
764
+ conversationTimeboxController=controller;
765
+ lastQueuedTimeboxRevision=null;
766
+ clearConversationTimeboxError();
767
+ conversationTimeboxPanel.hidden=false;
768
+ conversationTimeboxStatus.textContent='Starting the elapsed timer.';
769
+ conversationTimeboxUnsubscribe=controller.subscribe(
770
+ function renderBoundConversationTimebox(state,event){
771
+ renderConversationTimebox(state,event);
772
+ }
773
+ );
774
+ try{
775
+ controller.start();
776
+ }catch(error){
777
+ conversationTimeboxStatus.textContent='The elapsed timer could not start.';
778
+ conversationTimeboxPanel.dataset.error='true';
779
+ conversationTimeboxUnsubscribe?.();
780
+ conversationTimeboxUnsubscribe=null;
781
+ conversationTimeboxController=null;
782
+ throw error;
783
+ }
784
+ return controller;
785
+ }
786
+
787
+ function clearConversationTimeboxError(){
788
+ conversationTimeboxError='';
789
+ conversationTimeboxPanel.removeAttribute('data-error');
790
+ }
791
+
792
+ function setConversationTimeboxError(message){
793
+ conversationTimeboxError=message;
794
+ conversationTimeboxPanel.dataset.error='true';
795
+ if(conversationTimeboxStatus.textContent!==message){
796
+ conversationTimeboxStatus.textContent=message;
797
+ }
798
+ }
799
+
800
+ function renderConversationTimebox(state={},event={}){
801
+ if(event.type==='change'){
802
+ clearConversationTimeboxError();
803
+ }
804
+ const countsDown=state.status==='armed'||state.status==='due';
805
+ const displayMilliseconds=countsDown
806
+ ?state.remainingMs??0
807
+ :state.elapsedMs||0;
808
+ conversationTimeboxElapsed.value=formatConversationTime(displayMilliseconds);
809
+ conversationTimeboxElapsed.textContent=conversationTimeboxElapsed.value;
810
+ conversationTimeboxPanel.dataset.state=state.status||'idle';
811
+ let statusMessage='Starting the elapsed timer.';
812
+ let timerLabel=`Conversation elapsed time ${conversationTimeboxElapsed.value}.`;
813
+ if(state.status==='armed'){
814
+ statusMessage=`Check-in set for ${formatConversationTime(state.durationMilliseconds)} from the latest setting.`;
815
+ timerLabel=`Time remaining until the conversation check-in: ${conversationTimeboxElapsed.value}.`;
816
+ }else if(state.status==='due'){
817
+ statusMessage='The time check is due. Asking whether to end or continue.';
818
+ timerLabel='The conversation time check is due.';
819
+ }else if(state.status==='disposed'){
820
+ statusMessage='The elapsed timer has stopped.';
821
+ timerLabel=`Conversation timer stopped at ${conversationTimeboxElapsed.value}.`;
822
+ }else if(state.status==='unlimited'){
823
+ statusMessage='No time limit set. Elapsed time will keep running.';
824
+ }
825
+ conversationTimeboxElapsed.setAttribute('aria-label',timerLabel);
826
+ if(conversationTimeboxError){
827
+ statusMessage=conversationTimeboxError;
828
+ }
829
+ if(conversationTimeboxStatus.textContent!==statusMessage){
830
+ conversationTimeboxStatus.textContent=statusMessage;
831
+ }
832
+
833
+ if(state.status==='due'&&lastQueuedTimeboxRevision!==state.revision){
834
+ const dueRevision=state.revision;
835
+ const dueController=conversationTimeboxController;
836
+ lastQueuedTimeboxRevision=dueRevision;
837
+ void queueConversationTimeboxMessage(
838
+ conversationTimeboxController.limitReachedMessage,
839
+ {
840
+ source:'conversation-timebox-limit',
841
+ synthetic:true,
842
+ preserveDraft:true,
843
+ timeboxRevision:dueRevision
844
+ },
845
+ function conversationTimeboxRevisionIsStillDue(){
846
+ const current=dueController?.snapshot();
847
+ return conversationTimeboxController===dueController
848
+ &&current?.status==='due'
849
+ &&current.revision===dueRevision;
850
+ }
851
+ );
852
+ }
853
+ }
854
+
855
+ function queueConversationTimeboxMessage(message,context,guard){
856
+ const prior=programmaticSubmissionQueue.catch(
857
+ function ignorePriorProgrammaticSubmissionFailure(){
858
+ return false;
859
+ }
860
+ );
861
+ const queued=prior.then(
862
+ async function dispatchConversationTimeboxMessage(){
863
+ let lastError;
864
+ let attemptedSubmissionGeneration=null;
865
+ for(
866
+ let attempt=0;
867
+ attempt<conversationTimeboxRetryDelays.length;
868
+ attempt++
869
+ ){
870
+ const retryDelay=conversationTimeboxRetryDelays[attempt];
871
+ if(retryDelay>0){
872
+ await waitForConversationTimeboxRetry(retryDelay);
873
+ }
874
+ await hostSubmissionBarrier.waitForIdle();
875
+ if(typeof guard==='function'&&!guard()){
876
+ return false;
877
+ }
878
+ if(
879
+ attemptedSubmissionGeneration!==null
880
+ &&attemptedSubmissionGeneration!==hostSubmissionGeneration
881
+ ){
882
+ return false;
883
+ }
884
+ try{
885
+ const delivery=submitMessage(
886
+ message,
887
+ {
888
+ ...context,
889
+ retry:attempt>0,
890
+ reuseVisibleMessage:attempt>0
891
+ }
892
+ );
893
+ attemptedSubmissionGeneration=hostSubmissionGeneration;
894
+ const delivered=await delivery;
895
+ return requireConversationTimeboxDelivery(delivered);
896
+ }catch(error){
897
+ lastError=error;
898
+ }
899
+ }
900
+ throw lastError;
901
+ }
902
+ );
903
+ programmaticSubmissionQueue=queued.then(
904
+ function finishConversationTimeboxSubmission(result){
905
+ if(result!==false){
906
+ clearConversationTimeboxError();
907
+ const current=conversationTimeboxController?.snapshot();
908
+ if(current){
909
+ renderConversationTimebox(current,{type:'delivery'});
910
+ }
911
+ }
912
+ return result;
913
+ },
914
+ function reportConversationTimeboxSubmissionFailure(error){
915
+ if(typeof guard==='function'&&!guard()){
916
+ return false;
917
+ }
918
+ setConversationTimeboxError(
919
+ 'The time check could not reach the AI.'
920
+ );
921
+ host.dispatchEvent(
922
+ new CustomEvent(
923
+ 'conversation-timebox-error',
924
+ {
925
+ bubbles:true,
926
+ composed:true,
927
+ detail:{error,context:{...context}}
928
+ }
929
+ )
930
+ );
931
+ console.error('The conversation timebox message could not be sent.',error);
932
+ return false;
933
+ }
934
+ );
935
+ return programmaticSubmissionQueue;
936
+ }
937
+
938
+ function waitForConversationTimeboxRetry(delayMilliseconds){
939
+ return new Promise(function scheduleConversationTimeboxRetry(resolve){
940
+ setTimeout(resolve,delayMilliseconds);
941
+ });
942
+ }
943
+
944
+ {
945
+ const chatReady=new CustomEvent(
946
+ 'chat-ready', {
947
+ detail: { chat: host }
948
+ }
949
+ );
950
+
951
+ host.dispatchEvent(chatReady);
952
+ }
953
+
954
+ host.ready=true;
955
+
956
+ async function streamMessage(text='', id='', isThinking){
957
+ if(!id){
958
+ console.warn('Streaming messages require ids, none specified.')
959
+ return false;
960
+ }
961
+
962
+ let message=chatOutput.querySelector(`#message-${id}`);
963
+
964
+ if(!message){
965
+ await receivedMessage(host.aiName,isThinking?'':text,id);
966
+ if(!isThinking){
967
+ return false;
968
+ }
969
+ message=chatOutput.querySelector(`#message-${id}`);
970
+ if(!message){
971
+ return false;
972
+ }
973
+ }
974
+
975
+ let target=message.querySelector('.markdown');
976
+
977
+ let thinkingHTML=message.querySelector('.thinking');
978
+
979
+ if(isThinking && !thinkingHTML){
980
+ thinkingHTML=message.ownerDocument.createElement('span');
981
+ thinkingHTML.className='thinking';
982
+ message.append(thinkingHTML);
983
+ }
984
+
985
+ if(isThinking){
986
+ if(thinkingHTML.classList.contains('message_progress')){
987
+ thinkingHTML.replaceChildren();
988
+ thinkingHTML.classList.remove('message_progress');
989
+ }
990
+ thinkingHTML.append(
991
+ message.ownerDocument.createTextNode(String(text))
992
+ );
993
+ chatOutput.scrollTop = chatOutput.scrollHeight;
994
+ return true;
995
+ }
996
+
997
+ const clearThinking=thinkingHTML;
998
+
999
+ //console.table(arguments);
1000
+ if(!isThinking&&text&&host.aiAvailability.tts){
1001
+ ai.streamTTS(
1002
+ text
1003
+ )
1004
+ }
1005
+
1006
+ if(clearThinking){
1007
+ thinkingHTML.remove();
1008
+ }
1009
+
1010
+ target.raw ? target.raw+=text : target.raw=text;
1011
+
1012
+ target.innerHTML=new MD(target.raw).rendered;
1013
+
1014
+ chatOutput.scrollTop = chatOutput.scrollHeight;
1015
+ }
1016
+
1017
+ textArea.addEventListener('keyup', resizeTextArea);
1018
+
1019
+ send.addEventListener(
1020
+ 'click',
1021
+ function submitChatMessageFromButton(){
1022
+ void submitMessage('',{source:'user'});
1023
+ }
1024
+ );
1025
+
1026
+ uploadBtn.addEventListener(
1027
+ 'click',
1028
+ function(){
1029
+ fileInput.click();
1030
+ }
1031
+ );
1032
+
1033
+ fileInput.addEventListener(
1034
+ 'change',
1035
+ async function onFileSelected() {
1036
+ if (!fileInput.files.length>0) {
1037
+ return;
1038
+ }
1039
+
1040
+ for(let i=0;i<fileInput.files.length;i++){
1041
+ const file=fileInput.files[i];
1042
+ const newFile=new FileEntity(file.name, 'documents');
1043
+ newFile.save(file);
1044
+
1045
+ const uploadEvent = new CustomEvent(
1046
+ 'chat-file-uploaded',
1047
+ {
1048
+ detail: { file },
1049
+ bubbles: true,
1050
+ composed: true,
1051
+ cancelable: true
1052
+ }
1053
+ );
1054
+
1055
+ host.dispatchEvent(uploadEvent);
1056
+ }
1057
+
1058
+ fileInput.value = '';
1059
+ }
1060
+ );
1061
+
1062
+ speech.addEventListener(
1063
+ 'speech-transcription-complete',
1064
+ function submitTranscribedChatMessage(event){
1065
+ void submitMessage(
1066
+ event.detail?.text||'',
1067
+ {source:'speech'}
1068
+ );
1069
+ }
1070
+ )
1071
+
1072
+ shadowRoot.querySelector('#languages').addEventListener(
1073
+ 'change',
1074
+ function() {
1075
+ host.language = this.value;
1076
+ console.info("Language changed to:", host.language);
1077
+
1078
+ const chatEvent = new CustomEvent(
1079
+ 'chat-language-changed',
1080
+ {
1081
+ detail: {
1082
+ message:host.language
1083
+ },
1084
+ bubbles: true,
1085
+ cancelable: true
1086
+ }
1087
+ );
1088
+
1089
+ host.dispatchEvent(chatEvent);
1090
+ host.languageChanged(host.language);
1091
+ }
1092
+ );
1093
+
1094
+ function getMilTime() {
1095
+ const now = new Date();
1096
+ const hours = String(now.getHours()).padStart(2, '0');
1097
+ const minutes = String(now.getMinutes()).padStart(2, '0');
1098
+ return `${hours}:${minutes}`;
1099
+ }
1100
+
1101
+ async function resizeTextArea(e={}) {
1102
+ if(e.key && e.key == 'Enter' && !e.shiftKey && !send.disabled){
1103
+ e.preventDefault();
1104
+ void submitMessage('',{source:'keyboard'});
1105
+ }
1106
+
1107
+ // Reset height to shrink to content size
1108
+ textArea.style.height = 'auto';
1109
+ let scrollHeight = textArea.scrollHeight;
1110
+ let computedStyle = window.getComputedStyle(textArea);
1111
+ let lineHeight = parseInt(computedStyle.lineHeight, 10) || parseInt(computedStyle.fontSize, 10) * 1.2;
1112
+ let maxHeight = lineHeight * 10;
1113
+ textArea.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
1114
+ }
1115
+
1116
+ function observeHostSubmission(result,context){
1117
+ const observed=hostSubmissionBarrier.track(result).catch(
1118
+ function reportHostSubmissionFailure(error){
1119
+ host.dispatchEvent(
1120
+ new CustomEvent(
1121
+ 'chat-send-error',
1122
+ {
1123
+ bubbles:true,
1124
+ composed:true,
1125
+ detail:{error,context:{...context}}
1126
+ }
1127
+ )
1128
+ );
1129
+ console.error('The chat message could not be submitted.',error);
1130
+ return false;
1131
+ }
1132
+ );
1133
+ return observed;
1134
+ }
1135
+
1136
+ async function submitMessage(textOverride='',context={}) {
1137
+ if(typeof textOverride!=='string'){
1138
+ throw new TypeError('Programmatic chat messages must be strings.');
1139
+ }
1140
+ if(!context||typeof context!=='object'||Array.isArray(context)){
1141
+ throw new TypeError('Chat submission context must be an object.');
1142
+ }
1143
+ if(host.conversationComplete){
1144
+ return false;
1145
+ }
1146
+ const submissionContext=Object.freeze({
1147
+ source:'user',
1148
+ preserveDraft:false,
1149
+ synthetic:false,
1150
+ ...context
1151
+ });
1152
+ const text=textOverride.length>0?textOverride:textArea.value;
1153
+ if(!text.trim()){
1154
+ return false;
1155
+ }
1156
+ const messageTime = getMilTime();
1157
+ console.info("Dispatching message:", text);
1158
+ if(!submissionContext.preserveDraft){
1159
+ textArea.value='';
1160
+ }
1161
+
1162
+ //console.log(host,host.name);
1163
+ // console.log(forceText,text,1);
1164
+ if(!submissionContext.synthetic&&!submissionContext.reuseVisibleMessage){
1165
+ hostSubmissionGeneration++;
1166
+ chatOutput.innerHTML += `
1167
+ <li class='user'>
1168
+ <strong>${host.name} ${messageTime}:</strong>
1169
+ <br>
1170
+ <br>
1171
+ <div class='markdown'>
1172
+ ${new MD(text).rendered}
1173
+ </div>
1174
+ </li>
1175
+ `;
1176
+ }
1177
+ chatOutput.scrollTop = chatOutput.scrollHeight;
1178
+
1179
+ const chatEvent = new CustomEvent(
1180
+ 'chat-send-message',
1181
+ {
1182
+ detail: {
1183
+ message:text,
1184
+ context:submissionContext
1185
+ },
1186
+ bubbles: true,
1187
+ composed:true,
1188
+ cancelable: true
1189
+ }
1190
+ );
1191
+
1192
+ ai.stopAudio();
1193
+ ai.resumeAudio?.();
1194
+
1195
+ host.dispatchEvent(chatEvent);
1196
+
1197
+ let result;
1198
+ try{
1199
+ result=host.sendMessage(text,submissionContext);
1200
+ }catch(error){
1201
+ result=Promise.reject(error);
1202
+ }
1203
+ return observeHostSubmission(result,submissionContext);
1204
+ }
1205
+
1206
+ async function receivedMessage(name = 'Assistant', text = '', id=''){
1207
+ const messageTime = getMilTime();
1208
+ //console.log(text)
1209
+ chatOutput.innerHTML += `
1210
+ <li class='ai' ${id? `id="message-${id}"`:''}>
1211
+ <strong>${name} ${messageTime}:</strong>
1212
+ <br>
1213
+ <br>
1214
+ <div class='markdown'>
1215
+ ${new MD(text).rendered}
1216
+ </div>
1217
+ </li>
1218
+ `;
1219
+ chatOutput.scrollTop = chatOutput.scrollHeight;
1220
+ }
1221
+
1222
+ // Initialize and adjust on input
1223
+ resizeTextArea();
1224
+
1225
+ </script>