@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.
- package/client/bootstrap.ts +25 -0
- package/client/components/card-dialogs.ts +422 -0
- package/client/components/card-library.ts +272 -0
- package/client/components/dashboard-card.ts +450 -0
- package/client/components/proposal-preview.ts +96 -0
- package/client/index.ts +3 -0
- package/client/pages/my-dashboard-page.ts +371 -0
- package/client/route.ts +8 -0
- package/client/tsconfig.json +13 -0
- package/client/utils/card-api.ts +110 -0
- package/client/utils/card-words.ts +80 -0
- package/dist-client/bootstrap.d.ts +2 -0
- package/dist-client/bootstrap.js +24 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/components/card-dialogs.d.ts +59 -0
- package/dist-client/components/card-dialogs.js +457 -0
- package/dist-client/components/card-dialogs.js.map +1 -0
- package/dist-client/components/card-library.d.ts +23 -0
- package/dist-client/components/card-library.js +276 -0
- package/dist-client/components/card-library.js.map +1 -0
- package/dist-client/components/dashboard-card.d.ts +35 -0
- package/dist-client/components/dashboard-card.js +490 -0
- package/dist-client/components/dashboard-card.js.map +1 -0
- package/dist-client/components/proposal-preview.d.ts +17 -0
- package/dist-client/components/proposal-preview.js +111 -0
- package/dist-client/components/proposal-preview.js.map +1 -0
- package/dist-client/index.d.ts +3 -0
- package/dist-client/index.js +4 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/pages/my-dashboard-page.d.ts +46 -0
- package/dist-client/pages/my-dashboard-page.js +376 -0
- package/dist-client/pages/my-dashboard-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +9 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-client/utils/card-api.d.ts +95 -0
- package/dist-client/utils/card-api.js +39 -0
- package/dist-client/utils/card-api.js.map +1 -0
- package/dist-client/utils/card-words.d.ts +24 -0
- package/dist-client/utils/card-words.js +72 -0
- package/dist-client/utils/card-words.js.map +1 -0
- package/dist-server/index.d.ts +11 -0
- package/dist-server/index.js +19 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/role-tasks.d.ts +2 -0
- package/dist-server/role-tasks.js +13 -0
- package/dist-server/role-tasks.js.map +1 -0
- package/dist-server/service/card/card-access.d.ts +32 -0
- package/dist-server/service/card/card-access.js +45 -0
- package/dist-server/service/card/card-access.js.map +1 -0
- package/dist-server/service/card/card-definition.d.ts +100 -0
- package/dist-server/service/card/card-definition.js +237 -0
- package/dist-server/service/card/card-definition.js.map +1 -0
- package/dist-server/service/card/card-names.d.ts +5 -0
- package/dist-server/service/card/card-names.js +9 -0
- package/dist-server/service/card/card-names.js.map +1 -0
- package/dist-server/service/card/card-proposal.d.ts +21 -0
- package/dist-server/service/card/card-proposal.js +28 -0
- package/dist-server/service/card/card-proposal.js.map +1 -0
- package/dist-server/service/card/card-resolver.d.ts +29 -0
- package/dist-server/service/card/card-resolver.js +541 -0
- package/dist-server/service/card/card-resolver.js.map +1 -0
- package/dist-server/service/card/card-run.d.ts +48 -0
- package/dist-server/service/card/card-run.js +239 -0
- package/dist-server/service/card/card-run.js.map +1 -0
- package/dist-server/service/card/card-tool.d.ts +218 -0
- package/dist-server/service/card/card-tool.js +313 -0
- package/dist-server/service/card/card-tool.js.map +1 -0
- package/dist-server/service/card/card-types.d.ts +56 -0
- package/dist-server/service/card/card-types.js +212 -0
- package/dist-server/service/card/card-types.js.map +1 -0
- package/dist-server/service/card/dashboard-card.d.ts +87 -0
- package/dist-server/service/card/dashboard-card.js +388 -0
- package/dist-server/service/card/dashboard-card.js.map +1 -0
- package/dist-server/service/card/long-text.d.ts +6 -0
- package/dist-server/service/card/long-text.js +28 -0
- package/dist-server/service/card/long-text.js.map +1 -0
- package/dist-server/service/index.d.ts +12 -0
- package/dist-server/service/index.js +24 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +47 -4
- package/server/index.ts +16 -0
- package/server/role-tasks.ts +12 -0
- package/server/service/card/card-access.test.ts +138 -0
- package/server/service/card/card-access.ts +53 -0
- package/server/service/card/card-definition.ts +298 -0
- package/server/service/card/card-names.ts +5 -0
- package/server/service/card/card-proposal.ts +37 -0
- package/server/service/card/card-resolver.ts +434 -0
- package/server/service/card/card-run.test.ts +237 -0
- package/server/service/card/card-run.ts +288 -0
- package/server/service/card/card-tool.ts +299 -0
- package/server/service/card/card-types.ts +145 -0
- package/server/service/card/dashboard-card.ts +301 -0
- package/server/service/card/long-text.ts +25 -0
- package/server/service/index.ts +16 -0
- package/test/translations.test.ts +42 -0
- package/things-factory.config.js +8 -0
- package/translations/en.json +126 -0
- package/translations/es.json +126 -0
- package/translations/ja.json +126 -0
- package/translations/ko.json +126 -0
- package/translations/ms.json +126 -0
- package/translations/zh.json +126 -0
- package/tsconfig.json +9 -0
- 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
|
+
}
|
package/client/route.ts
ADDED
|
@@ -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,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 {};
|