@things-factory/ai-dashboard 0.0.0-stage → 10.1.213

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 (108) hide show
  1. package/client/bootstrap.ts +25 -0
  2. package/client/components/card-dialogs.ts +422 -0
  3. package/client/components/card-library.ts +272 -0
  4. package/client/components/dashboard-card.ts +450 -0
  5. package/client/components/proposal-preview.ts +96 -0
  6. package/client/index.ts +3 -0
  7. package/client/pages/my-dashboard-page.ts +371 -0
  8. package/client/route.ts +8 -0
  9. package/client/tsconfig.json +13 -0
  10. package/client/utils/card-api.ts +110 -0
  11. package/client/utils/card-words.ts +80 -0
  12. package/dist-client/bootstrap.d.ts +2 -0
  13. package/dist-client/bootstrap.js +24 -0
  14. package/dist-client/bootstrap.js.map +1 -0
  15. package/dist-client/components/card-dialogs.d.ts +59 -0
  16. package/dist-client/components/card-dialogs.js +457 -0
  17. package/dist-client/components/card-dialogs.js.map +1 -0
  18. package/dist-client/components/card-library.d.ts +23 -0
  19. package/dist-client/components/card-library.js +276 -0
  20. package/dist-client/components/card-library.js.map +1 -0
  21. package/dist-client/components/dashboard-card.d.ts +35 -0
  22. package/dist-client/components/dashboard-card.js +490 -0
  23. package/dist-client/components/dashboard-card.js.map +1 -0
  24. package/dist-client/components/proposal-preview.d.ts +17 -0
  25. package/dist-client/components/proposal-preview.js +111 -0
  26. package/dist-client/components/proposal-preview.js.map +1 -0
  27. package/dist-client/index.d.ts +3 -0
  28. package/dist-client/index.js +4 -0
  29. package/dist-client/index.js.map +1 -0
  30. package/dist-client/pages/my-dashboard-page.d.ts +46 -0
  31. package/dist-client/pages/my-dashboard-page.js +376 -0
  32. package/dist-client/pages/my-dashboard-page.js.map +1 -0
  33. package/dist-client/route.d.ts +1 -0
  34. package/dist-client/route.js +9 -0
  35. package/dist-client/route.js.map +1 -0
  36. package/dist-client/tsconfig.tsbuildinfo +1 -0
  37. package/dist-client/utils/card-api.d.ts +95 -0
  38. package/dist-client/utils/card-api.js +39 -0
  39. package/dist-client/utils/card-api.js.map +1 -0
  40. package/dist-client/utils/card-words.d.ts +24 -0
  41. package/dist-client/utils/card-words.js +72 -0
  42. package/dist-client/utils/card-words.js.map +1 -0
  43. package/dist-server/index.d.ts +11 -0
  44. package/dist-server/index.js +19 -0
  45. package/dist-server/index.js.map +1 -0
  46. package/dist-server/role-tasks.d.ts +2 -0
  47. package/dist-server/role-tasks.js +13 -0
  48. package/dist-server/role-tasks.js.map +1 -0
  49. package/dist-server/service/card/card-access.d.ts +32 -0
  50. package/dist-server/service/card/card-access.js +45 -0
  51. package/dist-server/service/card/card-access.js.map +1 -0
  52. package/dist-server/service/card/card-definition.d.ts +100 -0
  53. package/dist-server/service/card/card-definition.js +237 -0
  54. package/dist-server/service/card/card-definition.js.map +1 -0
  55. package/dist-server/service/card/card-names.d.ts +5 -0
  56. package/dist-server/service/card/card-names.js +9 -0
  57. package/dist-server/service/card/card-names.js.map +1 -0
  58. package/dist-server/service/card/card-proposal.d.ts +21 -0
  59. package/dist-server/service/card/card-proposal.js +28 -0
  60. package/dist-server/service/card/card-proposal.js.map +1 -0
  61. package/dist-server/service/card/card-resolver.d.ts +29 -0
  62. package/dist-server/service/card/card-resolver.js +541 -0
  63. package/dist-server/service/card/card-resolver.js.map +1 -0
  64. package/dist-server/service/card/card-run.d.ts +48 -0
  65. package/dist-server/service/card/card-run.js +239 -0
  66. package/dist-server/service/card/card-run.js.map +1 -0
  67. package/dist-server/service/card/card-tool.d.ts +218 -0
  68. package/dist-server/service/card/card-tool.js +313 -0
  69. package/dist-server/service/card/card-tool.js.map +1 -0
  70. package/dist-server/service/card/card-types.d.ts +56 -0
  71. package/dist-server/service/card/card-types.js +212 -0
  72. package/dist-server/service/card/card-types.js.map +1 -0
  73. package/dist-server/service/card/dashboard-card.d.ts +87 -0
  74. package/dist-server/service/card/dashboard-card.js +388 -0
  75. package/dist-server/service/card/dashboard-card.js.map +1 -0
  76. package/dist-server/service/card/long-text.d.ts +6 -0
  77. package/dist-server/service/card/long-text.js +28 -0
  78. package/dist-server/service/card/long-text.js.map +1 -0
  79. package/dist-server/service/index.d.ts +12 -0
  80. package/dist-server/service/index.js +24 -0
  81. package/dist-server/service/index.js.map +1 -0
  82. package/dist-server/tsconfig.tsbuildinfo +1 -0
  83. package/package.json +47 -4
  84. package/server/index.ts +16 -0
  85. package/server/role-tasks.ts +12 -0
  86. package/server/service/card/card-access.test.ts +138 -0
  87. package/server/service/card/card-access.ts +53 -0
  88. package/server/service/card/card-definition.ts +298 -0
  89. package/server/service/card/card-names.ts +5 -0
  90. package/server/service/card/card-proposal.ts +37 -0
  91. package/server/service/card/card-resolver.ts +434 -0
  92. package/server/service/card/card-run.test.ts +237 -0
  93. package/server/service/card/card-run.ts +288 -0
  94. package/server/service/card/card-tool.ts +299 -0
  95. package/server/service/card/card-types.ts +145 -0
  96. package/server/service/card/dashboard-card.ts +301 -0
  97. package/server/service/card/long-text.ts +25 -0
  98. package/server/service/index.ts +16 -0
  99. package/test/translations.test.ts +42 -0
  100. package/things-factory.config.js +8 -0
  101. package/translations/en.json +126 -0
  102. package/translations/es.json +126 -0
  103. package/translations/ja.json +126 -0
  104. package/translations/ko.json +126 -0
  105. package/translations/ms.json +126 -0
  106. package/translations/zh.json +126 -0
  107. package/tsconfig.json +9 -0
  108. package/README.md +0 -3
