@things-factory/ai-assistant 10.1.212 → 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/components/assistant-chat.ts +115 -2
- package/client/components/assistant-dock.ts +106 -3
- package/client/components/workspace-view.ts +56 -3
- package/client/index.ts +3 -0
- package/client/utils/assistant-ask.test.ts +38 -0
- package/client/utils/assistant-ask.ts +35 -1
- package/client/utils/assistant-proposals.ts +23 -0
- package/client/utils/assistant-selection.test.ts +30 -0
- package/client/utils/assistant-selection.ts +47 -0
- package/client/utils/assistant-session-controller.ts +2 -0
- package/client/utils/assistant-session-transport.ts +1 -1
- package/client/utils/invite-proposal-register.ts +31 -0
- package/client/utils/invite-proposal.test.ts +40 -0
- package/client/utils/invite-proposal.ts +54 -0
- package/dist-client/components/assistant-chat.d.ts +18 -0
- package/dist-client/components/assistant-chat.js +117 -2
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.d.ts +11 -0
- package/dist-client/components/assistant-dock.js +97 -4
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/workspace-view.d.ts +2 -0
- package/dist-client/components/workspace-view.js +58 -3
- package/dist-client/components/workspace-view.js.map +1 -1
- package/dist-client/index.d.ts +2 -0
- package/dist-client/index.js +3 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-ask.d.ts +22 -1
- package/dist-client/utils/assistant-ask.js +19 -0
- package/dist-client/utils/assistant-ask.js.map +1 -1
- package/dist-client/utils/assistant-ask.test.d.ts +1 -0
- package/dist-client/utils/assistant-ask.test.js +40 -0
- package/dist-client/utils/assistant-ask.test.js.map +1 -0
- package/dist-client/utils/assistant-proposals.d.ts +14 -0
- package/dist-client/utils/assistant-proposals.js +7 -0
- package/dist-client/utils/assistant-proposals.js.map +1 -1
- package/dist-client/utils/assistant-selection.d.ts +19 -0
- package/dist-client/utils/assistant-selection.js +35 -0
- package/dist-client/utils/assistant-selection.js.map +1 -0
- package/dist-client/utils/assistant-selection.test.d.ts +1 -0
- package/dist-client/utils/assistant-selection.test.js +26 -0
- package/dist-client/utils/assistant-selection.test.js.map +1 -0
- package/dist-client/utils/assistant-session-controller.d.ts +2 -0
- package/dist-client/utils/assistant-session-controller.js.map +1 -1
- package/dist-client/utils/assistant-session-transport.js +1 -1
- package/dist-client/utils/assistant-session-transport.js.map +1 -1
- package/dist-client/utils/invite-proposal-register.d.ts +1 -0
- package/dist-client/utils/invite-proposal-register.js +24 -0
- package/dist-client/utils/invite-proposal-register.js.map +1 -0
- package/dist-client/utils/invite-proposal.d.ts +17 -0
- package/dist-client/utils/invite-proposal.js +37 -0
- package/dist-client/utils/invite-proposal.js.map +1 -0
- package/dist-client/utils/invite-proposal.test.d.ts +1 -0
- package/dist-client/utils/invite-proposal.test.js +35 -0
- package/dist-client/utils/invite-proposal.test.js.map +1 -0
- package/dist-server/service/assistant-chat-resolver.js +2 -0
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/index.d.ts +2 -1
- package/dist-server/service/assistant-mode/index.js +6 -1
- package/dist-server/service/assistant-mode/index.js.map +1 -1
- package/dist-server/service/assistant-mode/mode-categories.d.ts +14 -1
- package/dist-server/service/assistant-mode/mode-categories.js +29 -4
- package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
- package/dist-server/service/assistant-mode/screen-rules.d.ts +6 -0
- package/dist-server/service/assistant-mode/screen-rules.js +21 -6
- package/dist-server/service/assistant-mode/screen-rules.js.map +1 -1
- package/dist-server/service/data-read/read-target.d.ts +24 -3
- package/dist-server/service/data-read/read-target.js +42 -10
- package/dist-server/service/data-read/read-target.js.map +1 -1
- package/dist-server/service/data-read/read-tools.d.ts +18 -1
- package/dist-server/service/data-read/read-tools.js +19 -5
- package/dist-server/service/data-read/read-tools.js.map +1 -1
- package/dist-server/service/index.d.ts +5 -1
- package/dist-server/service/index.js +10 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/people/index.d.ts +4 -0
- package/dist-server/service/people/index.js +22 -0
- package/dist-server/service/people/index.js.map +1 -0
- package/dist-server/service/people/invite-tools.d.ts +34 -0
- package/dist-server/service/people/invite-tools.js +144 -0
- package/dist-server/service/people/invite-tools.js.map +1 -0
- package/dist-server/service/people/people-keys.d.ts +8 -0
- package/dist-server/service/people/people-keys.js +16 -0
- package/dist-server/service/people/people-keys.js.map +1 -0
- package/dist-server/service/people/people-read.d.ts +4 -0
- package/dist-server/service/people/people-read.js +57 -0
- package/dist-server/service/people/people-read.js.map +1 -0
- package/dist-server/service/testing/mode-conformance.js +3 -0
- package/dist-server/service/testing/mode-conformance.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/service/assistant-chat-resolver.ts +2 -0
- package/server/service/assistant-mode/conversation-category.test.ts +35 -0
- package/server/service/assistant-mode/index.ts +3 -1
- package/server/service/assistant-mode/mode-categories.ts +41 -4
- package/server/service/assistant-mode/mode-front.test.ts +5 -3
- package/server/service/assistant-mode/resolve.test.ts +1 -1
- package/server/service/assistant-mode/screen-rules.test.ts +23 -1
- package/server/service/assistant-mode/screen-rules.ts +22 -5
- package/server/service/data-entry/data-entry.test.ts +2 -2
- package/server/service/data-read/detail-lists.test.ts +61 -0
- package/server/service/data-read/read-target.ts +54 -9
- package/server/service/data-read/read-tools.ts +32 -6
- package/server/service/index.ts +7 -1
- package/server/service/people/index.ts +13 -0
- package/server/service/people/invite-tools.ts +159 -0
- package/server/service/people/invite.test.ts +159 -0
- package/server/service/people/people-keys.ts +16 -0
- package/server/service/people/people-read.test.ts +82 -0
- package/server/service/people/people-read.ts +56 -0
- package/server/service/testing/mode-conformance.test.ts +14 -1
- package/server/service/testing/mode-conformance.ts +2 -0
- package/test/assistant-dock.test.ts +73 -0
- package/translations/en.json +18 -1
- package/translations/es.json +18 -1
- package/translations/ja.json +18 -1
- package/translations/ko.json +18 -1
- package/translations/ms.json +18 -1
- package/translations/zh.json +18 -1
|
@@ -20,7 +20,7 @@ export const assistantSessionTransport: AssistantSessionTransport = {
|
|
|
20
20
|
* Every readable conversation of this kind on the anchor, not only mine - the same scope the manager
|
|
21
21
|
* shows by default (design/ai/shared-dock.md §2-6). Without a kind: every kind, as before.
|
|
22
22
|
*/
|
|
23
|
-
query: gql`query($type:String!,$id:String!,$kind:String){chatSessionInbox(anchorType:$type,anchorId:$id,kind:$kind,limit:100){id name kind visibility}}`,
|
|
23
|
+
query: gql`query($type:String!,$id:String!,$kind:String){chatSessionInbox(anchorType:$type,anchorId:$id,kind:$kind,limit:100){id name kind visibility participant}}`,
|
|
24
24
|
variables: { type: target.anchorType, id: target.anchorId, kind: target.kind || null }, fetchPolicy: 'no-cache'
|
|
25
25
|
})
|
|
26
26
|
if (result.errors?.length) throw new Error(result.errors[0].message)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Pressing an invite card seats the person through the invite door, as the one who pressed (ADR-0073 decision 3).
|
|
3
|
+
* The card is ai-assistant's own tool (server: people/invite-tools.ts), so its handler is registered here, by the
|
|
4
|
+
* package that declared it - the dock knows no proposal kind (design/ai/shared-dock.md §4).
|
|
5
|
+
*/
|
|
6
|
+
import gql from 'graphql-tag'
|
|
7
|
+
|
|
8
|
+
import { i18next } from '@operato/i18n'
|
|
9
|
+
import { client } from '@operato/graphql'
|
|
10
|
+
|
|
11
|
+
import { registerProposalHandler } from './assistant-proposals.js'
|
|
12
|
+
import { inviteProposalHandler } from './invite-proposal.js'
|
|
13
|
+
|
|
14
|
+
registerProposalHandler(
|
|
15
|
+
'proposeInvite',
|
|
16
|
+
inviteProposalHandler(
|
|
17
|
+
async (sessionId, userIds) => {
|
|
18
|
+
const result = await client.mutate({
|
|
19
|
+
mutation: gql`
|
|
20
|
+
mutation ($id: String!, $users: [String!]!) {
|
|
21
|
+
inviteToAISession(sessionId: $id, userIds: $users)
|
|
22
|
+
}
|
|
23
|
+
`,
|
|
24
|
+
variables: { id: sessionId, users: userIds }
|
|
25
|
+
})
|
|
26
|
+
if (result.errors?.length) throw new Error(result.errors[0].message)
|
|
27
|
+
return result.data?.inviteToAISession ?? []
|
|
28
|
+
},
|
|
29
|
+
(key, options) => String(i18next.t(key, options))
|
|
30
|
+
)
|
|
31
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { inviteProposalHandler, invitedBy, isInviteProposal } from './invite-proposal'
|
|
2
|
+
|
|
3
|
+
const card = (people: any[], extra: any = {}) => ({ tool: 'proposeInvite', kind: 'invite', people, ...extra })
|
|
4
|
+
const kim = { userId: 'u-1', name: '정연희', department: 'Sales' }
|
|
5
|
+
const lee = { userId: 'u-2', name: '정연희', department: 'Finance' }
|
|
6
|
+
const t = (_key: string, options: any) => String(options.defaultValue).replace('{names}', options.names)
|
|
7
|
+
const context = (sessionId?: string) => ({ sessionId, confirm: async () => true, notify: () => {} }) as any
|
|
8
|
+
|
|
9
|
+
describe('the invite card', () => {
|
|
10
|
+
it('is the invite tool\'s card with someone on it', () => {
|
|
11
|
+
expect(isInviteProposal(card([kim]))).toBe(true)
|
|
12
|
+
expect(isInviteProposal(card([]))).toBe(false)
|
|
13
|
+
expect(isInviteProposal({ ...card([kim]), tool: 'proposeAction' })).toBe(false)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('★ one person: pressing invites them; several: only the one picked - nothing is guessed', () => {
|
|
17
|
+
expect(invitedBy(card([kim]))).toEqual([kim])
|
|
18
|
+
expect(invitedBy(card([kim, lee]))).toEqual([])
|
|
19
|
+
expect(invitedBy(card([kim, lee], { chosen: 'u-2' }))).toEqual([lee])
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('★ pressing calls the invite door with the conversation and the person picked', async () => {
|
|
23
|
+
const calls: any[] = []
|
|
24
|
+
const handler = inviteProposalHandler(async (sessionId, userIds) => (calls.push([sessionId, userIds]), userIds), t)
|
|
25
|
+
expect(await handler(card([kim, lee], { chosen: 'u-2' }), context('s1'))).toMatchObject({ verdict: 'accepted', applied: 1, note: '정연희 joined this conversation and was told.' })
|
|
26
|
+
expect(calls).toEqual([['s1', ['u-2']]])
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('no pick, no conversation, or nobody seated - said, never accepted', async () => {
|
|
30
|
+
const handler = inviteProposalHandler(async (_s, userIds) => userIds, t)
|
|
31
|
+
expect(await handler(card([kim, lee]), context('s1'))).toEqual({ verdict: 'refused', errorCode: 'no-person-chosen' })
|
|
32
|
+
expect(await handler(card([kim]), context())).toEqual({ verdict: 'unavailable', errorCode: 'no-conversation' })
|
|
33
|
+
expect(await inviteProposalHandler(async () => [], t)(card([kim]), context('s1'))).toEqual({ verdict: 'refused', errorCode: 'already-here' })
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("the door's refusal is not swallowed - it is thrown to the dock, which reports it", async () => {
|
|
37
|
+
const handler = inviteProposalHandler(async () => { throw new Error('only the person who started this group conversation can bring people in.') }, t)
|
|
38
|
+
await expect(handler(card([kim]), context('s1'))).rejects.toThrow(/only the person who started/)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The invite card (server: people/invite-tools.ts) - who it offers, and what pressing it does.
|
|
3
|
+
*
|
|
4
|
+
* Pressing is the person's act: the dock calls `inviteToAISession` with their own request, and the door decides
|
|
5
|
+
* whether they may seat anyone here (`maySeat`). The model's card seats nobody by itself. A card with several people
|
|
6
|
+
* of one name is pressed on one of them - the press carries `chosen`; nothing is guessed.
|
|
7
|
+
*/
|
|
8
|
+
import type { ProposalHandler } from './assistant-proposals.js'
|
|
9
|
+
|
|
10
|
+
export interface InvitePerson {
|
|
11
|
+
userId: string
|
|
12
|
+
name: string
|
|
13
|
+
department?: string
|
|
14
|
+
position?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isInviteProposal(proposal: any): boolean {
|
|
18
|
+
return proposal?.kind === 'invite' && proposal?.tool === 'proposeInvite' && Array.isArray(proposal?.people) && proposal.people.length > 0
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The people a press brings in: the one picked, or the only one on the card. */
|
|
22
|
+
export function invitedBy(proposal: any): InvitePerson[] {
|
|
23
|
+
const people: InvitePerson[] = Array.isArray(proposal?.people) ? proposal.people.filter((one: any) => one?.userId) : []
|
|
24
|
+
if (proposal?.chosen) return people.filter(one => one.userId === proposal.chosen)
|
|
25
|
+
return people.length === 1 ? people : []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One line under a person's name - where they work. */
|
|
29
|
+
export function personLine(person: InvitePerson): string {
|
|
30
|
+
return [person.department, person.position].filter(Boolean).join(' · ')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Presses the card through the invite door. `invite` is the door - `inviteToAISession(sessionId, userIds)`, answering the
|
|
35
|
+
* ids seated now; it throws the door's refusal, which the dock reports as `refused` with that message.
|
|
36
|
+
*/
|
|
37
|
+
export function inviteProposalHandler(
|
|
38
|
+
invite: (sessionId: string, userIds: string[]) => Promise<string[]>,
|
|
39
|
+
t: (key: string, options: Record<string, unknown>) => string
|
|
40
|
+
): ProposalHandler {
|
|
41
|
+
return async (proposal, context) => {
|
|
42
|
+
const people = invitedBy(proposal)
|
|
43
|
+
if (!people.length) return { verdict: 'refused', errorCode: 'no-person-chosen' }
|
|
44
|
+
if (!context.sessionId) return { verdict: 'unavailable', errorCode: 'no-conversation' }
|
|
45
|
+
const seated = await invite(context.sessionId, people.map(one => one.userId))
|
|
46
|
+
if (!seated.length) return { verdict: 'refused', errorCode: 'already-here' }
|
|
47
|
+
const names = people.filter(one => seated.includes(one.userId)).map(one => one.name).join(', ')
|
|
48
|
+
return {
|
|
49
|
+
verdict: 'accepted',
|
|
50
|
+
applied: seated.length,
|
|
51
|
+
note: t('ai-assistant.people.invited', { names, defaultValue: '{names} joined this conversation and was told.' })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -309,6 +309,15 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
309
309
|
text: string;
|
|
310
310
|
attachments: ChatAttachment[];
|
|
311
311
|
}): void;
|
|
312
|
+
/**
|
|
313
|
+
* A request made on the screen (「AI와 작업」, design/ai/shared-dock.md §2-11-1) - in this conversation, made now if it
|
|
314
|
+
* has none yet: the selection's note first, then the request, sent at once. What the person was writing is set aside
|
|
315
|
+
* while it goes and put back after, whatever happened. Says whether it was sent and, if not, why.
|
|
316
|
+
*/
|
|
317
|
+
sendScreenRequest(text: string, note: string): Promise<{
|
|
318
|
+
sent: boolean;
|
|
319
|
+
reason?: string;
|
|
320
|
+
}>;
|
|
312
321
|
private errorMessage?;
|
|
313
322
|
/** C4 presence/roster — 세션 참여자 목록. lastSeenAt 으로 online 판정. */
|
|
314
323
|
private participants;
|
|
@@ -560,6 +569,15 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
560
569
|
* 실행 결과는 호스트가 **대화에 기록**한다 — 누가 무엇을 실행했는지가 협의 이력에 남아야 한다.
|
|
561
570
|
*/
|
|
562
571
|
private renderProposals;
|
|
572
|
+
/** A package's preview under its card - the element is the package's; it is handed the proposal and where it is. */
|
|
573
|
+
private renderProposalPreview;
|
|
574
|
+
/** One preview element per card, kept across renders so it does not read again on every keystroke. */
|
|
575
|
+
private proposalPreviews;
|
|
576
|
+
/**
|
|
577
|
+
* The invite card. Pressing it is the person's act - the dock calls the invite door with their request. With several
|
|
578
|
+
* people of one name each is a button with where they work; the press carries whom they picked.
|
|
579
|
+
*/
|
|
580
|
+
private renderInviteCard;
|
|
563
581
|
/** The screen card - which list, the conditions the screen does not take, and the way to open it. */
|
|
564
582
|
private renderOpenPageCard;
|
|
565
583
|
/** The AI 작업판 card - what it holds, which version, and the way to open it. The person opens it; the model does not. */
|
|
@@ -5,6 +5,8 @@ import { rosterName } from './roster-identity';
|
|
|
5
5
|
import { notify } from '@operato/layout';
|
|
6
6
|
import './assistant-invite.js';
|
|
7
7
|
import { TypingRoom } from '../utils/assistant-typing.js';
|
|
8
|
+
import { isInviteProposal, personLine } from '../utils/invite-proposal.js';
|
|
9
|
+
import { noteForDisplay } from '../utils/assistant-selection.js';
|
|
8
10
|
/**
|
|
9
11
|
* <ox-assistant-chat> — AI 도우미 채팅 컴포넌트 (Lit).
|
|
10
12
|
*
|
|
@@ -63,6 +65,7 @@ import './entry-review.js';
|
|
|
63
65
|
import { entryGroupOf, entryReadyCount, entryTitle, isChangeProposal, isEntryProposal, latestEntryKeys } from '../utils/entry-proposal.js';
|
|
64
66
|
import { isWorkspaceProposal, versionOfCard, workspaceAddress } from '../utils/workspace.js';
|
|
65
67
|
import { isOpenPageProposal, openPageNote } from '../utils/open-page.js';
|
|
68
|
+
import { proposalViewOf } from '../utils/assistant-proposals.js';
|
|
66
69
|
function formatFileSize(bytes) {
|
|
67
70
|
if (!bytes || bytes < 1024)
|
|
68
71
|
return `${bytes || 0} B`;
|
|
@@ -279,6 +282,12 @@ const CHAT_TYPING_SUBSCRIPTION = gql `
|
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
`;
|
|
285
|
+
/* The selection a screen request carries, noted before the request (design/ai/shared-dock.md §2-11-1). */
|
|
286
|
+
const APPEND_SESSION_NOTE_MUTATION = gql `
|
|
287
|
+
mutation AppendSessionNote($sessionId: String!, $content: String!) {
|
|
288
|
+
appendSessionNote(sessionId: $sessionId, content: $content)
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
282
291
|
const TOUCH_PRESENCE_MUTATION = gql `
|
|
283
292
|
mutation TouchPresence($sessionId: String!) {
|
|
284
293
|
touchPresence(sessionId: $sessionId)
|
|
@@ -442,6 +451,8 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
442
451
|
this.foldNoticeDismissed = true;
|
|
443
452
|
this.dispatchEvent(new CustomEvent('assistant-start-new-session', { bubbles: true, composed: true }));
|
|
444
453
|
};
|
|
454
|
+
/** One preview element per card, kept across renders so it does not read again on every keystroke. */
|
|
455
|
+
this.proposalPreviews = new Map();
|
|
445
456
|
/**
|
|
446
457
|
* 실행은 호스트가 한다 — 이 컴포넌트는 도메인(트윈 명령 채널)을 모른다.
|
|
447
458
|
* 확인 대화·명령 전송·결과 기록은 호스트의 몫이고, 여기서는 중복 클릭만 막는다.
|
|
@@ -715,6 +726,47 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
715
726
|
this.input = draft.text;
|
|
716
727
|
this.attachments = draft.attachments;
|
|
717
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* A request made on the screen (「AI와 작업」, design/ai/shared-dock.md §2-11-1) - in this conversation, made now if it
|
|
731
|
+
* has none yet: the selection's note first, then the request, sent at once. What the person was writing is set aside
|
|
732
|
+
* while it goes and put back after, whatever happened. Says whether it was sent and, if not, why.
|
|
733
|
+
*/
|
|
734
|
+
async sendScreenRequest(text, note) {
|
|
735
|
+
if (this.busy)
|
|
736
|
+
return { sent: false, reason: i18next.t('ai-assistant.text.screen-request-busy', { defaultValue: 'an answer is still being written.' }) };
|
|
737
|
+
const kept = this.takeDraft();
|
|
738
|
+
try {
|
|
739
|
+
if (!this.sessionId && this.prepareSession) {
|
|
740
|
+
await this.prepareSession(id => {
|
|
741
|
+
this.adoptingSession = true;
|
|
742
|
+
this.sessionId = id;
|
|
743
|
+
});
|
|
744
|
+
await this.updateComplete;
|
|
745
|
+
}
|
|
746
|
+
if (!this.sessionId)
|
|
747
|
+
return { sent: false, reason: i18next.t('ai-assistant.text.screen-request-no-session', { defaultValue: 'this screen has no conversation to send to.' }) };
|
|
748
|
+
if (note) {
|
|
749
|
+
const noted = await client.mutate({ mutation: APPEND_SESSION_NOTE_MUTATION, variables: { sessionId: this.sessionId, content: note } });
|
|
750
|
+
if (noted.errors?.length)
|
|
751
|
+
throw new Error(noted.errors[0].message);
|
|
752
|
+
await this.loadHistory();
|
|
753
|
+
}
|
|
754
|
+
this.errorMessage = undefined;
|
|
755
|
+
this.input = text;
|
|
756
|
+
await this.send();
|
|
757
|
+
return this.errorMessage ? { sent: false, reason: this.errorMessage } : { sent: true };
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
return { sent: false, reason: error instanceof Error ? error.message : String(error) };
|
|
761
|
+
}
|
|
762
|
+
finally {
|
|
763
|
+
/* The request is the screen's - it never stays in the box; the box gets back what the person had written. */
|
|
764
|
+
if (this.input.trim() === text.trim())
|
|
765
|
+
this.input = '';
|
|
766
|
+
if (kept && !this.input.trim() && !this.attachments.length)
|
|
767
|
+
this.restoreDraft(kept);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
718
770
|
/** 현재 로그인 사용자 email — 그룹챗에서 내 메시지 vs 타인 메시지 구분.
|
|
719
771
|
* email 을 쓰는 이유: /auth/profile 이 항상 내려주는 로그인 식별자이고(비어 있지 않다),
|
|
720
772
|
* 내부 관리키(User.id)를 클라이언트 판정에 의존하지 않기 위함. */
|
|
@@ -1664,6 +1716,16 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
1664
1716
|
--md-icon-size: 13px;
|
|
1665
1717
|
color: inherit;
|
|
1666
1718
|
}
|
|
1719
|
+
/* The invite card's people - where each works, quieter than the name; a picked card greys every button. */
|
|
1720
|
+
.proposal-choices .choice-chip .invite-where {
|
|
1721
|
+
margin-left: 4px;
|
|
1722
|
+
opacity: 0.7;
|
|
1723
|
+
font-size: 0.92em;
|
|
1724
|
+
}
|
|
1725
|
+
.proposal-choices .choice-chip:disabled {
|
|
1726
|
+
opacity: 0.5;
|
|
1727
|
+
cursor: default;
|
|
1728
|
+
}
|
|
1667
1729
|
|
|
1668
1730
|
/* The entry card's way to the large review - a quiet action under its table, never a second run button. */
|
|
1669
1731
|
.workspace-review {
|
|
@@ -2924,7 +2986,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2924
2986
|
class="sys-text"
|
|
2925
2987
|
title=${i18next.t('ai-assistant.text.toggle-system-note', { defaultValue: 'click to expand' })}
|
|
2926
2988
|
@click=${() => this.toggleSystemNote(idx)}
|
|
2927
|
-
>${content}</span
|
|
2989
|
+
>${noteForDisplay(content)}</span
|
|
2928
2990
|
>`
|
|
2929
2991
|
: content}
|
|
2930
2992
|
${line.attachments && line.attachments.length > 0
|
|
@@ -4022,7 +4084,12 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4022
4084
|
if (isOpenPageProposal(p))
|
|
4023
4085
|
return this.renderOpenPageCard(p);
|
|
4024
4086
|
const card = proposalCardState(key, { outcomes, requested, sentHere: this.sentProposals.has(key) });
|
|
4087
|
+
/* An invite card: one person, one button; several of one name, one button each - the person picks (ADR-0073 decision 3). */
|
|
4088
|
+
if (isInviteProposal(p))
|
|
4089
|
+
return this.renderInviteCard(p, key, card.state);
|
|
4025
4090
|
const entry = isEntryProposal(p);
|
|
4091
|
+
/* A package's own card - its preview under the line, its words on the button (assistant-proposals.ts ProposalView). */
|
|
4092
|
+
const view = entry ? undefined : proposalViewOf(p.tool);
|
|
4026
4093
|
const replaced = entry && latestEntry?.get(entryGroupOf(p)) !== key && card.state === 'ready';
|
|
4027
4094
|
const ready = entry ? entryReadyCount(p) : 0;
|
|
4028
4095
|
const sent = card.state !== 'ready' || replaced || (entry && ready === 0);
|
|
@@ -4068,7 +4135,9 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4068
4135
|
? i18next.t('ai-assistant.entry.change', { defaultValue: 'change {count} records', count: ready })
|
|
4069
4136
|
: i18next.t('ai-assistant.entry.enter', { defaultValue: 'enter {count} rows', count: ready })
|
|
4070
4137
|
: i18next.t('ai-assistant.entry.nothing-to-enter', { defaultValue: 'nothing to enter' })
|
|
4071
|
-
:
|
|
4138
|
+
: view?.runLabelKey || view?.runLabel
|
|
4139
|
+
? i18next.t(view.runLabelKey ?? '', { defaultValue: view.runLabel ?? 'run' })
|
|
4140
|
+
: i18next.t('ai-assistant.button.run-proposal', { defaultValue: 'run' })}
|
|
4072
4141
|
</button>
|
|
4073
4142
|
`}
|
|
4074
4143
|
${ /*
|
|
@@ -4085,6 +4154,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4085
4154
|
` : nothing}
|
|
4086
4155
|
</div>
|
|
4087
4156
|
${entry ? html `<ox-assistant-entry-review .proposal=${p}></ox-assistant-entry-review>` : nothing}
|
|
4157
|
+
${view?.previewTag ? this.renderProposalPreview(view.previewTag, p, key) : nothing}
|
|
4088
4158
|
${entry && this.sessionId && offersWorkspaceReview(p)
|
|
4089
4159
|
? html `<button
|
|
4090
4160
|
class="workspace-review"
|
|
@@ -4112,6 +4182,51 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4112
4182
|
</div>
|
|
4113
4183
|
`;
|
|
4114
4184
|
}
|
|
4185
|
+
/** A package's preview under its card - the element is the package's; it is handed the proposal and where it is. */
|
|
4186
|
+
renderProposalPreview(tag, p, key) {
|
|
4187
|
+
const element = this.proposalPreviews.get(key) ?? document.createElement(tag);
|
|
4188
|
+
this.proposalPreviews.set(key, element);
|
|
4189
|
+
element.proposal = p;
|
|
4190
|
+
element.sessionId = this.sessionId;
|
|
4191
|
+
element.proposalKey = key;
|
|
4192
|
+
return html `${element}`;
|
|
4193
|
+
}
|
|
4194
|
+
/**
|
|
4195
|
+
* The invite card. Pressing it is the person's act - the dock calls the invite door with their request. With several
|
|
4196
|
+
* people of one name each is a button with where they work; the press carries whom they picked.
|
|
4197
|
+
*/
|
|
4198
|
+
renderInviteCard(p, key, state) {
|
|
4199
|
+
const people = p.people;
|
|
4200
|
+
const sent = state !== 'ready';
|
|
4201
|
+
const status = sent ? this.proposalStateText(key) : '';
|
|
4202
|
+
return html `
|
|
4203
|
+
<div class="proposal invite ${sent ? 'sent' : ''}">
|
|
4204
|
+
<md-icon>${p.icon || 'person_add'}</md-icon>
|
|
4205
|
+
<span class="pbody">
|
|
4206
|
+
<span class="plabel">${p.label}</span>
|
|
4207
|
+
${p.reason ? html `<span class="preason">${p.reason}</span>` : nothing}
|
|
4208
|
+
${sent && people.length > 1 ? html `<span class="preason">${status}</span>` : nothing}
|
|
4209
|
+
</span>
|
|
4210
|
+
${people.length === 1
|
|
4211
|
+
? html `<button class="run" ?disabled=${sent} @click=${() => this.executeProposal(p, key)}>
|
|
4212
|
+
${sent ? status : i18next.t('ai-assistant.button.invite', { defaultValue: 'Invite' })}
|
|
4213
|
+
</button>`
|
|
4214
|
+
: nothing}
|
|
4215
|
+
</div>
|
|
4216
|
+
${people.length > 1
|
|
4217
|
+
? html `<div class="proposal-choices invite-people" role="group" aria-label=${p.label}>
|
|
4218
|
+
${people.map(person => html `<button
|
|
4219
|
+
class="choice-chip"
|
|
4220
|
+
?disabled=${sent}
|
|
4221
|
+
title=${personLine(person)}
|
|
4222
|
+
@click=${() => this.executeProposal({ ...p, chosen: person.userId }, key)}>
|
|
4223
|
+
<md-icon>person_add</md-icon>
|
|
4224
|
+
<span>${person.name}${personLine(person) ? html ` <span class="invite-where">${personLine(person)}</span>` : nothing}</span>
|
|
4225
|
+
</button>`)}
|
|
4226
|
+
</div>`
|
|
4227
|
+
: nothing}
|
|
4228
|
+
`;
|
|
4229
|
+
}
|
|
4115
4230
|
/** The screen card - which list, the conditions the screen does not take, and the way to open it. */
|
|
4116
4231
|
renderOpenPageCard(p) {
|
|
4117
4232
|
const note = openPageNote(p, (key, options) => String(i18next.t(key, options)));
|