@things-factory/ai-assistant 10.1.46 → 10.1.50

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 (174) hide show
  1. package/client/components/assistant-chat.ts +100 -20
  2. package/client/components/assistant-dock.ts +617 -0
  3. package/client/components/assistant-mode.ts +29 -0
  4. package/client/components/assistant-session-manager.ts +607 -0
  5. package/client/components/chat-echo-dedup.ts +4 -0
  6. package/client/components/chat-input-builder.ts +12 -2
  7. package/client/components/panel-aria.test.ts +13 -8
  8. package/client/index.ts +10 -0
  9. package/client/setup-assistant-dock.ts +109 -0
  10. package/client/utils/assistant-activity.ts +122 -0
  11. package/client/utils/assistant-ask.ts +44 -0
  12. package/client/utils/assistant-capability-read.ts +24 -0
  13. package/client/utils/assistant-capability.test.ts +17 -0
  14. package/client/utils/assistant-capability.ts +19 -0
  15. package/client/utils/assistant-proposals.ts +50 -0
  16. package/client/utils/assistant-session-actions.ts +18 -0
  17. package/client/utils/assistant-session-admin.ts +167 -0
  18. package/client/utils/assistant-session-controller.ts +2 -0
  19. package/client/utils/assistant-session-transport.ts +6 -2
  20. package/client/utils/assistant-surface.ts +157 -0
  21. package/client/utils/attachment-policy.ts +71 -0
  22. package/dist-client/components/assistant-chat.d.ts +6 -0
  23. package/dist-client/components/assistant-chat.js +101 -21
  24. package/dist-client/components/assistant-chat.js.map +1 -1
  25. package/dist-client/components/assistant-dock.d.ts +74 -0
  26. package/dist-client/components/assistant-dock.js +641 -0
  27. package/dist-client/components/assistant-dock.js.map +1 -0
  28. package/dist-client/components/assistant-mode.d.ts +6 -0
  29. package/dist-client/components/assistant-mode.js +24 -0
  30. package/dist-client/components/assistant-mode.js.map +1 -1
  31. package/dist-client/components/assistant-session-manager.d.ts +77 -0
  32. package/dist-client/components/assistant-session-manager.js +652 -0
  33. package/dist-client/components/assistant-session-manager.js.map +1 -0
  34. package/dist-client/components/chat-echo-dedup.js +4 -0
  35. package/dist-client/components/chat-echo-dedup.js.map +1 -1
  36. package/dist-client/components/chat-input-builder.d.ts +11 -0
  37. package/dist-client/components/chat-input-builder.js +4 -2
  38. package/dist-client/components/chat-input-builder.js.map +1 -1
  39. package/dist-client/components/panel-aria.test.js +12 -8
  40. package/dist-client/components/panel-aria.test.js.map +1 -1
  41. package/dist-client/index.d.ts +10 -0
  42. package/dist-client/index.js +10 -0
  43. package/dist-client/index.js.map +1 -1
  44. package/dist-client/setup-assistant-dock.d.ts +15 -0
  45. package/dist-client/setup-assistant-dock.js +110 -0
  46. package/dist-client/setup-assistant-dock.js.map +1 -0
  47. package/dist-client/tsconfig.tsbuildinfo +1 -1
  48. package/dist-client/utils/assistant-activity.d.ts +22 -0
  49. package/dist-client/utils/assistant-activity.js +104 -0
  50. package/dist-client/utils/assistant-activity.js.map +1 -0
  51. package/dist-client/utils/assistant-ask.d.ts +23 -0
  52. package/dist-client/utils/assistant-ask.js +18 -0
  53. package/dist-client/utils/assistant-ask.js.map +1 -0
  54. package/dist-client/utils/assistant-capability-read.d.ts +6 -0
  55. package/dist-client/utils/assistant-capability-read.js +23 -0
  56. package/dist-client/utils/assistant-capability-read.js.map +1 -0
  57. package/dist-client/utils/assistant-capability.d.ts +4 -0
  58. package/dist-client/utils/assistant-capability.js +17 -0
  59. package/dist-client/utils/assistant-capability.js.map +1 -0
  60. package/dist-client/utils/assistant-capability.test.d.ts +1 -0
  61. package/dist-client/utils/assistant-capability.test.js +16 -0
  62. package/dist-client/utils/assistant-capability.test.js.map +1 -0
  63. package/dist-client/utils/assistant-proposals.d.ts +29 -0
  64. package/dist-client/utils/assistant-proposals.js +22 -0
  65. package/dist-client/utils/assistant-proposals.js.map +1 -0
  66. package/dist-client/utils/assistant-session-actions.d.ts +12 -0
  67. package/dist-client/utils/assistant-session-actions.js +19 -0
  68. package/dist-client/utils/assistant-session-actions.js.map +1 -0
  69. package/dist-client/utils/assistant-session-admin.d.ts +72 -0
  70. package/dist-client/utils/assistant-session-admin.js +81 -0
  71. package/dist-client/utils/assistant-session-admin.js.map +1 -0
  72. package/dist-client/utils/assistant-session-controller.d.ts +2 -0
  73. package/dist-client/utils/assistant-session-controller.js.map +1 -1
  74. package/dist-client/utils/assistant-session-transport.js +6 -2
  75. package/dist-client/utils/assistant-session-transport.js.map +1 -1
  76. package/dist-client/utils/assistant-surface.d.ts +72 -0
  77. package/dist-client/utils/assistant-surface.js +99 -0
  78. package/dist-client/utils/assistant-surface.js.map +1 -0
  79. package/dist-client/utils/attachment-policy.d.ts +35 -0
  80. package/dist-client/utils/attachment-policy.js +56 -0
  81. package/dist-client/utils/attachment-policy.js.map +1 -0
  82. package/dist-server/service/assistant-chat-resolver.d.ts +1 -0
  83. package/dist-server/service/assistant-chat-resolver.js +72 -4
  84. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  85. package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +1 -0
  86. package/dist-server/service/assistant-mode/assistant-mode-resolver.js +7 -0
  87. package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -1
  88. package/dist-server/service/assistant-mode/resolve.d.ts +3 -0
  89. package/dist-server/service/assistant-mode/resolve.js +6 -2
  90. package/dist-server/service/assistant-mode/resolve.js.map +1 -1
  91. package/dist-server/service/assistant-mode/types.d.ts +6 -0
  92. package/dist-server/service/assistant-mode/types.js +0 -9
  93. package/dist-server/service/assistant-mode/types.js.map +1 -1
  94. package/dist-server/service/chat-message/chat-message.d.ts +6 -0
  95. package/dist-server/service/chat-message/chat-message.js +19 -0
  96. package/dist-server/service/chat-message/chat-message.js.map +1 -1
  97. package/dist-server/service/chat-session/attachment-policy.d.ts +44 -0
  98. package/dist-server/service/chat-session/attachment-policy.js +56 -0
  99. package/dist-server/service/chat-session/attachment-policy.js.map +1 -0
  100. package/dist-server/service/chat-session/attachment-route.d.ts +3 -0
  101. package/dist-server/service/chat-session/attachment-route.js +44 -0
  102. package/dist-server/service/chat-session/attachment-route.js.map +1 -0
  103. package/dist-server/service/chat-session/attachment-tools.d.ts +3 -0
  104. package/dist-server/service/chat-session/attachment-tools.js +59 -0
  105. package/dist-server/service/chat-session/attachment-tools.js.map +1 -0
  106. package/dist-server/service/chat-session/session-attachments.d.ts +36 -0
  107. package/dist-server/service/chat-session/session-attachments.js +84 -0
  108. package/dist-server/service/chat-session/session-attachments.js.map +1 -0
  109. package/dist-server/service/chat-session/session-inbox.d.ts +29 -0
  110. package/dist-server/service/chat-session/session-inbox.js +32 -8
  111. package/dist-server/service/chat-session/session-inbox.js.map +1 -1
  112. package/dist-server/service/chat-session/session-purge.d.ts +14 -0
  113. package/dist-server/service/chat-session/session-purge.js +77 -0
  114. package/dist-server/service/chat-session/session-purge.js.map +1 -0
  115. package/dist-server/service/chat-session/session-retention.d.ts +6 -0
  116. package/dist-server/service/chat-session/session-retention.js +20 -0
  117. package/dist-server/service/chat-session/session-retention.js.map +1 -0
  118. package/dist-server/service/chat-session/session-visibility.d.ts +8 -0
  119. package/dist-server/service/chat-session/session-visibility.js +11 -0
  120. package/dist-server/service/chat-session/session-visibility.js.map +1 -1
  121. package/dist-server/service/chat-session/session-write-gate.d.ts +11 -0
  122. package/dist-server/service/chat-session/session-write-gate.js +24 -0
  123. package/dist-server/service/chat-session/session-write-gate.js.map +1 -0
  124. package/dist-server/service/chat-session-participant/invite-notification.d.ts +1 -1
  125. package/dist-server/service/chat-session-participant/invite-notification.js +21 -6
  126. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
  127. package/dist-server/service/chat-session-participant/participant-lookup-roles.d.ts +3 -0
  128. package/dist-server/service/chat-session-participant/participant-lookup-roles.js +10 -0
  129. package/dist-server/service/chat-session-participant/participant-lookup-roles.js.map +1 -0
  130. package/dist-server/service/chat-session-participant/participant-lookup.d.ts +2 -0
  131. package/dist-server/service/chat-session-participant/participant-lookup.js +26 -0
  132. package/dist-server/service/chat-session-participant/participant-lookup.js.map +1 -0
  133. package/dist-server/service/chat-session-resolver.d.ts +17 -2
  134. package/dist-server/service/chat-session-resolver.js +135 -15
  135. package/dist-server/service/chat-session-resolver.js.map +1 -1
  136. package/dist-server/service/index.d.ts +6 -0
  137. package/dist-server/service/index.js +8 -1
  138. package/dist-server/service/index.js.map +1 -1
  139. package/dist-server/tsconfig.tsbuildinfo +1 -1
  140. package/package.json +11 -4
  141. package/server/service/assistant-chat-resolver.ts +72 -5
  142. package/server/service/assistant-mode/assistant-mode-resolver.ts +6 -0
  143. package/server/service/assistant-mode/resolve.ts +9 -2
  144. package/server/service/assistant-mode/types.ts +6 -0
  145. package/server/service/chat-message/chat-message.ts +17 -0
  146. package/server/service/chat-message/fold-session-history.test.ts +18 -0
  147. package/server/service/chat-session/attachment-policy.test.ts +42 -0
  148. package/server/service/chat-session/attachment-policy.ts +68 -0
  149. package/server/service/chat-session/attachment-route.ts +45 -0
  150. package/server/service/chat-session/attachment-tools.ts +63 -0
  151. package/server/service/chat-session/session-attachments.ts +89 -0
  152. package/server/service/chat-session/session-inbox.test.ts +71 -1
  153. package/server/service/chat-session/session-inbox.ts +61 -11
  154. package/server/service/chat-session/session-purge.test.ts +24 -0
  155. package/server/service/chat-session/session-purge.ts +81 -0
  156. package/server/service/chat-session/session-retention.ts +17 -0
  157. package/server/service/chat-session/session-visibility.ts +11 -0
  158. package/server/service/chat-session/session-write-gate.test.ts +53 -0
  159. package/server/service/chat-session/session-write-gate.ts +31 -0
  160. package/server/service/chat-session-participant/invite-notification.test.ts +15 -0
  161. package/server/service/chat-session-participant/invite-notification.ts +24 -6
  162. package/server/service/chat-session-participant/participant-lookup-roles.ts +6 -0
  163. package/server/service/chat-session-participant/participant-lookup.ts +22 -0
  164. package/server/service/chat-session-resolver.ts +130 -13
  165. package/server/service/index.ts +6 -0
  166. package/test/assistant-dock.test.ts +118 -0
  167. package/test/assistant-session-manager.test.ts +126 -0
  168. package/test/attachment-policy-agree.test.ts +32 -0
  169. package/test/open-doors.test.ts +5 -2
  170. package/translations/en.json +61 -1
  171. package/translations/ja.json +61 -1
  172. package/translations/ko.json +61 -1
  173. package/translations/ms.json +61 -1
  174. package/translations/zh.json +61 -1