@@ -0,0 +1,371 @@
1
+ /*
2
+ * 나의 대시보드 - the cards a person placed, drawn from the data as them (ADR-0160 decision 8).
3
+ *
4
+ * Cards are made in the AI conversation beside the page (its card tool is in front here) and saved by pressing the
5
+ * card there; the library holds one's own cards and those shared with one. The page asks no model: each card asks
6
+ * the server to run its definition, each on its own, so a slow card waits in its place.
7
+ */
8
+ import '@material/web/icon/icon.js'
9
+ import '../components/dashboard-card.js'
10
+ import '../components/card-library.js'
11
+
12
+ import { css, html, nothing } from 'lit'
13
+ import { customElement, state } from 'lit/decorators.js'
14
+
15
+ import { i18next, localize } from '@operato/i18n'
16
+ import { notify, openPopup } from '@operato/layout'
17
+ import { PageView } from '@operato/shell'
18
+ import { DocumentPageStyles, ScrollbarStyles } from '@operato/styles'
19
+ import { askAssistant } from '@things-factory/ai-assistant/dist-client/index.js'
20
+
21
+ import { cardApi, DASHBOARD_CHANGED, dashboardChanged, type CardItem, type PlaceItem } from '../utils/card-api.js'
22
+ import { movedPlaces } from '../utils/card-words.js'
23
+ import { openCardAiPrompt, openCardRename, openCardShare, openCardVersions } from '../components/card-dialogs.js'
24
+ import type { CardAction } from '../components/dashboard-card.js'
25
+
26
+ const t = (key: string, options: Record<string, unknown> = {}) => String(i18next.t(key, options))
27
+
28
+ /** The requests the empty dashboard offers - each sent to the conversation beside the page as it is. */
29
+ const EXAMPLES = ['ai-dashboard.example.receivables', 'ai-dashboard.example.cash-trend', 'ai-dashboard.example.month-count']
30
+
31
+ @customElement('my-dashboard-page')
32
+ export class MyDashboardPage extends localize(i18next)(PageView) {
33
+ static styles = [
34
+ DocumentPageStyles,
35
+ ScrollbarStyles,
36
+ css`
37
+ .page {
38
+ gap: var(--spacing-large, 16px);
39
+ }
40
+
41
+ .lead {
42
+ display: flex;
43
+ flex-wrap: wrap;
44
+ align-items: center;
45
+ gap: 8px 16px;
46
+ }
47
+
48
+ .lead p {
49
+ flex: 1;
50
+ min-width: 240px;
51
+ margin: 0;
52
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
53
+ }
54
+
55
+ .chip {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ gap: 6px;
59
+ padding: 4px 10px;
60
+ border-radius: 999px;
61
+ background: var(--ui-accent-wash, color-mix(in srgb, var(--md-sys-color-primary) 12%, transparent));
62
+ color: var(--ui-accent, var(--md-sys-color-primary));
63
+ font-size: var(--ui-t-small, 12px);
64
+ font-weight: 600;
65
+ --md-icon-size: 16px;
66
+ }
67
+
68
+ .board {
69
+ container-type: inline-size;
70
+ }
71
+
72
+ .grid {
73
+ display: grid;
74
+ grid-template-columns: repeat(4, minmax(0, 1fr));
75
+ grid-auto-rows: minmax(250px, auto);
76
+ grid-auto-flow: dense;
77
+ gap: 16px;
78
+ }
79
+
80
+ .grid > * {
81
+ grid-column: span var(--w, 2);
82
+ grid-row: span var(--h, 1);
83
+ }
84
+
85
+ @container (max-width: 1100px) {
86
+ .grid {
87
+ grid-template-columns: repeat(2, minmax(0, 1fr));
88
+ }
89
+
90
+ .grid > .w3,
91
+ .grid > .w4 {
92
+ --w: 2;
93
+ }
94
+ }
95
+
96
+ @container (max-width: 640px) {
97
+ .grid {
98
+ grid-template-columns: minmax(0, 1fr);
99
+ }
100
+
101
+ .grid > * {
102
+ --w: 1 !important;
103
+ }
104
+ }
105
+
106
+ .empty {
107
+ display: flex;
108
+ flex-direction: column;
109
+ align-items: center;
110
+ gap: 14px;
111
+ max-width: 640px;
112
+ margin: 6vh auto 0;
113
+ padding: 36px 28px;
114
+ border: 1px dashed var(--ui-line, var(--md-sys-color-outline-variant));
115
+ border-radius: 16px;
116
+ background: radial-gradient(120% 100% at 50% 0%, var(--ui-accent-wash, color-mix(in srgb, var(--md-sys-color-primary) 10%, transparent)) 0%, transparent 70%);
117
+ text-align: center;
118
+ }
119
+
120
+ .empty .mark {
121
+ display: grid;
122
+ place-items: center;
123
+ width: 56px;
124
+ height: 56px;
125
+ border-radius: 16px;
126
+ background: var(--ui-accent, var(--md-sys-color-primary));
127
+ color: var(--ui-on-accent, var(--md-sys-color-on-primary));
128
+ --md-icon-size: 30px;
129
+ box-shadow: 0 10px 24px -12px var(--ui-accent, var(--md-sys-color-primary));
130
+ }
131
+
132
+ .empty h2 {
133
+ margin: 4px 0 0;
134
+ font-size: var(--ui-t-page, 18px);
135
+ font-weight: 650;
136
+ }
137
+
138
+ .empty p {
139
+ margin: 0;
140
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
141
+ }
142
+
143
+ .examples {
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 8px;
147
+ width: 100%;
148
+ margin-top: 6px;
149
+ }
150
+
151
+ .examples button,
152
+ .empty .library {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 10px;
156
+ padding: 10px 14px;
157
+ border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));
158
+ border-radius: 10px;
159
+ background: var(--ui-panel, var(--md-sys-color-surface-container-lowest));
160
+ color: var(--ui-ink, var(--md-sys-color-on-surface));
161
+ font: inherit;
162
+ text-align: start;
163
+ cursor: pointer;
164
+ transition:
165
+ border-color 0.15s ease,
166
+ transform 0.15s ease;
167
+ --md-icon-size: 18px;
168
+ }
169
+
170
+ .examples button md-icon {
171
+ color: var(--ui-accent, var(--md-sys-color-primary));
172
+ }
173
+
174
+ .examples button:hover,
175
+ .examples button:focus-visible,
176
+ .empty .library:hover {
177
+ border-color: var(--ui-accent, var(--md-sys-color-primary));
178
+ transform: translateY(-1px);
179
+ outline: none;
180
+ }
181
+
182
+ .empty .library {
183
+ justify-content: center;
184
+ border-style: dashed;
185
+ background: transparent;
186
+ }
187
+
188
+ .notice {
189
+ margin: 0;
190
+ padding: 12px 14px;
191
+ border-radius: 10px;
192
+ background: color-mix(in srgb, var(--ui-crit, #c62828) 8%, transparent);
193
+ }
194
+
195
+ .note {
196
+ margin: 0;
197
+ font-size: var(--ui-t-small, 12px);
198
+ color: var(--ui-faint, var(--md-sys-color-on-surface-variant));
199
+ }
200
+ `
201
+ ]
202
+
203
+ @state() private places: PlaceItem[] = []
204
+ @state() private loaded = false
205
+ @state() private error?: string
206
+ @state() private editing = false
207
+
208
+ private onChanged = () => this.load()
209
+
210
+ get context() {
211
+ return {
212
+ title: t('ai-dashboard.title'),
213
+ frame: 'document',
214
+ actions: [
215
+ { title: t(this.editing ? 'ai-dashboard.button.done' : 'ai-dashboard.button.arrange'), action: () => (this.editing = !this.editing), icon: this.editing ? 'check' : 'dashboard_customize' },
216
+ { title: t('ai-dashboard.button.library'), action: () => this.openLibrary(), icon: 'collections_bookmark' },
217
+ { title: t('ai-dashboard.button.make'), action: () => this.make(), icon: 'auto_awesome', emphasis: { raised: true } }
218
+ ]
219
+ }
220
+ }
221
+
222
+ connectedCallback() {
223
+ super.connectedCallback()
224
+ window.addEventListener(DASHBOARD_CHANGED, this.onChanged)
225
+ }
226
+
227
+ disconnectedCallback() {
228
+ window.removeEventListener(DASHBOARD_CHANGED, this.onChanged)
229
+ super.disconnectedCallback()
230
+ }
231
+
232
+ updated(changes: Map<string, unknown>) {
233
+ /* The action bar follows the mode - its words say what pressing it does now. */
234
+ if (changes.has('editing')) this.updateContext()
235
+ }
236
+
237
+ /* Not awaited: the shell sets the tab's title only after this returns. */
238
+ async pageUpdated(changes: any) {
239
+ if ((this as any).active && changes.active) this.load()
240
+ }
241
+
242
+ private async load() {
243
+ try {
244
+ this.places = await cardApi.myDashboard()
245
+ this.error = undefined
246
+ } catch (error: any) {
247
+ this.error = error?.message ?? String(error)
248
+ } finally {
249
+ this.loaded = true
250
+ }
251
+ }
252
+
253
+ render() {
254
+ return html`<div class="page">
255
+ ${this.error
256
+ ? html`<p class="notice">${this.error}</p>`
257
+ : this.loaded && !this.places.length
258
+ ? this.renderEmpty()
259
+ : this.places.length
260
+ ? this.renderBoard()
261
+ : nothing}
262
+ </div>`
263
+ }
264
+
265
+ private renderBoard() {
266
+ const sorted = [...this.places].sort((a, b) => a.order - b.order)
267
+ return html`<div class="lead">
268
+ <p>${t('ai-dashboard.page.lead')}</p>
269
+ ${this.editing ? html`<span class="chip"><md-icon>dashboard_customize</md-icon>${t('ai-dashboard.page.arranging')}</span>` : nothing}
270
+ </div>
271
+ <div class="board">
272
+ <div class="grid" @card-action=${(event: CustomEvent) => this.onCardAction(event)}>
273
+ ${sorted.map(
274
+ (place, i) => html`<ai-dashboard-card
275
+ class="w${place.width} h${place.height}"
276
+ style="--w: ${place.width}; --h: ${place.height}"
277
+ data-place=${place.id}
278
+ .card=${place.card}
279
+ .width=${place.width}
280
+ .height=${place.height}
281
+ ?editing=${this.editing}
282
+ ?first=${i === 0}
283
+ ?last=${i === sorted.length - 1}
284
+ ></ai-dashboard-card>`
285
+ )}
286
+ </div>
287
+ </div>
288
+ <p class="note">${t('ai-dashboard.page.no-tokens')}</p>`
289
+ }
290
+
291
+ private renderEmpty() {
292
+ return html`<div class="empty">
293
+ <span class="mark"><md-icon>auto_awesome</md-icon></span>
294
+ <h2>${t('ai-dashboard.page.empty-title')}</h2>
295
+ <p>${t('ai-dashboard.page.empty-text')}</p>
296
+ <div class="examples">
297
+ ${EXAMPLES.map(key => html`<button @click=${() => this.askFor(t(key))}><md-icon>chat_bubble</md-icon><span>${t(key)}</span></button>`)}
298
+ </div>
299
+ <button class="library" @click=${() => this.openLibrary()}><md-icon>collections_bookmark</md-icon>${t('ai-dashboard.page.from-library')}</button>
300
+ <p class="note">${t('ai-dashboard.page.no-tokens')}</p>
301
+ </div>`
302
+ }
303
+
304
+ /** Sends a request to the conversation beside the page - this screen's, where the card tool is in front. */
305
+ private async askFor(ask: string) {
306
+ await askAssistant({ ask, continueCurrent: true })
307
+ }
308
+
309
+ private make() {
310
+ askAssistant({ draft: t('ai-dashboard.page.make-draft'), continueCurrent: true })
311
+ }
312
+
313
+ private openLibrary() {
314
+ openPopup(html`<ai-dashboard-card-library></ai-dashboard-card-library>`, { backdrop: true, size: 'large', title: t('ai-dashboard.library.title') })
315
+ }
316
+
317
+ private placeOf(card: CardItem) {
318
+ return this.places.find(one => one.card.id === card.id)
319
+ }
320
+
321
+ private async arrange(next: PlaceItem[]) {
322
+ const before = this.places
323
+ this.places = next
324
+ try {
325
+ await cardApi.arrange(next.map(({ id, order, width, height }) => ({ id, order, width, height })))
326
+ } catch (error: any) {
327
+ this.places = before
328
+ notify({ level: 'error', message: error?.message ?? String(error) })
329
+ }
330
+ }
331
+
332
+ private async onCardAction(event: CustomEvent) {
333
+ const { action, card } = event.detail as { action: CardAction; card: CardItem }
334
+ const place = this.placeOf(card)
335
+ try {
336
+ switch (action) {
337
+ case 'ai':
338
+ return openCardAiPrompt(card)
339
+ case 'rename':
340
+ return openCardRename(card)
341
+ case 'share':
342
+ return openCardShare(card)
343
+ case 'versions':
344
+ return openCardVersions(card)
345
+ case 'copy':
346
+ await cardApi.copy(card.id)
347
+ notify({ level: 'info', message: t('ai-dashboard.text.copied', { name: card.name }) })
348
+ return dashboardChanged()
349
+ case 'unplace':
350
+ if (!place) return
351
+ await cardApi.unplace(place.id)
352
+ notify({ level: 'info', message: t('ai-dashboard.text.unplaced', { name: card.name }) })
353
+ return this.load()
354
+ case 'left':
355
+ case 'right':
356
+ return place && this.arrange(movedPlaces(this.places, place.id, action === 'left' ? -1 : 1))
357
+ case 'wider':
358
+ case 'narrower':
359
+ case 'taller':
360
+ case 'shorter': {
361
+ if (!place) return
362
+ const width = Math.min(4, Math.max(1, place.width + (action === 'wider' ? 1 : action === 'narrower' ? -1 : 0)))
363
+ const height = Math.min(2, Math.max(1, place.height + (action === 'taller' ? 1 : action === 'shorter' ? -1 : 0)))
364
+ return this.arrange(this.places.map(one => (one.id === place.id ? { ...one, width, height } : one)))
365
+ }
366
+ }
367
+ } catch (error: any) {
368
+ notify({ level: 'error', message: error?.message ?? String(error) })
369
+ }
370
+ }
371
+ }
@@ -0,0 +1,8 @@
1
+ /* The page this module brings to an application that puts it in its menu (ADR-0160 decision 10). */
2
+ export default function route(page: string) {
3
+ switch (page) {
4
+ case 'my-dashboard':
5
+ import('./pages/my-dashboard-page.js')
6
+ return page
7
+ }
8
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../tsconfig-base.json",
3
+ "compilerOptions": {
4
+ "experimentalDecorators": true,
5
+ "skipLibCheck": true,
6
+ "strict": true,
7
+ "declaration": true,
8
+ "module": "esnext",
9
+ "outDir": "../dist-client",
10
+ "baseUrl": "./"
11
+ },
12
+ "include": ["./**/*"]
13
+ }
@@ -0,0 +1,110 @@
1
+ /*
2
+ * The doors the dashboard screens call (server: card-resolver.ts). One place, so the page, the library and the
3
+ * conversation's card read the same fields.
4
+ */
5
+ import gql from 'graphql-tag'
6
+
7
+ import { client } from '@operato/graphql'
8
+
9
+ export interface CardShare {
10
+ scope: 'USER' | 'DEPARTMENT' | 'DOMAIN'
11
+ targetId: string
12
+ targetName?: string | null
13
+ }
14
+
15
+ export interface CardItem {
16
+ id: string
17
+ name: string
18
+ description?: string | null
19
+ version: number
20
+ definition: any
21
+ access: 'owner' | 'shared'
22
+ ownerName?: string | null
23
+ copiedFromId?: string | null
24
+ placed: boolean
25
+ shares?: CardShare[] | null
26
+ updatedAt?: string | null
27
+ }
28
+
29
+ export interface PlaceItem {
30
+ id: string
31
+ order: number
32
+ width: number
33
+ height: number
34
+ card: CardItem
35
+ }
36
+
37
+ export interface CardCounts {
38
+ read: number
39
+ total: number
40
+ complete: boolean
41
+ shaped: number
42
+ shown: number
43
+ }
44
+
45
+ export interface CardRun {
46
+ block?: any
47
+ issue?: { code: string; params?: Record<string, unknown> }
48
+ counts?: CardCounts
49
+ asOf: string
50
+ source?: { key: string; labelKey?: string }
51
+ }
52
+
53
+ const CARD_FIELDS = `id name description version definition access ownerName copiedFromId placed shares { scope targetId targetName } updatedAt`
54
+
55
+ async function ask<T>(document: string, variables: Record<string, unknown> = {}, mutate = false): Promise<T> {
56
+ const response: any = mutate
57
+ ? await client.mutate({ mutation: gql(document), variables })
58
+ : await client.query({ query: gql(document), variables, fetchPolicy: 'no-cache' })
59
+ if (response.errors?.length) throw new Error(response.errors[0].message)
60
+ return response.data
61
+ }
62
+
63
+ export const cardApi = {
64
+ myDashboard: async () => (await ask<any>(`query { myDashboard { id order width height card { ${CARD_FIELDS} } } }`)).myDashboard as PlaceItem[],
65
+ library: async (which: 'mine' | 'shared') => (await ask<any>(`query ($which: String!) { dashboardCards(which: $which) { ${CARD_FIELDS} } }`, { which })).dashboardCards as CardItem[],
66
+ run: async (id: string) => (await ask<any>(`query ($id: String!) { dashboardCardRun(id: $id) }`, { id })).dashboardCardRun as CardRun,
67
+ preview: async (sessionId: string, proposalKey: string) =>
68
+ (await ask<any>(`query ($s: String!, $k: String!) { dashboardCardProposalPreview(sessionId: $s, proposalKey: $k) }`, { s: sessionId, k: proposalKey })).dashboardCardProposalPreview as CardRun,
69
+ versions: async (id: string) =>
70
+ (await ask<any>(`query ($id: String!) { dashboardCardVersions(id: $id) { version name definition creatorName createdAt } }`, { id })).dashboardCardVersions as {
71
+ version: number
72
+ name: string
73
+ definition: any
74
+ creatorName?: string
75
+ createdAt?: string
76
+ }[],
77
+ shareOptions: async () =>
78
+ (await ask<any>(`query { dashboardShareOptions { domainId domainName departmentsAvailable departments { id name } peopleAvailable } }`)).dashboardShareOptions as {
79
+ domainId: string
80
+ domainName: string
81
+ departmentsAvailable: boolean
82
+ departments: { id: string; name: string }[]
83
+ peopleAvailable: boolean
84
+ },
85
+ people: async (text: string) => (await ask<any>(`query ($text: String!) { dashboardSharePeople(text: $text) { id name note } }`, { text })).dashboardSharePeople as { id: string; name: string; note?: string }[],
86
+ saveProposal: async (sessionId: string, proposalKey: string) =>
87
+ (await ask<any>(`mutation ($s: String!, $k: String!) { saveDashboardCardProposal(sessionId: $s, proposalKey: $k) }`, { s: sessionId, k: proposalKey }, true)).saveDashboardCardProposal as {
88
+ cardId: string
89
+ version: number
90
+ placeId: string
91
+ again?: boolean
92
+ },
93
+ rename: async (id: string, name: string) => (await ask<any>(`mutation ($id: String!, $name: String!) { renameDashboardCard(id: $id, name: $name) { id } }`, { id, name }, true)).renameDashboardCard,
94
+ restore: async (id: string, version: number) => (await ask<any>(`mutation ($id: String!, $v: Int!) { restoreDashboardCardVersion(id: $id, version: $v) { id } }`, { id, v: version }, true)).restoreDashboardCardVersion,
95
+ copy: async (id: string) => (await ask<any>(`mutation ($id: String!) { copyDashboardCard(id: $id) { id } }`, { id }, true)).copyDashboardCard,
96
+ remove: async (id: string) => (await ask<any>(`mutation ($id: String!) { deleteDashboardCard(id: $id) }`, { id }, true)).deleteDashboardCard,
97
+ share: async (id: string, shares: CardShare[]) =>
98
+ (await ask<any>(`mutation ($id: String!, $shares: [DashboardCardShareInput!]!) { shareDashboardCard(id: $id, shares: $shares) { ${CARD_FIELDS} } }`, { id, shares }, true)).shareDashboardCard as CardItem,
99
+ place: async (cardId: string) => (await ask<any>(`mutation ($id: String!) { placeDashboardCard(cardId: $id) { id } }`, { id: cardId }, true)).placeDashboardCard,
100
+ unplace: async (id: string) => (await ask<any>(`mutation ($id: String!) { removeDashboardPlace(id: $id) }`, { id }, true)).removeDashboardPlace,
101
+ arrange: async (places: { id: string; order: number; width: number; height: number }[]) =>
102
+ (await ask<any>(`mutation ($places: [DashboardPlaceInput!]!) { arrangeDashboard(places: $places) }`, { places }, true)).arrangeDashboard
103
+ }
104
+
105
+ /** Told when a card is saved, copied, placed or removed elsewhere - an open dashboard reads itself again. */
106
+ export const DASHBOARD_CHANGED = 'ai-dashboard-changed'
107
+
108
+ export function dashboardChanged(): void {
109
+ window.dispatchEvent(new CustomEvent(DASHBOARD_CHANGED))
110
+ }
@@ -0,0 +1,80 @@
1
+ /*
2
+ * What a card says besides its block - why it is not drawn, how much of its source it was drawn from, when it was
3
+ * read, what it reads (ADR-0160 decisions 4, 6). Pure - tested without a browser.
4
+ */
5
+ import type { CardItem, CardRun } from './card-api.js'
6
+
7
+ export type Translate = (key: string, options?: Record<string, unknown>) => string
8
+
9
+ /** The refusals a card names in the reader's words - others are said by their code. */
10
+ export const KNOWN_ISSUES = [
11
+ 'door-closed',
12
+ 'not-found',
13
+ 'list-missing',
14
+ 'query-failed',
15
+ 'condition-unreadable',
16
+ 'mixed-currency-order',
17
+ 'too-many-series',
18
+ 'too-many-figures',
19
+ 'unknown-read',
20
+ 'not-a-card-source',
21
+ 'unknown-list',
22
+ 'unknown-column',
23
+ 'unknown-condition'
24
+ ] as const
25
+
26
+ /** Why a card is not drawn - one sentence, and whether it is the reader's access (shown calmly, not as a fault). */
27
+ export function issueLine(run: CardRun, t: Translate): { text: string; access: boolean } | undefined {
28
+ const issue = run.issue
29
+ if (!issue) return undefined
30
+ const code = issue.code
31
+ const known = (KNOWN_ISSUES as readonly string[]).includes(code)
32
+ const params = { ...(issue.params ?? {}), code } as Record<string, unknown>
33
+ if (Array.isArray(params.currencies)) params.currencies = (params.currencies as string[]).join(', ')
34
+ return {
35
+ text: t(known ? `ai-dashboard.issue.${code}` : 'ai-dashboard.issue.other', { ...params, defaultValue: known ? code : 'This card cannot be drawn ({code}).' }),
36
+ access: code === 'door-closed'
37
+ }
38
+ }
39
+
40
+ /** How much of its source a card was drawn from - said only when it was not all of it. */
41
+ export function partialLine(run: CardRun, t: Translate): string | undefined {
42
+ const counts = run.counts
43
+ if (!counts || counts.complete) return undefined
44
+ return t('ai-dashboard.card.partial', { read: counts.read.toLocaleString(), total: counts.total.toLocaleString(), defaultValue: 'Drawn from {read} of {total} rows - sums and ranks are of those.' })
45
+ }
46
+
47
+ /** A table or chart with no row to draw - said in words, never an empty axis. A number of nothing is 0, and drawn. */
48
+ export function drawsNothing(run: CardRun): boolean {
49
+ return !run.issue && !!run.block && run.block.kind !== 'metrics' && (run.counts?.shown ?? 0) === 0
50
+ }
51
+
52
+ /** The day relative periods were read for, and the time it was read. */
53
+ export function readLine(run: CardRun, readAt: Date, t: Translate, locale?: string): string {
54
+ const time = readAt.toLocaleTimeString(locale || undefined, { hour: '2-digit', minute: '2-digit' })
55
+ return t('ai-dashboard.card.read-at', { day: run.asOf, time, defaultValue: 'as of {day} · read {time}' })
56
+ }
57
+
58
+ /** Who a card came from, for a card shared with the reader; whom it is shared with, for its owner. */
59
+ export function ownershipLine(card: CardItem, t: Translate): string | undefined {
60
+ if (card.access === 'shared') return t('ai-dashboard.card.shared-by', { name: card.ownerName ?? '', defaultValue: 'shared by {name}' })
61
+ const shares = card.shares ?? []
62
+ if (!shares.length) return undefined
63
+ const names = shares.map(one => (one.scope === 'DOMAIN' ? t('ai-dashboard.share.everyone', { defaultValue: 'everyone' }) : one.targetName || one.targetId))
64
+ return t('ai-dashboard.card.shared-with', { names: names.slice(0, 3).join(', ') + (names.length > 3 ? ` +${names.length - 3}` : ''), defaultValue: 'shared with {names}' })
65
+ }
66
+
67
+ /** The kind of drawing a definition asks for - the icon a card and the library show. */
68
+ export function viewIcon(definition: any): string {
69
+ return ({ number: 'pin', table: 'table_rows', bar: 'bar_chart', line: 'show_chart' } as Record<string, string>)[definition?.view?.kind] ?? 'dashboard'
70
+ }
71
+
72
+ /** Moves one place in a list of places and gives every place its order again - what arranging sends. */
73
+ export function movedPlaces<P extends { id: string; order: number }>(places: readonly P[], id: string, step: -1 | 1): P[] {
74
+ const list = [...places].sort((a, b) => a.order - b.order)
75
+ const at = list.findIndex(one => one.id === id)
76
+ const to = at + step
77
+ if (at < 0 || to < 0 || to >= list.length) return list.map((one, order) => ({ ...one, order }))
78
+ ;[list[at], list[to]] = [list[to], list[at]]
79
+ return list.map((one, order) => ({ ...one, order }))
80
+ }
@@ -0,0 +1,2 @@
1
+ import './components/proposal-preview.js';
2
+ export default function bootstrap(): void;
@@ -0,0 +1,24 @@
1
+ /*
2
+ * The conversation's dashboard card (ADR-0160 decision 9): its preview under the card, and pressing it saves the stored
3
+ * proposal to the person's dashboard. Registered here, by the package that declares the tool - the dock knows no kind.
4
+ */
5
+ import { i18next } from '@operato/i18n';
6
+ import { registerProposalHandler, registerProposalView } from '@things-factory/ai-assistant/dist-client/index.js';
7
+ import './components/proposal-preview.js';
8
+ import { cardApi, dashboardChanged } from './utils/card-api.js';
9
+ const TOOL = 'proposeDashboardCard';
10
+ export default function bootstrap() {
11
+ registerProposalView(TOOL, { previewTag: 'ai-dashboard-proposal-preview', runLabelKey: 'ai-dashboard.preview.save', runLabel: 'Save to my dashboard' });
12
+ registerProposalHandler(TOOL, async (proposal, context) => {
13
+ if (!context.sessionId || !proposal?.key)
14
+ return { verdict: 'unavailable', errorCode: 'no-conversation' };
15
+ const saved = await cardApi.saveProposal(context.sessionId, proposal.key);
16
+ dashboardChanged();
17
+ return {
18
+ verdict: 'accepted',
19
+ applied: 1,
20
+ note: String(i18next.t(saved.version > 1 ? 'ai-dashboard.preview.saved-version' : 'ai-dashboard.preview.saved', { name: proposal.card?.name ?? '', version: saved.version }))
21
+ };
22
+ });
23
+ }
24
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,mDAAmD,CAAA;AAEjH,OAAO,kCAAkC,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAE/D,MAAM,IAAI,GAAG,sBAAsB,CAAA;AAEnC,MAAM,CAAC,OAAO,UAAU,SAAS;IAC/B,oBAAoB,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,+BAA+B,EAAE,WAAW,EAAE,2BAA2B,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC,CAAA;IACvJ,uBAAuB,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QACxD,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,GAAG;YAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAA;QACzG,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAA;QACzE,gBAAgB,EAAE,CAAA;QAClB,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,OAAO,EAAE,CAAC;YACV,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,oCAAoC,CAAC,CAAC,CAAC,4BAA4B,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;SAC9K,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/*\n * The conversation's dashboard card (ADR-0160 decision 9): its preview under the card, and pressing it saves the stored\n * proposal to the person's dashboard. Registered here, by the package that declares the tool - the dock knows no kind.\n */\nimport { i18next } from '@operato/i18n'\nimport { registerProposalHandler, registerProposalView } from '@things-factory/ai-assistant/dist-client/index.js'\n\nimport './components/proposal-preview.js'\nimport { cardApi, dashboardChanged } from './utils/card-api.js'\n\nconst TOOL = 'proposeDashboardCard'\n\nexport default function bootstrap() {\n registerProposalView(TOOL, { previewTag: 'ai-dashboard-proposal-preview', runLabelKey: 'ai-dashboard.preview.save', runLabel: 'Save to my dashboard' })\n registerProposalHandler(TOOL, async (proposal, context) => {\n if (!context.sessionId || !proposal?.key) return { verdict: 'unavailable', errorCode: 'no-conversation' }\n const saved = await cardApi.saveProposal(context.sessionId, proposal.key)\n dashboardChanged()\n return {\n verdict: 'accepted',\n applied: 1,\n note: String(i18next.t(saved.version > 1 ? 'ai-dashboard.preview.saved-version' : 'ai-dashboard.preview.saved', { name: proposal.card?.name ?? '', version: saved.version }))\n }\n })\n}\n"]}
@@ -0,0 +1,59 @@
1
+ import '@material/web/icon/icon.js';
2
+ import '@material/web/button/filled-button.js';
3
+ import '@material/web/button/text-button.js';
4
+ import { LitElement } from 'lit';
5
+ import { type CardItem } from '../utils/card-api.js';
6
+ declare const CardAiForm_base: (new (...args: any[]) => LitElement) & typeof LitElement;
7
+ /** What to change in a card - sent to the conversation beside the page with the card picked, as a screen request. */
8
+ export declare class CardAiForm extends CardAiForm_base {
9
+ static styles: import("lit").CSSResult;
10
+ card: CardItem;
11
+ private text;
12
+ render(): import("lit-html").TemplateResult<1>;
13
+ private close;
14
+ private send;
15
+ }
16
+ declare const CardRenameForm_base: (new (...args: any[]) => LitElement) & typeof LitElement;
17
+ /** A card's new name - a new version. */
18
+ export declare class CardRenameForm extends CardRenameForm_base {
19
+ static styles: import("lit").CSSResult;
20
+ card: CardItem;
21
+ private name;
22
+ connectedCallback(): void;
23
+ render(): import("lit-html").TemplateResult<1>;
24
+ private close;
25
+ private save;
26
+ }
27
+ declare const CardShareForm_base: (new (...args: any[]) => LitElement) & typeof LitElement;
28
+ /** Whom one's own card is shared with - the whole domain, one's own departments, people found by name. */
29
+ export declare class CardShareForm extends CardShareForm_base {
30
+ static styles: import("lit").CSSResult;
31
+ card: CardItem;
32
+ private options?;
33
+ private shares;
34
+ private found;
35
+ private search;
36
+ private searching?;
37
+ connectedCallback(): Promise<void>;
38
+ private has;
39
+ private toggle;
40
+ private find;
41
+ render(): import("lit-html").TemplateResult<1>;
42
+ private close;
43
+ private save;
44
+ }
45
+ declare const CardVersions_base: (new (...args: any[]) => LitElement) & typeof LitElement;
46
+ /** One's own card's versions, newest first - an earlier one comes back as a new version. */
47
+ export declare class CardVersions extends CardVersions_base {
48
+ static styles: import("lit").CSSResult;
49
+ card: CardItem;
50
+ private versions?;
51
+ connectedCallback(): Promise<void>;
52
+ render(): import("lit-html").TemplateResult<1>;
53
+ private restore;
54
+ }
55
+ export declare const openCardAiPrompt: (card: CardItem) => any;
56
+ export declare const openCardRename: (card: CardItem) => any;
57
+ export declare const openCardShare: (card: CardItem) => any;
58
+ export declare const openCardVersions: (card: CardItem) => any;
59
+ export {};