@things-factory/ai-assistant 10.1.214 → 10.1.218
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/components/assistant-dock.ts +6 -3
- package/client/components/assistant-session-manager.ts +263 -12
- package/client/utils/assistant-session-actions.ts +12 -0
- package/client/utils/assistant-session-admin.ts +70 -5
- package/dist-client/components/assistant-dock.js +6 -3
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/assistant-session-manager.d.ts +37 -0
- package/dist-client/components/assistant-session-manager.js +281 -13
- package/dist-client/components/assistant-session-manager.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-session-actions.d.ts +15 -0
- package/dist-client/utils/assistant-session-actions.js +11 -0
- package/dist-client/utils/assistant-session-actions.js.map +1 -1
- package/dist-client/utils/assistant-session-admin.d.ts +32 -1
- package/dist-client/utils/assistant-session-admin.js +36 -5
- package/dist-client/utils/assistant-session-admin.js.map +1 -1
- package/dist-server/service/assistant-chat-resolver.js +1 -1
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/index.d.ts +1 -1
- package/dist-server/service/assistant-mode/index.js +2 -1
- package/dist-server/service/assistant-mode/index.js.map +1 -1
- package/dist-server/service/assistant-mode/mode-categories.d.ts +15 -0
- package/dist-server/service/assistant-mode/mode-categories.js +7 -1
- package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
- package/dist-server/service/chat-session/session-cleanup.d.ts +28 -0
- package/dist-server/service/chat-session/session-cleanup.js +69 -0
- package/dist-server/service/chat-session/session-cleanup.js.map +1 -0
- package/dist-server/service/chat-session/session-inbox.d.ts +15 -0
- package/dist-server/service/chat-session/session-inbox.js +11 -0
- package/dist-server/service/chat-session/session-inbox.js.map +1 -1
- package/dist-server/service/chat-session/session-purge.d.ts +4 -0
- package/dist-server/service/chat-session/session-purge.js +45 -11
- package/dist-server/service/chat-session/session-purge.js.map +1 -1
- package/dist-server/service/chat-session-resolver.d.ts +24 -2
- package/dist-server/service/chat-session-resolver.js +193 -13
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/data-read/list-read.d.ts +3 -1
- package/dist-server/service/data-read/list-read.js +27 -2
- package/dist-server/service/data-read/list-read.js.map +1 -1
- package/dist-server/service/data-read/read-target.d.ts +16 -0
- package/dist-server/service/data-read/read-target.js +18 -1
- package/dist-server/service/data-read/read-target.js.map +1 -1
- package/dist-server/service/data-read/read-tools.js +3 -1
- package/dist-server/service/data-read/read-tools.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/server/service/assistant-chat-resolver.ts +2 -2
- package/server/service/assistant-mode/conversation-category.test.ts +15 -2
- package/server/service/assistant-mode/index.ts +1 -1
- package/server/service/assistant-mode/mode-categories.ts +17 -1
- package/server/service/assistant-mode/open-subject-door.test.ts +3 -1
- package/server/service/chat-session/session-cleanup.test.ts +83 -0
- package/server/service/chat-session/session-cleanup.ts +69 -0
- package/server/service/chat-session/session-inbox.ts +13 -0
- package/server/service/chat-session/session-purge.ts +46 -10
- package/server/service/chat-session-resolver.ts +150 -14
- package/server/service/data-read/list-read.ts +30 -3
- package/server/service/data-read/list-status.test.ts +46 -0
- package/server/service/data-read/read-target.ts +32 -1
- package/server/service/data-read/read-tools.ts +2 -1
- package/test/assistant-dock.test.ts +12 -1
- package/test/assistant-session-manager.test.ts +53 -1
- package/test/open-doors.test.ts +3 -1
- package/translations/en.json +37 -1
- package/translations/es.json +37 -1
- package/translations/ja.json +37 -1
- package/translations/ko.json +37 -1
- package/translations/ms.json +37 -1
- package/translations/zh.json +37 -1
|
@@ -6,7 +6,7 @@ import { i18next } from '@operato/i18n';
|
|
|
6
6
|
import { OxPrompt } from '@operato/popup';
|
|
7
7
|
import '@operato/popup/ox-popup-menu.js';
|
|
8
8
|
import { OxPopupMenu } from '@operato/popup/ox-popup-menu.js';
|
|
9
|
-
import { assistantSessionAdmin, sessionActions } from '../utils/assistant-session-admin.js';
|
|
9
|
+
import { assistantSessionAdmin, bulkActions, sessionActions } from '../utils/assistant-session-admin.js';
|
|
10
10
|
const PAGE = 30;
|
|
11
11
|
const t = (key, defaultValue, options = {}) => i18next.t(`ai-assistant.session.${key}`, { defaultValue, ...options });
|
|
12
12
|
/**
|
|
@@ -32,6 +32,10 @@ const t = (key, defaultValue, options = {}) => i18next.t(`ai-assistant.session.$
|
|
|
32
32
|
* ── Asking before what cannot be undone ─────────────────────────────────────────
|
|
33
33
|
* Delete, and opening a private conversation to the domain, ask on the row itself - [Cancel][OK].
|
|
34
34
|
* Making it private, hiding and renaming do not: each can be undone from the same row.
|
|
35
|
+
*
|
|
36
|
+
* ── Many at once (2026-10-25, the user: 「멀티셀렉트로 한번에 지울 수 있게」) ──────────
|
|
37
|
+
* Each row has a box; the picked ones are deleted, left or hidden together, each by its own row's hints
|
|
38
|
+
* (`bulkActions`), with one question. A deleted one is kept for `deletedDays` and comes back from 「Deleted」.
|
|
35
39
|
*/
|
|
36
40
|
let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
37
41
|
constructor() {
|
|
@@ -57,6 +61,18 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
57
61
|
this.loading = false;
|
|
58
62
|
this.error = '';
|
|
59
63
|
this.renamingId = '';
|
|
64
|
+
this.view = 'list';
|
|
65
|
+
this.archivedCount = 0;
|
|
66
|
+
this.cleanupCount = 0;
|
|
67
|
+
this.keeping = { deletedDays: null, archiveDays: null };
|
|
68
|
+
this.deleted = [];
|
|
69
|
+
/** The rows picked for doing at once - ids, kept across a reload for the rows still listed. */
|
|
70
|
+
this.picked = [];
|
|
71
|
+
this.busy = false;
|
|
72
|
+
/** What was just done, said once - and the conversation brought back, to open it from here. */
|
|
73
|
+
this.notice = null;
|
|
74
|
+
/** The suggestions arrive picked - set when the view turns to them, spent by the first read. */
|
|
75
|
+
this.pickAllOnRead = false;
|
|
60
76
|
this.generation = 0;
|
|
61
77
|
}
|
|
62
78
|
static { this.styles = css `
|
|
@@ -305,6 +321,38 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
305
321
|
.hiddenfilter md-icon {
|
|
306
322
|
--md-icon-size: 16px;
|
|
307
323
|
}
|
|
324
|
+
|
|
325
|
+
.pick { margin: 0; cursor: pointer; }
|
|
326
|
+
/* The box is small; the space around it picks too - a press beside it must not open the conversation. */
|
|
327
|
+
.pick-area { flex: none; display: inline-flex; align-items: center; align-self: stretch; padding: 0 8px; margin: -7px -8px -7px -10px; padding-left: 10px; cursor: pointer; }
|
|
328
|
+
.pickbar {
|
|
329
|
+
display: flex;
|
|
330
|
+
flex-wrap: wrap;
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 4px 10px;
|
|
333
|
+
padding: 6px 10px;
|
|
334
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
335
|
+
font-size: 11px;
|
|
336
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
337
|
+
}
|
|
338
|
+
.pickbar label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
|
|
339
|
+
.pickbar .act,
|
|
340
|
+
.suggest,
|
|
341
|
+
.note .open-link,
|
|
342
|
+
.restore {
|
|
343
|
+
border: 0;
|
|
344
|
+
background: none;
|
|
345
|
+
padding: 0;
|
|
346
|
+
color: var(--md-sys-color-primary);
|
|
347
|
+
font: inherit;
|
|
348
|
+
cursor: pointer;
|
|
349
|
+
}
|
|
350
|
+
.pickbar .act:disabled { color: var(--md-sys-color-on-surface-variant); cursor: default; }
|
|
351
|
+
.pickbar .act.danger { color: var(--md-sys-color-error); }
|
|
352
|
+
.pickbar .unpick { margin-left: auto; }
|
|
353
|
+
.suggest { display: block; width: 100%; padding: 8px 10px; text-align: start; font-size: 12px; border-bottom: 1px solid var(--md-sys-color-outline-variant); }
|
|
354
|
+
.restore { flex: none; font-size: 12px; }
|
|
355
|
+
.row.deleted { cursor: default; }
|
|
308
356
|
`; }
|
|
309
357
|
/*
|
|
310
358
|
* The search and the filters stand open together, always (2026-10-12) - a filter behind a "Filters" button is one
|
|
@@ -312,7 +360,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
312
360
|
* to choose.
|
|
313
361
|
*/
|
|
314
362
|
get filtering() {
|
|
315
|
-
return !!this.search.trim() || this.whose !== 'all' || this.visibility !== 'all' || this.includeHidden || this.unreadOnly;
|
|
363
|
+
return !!this.search.trim() || this.whose !== 'all' || this.visibility !== 'all' || this.includeHidden || this.unreadOnly || this.view !== 'list';
|
|
316
364
|
}
|
|
317
365
|
clearFilters() {
|
|
318
366
|
clearTimeout(this.searchTimer);
|
|
@@ -321,6 +369,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
321
369
|
this.whose = 'all';
|
|
322
370
|
this.visibility = 'all';
|
|
323
371
|
this.includeHidden = this.unreadOnly = false;
|
|
372
|
+
this.view = 'list';
|
|
324
373
|
});
|
|
325
374
|
}
|
|
326
375
|
/** Read the list again - the host calls this after it opened or started a conversation. */
|
|
@@ -334,6 +383,18 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
334
383
|
this.loading = true;
|
|
335
384
|
this.error = '';
|
|
336
385
|
try {
|
|
386
|
+
if (this.view === 'deleted') {
|
|
387
|
+
/* The deleted ones are few (kept for deletedDays) - the search narrows them by name here. */
|
|
388
|
+
const word = this.search.trim().toLowerCase();
|
|
389
|
+
const deleted = (await this.admin.deleted(this.anchorType, this.anchorId)).filter(one => !word || (one.name ?? '').toLowerCase().includes(word));
|
|
390
|
+
if (generation !== this.generation)
|
|
391
|
+
return;
|
|
392
|
+
this.deleted = deleted;
|
|
393
|
+
this.entries = [];
|
|
394
|
+
this.total = deleted.length;
|
|
395
|
+
this.keepPicked(deleted.map(one => one.id));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
337
398
|
/*
|
|
338
399
|
* Paging reads again from the start rather than appending - live activity reorders the list, and
|
|
339
400
|
* appending a later page to an earlier order duplicates and drops rows.
|
|
@@ -347,7 +408,9 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
347
408
|
whose: this.whose,
|
|
348
409
|
visibility: this.visibility,
|
|
349
410
|
includeHidden: this.includeHidden,
|
|
350
|
-
unreadOnly: this.unreadOnly
|
|
411
|
+
unreadOnly: this.unreadOnly,
|
|
412
|
+
includeArchived: this.view === 'archived',
|
|
413
|
+
cleanupOnly: this.view === 'cleanup'
|
|
351
414
|
},
|
|
352
415
|
limit: this.pageSize,
|
|
353
416
|
offset: 0
|
|
@@ -357,6 +420,10 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
357
420
|
this.entries = page.entries;
|
|
358
421
|
this.total = page.total;
|
|
359
422
|
this.hiddenCount = page.hiddenCount;
|
|
423
|
+
this.archivedCount = page.archivedCount;
|
|
424
|
+
this.cleanupCount = page.cleanupCount;
|
|
425
|
+
this.keeping = page.keeping;
|
|
426
|
+
this.keepPicked(page.entries.map(entry => entry.id));
|
|
360
427
|
}
|
|
361
428
|
catch (error) {
|
|
362
429
|
if (generation !== this.generation)
|
|
@@ -370,6 +437,26 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
370
437
|
this.loading = false;
|
|
371
438
|
}
|
|
372
439
|
}
|
|
440
|
+
/** After a read: the suggestions arrive all picked; otherwise only the picked rows still listed stay picked. */
|
|
441
|
+
keepPicked(listed) {
|
|
442
|
+
if (this.pickAllOnRead) {
|
|
443
|
+
this.pickAllOnRead = false;
|
|
444
|
+
this.picked = listed;
|
|
445
|
+
}
|
|
446
|
+
else
|
|
447
|
+
this.picked = this.picked.filter(id => listed.includes(id));
|
|
448
|
+
}
|
|
449
|
+
setView(view) {
|
|
450
|
+
this.refilter(() => {
|
|
451
|
+
this.view = view;
|
|
452
|
+
this.picked = [];
|
|
453
|
+
this.notice = null;
|
|
454
|
+
this.pickAllOnRead = view === 'cleanup';
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
togglePick(id, on) {
|
|
458
|
+
this.picked = on ? [...new Set([...this.picked, id])] : this.picked.filter(one => one !== id);
|
|
459
|
+
}
|
|
373
460
|
updated(changed) {
|
|
374
461
|
if (changed.has('anchorType') || changed.has('anchorId') || changed.has('admin') || changed.has('kind'))
|
|
375
462
|
void this.reload();
|
|
@@ -414,6 +501,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
414
501
|
/** Runs one door, then reads the list again - on success and on failure alike, so the row shows the truth. */
|
|
415
502
|
async act(entry, change, run, refused) {
|
|
416
503
|
this.renamingId = '';
|
|
504
|
+
this.notice = null;
|
|
417
505
|
try {
|
|
418
506
|
if (!(await run()))
|
|
419
507
|
this.error = refused;
|
|
@@ -445,6 +533,69 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
445
533
|
_removeSession(entry) {
|
|
446
534
|
void this.act(entry, 'removed', () => this.admin.remove(entry.id), t('remove-refused', 'this conversation could not be deleted.'));
|
|
447
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* Runs one door for each picked row, then reads the list again. Every success is told to the host like a row's own
|
|
538
|
+
* change, so it moves off an open conversation that went; every refusal is said by name.
|
|
539
|
+
*/
|
|
540
|
+
async runMany(ids, change, run, done) {
|
|
541
|
+
if (!ids.length || this.busy)
|
|
542
|
+
return;
|
|
543
|
+
this.busy = true;
|
|
544
|
+
this.error = '';
|
|
545
|
+
this.notice = null;
|
|
546
|
+
const names = new Map([...this.entries.map(entry => [entry.id, entry.name]), ...this.deleted.map(one => [one.id, one.name])]);
|
|
547
|
+
const refused = [];
|
|
548
|
+
let count = 0;
|
|
549
|
+
for (const id of ids) {
|
|
550
|
+
try {
|
|
551
|
+
if (await run(id)) {
|
|
552
|
+
count++;
|
|
553
|
+
this.emit('assistant-session-changed', { sessionId: id, change });
|
|
554
|
+
}
|
|
555
|
+
else
|
|
556
|
+
refused.push(names.get(id) || t('untitled', 'Untitled conversation'));
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
refused.push(`${names.get(id) || t('untitled', 'Untitled conversation')} (${error.message})`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
this.picked = [];
|
|
563
|
+
this.busy = false;
|
|
564
|
+
const error = refused.length ? t('some-refused', 'Not done for {count}: {names}', { count: refused.length, names: refused.join(', ') }) : '';
|
|
565
|
+
await this.reload();
|
|
566
|
+
if (count)
|
|
567
|
+
this.notice = { text: done(count) };
|
|
568
|
+
if (error)
|
|
569
|
+
this.error = error;
|
|
570
|
+
}
|
|
571
|
+
/** The sentence that says a deleted conversation can still come back - from 「Deleted」, for as long as it is kept. */
|
|
572
|
+
keptFor() {
|
|
573
|
+
return this.keeping.deletedDays
|
|
574
|
+
? t('kept-for', 'You can bring it back from Deleted for {days} days.', { days: this.keeping.deletedDays })
|
|
575
|
+
: t('kept', 'You can bring it back from Deleted.');
|
|
576
|
+
}
|
|
577
|
+
async askRemoveMany(ids) {
|
|
578
|
+
const ok = await this.ask(t('remove', 'Delete'), `${t('confirm-remove-many', 'Delete {count} conversations? They disappear for every participant.', { count: ids.length })} ${this.keptFor()}`, t('remove', 'Delete'));
|
|
579
|
+
if (ok)
|
|
580
|
+
void this.runMany(ids, 'removed', id => this.admin.remove(id), count => t('removed-many', '{count} deleted.', { count }));
|
|
581
|
+
}
|
|
582
|
+
async askLeaveMany(ids) {
|
|
583
|
+
const ok = await this.ask(t('leave', 'Leave'), t('confirm-leave-many', 'Leave {count} conversations? They stop coming to your list, and a group or private one is read again only when its owner brings you back.', { count: ids.length }), t('leave', 'Leave'));
|
|
584
|
+
if (ok)
|
|
585
|
+
void this.runMany(ids, 'left', id => this.admin.leave(id), count => t('left-many', 'Left {count}.', { count }));
|
|
586
|
+
}
|
|
587
|
+
hideMany(ids) {
|
|
588
|
+
void this.runMany(ids, 'hidden', id => this.admin.hide(id, true), count => t('hidden-many', '{count} hidden from your list.', { count }));
|
|
589
|
+
}
|
|
590
|
+
restoreMany(ids) {
|
|
591
|
+
void this.runMany(ids, 'restored', id => this.admin.restore(id), count => t('restored-many', '{count} brought back.', { count }));
|
|
592
|
+
}
|
|
593
|
+
async restoreOne(one) {
|
|
594
|
+
const name = one.name || t('untitled', 'Untitled conversation');
|
|
595
|
+
await this.runMany([one.id], 'restored', id => this.admin.restore(id), () => t('restored', '"{name}" is back.', { name }));
|
|
596
|
+
if (this.notice)
|
|
597
|
+
this.notice = { ...this.notice, open: { sessionId: one.id, kind: one.kind } };
|
|
598
|
+
}
|
|
448
599
|
/* ── drawing ─────────────────────────────────────────────────────────────────────── */
|
|
449
600
|
render() {
|
|
450
601
|
const shown = this.entries.length;
|
|
@@ -463,7 +614,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
463
614
|
<button class="new" @click=${() => this.emit('assistant-session-reset')}>+ ${t('new', 'New conversation')}</button>
|
|
464
615
|
</div>
|
|
465
616
|
<div class="filters">
|
|
466
|
-
|
|
617
|
+
${this.view === 'deleted' ? nothing : html `<select aria-label=${t('filter-whose', 'Whose')} .value=${this.whose}
|
|
467
618
|
@change=${(e) => this.refilter(() => (this.whose = e.target.value))}>
|
|
468
619
|
<option value="all">${t('filter-all', 'All')}</option>
|
|
469
620
|
<option value="mine">${t('filter-mine', 'Started by me')}</option>
|
|
@@ -475,31 +626,114 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
475
626
|
<option value="open">${t('filter-open', 'Public')}</option>
|
|
476
627
|
<option value="group">${t('filter-group', 'Group')}</option>
|
|
477
628
|
<option value="private">${t('filter-private', 'Private')}</option>
|
|
629
|
+
</select>`}
|
|
630
|
+
<select aria-label=${t('filter-view', 'Show')} .value=${this.view}
|
|
631
|
+
@change=${(e) => this.setView(e.target.value)}>
|
|
632
|
+
<option value="list">${t('view-list', 'Active')}</option>
|
|
633
|
+
<option value="archived">${t('view-archived', 'Including archived')}</option>
|
|
634
|
+
<option value="cleanup">${t('view-cleanup', 'Suggested to clear')}</option>
|
|
635
|
+
<option value="deleted">${t('view-deleted', 'Deleted')}</option>
|
|
478
636
|
</select>
|
|
479
|
-
|
|
637
|
+
${this.view === 'deleted' ? nothing : html `<label><input type="checkbox" .checked=${this.unreadOnly}
|
|
480
638
|
@change=${(e) => this.refilter(() => (this.unreadOnly = e.target.checked))} />${t('filter-unread', 'Unread')}</label>
|
|
481
639
|
<label><input type="checkbox" .checked=${this.includeHidden}
|
|
482
|
-
@change=${(e) => this.refilter(() => (this.includeHidden = e.target.checked))} />${t('filter-hidden', 'Including hidden')}</label
|
|
640
|
+
@change=${(e) => this.refilter(() => (this.includeHidden = e.target.checked))} />${t('filter-hidden', 'Including hidden')}</label>`}
|
|
483
641
|
${this.filtering ? html `<button class="clear" @click=${() => this.clearFilters()}>${t('reset-filters', 'Clear search and filters')}</button>` : nothing}
|
|
484
642
|
</div>
|
|
643
|
+
${this.renderPickBar()}
|
|
485
644
|
<div class="list">
|
|
486
645
|
${this.error ? html `<div class="note" role="alert">${this.error}</div>` : nothing}
|
|
646
|
+
${this.notice
|
|
647
|
+
? html `<div class="note" role="status">${this.notice.text}${this.notice.open
|
|
648
|
+
? html ` <button class="open-link" @click=${() => this.emit('assistant-session-select', this.notice.open)}>${t('open', 'Open')}</button>`
|
|
649
|
+
: nothing}</div>`
|
|
650
|
+
: nothing}
|
|
487
651
|
${this.loading ? html `<div class="note" role="status">${t('loading', 'Loading conversations…')}</div>` : nothing}
|
|
488
|
-
${
|
|
489
|
-
? html `<
|
|
490
|
-
:
|
|
491
|
-
${
|
|
652
|
+
${this.view === 'list' && this.cleanupCount > 0 && !this.search.trim()
|
|
653
|
+
? html `<button class="suggest" @click=${() => this.setView('cleanup')}>${t('cleanup-suggest', '{count} of yours look ready to clear - see them', { count: this.cleanupCount })}</button>`
|
|
654
|
+
: nothing}
|
|
655
|
+
${this.view === 'deleted'
|
|
656
|
+
? this.deleted.length === 0 && !this.loading && !this.error
|
|
657
|
+
? html `<div class="empty" role="status">${t('deleted-empty', 'Nothing you deleted here is kept.')}</div>`
|
|
658
|
+
: this.deleted.map(one => this.renderDeletedRow(one))
|
|
659
|
+
: shown === 0 && !this.loading && !this.error
|
|
660
|
+
? html `<div class="empty" role="status">${this.view === 'cleanup'
|
|
661
|
+
? t('cleanup-empty', 'Nothing of yours to clear here.')
|
|
662
|
+
: this.filtering ? t('filtered-empty', 'No conversations match these filters here.') : t('scope-empty', 'No conversations yet.')}</div>`
|
|
663
|
+
: this.entries.map(entry => this.renderRow(entry))}
|
|
664
|
+
${this.view !== 'deleted' && shown < this.total
|
|
492
665
|
? html `<button class="more" @click=${() => this.loadMore()}>${t('load-more', 'Show more ({rest})', { rest: this.total - shown })}</button>`
|
|
493
666
|
: nothing}
|
|
494
667
|
</div>
|
|
495
668
|
<!-- A hidden conversation must not be forgotten: say how many, and one press shows them. -->
|
|
496
|
-
${this.hiddenCount > 0 && !this.includeHidden
|
|
669
|
+
${this.hiddenCount > 0 && !this.includeHidden && (this.view === 'list' || this.view === 'archived')
|
|
497
670
|
? html `<button class="hiddenfilter" @click=${() => this.refilter(() => (this.includeHidden = true))}>
|
|
498
671
|
<md-icon>visibility_off</md-icon>${t('hidden-count', '{count} hidden', { count: this.hiddenCount })}
|
|
499
672
|
</button>`
|
|
500
673
|
: nothing}
|
|
674
|
+
${this.archivedCount > 0 && this.view === 'list'
|
|
675
|
+
? html `<button class="hiddenfilter" @click=${() => this.setView('archived')}>
|
|
676
|
+
<md-icon>inventory_2</md-icon>${t('archived-count', '{count} archived - no activity for {days} days', { count: this.archivedCount, days: this.keeping.archiveDays ?? '' })}
|
|
677
|
+
</button>`
|
|
678
|
+
: nothing}
|
|
501
679
|
`;
|
|
502
680
|
}
|
|
681
|
+
/** The box over the list: pick all, and what the picked ones can have done to them together. */
|
|
682
|
+
renderPickBar() {
|
|
683
|
+
const rows = this.view === 'deleted' ? this.deleted : this.entries;
|
|
684
|
+
if (!rows.length)
|
|
685
|
+
return nothing;
|
|
686
|
+
const picked = this.picked.length;
|
|
687
|
+
const all = picked === rows.length;
|
|
688
|
+
const toggleAll = (on) => (this.picked = on ? rows.map(row => row.id) : []);
|
|
689
|
+
const label = picked ? t('picked', '{count} picked', { count: picked }) : t('pick-all', 'Pick all');
|
|
690
|
+
if (this.view === 'deleted') {
|
|
691
|
+
return html `<div class="pickbar">
|
|
692
|
+
<label><input type="checkbox" .checked=${all} .indeterminate=${picked > 0 && !all} @change=${(e) => toggleAll(e.target.checked)} />${label}</label>
|
|
693
|
+
${picked
|
|
694
|
+
? html `<button class="act" ?disabled=${this.busy} @click=${() => this.restoreMany(this.picked)}>${t('restore-many', 'Bring back {count}', { count: picked })}</button>
|
|
695
|
+
<button class="act unpick" @click=${() => (this.picked = [])}>${t('unpick', 'Clear the pick')}</button>`
|
|
696
|
+
: nothing}
|
|
697
|
+
</div>`;
|
|
698
|
+
}
|
|
699
|
+
const can = bulkActions(this.entries.filter(entry => this.picked.includes(entry.id)));
|
|
700
|
+
return html `<div class="pickbar">
|
|
701
|
+
<label><input type="checkbox" .checked=${all} .indeterminate=${picked > 0 && !all} @change=${(e) => toggleAll(e.target.checked)} />${label}</label>
|
|
702
|
+
${picked
|
|
703
|
+
? html `${can.remove.length
|
|
704
|
+
? html `<button class="act danger" ?disabled=${this.busy} @click=${() => this.askRemoveMany(can.remove)}>${t('remove-many', 'Delete {count}', { count: can.remove.length })}</button>`
|
|
705
|
+
: nothing}
|
|
706
|
+
${can.leave.length
|
|
707
|
+
? html `<button class="act" ?disabled=${this.busy} @click=${() => this.askLeaveMany(can.leave)}>${t('leave-many', 'Leave {count}', { count: can.leave.length })}</button>`
|
|
708
|
+
: nothing}
|
|
709
|
+
${can.hide.length
|
|
710
|
+
? html `<button class="act" ?disabled=${this.busy} @click=${() => this.hideMany(can.hide)}>${t('hide-many', 'Hide {count}', { count: can.hide.length })}</button>`
|
|
711
|
+
: nothing}
|
|
712
|
+
<button class="act unpick" @click=${() => (this.picked = [])}>${t('unpick', 'Clear the pick')}</button>`
|
|
713
|
+
: nothing}
|
|
714
|
+
</div>`;
|
|
715
|
+
}
|
|
716
|
+
renderDeletedRow(one) {
|
|
717
|
+
const name = one.name || t('untitled', 'Untitled conversation');
|
|
718
|
+
const day = (at) => (at && Number.isFinite(Date.parse(at)) ? new Intl.DateTimeFormat(i18next.language || 'en', { month: 'short', day: 'numeric' }).format(new Date(at)) : '');
|
|
719
|
+
return html `<div class="row deleted">
|
|
720
|
+
<label class="pick-area"><input type="checkbox" class="pick" aria-label=${t('pick', 'Pick {name}', { name })} .checked=${this.picked.includes(one.id)}
|
|
721
|
+
@change=${(e) => this.togglePick(one.id, e.target.checked)} /></label>
|
|
722
|
+
<div class="open-row">
|
|
723
|
+
<div class="title">${name}</div>
|
|
724
|
+
<span class="meta">${t('deleted-on', 'Deleted {date}', { date: day(one.deletedAt) })}${one.purgesAt ? html ` · ${t('purges-on', 'emptied {date}', { date: day(one.purgesAt) })}` : nothing}</span>
|
|
725
|
+
</div>
|
|
726
|
+
<button class="restore" ?disabled=${this.busy} @click=${() => this.restoreOne(one)}>${t('restore', 'Bring back')}</button>
|
|
727
|
+
</div>`;
|
|
728
|
+
}
|
|
729
|
+
/** Why a row is where it is: archived, or suggested for clearing. */
|
|
730
|
+
renderState(entry) {
|
|
731
|
+
if (entry.cleanupReason === 'empty')
|
|
732
|
+
return html ` · ${t('reason-empty', 'Never asked anything')}`;
|
|
733
|
+
if (entry.cleanupReason === 'inactive')
|
|
734
|
+
return html ` · ${t('reason-inactive', 'No activity for {days} days', { days: entry.idleDays ?? '' })}`;
|
|
735
|
+
return entry.archived ? html ` · ${t('archived', 'Archived')}` : nothing;
|
|
736
|
+
}
|
|
503
737
|
renderRow(entry) {
|
|
504
738
|
const name = entry.name || t('untitled', 'Untitled conversation');
|
|
505
739
|
const actions = sessionActions(entry);
|
|
@@ -526,11 +760,13 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
526
760
|
return html `<div
|
|
527
761
|
class="row ${entry.unread ? 'unread' : ''} ${entry.hidden ? 'hidden' : ''} ${entry.id === this.sessionId ? 'current' : ''} ${isOpen ? 'open-audience' : ''}"
|
|
528
762
|
>
|
|
763
|
+
<label class="pick-area"><input type="checkbox" class="pick" aria-label=${t('pick', 'Pick {name}', { name })} .checked=${this.picked.includes(entry.id)}
|
|
764
|
+
@change=${(e) => this.togglePick(entry.id, e.target.checked)} /></label>
|
|
529
765
|
<span class="dot"></span>
|
|
530
766
|
<button class="open-row" aria-current=${entry.id === this.sessionId ? 'true' : 'false'} @click=${() => this.emit('assistant-session-select', { sessionId: entry.id, kind: entry.kind })}>
|
|
531
767
|
<div class="title">${name}</div>
|
|
532
768
|
<div class="prev">${entry.lastMessagePreview || ''}</div>
|
|
533
|
-
<span class="meta">${this.renderAudience(entry.visibility)}${entry.lastMessageAt && Number.isFinite(Date.parse(entry.lastMessageAt)) ? html ` · <time datetime=${entry.lastMessageAt}>${new Intl.DateTimeFormat(i18next.language || 'en', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(entry.lastMessageAt))}</time>` : nothing}${entry.mine ? html ` · ${t('filter-mine', 'Started by me')}` : nothing}</span>
|
|
769
|
+
<span class="meta">${this.renderAudience(entry.visibility)}${entry.lastMessageAt && Number.isFinite(Date.parse(entry.lastMessageAt)) ? html ` · <time datetime=${entry.lastMessageAt}>${new Intl.DateTimeFormat(i18next.language || 'en', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(entry.lastMessageAt))}</time>` : nothing}${entry.mine ? html ` · ${t('filter-mine', 'Started by me')}` : nothing}${this.renderState(entry)}</span>
|
|
534
770
|
</button>
|
|
535
771
|
<button
|
|
536
772
|
class="more-menu"
|
|
@@ -582,7 +818,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
|
|
|
582
818
|
}));
|
|
583
819
|
}
|
|
584
820
|
async askRemove(entry, name) {
|
|
585
|
-
const ok = await this.ask(t('remove', 'Delete'), t('confirm-remove', 'Delete "{name}"? It disappears for every participant
|
|
821
|
+
const ok = await this.ask(t('remove', 'Delete'), `${t('confirm-remove', 'Delete "{name}"? It disappears for every participant.', { name })} ${this.keptFor()}`, t('remove', 'Delete'));
|
|
586
822
|
if (ok)
|
|
587
823
|
this._removeSession(entry);
|
|
588
824
|
}
|
|
@@ -684,6 +920,38 @@ __decorate([
|
|
|
684
920
|
state(),
|
|
685
921
|
__metadata("design:type", Object)
|
|
686
922
|
], AssistantSessionManager.prototype, "renamingId", void 0);
|
|
923
|
+
__decorate([
|
|
924
|
+
state(),
|
|
925
|
+
__metadata("design:type", String)
|
|
926
|
+
], AssistantSessionManager.prototype, "view", void 0);
|
|
927
|
+
__decorate([
|
|
928
|
+
state(),
|
|
929
|
+
__metadata("design:type", Object)
|
|
930
|
+
], AssistantSessionManager.prototype, "archivedCount", void 0);
|
|
931
|
+
__decorate([
|
|
932
|
+
state(),
|
|
933
|
+
__metadata("design:type", Object)
|
|
934
|
+
], AssistantSessionManager.prototype, "cleanupCount", void 0);
|
|
935
|
+
__decorate([
|
|
936
|
+
state(),
|
|
937
|
+
__metadata("design:type", Object)
|
|
938
|
+
], AssistantSessionManager.prototype, "keeping", void 0);
|
|
939
|
+
__decorate([
|
|
940
|
+
state(),
|
|
941
|
+
__metadata("design:type", Array)
|
|
942
|
+
], AssistantSessionManager.prototype, "deleted", void 0);
|
|
943
|
+
__decorate([
|
|
944
|
+
state(),
|
|
945
|
+
__metadata("design:type", Array)
|
|
946
|
+
], AssistantSessionManager.prototype, "picked", void 0);
|
|
947
|
+
__decorate([
|
|
948
|
+
state(),
|
|
949
|
+
__metadata("design:type", Object)
|
|
950
|
+
], AssistantSessionManager.prototype, "busy", void 0);
|
|
951
|
+
__decorate([
|
|
952
|
+
state(),
|
|
953
|
+
__metadata("design:type", Object)
|
|
954
|
+
], AssistantSessionManager.prototype, "notice", void 0);
|
|
687
955
|
AssistantSessionManager = __decorate([
|
|
688
956
|
customElement('ox-assistant-session-manager')
|
|
689
957
|
], AssistantSessionManager);
|