@things-factory/ai-assistant 10.1.227 → 10.1.229
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-chat.ts +27 -4
- package/client/components/assistant-dock.ts +18 -2
- package/client/components/proposal-outcome-line.ts +5 -0
- package/client/utils/applied-screens.ts +36 -0
- package/client/utils/assistant-proposals.ts +2 -0
- package/client/utils/assistant-selection.ts +3 -2
- package/client/utils/entry-proposal.ts +58 -3
- package/dist-client/components/assistant-chat.d.ts +3 -0
- package/dist-client/components/assistant-chat.js +27 -4
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.js +18 -2
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/proposal-outcome-line.d.ts +2 -0
- package/dist-client/components/proposal-outcome-line.js +4 -0
- package/dist-client/components/proposal-outcome-line.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/applied-screens.d.ts +21 -0
- package/dist-client/utils/applied-screens.js +32 -0
- package/dist-client/utils/applied-screens.js.map +1 -0
- package/dist-client/utils/assistant-proposals.d.ts +2 -0
- package/dist-client/utils/assistant-proposals.js.map +1 -1
- package/dist-client/utils/assistant-selection.d.ts +1 -1
- package/dist-client/utils/assistant-selection.js +3 -2
- package/dist-client/utils/assistant-selection.js.map +1 -1
- package/dist-client/utils/entry-proposal.d.ts +22 -0
- package/dist-client/utils/entry-proposal.js +56 -3
- package/dist-client/utils/entry-proposal.js.map +1 -1
- package/dist-server/service/assistant-mode/index.d.ts +1 -0
- package/dist-server/service/assistant-mode/index.js +5 -1
- package/dist-server/service/assistant-mode/index.js.map +1 -1
- package/dist-server/service/assistant-mode/record-subject.d.ts +25 -0
- package/dist-server/service/assistant-mode/record-subject.js +35 -0
- package/dist-server/service/assistant-mode/record-subject.js.map +1 -0
- package/dist-server/service/chat-session/attachment-tool-result.d.ts +1 -0
- package/dist-server/service/chat-session/attachment-tool-result.js +11 -0
- package/dist-server/service/chat-session/attachment-tool-result.js.map +1 -1
- package/dist-server/service/chat-session/attachment-tools.js +5 -2
- package/dist-server/service/chat-session/attachment-tools.js.map +1 -1
- package/dist-server/service/data-entry/entry-screen.d.ts +10 -0
- package/dist-server/service/data-entry/entry-screen.js +18 -0
- package/dist-server/service/data-entry/entry-screen.js.map +1 -0
- package/dist-server/service/data-entry/entry-sources.js +6 -1
- package/dist-server/service/data-entry/entry-sources.js.map +1 -1
- package/dist-server/service/data-entry/entry-target.d.ts +6 -0
- package/dist-server/service/data-entry/entry-target.js.map +1 -1
- package/dist-server/service/data-entry/entry-tools.d.ts +3 -0
- package/dist-server/service/data-entry/entry-tools.js +3 -0
- package/dist-server/service/data-entry/entry-tools.js.map +1 -1
- package/dist-server/service/data-entry/entry-update.d.ts +3 -0
- package/dist-server/service/data-entry/entry-update.js +3 -0
- package/dist-server/service/data-entry/entry-update.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/server/service/assistant-mode/index.ts +2 -0
- package/server/service/assistant-mode/record-subject.test.ts +41 -0
- package/server/service/assistant-mode/record-subject.ts +44 -0
- package/server/service/chat-session/attachment-tool-result.test.ts +30 -1
- package/server/service/chat-session/attachment-tool-result.ts +12 -0
- package/server/service/chat-session/attachment-tools.ts +5 -3
- package/server/service/data-entry/data-entry.test.ts +18 -0
- package/server/service/data-entry/entry-screen.ts +14 -0
- package/server/service/data-entry/entry-sources.ts +5 -1
- package/server/service/data-entry/entry-target.ts +6 -0
- package/server/service/data-entry/entry-tools.ts +3 -0
- package/server/service/data-entry/entry-update.ts +3 -0
- package/server/service/data-read/data-read.test.ts +3 -1
- package/test/applied-screens.test.ts +65 -0
- package/test/assistant-dock.test.ts +6 -1
- package/test/entry-proposal.test.ts +46 -0
- package/translations/en.json +3 -0
- package/translations/es.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +3 -0
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { replyDestination, quotedReply } from './reply-message.js'
|
|
2
2
|
import { rosterName } from './roster-identity'
|
|
3
|
-
import { notify } from '@operato/layout'
|
|
3
|
+
import { entity360KindRegistered, notify } from '@operato/layout'
|
|
4
4
|
import './assistant-invite.js'
|
|
5
5
|
import { TypingRoom } from '../utils/assistant-typing.js'
|
|
6
6
|
import { isInviteProposal, personLine, type InvitePerson } from '../utils/invite-proposal.js'
|
|
@@ -78,11 +78,11 @@ import {
|
|
|
78
78
|
*/
|
|
79
79
|
import { chatDefaults } from './chat-defaults.js'
|
|
80
80
|
import { isDifferentConversation, resolveConversation, type AssistantMode, type AssistantModeView } from './assistant-mode.js'
|
|
81
|
-
import { parseProposalOutcomeLine, parseProposalRequestLine, proposalCardState, type ParsedProposalOutcome } from './proposal-outcome-line.js'
|
|
81
|
+
import { entryWentIn, parseProposalOutcomeLine, parseProposalRequestLine, proposalCardState, type ParsedProposalOutcome } from './proposal-outcome-line.js'
|
|
82
82
|
import './entry-review.js'
|
|
83
|
-
import { entryGroupOf, entryReadyCount, entryTitle, isChangeProposal, isEntryProposal, latestEntryKeys } from '../utils/entry-proposal.js'
|
|
83
|
+
import { entryGroupOf, entryReadyCount, entryRecordsOf, entryScreenAddress, entryTitle, isChangeProposal, isEntryProposal, latestEntryKeys } from '../utils/entry-proposal.js'
|
|
84
84
|
import { isWorkspaceProposal, versionOfCard, workspaceAddress } from '../utils/workspace.js'
|
|
85
|
-
import { isOpenPageProposal, openPageNote } from '../utils/open-page.js'
|
|
85
|
+
import { isAppAddress, isOpenPageProposal, openPageNote } from '../utils/open-page.js'
|
|
86
86
|
import { proposalViewOf } from '../utils/assistant-proposals.js'
|
|
87
87
|
|
|
88
88
|
interface ChatLine {
|
|
@@ -4559,6 +4559,7 @@ export class OxAssistantChat extends LitElement {
|
|
|
4559
4559
|
/* The card's state is the conversation's rows - read once for every card of this line. */
|
|
4560
4560
|
const outcomes = this.proposalOutcomes
|
|
4561
4561
|
const requested = this.proposalRequests
|
|
4562
|
+
const records = this.entryRecords
|
|
4562
4563
|
/* A data entry card replaced by a newer one for the same record cannot be entered (design/ai/data-entry.md §5). */
|
|
4563
4564
|
const latestEntry = items.some(isEntryProposal) ? latestEntryKeys(this.lines.map(one => one.proposals ?? [])) : undefined
|
|
4564
4565
|
return html`
|
|
@@ -4626,6 +4627,12 @@ export class OxAssistantChat extends LitElement {
|
|
|
4626
4627
|
: i18next.t('ai-assistant.button.run-proposal', { defaultValue: 'run' })}
|
|
4627
4628
|
</button>
|
|
4628
4629
|
`}
|
|
4630
|
+
${/* What went in is seen on its screen - the card's next step once applied, beside its outcome. */ ''}
|
|
4631
|
+
${entry && entryWentIn(card) && isAppAddress(p.screen)
|
|
4632
|
+
? html`<button class="run see" @click=${() => this.openEntryScreen(p, key, records)}>
|
|
4633
|
+
${i18next.t('ai-assistant.entry.see-on-screen', { defaultValue: 'see on the screen' })}
|
|
4634
|
+
</button>`
|
|
4635
|
+
: nothing}
|
|
4629
4636
|
${/*
|
|
4630
4637
|
Drawn only where the host has said it staged this candidate, in the host's own
|
|
4631
4638
|
words. Not knowing is not a claim: with no word from the host, nothing is drawn.
|
|
@@ -4953,6 +4960,22 @@ export class OxAssistantChat extends LitElement {
|
|
|
4953
4960
|
)
|
|
4954
4961
|
}
|
|
4955
4962
|
|
|
4963
|
+
/** The records each entry card entered - from the conversation's notes, like the outcomes, so a refresh keeps them. */
|
|
4964
|
+
private get entryRecords(): Map<string, string[]> {
|
|
4965
|
+
const records = new Map<string, string[]>()
|
|
4966
|
+
for (const line of this.lines) {
|
|
4967
|
+
if (line.role !== 'system') continue
|
|
4968
|
+
const entered = entryRecordsOf(line.content ?? '')
|
|
4969
|
+
if (entered) records.set(entered.key, entered.ids)
|
|
4970
|
+
}
|
|
4971
|
+
return records
|
|
4972
|
+
}
|
|
4973
|
+
|
|
4974
|
+
private openEntryScreen(p: any, key: string, records: Map<string, string[]>) {
|
|
4975
|
+
const href = entryScreenAddress(p, records.get(key), entity360KindRegistered)
|
|
4976
|
+
this.dispatchEvent(new CustomEvent('assistant-open-page', { detail: { href }, bubbles: true, composed: true }))
|
|
4977
|
+
}
|
|
4978
|
+
|
|
4956
4979
|
/** Keys whose run was requested - from the conversation's rows, like the outcomes. */
|
|
4957
4980
|
private get proposalRequests(): Set<string> {
|
|
4958
4981
|
const requested = new Set<string>()
|
|
@@ -11,7 +11,7 @@ import { keyed } from 'lit/directives/keyed.js'
|
|
|
11
11
|
|
|
12
12
|
import { client } from '@operato/graphql'
|
|
13
13
|
import { i18next, localize } from '@operato/i18n'
|
|
14
|
-
import { closeOverlay, notify } from '@operato/layout'
|
|
14
|
+
import { closeOverlay, notify, refreshEntity360 } from '@operato/layout'
|
|
15
15
|
import { OxPrompt } from '@operato/popup'
|
|
16
16
|
import { AppController, navigate, store } from '@operato/shell'
|
|
17
17
|
import { ScrollbarStyles } from '@operato/styles'
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type AssistantSurface
|
|
32
32
|
} from '../utils/assistant-surface.js'
|
|
33
33
|
import { runProposal, setEntryProposalHandler, type ProposalContext, type ProposalOutcome } from '../utils/assistant-proposals.js'
|
|
34
|
+
import { refreshAppliedScreens, type AppliedScreensDeps } from '../utils/applied-screens.js'
|
|
34
35
|
/* The invite card's handler - ai-assistant's own tool, registered by its own module (design/ai/shared-dock.md §4). */
|
|
35
36
|
import '../utils/invite-proposal-register.js'
|
|
36
37
|
import { entryProposalHandler, type EntryApplyAnswer } from '../utils/entry-proposal.js'
|
|
@@ -62,6 +63,15 @@ setEntryProposalHandler(
|
|
|
62
63
|
)
|
|
63
64
|
)
|
|
64
65
|
|
|
66
|
+
/* The page on screen and the shown 360 - what an applied card's records may be shown on. */
|
|
67
|
+
const APPLIED_SCREENS: AppliedScreensDeps = {
|
|
68
|
+
page: () => {
|
|
69
|
+
const route = (store.getState() as any)?.route ?? {}
|
|
70
|
+
return { name: route.page, element: route.activePage }
|
|
71
|
+
},
|
|
72
|
+
refresh360: refreshEntity360
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
const MODE_QUERY = gql`
|
|
66
76
|
query ($subject: String!) {
|
|
67
77
|
assistantModeFor(subject: $subject) {
|
|
@@ -785,9 +795,15 @@ export class AssistantDock extends localize(i18next)(LitElement) {
|
|
|
785
795
|
if (outcome.verdict === 'unavailable' && outcome.errorCode === 'unknown-proposal-kind') {
|
|
786
796
|
notify({ level: 'error', message: t('proposal-not-runnable', 'this proposal cannot be run on this screen.') })
|
|
787
797
|
}
|
|
788
|
-
|
|
798
|
+
const { records, ...reported } = outcome
|
|
799
|
+
await this.report(sessionId, proposalKey, reported)
|
|
789
800
|
/* What the handler did, in the person's words, stays in the conversation - what was created, why it did not run. */
|
|
790
801
|
if (outcome.note && sessionId) await this.appendNote(sessionId, outcome.note)
|
|
802
|
+
/* The list or 360 behind the dock shows what went in, without the person refreshing it. */
|
|
803
|
+
if (outcome.verdict === 'accepted' && (outcome.applied ?? 1) > 0)
|
|
804
|
+
void refreshAppliedScreens(proposal, records, APPLIED_SCREENS).then(kept => {
|
|
805
|
+
if (kept) notify({ level: 'info', message: t('screen-kept-edits', 'the list was not read again - it has unsaved edits. save them, then refresh it to see what went in.') })
|
|
806
|
+
})
|
|
791
807
|
}
|
|
792
808
|
|
|
793
809
|
/**
|
|
@@ -67,3 +67,8 @@ export function proposalCardState(
|
|
|
67
67
|
if (seen.requested.has(key)) return { state: 'unconfirmed' }
|
|
68
68
|
return { state: 'ready' }
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
/** Something went in - the card then offers the screen where it is seen. A refusal, or nothing entered, offers none. */
|
|
72
|
+
export function entryWentIn(card: ProposalCardState): boolean {
|
|
73
|
+
return card.state === 'answered' && card.outcome.verdict === 'accepted' && (card.outcome.applied ?? 0) > 0
|
|
74
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The screens that show what an applied card wrote read it again (2026-10-29). Applied from the dock, a card left the
|
|
3
|
+
* list and the 360 behind it as they were, and people took it for not applied.
|
|
4
|
+
*
|
|
5
|
+
* Only a screen that shows those records reads again - the shown 360 of the record or of a record the card is about,
|
|
6
|
+
* and the page that is the card's screen. Another page keeps its rows and its scroll. A page with unsaved edits is
|
|
7
|
+
* not asked about them by a refresh nobody pressed: it is left as it is, and its own refresh control reads it later.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface AppliedScreensDeps {
|
|
11
|
+
/** The page on screen - its route name and the shell's page element (`PageView.refresh`, ADR-0174). */
|
|
12
|
+
page(): { name?: string; element?: { refresh?: (options?: { ask?: boolean }) => Promise<boolean> } }
|
|
13
|
+
/** The 360's refresh, asked whether it is about the shown subject. */
|
|
14
|
+
refresh360(about: (subject: { kind: string; id: string }) => boolean): boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reads again what shows the records of an applied card. Never throws - a screen that cannot read keeps what it shows.
|
|
19
|
+
* `kept-edits` when the card's page was left unread for its unsaved edits - the person is told, or the list looks unapplied.
|
|
20
|
+
*/
|
|
21
|
+
export async function refreshAppliedScreens(proposal: any, ids: string[] | undefined, deps: AppliedScreensDeps): Promise<'kept-edits' | undefined> {
|
|
22
|
+
const kind = String(proposal?.target ?? '').split('.')[1] ?? ''
|
|
23
|
+
const axes: string[] = Array.isArray(proposal?.axes) ? proposal.axes : []
|
|
24
|
+
try {
|
|
25
|
+
deps.refresh360(subject => (subject.kind === kind && !!ids?.includes(subject.id)) || axes.includes(subject.kind))
|
|
26
|
+
} catch {
|
|
27
|
+
/* the 360 keeps what it shows; its refresh button reads it */
|
|
28
|
+
}
|
|
29
|
+
const page = deps.page()
|
|
30
|
+
if (!proposal?.screen || page.name !== proposal.screen || typeof page.element?.refresh !== 'function') return
|
|
31
|
+
try {
|
|
32
|
+
return (await page.element.refresh({ ask: false })) === false ? 'kept-edits' : undefined
|
|
33
|
+
} catch {
|
|
34
|
+
/* the page keeps what it shows; its refresh control reads it */
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -14,6 +14,8 @@ export interface ProposalOutcome {
|
|
|
14
14
|
missed?: number
|
|
15
15
|
/** A line for the conversation, besides the outcome itself (e.g. what was created). */
|
|
16
16
|
note?: string
|
|
17
|
+
/** The ids of the records it wrote - the screens that show them read again. Not reported to the conversation. */
|
|
18
|
+
records?: string[]
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface ProposalContext {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* model is handed the note as stored. Until 2026-10-24 the screen sent the selection as JSON and the conversation
|
|
7
7
|
* opened on `{"source":"explicit-page-selection",…}`.
|
|
8
8
|
*/
|
|
9
|
+
import { ENTRY_RECORDS_PREFIX } from './entry-proposal.js'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* The grid's own row picker - a list that hands its rows to the assistant shows it beside `ox-assistant-selection-bar`
|
|
@@ -64,11 +65,11 @@ export function selectionNote(selection: AssistantSelection, t: SelectionTransla
|
|
|
64
65
|
return `${named}\n${keys}`
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
/** A note as the conversation draws it - without the
|
|
68
|
+
/** A note as the conversation draws it - without the lines only the model reads (a selection's keys, an entry's records). */
|
|
68
69
|
export function noteForDisplay(content: string): string {
|
|
69
70
|
return (content ?? '')
|
|
70
71
|
.split('\n')
|
|
71
|
-
.filter(line => !line.startsWith(SELECTION_KEYS_PREFIX))
|
|
72
|
+
.filter(line => !line.startsWith(SELECTION_KEYS_PREFIX) && !line.startsWith(ENTRY_RECORDS_PREFIX))
|
|
72
73
|
.join('\n')
|
|
73
74
|
.trim()
|
|
74
75
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* again - the newer card replaces the older one. Pressing asks the server to enter the stored rows; the
|
|
6
6
|
* screen sends no rows of its own.
|
|
7
7
|
*/
|
|
8
|
+
import { encodeListQuery } from '@operato/utils/list-query.js'
|
|
9
|
+
|
|
8
10
|
import type { ProposalContext, ProposalOutcome } from './assistant-proposals.js'
|
|
9
11
|
import { reviewedHashOf, reviewStillHolds } from './entry-workspace.js'
|
|
10
12
|
|
|
@@ -49,6 +51,9 @@ export interface EntryApplyAnswer extends ProposalOutcome {
|
|
|
49
51
|
refusal?: { message: string; extensions: Record<string, unknown>; path: string[] }
|
|
50
52
|
/** Set by the handler from the card - which words the conversation line uses. */
|
|
51
53
|
op?: string
|
|
54
|
+
/** Set by the handler from the card - which card and record kind the entered ids belong to. */
|
|
55
|
+
key?: string
|
|
56
|
+
target?: string
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
export function isEntryProposal(proposal: any): boolean {
|
|
@@ -90,7 +95,56 @@ export function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, opti
|
|
|
90
95
|
: t('ai-assistant.entry.entered-none', { defaultValue: 'no row was entered.' })
|
|
91
96
|
const lines = refused.slice(0, 20).map(row => t('ai-assistant.entry.row-refused', { defaultValue: 'row {n}: {reason}', n: row.index + 1, reason: row.error || '' }))
|
|
92
97
|
const more = refused.length > 20 ? [t('ai-assistant.entry.more-refused', { defaultValue: '{count} more rows were not entered.', count: refused.length - 20 })] : []
|
|
93
|
-
|
|
98
|
+
const records = entryRecordsLine(answer)
|
|
99
|
+
return [head, ...appliedNames(answer, t), ...lines, ...more, ...(records ? [records] : [])].join('\n')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The line of an entry note that names the records a card entered - for the card, which opens them on their screen, and
|
|
104
|
+
* for the model. The conversation does not draw it. Not `[keys]`: that line is the person's selection, and a card's
|
|
105
|
+
* records must not replace what "these" means.
|
|
106
|
+
*/
|
|
107
|
+
export const ENTRY_RECORDS_PREFIX = '[records]'
|
|
108
|
+
|
|
109
|
+
const enteredIds = (answer: EntryApplyAnswer): string[] => [...new Set((answer.rows ?? []).filter(row => row.ok && row.id).map(row => row.id as string))]
|
|
110
|
+
|
|
111
|
+
function entryRecordsLine(answer: EntryApplyAnswer): string {
|
|
112
|
+
const ids = enteredIds(answer)
|
|
113
|
+
return ids.length && answer.key && answer.target ? `${ENTRY_RECORDS_PREFIX} ${answer.key} ${answer.target} ${ids.join(' ')}` : ''
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The card key, record kind and ids of an entry note's records line - undefined for any other content. */
|
|
117
|
+
export function entryRecordsOf(content: string): { key: string; target: string; ids: string[] } | undefined {
|
|
118
|
+
const line = (content ?? '').split('\n').find(one => one.startsWith(`${ENTRY_RECORDS_PREFIX} `))
|
|
119
|
+
const [key, target, ...ids] = line ? line.slice(ENTRY_RECORDS_PREFIX.length).trim().split(/\s+/) : []
|
|
120
|
+
return key && target && ids.length ? { key, target, ids } : undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** At most this many ids narrow a list in its address - more open the list as it is. */
|
|
124
|
+
export const ENTRY_SCREEN_IDS = 50
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Where the card's "see on the screen" goes. One record of a kind with a 360 opens that 360 beside its list; records
|
|
128
|
+
* of the list the screen shows narrow it to them, a condition the person sees and removes; otherwise the screen as it is.
|
|
129
|
+
*/
|
|
130
|
+
export function entryScreenAddress(proposal: any, ids: string[] | undefined, has360: (kind: string) => boolean): string {
|
|
131
|
+
const screen: string = proposal?.screen
|
|
132
|
+
const kind = String(proposal?.target ?? '').split('.')[1] ?? ''
|
|
133
|
+
if (ids?.length === 1 && kind && has360(kind)) return `${screen}?${new URLSearchParams({ f: `${kind}:${ids[0]}` })}`
|
|
134
|
+
if (ids?.length && ids.length <= ENTRY_SCREEN_IDS && proposal?.screenLists)
|
|
135
|
+
return `${screen}?${encodeListQuery({ filters: [{ name: 'id', operator: 'in', value: ids }] })}`
|
|
136
|
+
return screen
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
* Which records went in, by name - the person finds them on the screen by it, and the model reads which ones they were.
|
|
141
|
+
* Lines of one record share its name; a record without one is only counted.
|
|
142
|
+
*/
|
|
143
|
+
function appliedNames(answer: EntryApplyAnswer, t: (key: string, options?: any) => string): string[] {
|
|
144
|
+
const names = [...new Set((answer.rows ?? []).filter(row => row.ok && row.label).map(row => row.label as string))]
|
|
145
|
+
if (!names.length) return []
|
|
146
|
+
const shown = names.slice(0, 10).join(', ')
|
|
147
|
+
return [names.length > 10 ? `${shown} ${t('ai-assistant.entry.more-applied', { defaultValue: 'and {count} more', count: names.length - 10 })}` : shown]
|
|
94
148
|
}
|
|
95
149
|
|
|
96
150
|
/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */
|
|
@@ -109,7 +163,7 @@ export function entryProposalHandler(
|
|
|
109
163
|
/* Reviewed in the 작업 패널: entered only as it was shown there (design/ai/workspace.md §6-1) - here, and again at the server. */
|
|
110
164
|
const reviewed = reviewedOf(proposal.key)
|
|
111
165
|
if (!reviewStillHolds(proposal, reviewed)) return { verdict: 'refused', errorCode: 'review-stale', note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) } as any
|
|
112
|
-
const answer = { ...(await apply(context.sessionId, proposal.key, reviewed)), op: proposal.op }
|
|
166
|
+
const answer = { ...(await apply(context.sessionId, proposal.key, reviewed)), op: proposal.op, key: proposal.key, target: proposal.target }
|
|
113
167
|
if (answer.verdict === 'refused' && answer.errorCode === 'door-closed' && answer.refusal) {
|
|
114
168
|
try {
|
|
115
169
|
await reportRefusal({ level: 'error', message: answer.refusal.message, ex: [answer.refusal] })
|
|
@@ -124,7 +178,8 @@ export function entryProposalHandler(
|
|
|
124
178
|
...(answer.errorCode ? { errorCode: answer.errorCode } : {}),
|
|
125
179
|
applied: answer.applied ?? 0,
|
|
126
180
|
missed: answer.missed ?? 0,
|
|
127
|
-
note: entryOutcomeNote(answer, t)
|
|
181
|
+
note: entryOutcomeNote(answer, t),
|
|
182
|
+
records: enteredIds(answer)
|
|
128
183
|
}
|
|
129
184
|
}
|
|
130
185
|
}
|
|
@@ -619,6 +619,9 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
619
619
|
* (`AssistantSurface.proposalOutcome`), never to run it again. The dock reports what it learns.
|
|
620
620
|
*/
|
|
621
621
|
private checkProposal;
|
|
622
|
+
/** The records each entry card entered - from the conversation's notes, like the outcomes, so a refresh keeps them. */
|
|
623
|
+
private get entryRecords();
|
|
624
|
+
private openEntryScreen;
|
|
622
625
|
/** Keys whose run was requested - from the conversation's rows, like the outcomes. */
|
|
623
626
|
private get proposalRequests();
|
|
624
627
|
/**
|
|
@@ -2,7 +2,7 @@ var OxAssistantChat_1;
|
|
|
2
2
|
import { __decorate, __metadata } from "tslib";
|
|
3
3
|
import { replyDestination, quotedReply } from './reply-message.js';
|
|
4
4
|
import { rosterName } from './roster-identity';
|
|
5
|
-
import { notify } from '@operato/layout';
|
|
5
|
+
import { entity360KindRegistered, notify } from '@operato/layout';
|
|
6
6
|
import './assistant-invite.js';
|
|
7
7
|
import { TypingRoom } from '../utils/assistant-typing.js';
|
|
8
8
|
import { isInviteProposal, personLine } from '../utils/invite-proposal.js';
|
|
@@ -62,11 +62,11 @@ import { buildMentionCandidates, buildCatalogCandidates, buildSlashCandidates, b
|
|
|
62
62
|
*/
|
|
63
63
|
import { chatDefaults } from './chat-defaults.js';
|
|
64
64
|
import { isDifferentConversation, resolveConversation } from './assistant-mode.js';
|
|
65
|
-
import { parseProposalOutcomeLine, parseProposalRequestLine, proposalCardState } from './proposal-outcome-line.js';
|
|
65
|
+
import { entryWentIn, parseProposalOutcomeLine, parseProposalRequestLine, proposalCardState } from './proposal-outcome-line.js';
|
|
66
66
|
import './entry-review.js';
|
|
67
|
-
import { entryGroupOf, entryReadyCount, entryTitle, isChangeProposal, isEntryProposal, latestEntryKeys } from '../utils/entry-proposal.js';
|
|
67
|
+
import { entryGroupOf, entryReadyCount, entryRecordsOf, entryScreenAddress, entryTitle, isChangeProposal, isEntryProposal, latestEntryKeys } from '../utils/entry-proposal.js';
|
|
68
68
|
import { isWorkspaceProposal, versionOfCard, workspaceAddress } from '../utils/workspace.js';
|
|
69
|
-
import { isOpenPageProposal, openPageNote } from '../utils/open-page.js';
|
|
69
|
+
import { isAppAddress, isOpenPageProposal, openPageNote } from '../utils/open-page.js';
|
|
70
70
|
import { proposalViewOf } from '../utils/assistant-proposals.js';
|
|
71
71
|
function formatFileSize(bytes) {
|
|
72
72
|
if (!bytes || bytes < 1024)
|
|
@@ -4117,6 +4117,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4117
4117
|
/* The card's state is the conversation's rows - read once for every card of this line. */
|
|
4118
4118
|
const outcomes = this.proposalOutcomes;
|
|
4119
4119
|
const requested = this.proposalRequests;
|
|
4120
|
+
const records = this.entryRecords;
|
|
4120
4121
|
/* A data entry card replaced by a newer one for the same record cannot be entered (design/ai/data-entry.md §5). */
|
|
4121
4122
|
const latestEntry = items.some(isEntryProposal) ? latestEntryKeys(this.lines.map(one => one.proposals ?? [])) : undefined;
|
|
4122
4123
|
return html `
|
|
@@ -4186,6 +4187,12 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4186
4187
|
: i18next.t('ai-assistant.button.run-proposal', { defaultValue: 'run' })}
|
|
4187
4188
|
</button>
|
|
4188
4189
|
`}
|
|
4190
|
+
${ /* What went in is seen on its screen - the card's next step once applied, beside its outcome. */''}
|
|
4191
|
+
${entry && entryWentIn(card) && isAppAddress(p.screen)
|
|
4192
|
+
? html `<button class="run see" @click=${() => this.openEntryScreen(p, key, records)}>
|
|
4193
|
+
${i18next.t('ai-assistant.entry.see-on-screen', { defaultValue: 'see on the screen' })}
|
|
4194
|
+
</button>`
|
|
4195
|
+
: nothing}
|
|
4189
4196
|
${ /*
|
|
4190
4197
|
Drawn only where the host has said it staged this candidate, in the host's own
|
|
4191
4198
|
words. Not knowing is not a claim: with no word from the host, nothing is drawn.
|
|
@@ -4469,6 +4476,22 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4469
4476
|
composed: true
|
|
4470
4477
|
}));
|
|
4471
4478
|
}
|
|
4479
|
+
/** The records each entry card entered - from the conversation's notes, like the outcomes, so a refresh keeps them. */
|
|
4480
|
+
get entryRecords() {
|
|
4481
|
+
const records = new Map();
|
|
4482
|
+
for (const line of this.lines) {
|
|
4483
|
+
if (line.role !== 'system')
|
|
4484
|
+
continue;
|
|
4485
|
+
const entered = entryRecordsOf(line.content ?? '');
|
|
4486
|
+
if (entered)
|
|
4487
|
+
records.set(entered.key, entered.ids);
|
|
4488
|
+
}
|
|
4489
|
+
return records;
|
|
4490
|
+
}
|
|
4491
|
+
openEntryScreen(p, key, records) {
|
|
4492
|
+
const href = entryScreenAddress(p, records.get(key), entity360KindRegistered);
|
|
4493
|
+
this.dispatchEvent(new CustomEvent('assistant-open-page', { detail: { href }, bubbles: true, composed: true }));
|
|
4494
|
+
}
|
|
4472
4495
|
/** Keys whose run was requested - from the conversation's rows, like the outcomes. */
|
|
4473
4496
|
get proposalRequests() {
|
|
4474
4497
|
const requested = new Set();
|