@@ -0,0 +1,641 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/icon/icon.js';
3
+ import './assistant-chat.js';
4
+ import './assistant-session-toolbar.js';
5
+ import './assistant-session-manager.js';
6
+ import gql from 'graphql-tag';
7
+ import { LitElement, css, html, nothing } from 'lit';
8
+ import { customElement, property, state } from 'lit/decorators.js';
9
+ import { keyed } from 'lit/directives/keyed.js';
10
+ import { client } from '@operato/graphql';
11
+ import { i18next, localize } from '@operato/i18n';
12
+ import { closeOverlay, notify } from '@operato/layout';
13
+ import { OxPrompt } from '@operato/popup';
14
+ import { AppController, store } from '@operato/shell';
15
+ import { ScrollbarStyles } from '@operato/styles';
16
+ import { followPickedConversation } from './assistant-mode.js';
17
+ import { AssistantSessionController } from '../utils/assistant-session-controller.js';
18
+ import { assistantSessionTransport } from '../utils/assistant-session-transport.js';
19
+ import { readAiCapability } from '../utils/assistant-capability-read.js';
20
+ import { setOpenSession, subscribeSessionActivity } from '../utils/assistant-activity.js';
21
+ import { assistantHomeSurface, currentAssistantSurface, isAnchor, subscribeAssistantSurface } from '../utils/assistant-surface.js';
22
+ import { runProposal } from '../utils/assistant-proposals.js';
23
+ import { subscribeAssistantAsk, takeAssistantAsk } from '../utils/assistant-ask.js';
24
+ export const ASSISTANT_DOCK_VIEWPART = 'assistant-dock';
25
+ const t = (key, defaultValue, options = {}) => i18next.t(`ai-assistant.dock.${key}`, { defaultValue, ...options });
26
+ const MODES_QUERY = gql `
27
+ query ($subject: String!) {
28
+ assistantModes(subject: $subject) {
29
+ kind
30
+ label
31
+ isDefault
32
+ }
33
+ }
34
+ `;
35
+ async function modesFor(subject) {
36
+ const response = await client.query({ query: MODES_QUERY, variables: { subject }, fetchPolicy: 'network-only' });
37
+ if (response.errors?.length)
38
+ throw new Error(response.errors[0].message);
39
+ return response.data?.assistantModes ?? [];
40
+ }
41
+ /**
42
+ * The AI dock - the same in every operato application (design/ai/shared-dock.md).
43
+ *
44
+ * ── What is here and what is not ────────────────────────────────────────────────
45
+ * here where it stands, whether an AI answers, which surface and anchor, which conversation (mode),
46
+ * sessions and their management, unread, proposals and their outcome, asks from other screens
47
+ * the app what its screens show (surfaces, assistant-surface.ts), how its proposal kinds apply
48
+ * (assistant-proposals.ts), and on the server its modes and tools
49
+ *
50
+ * Nothing in this file names an application or a proposal kind. A behaviour one app needs and another does
51
+ * not is a surface field or a handler, never a branch here.
52
+ */
53
+ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
54
+ constructor() {
55
+ super(...arguments);
56
+ /** The viewpart this dock lives in - for closing it. */
57
+ this.viewpart = ASSISTANT_DOCK_VIEWPART;
58
+ this.capability = 'unknown';
59
+ this.asked = false;
60
+ this.kind = '';
61
+ /** kind → its label, for the manager's rows. */
62
+ this.kindLabels = {};
63
+ this.sessions = [];
64
+ this.sessionId = '';
65
+ this.loading = false;
66
+ this.failure = '';
67
+ this.showingHistory = false;
68
+ this.conversationView = 0;
69
+ this.app = new AppController(this);
70
+ /** The kind the person picked; kept while the surface stays the same. */
71
+ this.chosenKind = '';
72
+ this.revision = 0;
73
+ /** What the conversation was opened for - a send is refused when the screen moved on. */
74
+ this.openedFor = '';
75
+ this.unsubscribers = [];
76
+ this.controller = new AssistantSessionController(assistantSessionTransport, next => {
77
+ if (next.sessionId !== this.sessionId) {
78
+ setOpenSession(next.sessionId);
79
+ this.surface?.sessionChanged?.(next.sessionId);
80
+ }
81
+ this.sessions = next.sessions;
82
+ this.sessionId = next.sessionId;
83
+ this.loading = next.loading;
84
+ this.failure = next.error;
85
+ }, { getItem: key => sessionStorage.getItem(key), setItem: (key, value) => sessionStorage.setItem(key, value) }, { lazy: true });
86
+ this.onStartNewSession = () => void this.newConversation();
87
+ this.onModeChange = (event) => {
88
+ const kind = event.detail?.kind;
89
+ if (!kind || kind === this.kind)
90
+ return;
91
+ this.chosenKind = kind;
92
+ void this.open();
93
+ };
94
+ /** Every proposal ends in exactly one reported outcome - a kind nobody handles is `unavailable`. */
95
+ this.onProposal = async (event) => {
96
+ const { proposal, sessionId, proposalKey } = event.detail ?? {};
97
+ const outcome = await runProposal(proposal, this.proposalContext(sessionId));
98
+ if (outcome.verdict === 'unavailable' && outcome.errorCode === 'unknown-proposal-kind') {
99
+ notify({ level: 'error', message: t('proposal-not-runnable', 'this proposal cannot be run on this screen.') });
100
+ }
101
+ await this.report(sessionId, proposalKey, outcome);
102
+ };
103
+ /* ── board surfaces: the chat's scene edits land on the open board ───────────────── */
104
+ this.onScenePatch = async (event) => {
105
+ const board = this.surface?.board;
106
+ if (!board)
107
+ return;
108
+ const result = await board.applyPatch(event.detail);
109
+ if (result?.missed)
110
+ notify({ level: 'info', message: t('patch-missed', '{missed} of the changes could not be applied.', { missed: result.missed }) });
111
+ };
112
+ this.onBoardRevert = (event) => void this.surface?.board?.revertPatch?.(event.detail?.patchId);
113
+ this.onSceneAction = (event) => void this.surface?.board?.applyAction?.(event.detail);
114
+ }
115
+ static { this.styles = [
116
+ ScrollbarStyles,
117
+ css `
118
+ :host {
119
+ display: flex;
120
+ flex-direction: column;
121
+ width: 420px;
122
+ max-width: 100vw;
123
+ box-sizing: border-box;
124
+ height: 100%;
125
+ min-height: 0;
126
+ background: var(--md-sys-color-surface-container-lowest);
127
+ border-left: 1px solid var(--md-sys-color-outline-variant);
128
+ color: var(--md-sys-color-on-surface);
129
+ font-size: 13px;
130
+ }
131
+
132
+ /* Below 1440px the page keeps its full width and only the dock floats. The DOM stays - the chat does not reconnect. */
133
+ @media (max-width: 1439.98px) {
134
+ :host {
135
+ position: fixed;
136
+ inset: 0 0 0 auto;
137
+ height: 100vh;
138
+ height: 100dvh;
139
+ z-index: 10;
140
+ box-shadow: var(--md-sys-elevation-shadow-3, -4px 0 16px rgb(0 0 0 / 20%));
141
+ }
142
+ }
143
+
144
+ @media (max-width: 599.98px) {
145
+ :host {
146
+ width: 100vw;
147
+ }
148
+ }
149
+
150
+ .scope {
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 6px;
154
+ min-height: 40px;
155
+ padding: 5px 8px;
156
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
157
+ }
158
+
159
+ .scope > md-icon {
160
+ --md-icon-size: 18px;
161
+ flex: none;
162
+ color: var(--md-sys-color-primary);
163
+ }
164
+
165
+ .what {
166
+ display: grid;
167
+ flex: 1;
168
+ min-width: 0;
169
+ font-size: 12px;
170
+ font-weight: 650;
171
+ line-height: 1.25;
172
+ }
173
+
174
+ .what span {
175
+ overflow: hidden;
176
+ text-overflow: ellipsis;
177
+ white-space: nowrap;
178
+ }
179
+
180
+ .detail {
181
+ color: var(--md-sys-color-on-surface-variant);
182
+ font-size: 10.5px;
183
+ font-weight: 400;
184
+ }
185
+
186
+ .sbtn {
187
+ display: inline-flex;
188
+ align-items: center;
189
+ flex: none;
190
+ padding: 4px;
191
+ border: none;
192
+ border-radius: 8px;
193
+ background: none;
194
+ color: var(--md-sys-color-on-surface-variant);
195
+ cursor: pointer;
196
+ }
197
+
198
+ .sbtn:hover {
199
+ background: var(--md-sys-color-surface-container-high);
200
+ color: var(--md-sys-color-on-surface);
201
+ }
202
+
203
+ .sbtn md-icon {
204
+ --md-icon-size: 18px;
205
+ }
206
+
207
+ ox-assistant-session-toolbar {
208
+ flex: none;
209
+ min-width: 0;
210
+ padding: 3px 8px;
211
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
212
+ --border-color: var(--md-sys-color-outline-variant);
213
+ --main-section-background-color: var(--md-sys-color-surface-container);
214
+ --primary-color: var(--md-sys-color-primary);
215
+ font: inherit;
216
+ font-size: 12px;
217
+ }
218
+
219
+ ox-assistant-chat,
220
+ ox-assistant-session-manager {
221
+ flex: 1;
222
+ min-height: 0;
223
+ }
224
+
225
+ /* No conversation could stand - say what stopped it, never an empty panel. */
226
+ .stuck {
227
+ margin: auto;
228
+ padding: 20px;
229
+ text-align: center;
230
+ line-height: 1.7;
231
+ color: var(--md-sys-color-on-surface-variant);
232
+ }
233
+
234
+ .stuck .sbtn {
235
+ margin-top: 6px;
236
+ gap: 4px;
237
+ font: inherit;
238
+ }
239
+ `
240
+ ]; }
241
+ connectedCallback() {
242
+ super.connectedCallback();
243
+ this.addEventListener('assistant-start-new-session', this.onStartNewSession);
244
+ this.unsubscribers.push(subscribeAssistantSurface(() => this.follow()), subscribeAssistantAsk(() => void this.takeAsk()), subscribeSessionActivity(activity => {
245
+ /* A rename or a new conversation elsewhere on this anchor - the tabs read again. */
246
+ if (activity.kind === 'meta' && isAnchor(this.anchor) && activity.anchorId === this.anchor.anchorId)
247
+ void this.reloadSessions();
248
+ }));
249
+ this.follow(true);
250
+ }
251
+ disconnectedCallback() {
252
+ this.revision++;
253
+ this.removeEventListener('assistant-start-new-session', this.onStartNewSession);
254
+ this.unsubscribers.splice(0).forEach(unsubscribe => unsubscribe());
255
+ setOpenSession('');
256
+ this.controller.clear();
257
+ super.disconnectedCallback();
258
+ }
259
+ scopeKey(surface = this.surface) {
260
+ return JSON.stringify([this.app.contextPath, surface?.key ?? '']);
261
+ }
262
+ /** The screen changed - a different surface or domain is a different conversation. */
263
+ follow(force = false) {
264
+ const next = currentAssistantSurface();
265
+ if (!force && next === this.surface && this.scopeKey(next) === this.openedFor)
266
+ return;
267
+ if (next?.key !== this.surface?.key)
268
+ this.chosenKind = '';
269
+ this.surface = next;
270
+ void this.open();
271
+ }
272
+ async open() {
273
+ const ticket = ++this.revision;
274
+ this.conversationView++;
275
+ this.showingHistory = false;
276
+ void this.controller.open(undefined);
277
+ this.loading = true;
278
+ this.failure = '';
279
+ this.anchor = undefined;
280
+ this.openedFor = this.scopeKey();
281
+ try {
282
+ if (this.capability !== 'configured') {
283
+ this.capability = await readAiCapability();
284
+ this.asked = true;
285
+ }
286
+ if (ticket !== this.revision || this.capability !== 'configured')
287
+ return;
288
+ const surface = this.surface;
289
+ if (!surface)
290
+ return;
291
+ const anchor = await surface.anchor();
292
+ if (ticket !== this.revision)
293
+ return;
294
+ this.anchor = anchor;
295
+ if (!isAnchor(anchor))
296
+ return;
297
+ const modes = await modesFor(surface.subject);
298
+ if (ticket !== this.revision)
299
+ return;
300
+ this.kindLabels = Object.fromEntries(modes.map(mode => [mode.kind, mode.label || mode.kind]));
301
+ const chosen = modes.find(mode => mode.kind === this.chosenKind) ?? modes.find(mode => mode.isDefault) ?? modes[0];
302
+ this.kind = chosen?.kind ?? '';
303
+ if (!this.kind) {
304
+ this.anchor = { stuck: 'no-conversation', message: t('no-conversation', 'there is no conversation for this screen.') };
305
+ return;
306
+ }
307
+ const user = store.getState().auth?.user;
308
+ const userId = user?.id || user?.email;
309
+ if (!userId)
310
+ throw new Error(t('no-user', 'your sign-in could not be read. sign in again.'));
311
+ await this.controller.open(this.target(userId, anchor));
312
+ await this.takeAsk();
313
+ }
314
+ catch (error) {
315
+ if (ticket === this.revision)
316
+ this.failure = error.message;
317
+ }
318
+ finally {
319
+ if (ticket === this.revision)
320
+ this.loading = false;
321
+ }
322
+ }
323
+ target(userId, anchor) {
324
+ /* The conversation kind is part of the key: two conversations on one anchor never share a session (ADR-0072 decision 3). */
325
+ return { scope: JSON.stringify([this.app.contextPath, userId, this.kind]), anchorType: anchor.anchorType, anchorId: anchor.anchorId, kind: this.kind };
326
+ }
327
+ async reloadSessions() {
328
+ const target = this.controller.state.target;
329
+ if (target)
330
+ await this.controller.open(target, true);
331
+ }
332
+ async newConversation() {
333
+ if (this.loading || !isAnchor(this.anchor))
334
+ return;
335
+ this.showingHistory = false;
336
+ const revision = this.controller.state.revision;
337
+ await this.controller.reset();
338
+ if (revision !== this.controller.state.revision)
339
+ this.conversationView++;
340
+ }
341
+ selectSession(id, pickedKind) {
342
+ this.showingHistory = false;
343
+ const follow = followPickedConversation(pickedKind, this.kind);
344
+ if (follow) {
345
+ /* A conversation of another kind on this anchor - the dock follows it rather than refusing it. */
346
+ this.chosenKind = follow;
347
+ void this.open().then(() => this.controller.select(id) && this.conversationView++);
348
+ return;
349
+ }
350
+ const previous = this.sessionId;
351
+ if (this.controller.select(id) && previous !== id)
352
+ this.conversationView++;
353
+ }
354
+ /* ── asks from other screens ────────────────────────────────────────────────────── */
355
+ async takeAsk() {
356
+ if (!isAnchor(this.anchor) || this.loading)
357
+ return;
358
+ const ask = takeAssistantAsk();
359
+ if (!ask)
360
+ return;
361
+ if (!ask.continueCurrent && (ask.name || ask.context || ask.transcript?.length)) {
362
+ const id = await this.startCarried(ask);
363
+ if (id) {
364
+ await this.reloadSessions();
365
+ this.controller.select(id);
366
+ }
367
+ }
368
+ else if (!ask.continueCurrent) {
369
+ await this.newConversation();
370
+ }
371
+ this.conversationView++;
372
+ this.asking = { ask: ask.ask, draft: ask.draft, requestId: String(Date.now()) };
373
+ }
374
+ /** A new conversation carrying what the other screen had - its name, a note, earlier turns. */
375
+ async startCarried(ask) {
376
+ const anchor = this.anchor;
377
+ const created = await client.mutate({
378
+ mutation: gql `
379
+ mutation ($t: String!, $i: String!, $n: String) {
380
+ createAISession(anchorType: $t, anchorId: $i, name: $n) {
381
+ id
382
+ }
383
+ }
384
+ `,
385
+ variables: { t: anchor.anchorType, i: anchor.anchorId, n: ask.name || null }
386
+ });
387
+ const id = created.data?.createAISession?.id;
388
+ if (!id)
389
+ return undefined;
390
+ if (ask.context) {
391
+ await client.mutate({
392
+ mutation: gql `
393
+ mutation ($id: String!, $c: String!) {
394
+ appendSessionNote(sessionId: $id, content: $c)
395
+ }
396
+ `,
397
+ variables: { id, c: ask.context }
398
+ });
399
+ }
400
+ if (ask.transcript?.length) {
401
+ await client.mutate({
402
+ mutation: gql `
403
+ mutation ($id: String!, $turns: JSON!) {
404
+ appendSessionTurns(sessionId: $id, turns: $turns)
405
+ }
406
+ `,
407
+ variables: { id, turns: ask.transcript.map(turn => ({ role: turn.role === 'ai' ? 'assistant' : 'user', content: turn.content })) }
408
+ });
409
+ }
410
+ return id;
411
+ }
412
+ /* ── proposals ───────────────────────────────────────────────────────────────────── */
413
+ proposalContext(sessionId) {
414
+ return {
415
+ sessionId,
416
+ surface: this.surface,
417
+ confirm: async ({ title, text, confirmText, cancelText, type }) => !!(await OxPrompt.open({
418
+ type: type ?? 'warning',
419
+ title,
420
+ text,
421
+ confirmButton: { text: confirmText ?? t('confirm', 'OK') },
422
+ cancelButton: { text: cancelText ?? t('cancel', 'Cancel') }
423
+ })),
424
+ notify: (level, message) => notify({ level, message })
425
+ };
426
+ }
427
+ async report(sessionId, proposalKey, outcome) {
428
+ if (!sessionId || !proposalKey)
429
+ return;
430
+ try {
431
+ await client.mutate({
432
+ mutation: gql `
433
+ mutation ($id: String!, $key: String!, $outcome: JSON!) {
434
+ reportProposalOutcome(sessionId: $id, proposalKey: $key, outcome: $outcome)
435
+ }
436
+ `,
437
+ variables: { id: sessionId, key: proposalKey, outcome }
438
+ });
439
+ }
440
+ catch {
441
+ /* the card then says it heard nothing - never "done" */
442
+ }
443
+ }
444
+ /* ── drawing ─────────────────────────────────────────────────────────────────────── */
445
+ stuckLine() {
446
+ if (this.capability === 'not-configured')
447
+ return t('not-configured', 'no AI is set up for this site. an administrator can set one up in the AI settings.');
448
+ if (this.capability === 'unknown' && this.asked && !this.loading && !this.failure)
449
+ return t('capability-unknown', 'whether AI is set up here could not be read.');
450
+ if (this.capability === 'configured' && !this.surface && !this.loading)
451
+ return t('no-surface', 'this screen has no conversation.');
452
+ const anchor = this.anchor;
453
+ if (anchor && !isAnchor(anchor))
454
+ return anchor.message;
455
+ return '';
456
+ }
457
+ header() {
458
+ const surface = this.surface;
459
+ const home = assistantHomeSurface();
460
+ const ready = this.capability === 'configured' && isAnchor(this.anchor);
461
+ return html `
462
+ <div class="scope">
463
+ <md-icon>${surface?.icon || 'forum'}</md-icon>
464
+ <span class="what">
465
+ <span>${surface?.label() || t('title', 'Assistant')}</span>
466
+ ${surface?.detail ? html `<span class="detail">${surface.detail()}</span>` : nothing}
467
+ </span>
468
+ ${home && surface && home !== surface
469
+ ? html `<button class="sbtn" title=${t('to-home', "back to this application's conversation")} @click=${() => this.useSurface(home)}>
470
+ <md-icon>home</md-icon>
471
+ </button>`
472
+ : nothing}
473
+ ${ready
474
+ ? html `<button
475
+ class="sbtn"
476
+ title=${this.showingHistory ? t('back-to-conversation', 'back to the conversation') : t('conversations', 'conversations')}
477
+ @click=${() => (this.showingHistory = !this.showingHistory)}
478
+ >
479
+ <md-icon>${this.showingHistory ? 'forum' : 'list'}</md-icon>
480
+ </button>`
481
+ : nothing}
482
+ <button class="sbtn" title=${t('rebind', 'follow this screen again')} @click=${() => this.follow(true)}>
483
+ <md-icon>refresh</md-icon>
484
+ </button>
485
+ <button class="sbtn" title=${t('close', 'close')} @click=${() => closeOverlay(this.viewpart)}>
486
+ <md-icon>close</md-icon>
487
+ </button>
488
+ </div>
489
+ `;
490
+ }
491
+ /** The home button: talk about the application instead of this screen, until the screen changes. */
492
+ useSurface(surface) {
493
+ this.surface = surface;
494
+ this.chosenKind = '';
495
+ void this.open();
496
+ }
497
+ render() {
498
+ const anchor = this.anchor;
499
+ const ready = this.capability === 'configured' && isAnchor(anchor);
500
+ const stuck = this.stuckLine();
501
+ const surface = this.surface;
502
+ const openedFor = this.openedFor;
503
+ const board = surface?.board;
504
+ const mode = ready ? { kind: this.kind, target: anchor.anchorId, hostContext: {} } : undefined;
505
+ return html `
506
+ ${this.header()}
507
+ ${ready && this.showingHistory
508
+ ? html `<ox-assistant-session-manager
509
+ .anchorType=${anchor.anchorType}
510
+ .anchorId=${anchor.anchorId}
511
+ .sessionId=${this.sessionId}
512
+ .kind=${this.kind}
513
+ .kindLabels=${this.kindLabels}
514
+ @assistant-session-select=${(event) => this.selectSession(event.detail.sessionId, event.detail.kind)}
515
+ @assistant-session-reset=${() => this.newConversation()}
516
+ @assistant-session-changed=${() => this.reloadSessions()}
517
+ ></ox-assistant-session-manager>`
518
+ : nothing}
519
+ ${ready && !this.showingHistory
520
+ ? html `<ox-assistant-session-toolbar
521
+ .sessions=${this.sessions}
522
+ .sessionId=${this.sessionId}
523
+ .loading=${this.loading}
524
+ .error=${this.failure}
525
+ historyEnabled
526
+ @assistant-session-select=${(event) => this.selectSession(event.detail.sessionId)}
527
+ @assistant-session-reset=${() => this.newConversation()}
528
+ @assistant-session-history=${() => (this.showingHistory = true)}
529
+ @assistant-session-retry=${() => this.open()}
530
+ ></ox-assistant-session-toolbar>`
531
+ : nothing}
532
+ ${ready && !this.showingHistory && !this.loading && !this.failure
533
+ ? keyed(this.conversationView, html `<ox-assistant-chat
534
+ .sessionId=${this.sessionId}
535
+ .prepareSession=${(adopt) => this.controller.prepareSession(adopt)}
536
+ .contextProvider=${() => {
537
+ /* The screen moved on between writing and sending - do not answer about the old one. */
538
+ if (openedFor !== this.scopeKey(currentAssistantSurface() ?? surface))
539
+ throw new Error(t('screen-changed', 'the screen changed while you were writing. send it again.'));
540
+ return { hostContext: surface?.hostContext?.() ?? {} };
541
+ }}
542
+ .mode=${mode}
543
+ .subject=${surface?.subject}
544
+ .chatEndpoint=${board?.chatEndpoint ?? 'assistantChat'}
545
+ ?board-tools=${!!board}
546
+ .currentBoard=${board?.currentBoard?.()}
547
+ .boardProvider=${board?.boardProvider}
548
+ .selectedRefids=${board?.selectedRefids?.() ?? []}
549
+ .knownTypes=${board?.knownTypes?.()}
550
+ .categories=${board?.categories?.()}
551
+ .componentSchemas=${board?.componentSchemas?.()}
552
+ .searchProvider=${board?.searchProvider}
553
+ .autoAsk=${this.asking?.ask}
554
+ .autoAskRequestId=${this.asking?.requestId}
555
+ .draft=${this.asking?.draft}
556
+ .draftRequestId=${this.asking?.requestId}
557
+ @assistant-mode-change=${this.onModeChange}
558
+ @assistant-proposal-execute=${this.onProposal}
559
+ @scene-edit-patch=${this.onScenePatch}
560
+ @board-edit-revert=${this.onBoardRevert}
561
+ @scene-action-execute=${this.onSceneAction}
562
+ ></ox-assistant-chat>`)
563
+ : nothing}
564
+ ${stuck
565
+ ? html `<div class="stuck">
566
+ ${stuck}
567
+ ${this.capability === 'unknown'
568
+ ? html `<div><button class="sbtn" @click=${() => this.open()}><md-icon>refresh</md-icon>${t('retry', 'try again')}</button></div>`
569
+ : nothing}
570
+ </div>`
571
+ : nothing}
572
+ ${this.failure && !stuck && !ready
573
+ ? html `<div class="stuck">
574
+ ${this.failure}
575
+ <div><button class="sbtn" @click=${() => this.open()}><md-icon>refresh</md-icon>${t('retry', 'try again')}</button></div>
576
+ </div>`
577
+ : nothing}
578
+ `;
579
+ }
580
+ };
581
+ __decorate([
582
+ property({ type: String }),
583
+ __metadata("design:type", Object)
584
+ ], AssistantDock.prototype, "viewpart", void 0);
585
+ __decorate([
586
+ state(),
587
+ __metadata("design:type", String)
588
+ ], AssistantDock.prototype, "capability", void 0);
589
+ __decorate([
590
+ state(),
591
+ __metadata("design:type", Object)
592
+ ], AssistantDock.prototype, "asked", void 0);
593
+ __decorate([
594
+ state(),
595
+ __metadata("design:type", Object)
596
+ ], AssistantDock.prototype, "surface", void 0);
597
+ __decorate([
598
+ state(),
599
+ __metadata("design:type", Object)
600
+ ], AssistantDock.prototype, "anchor", void 0);
601
+ __decorate([
602
+ state(),
603
+ __metadata("design:type", Object)
604
+ ], AssistantDock.prototype, "kind", void 0);
605
+ __decorate([
606
+ state(),
607
+ __metadata("design:type", Object)
608
+ ], AssistantDock.prototype, "kindLabels", void 0);
609
+ __decorate([
610
+ state(),
611
+ __metadata("design:type", Array)
612
+ ], AssistantDock.prototype, "sessions", void 0);
613
+ __decorate([
614
+ state(),
615
+ __metadata("design:type", Object)
616
+ ], AssistantDock.prototype, "sessionId", void 0);
617
+ __decorate([
618
+ state(),
619
+ __metadata("design:type", Object)
620
+ ], AssistantDock.prototype, "loading", void 0);
621
+ __decorate([
622
+ state(),
623
+ __metadata("design:type", Object)
624
+ ], AssistantDock.prototype, "failure", void 0);
625
+ __decorate([
626
+ state(),
627
+ __metadata("design:type", Object)
628
+ ], AssistantDock.prototype, "showingHistory", void 0);
629
+ __decorate([
630
+ state(),
631
+ __metadata("design:type", Object)
632
+ ], AssistantDock.prototype, "conversationView", void 0);
633
+ __decorate([
634
+ state(),
635
+ __metadata("design:type", Object)
636
+ ], AssistantDock.prototype, "asking", void 0);
637
+ AssistantDock = __decorate([
638
+ customElement('ox-assistant-dock')
639
+ ], AssistantDock);
640
+ export { AssistantDock };
641
+ //# sourceMappingURL=assistant-dock.js.map