@things-factory/ai-assistant 10.1.157 → 10.1.160
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 +25 -213
- package/client/components/assistant-dock.ts +11 -32
- package/client/components/assistant-mode.test.ts +1 -43
- package/client/components/assistant-mode.ts +0 -47
- package/client/utils/assistant-surface.ts +1 -1
- package/client/utils/composer-frame.ts +6 -4
- package/dist-client/components/assistant-chat.d.ts +5 -69
- package/dist-client/components/assistant-chat.js +25 -205
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.d.ts +0 -3
- package/dist-client/components/assistant-dock.js +10 -34
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/assistant-mode.d.ts +0 -33
- package/dist-client/components/assistant-mode.js +0 -37
- package/dist-client/components/assistant-mode.js.map +1 -1
- package/dist-client/components/assistant-mode.test.js +1 -35
- package/dist-client/components/assistant-mode.test.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-surface.d.ts +1 -1
- package/dist-client/utils/assistant-surface.js.map +1 -1
- package/dist-client/utils/composer-frame.d.ts +5 -3
- package/dist-client/utils/composer-frame.js +4 -4
- package/dist-client/utils/composer-frame.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +2 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/assistant-chat-resolver.d.ts +1 -0
- package/dist-server/service/assistant-chat-resolver.js +30 -6
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +1 -2
- package/dist-server/service/assistant-mode/assistant-mode-resolver.js +13 -24
- package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/fallback-subjects.d.ts +1 -0
- package/dist-server/service/assistant-mode/fallback-subjects.js +37 -0
- package/dist-server/service/assistant-mode/fallback-subjects.js.map +1 -0
- package/dist-server/service/assistant-mode/index.d.ts +2 -1
- package/dist-server/service/assistant-mode/index.js +7 -2
- package/dist-server/service/assistant-mode/index.js.map +1 -1
- package/dist-server/service/assistant-mode/registry.d.ts +2 -6
- package/dist-server/service/assistant-mode/registry.js +16 -56
- package/dist-server/service/assistant-mode/registry.js.map +1 -1
- package/dist-server/service/assistant-mode/resolve.js +3 -1
- package/dist-server/service/assistant-mode/resolve.js.map +1 -1
- package/dist-server/service/assistant-mode/screen-rules.d.ts +25 -0
- package/dist-server/service/assistant-mode/screen-rules.js +47 -0
- package/dist-server/service/assistant-mode/screen-rules.js.map +1 -0
- package/dist-server/service/assistant-mode/types.d.ts +8 -25
- package/dist-server/service/assistant-mode/types.js.map +1 -1
- package/dist-server/service/testing/mode-conformance.d.ts +11 -0
- package/dist-server/service/testing/mode-conformance.js +28 -0
- package/dist-server/service/testing/mode-conformance.js.map +1 -1
- package/dist-server/tool-registry-boot-report.d.ts +1 -1
- package/dist-server/tool-registry-boot-report.js +16 -7
- package/dist-server/tool-registry-boot-report.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/index.ts +2 -0
- package/server/service/assistant-chat-resolver.ts +33 -7
- package/server/service/assistant-mode/assistant-mode-resolver.ts +12 -25
- package/server/service/assistant-mode/fallback-subjects.ts +36 -0
- package/server/service/assistant-mode/index.ts +2 -1
- package/server/service/assistant-mode/one-conversation-per-subject.test.ts +37 -0
- package/server/service/assistant-mode/open-subject-door.test.ts +29 -0
- package/server/service/assistant-mode/registry.ts +14 -55
- package/server/service/assistant-mode/resolve.test.ts +10 -1
- package/server/service/assistant-mode/resolve.ts +3 -1
- package/server/service/assistant-mode/screen-rules.test.ts +39 -0
- package/server/service/assistant-mode/screen-rules.ts +47 -0
- package/server/service/assistant-mode/types.ts +8 -31
- package/server/service/testing/mode-conformance.ts +27 -1
- package/server/tool-registry-boot-report.test.ts +14 -1
- package/server/tool-registry-boot-report.ts +16 -7
- package/test/assistant-dock.test.ts +27 -4
- package/test/composer-frame.test.ts +12 -2
- package/server/service/assistant-mode/subject-defaults.test.ts +0 -86
|
@@ -136,13 +136,8 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
136
136
|
*/
|
|
137
137
|
mode?: AssistantMode;
|
|
138
138
|
/**
|
|
139
|
-
* 이 화면이 보여 주는 것의 **종류** — `'board'`, `'space'`, `'station'`, `'figure'
|
|
140
|
-
*
|
|
141
|
-
* `mode` 가 「지금 어느 대화인가」라면 이것은 「그 자리에 어떤 대화들이 설 수 있나」이다. 서버가
|
|
142
|
-
* `assistantModes(subject)` 로 답하고, 둘 이상이면 고르개가 뜬다. 하나면 고르개를 내지 않는다 —
|
|
143
|
-
* 고를 것이 없는 고르개는 자리만 차지한다.
|
|
144
|
-
*
|
|
145
|
-
* 화면이 이것을 안 주면 고르개는 없고 host 가 정한 대화 하나만 산다. 그것이 오늘까지의 모양이다.
|
|
139
|
+
* 이 화면이 보여 주는 것의 **종류** — `'board'`, `'space'`, `'station'`, `'figure'`. 화면에 대화는 하나다
|
|
140
|
+
* (design/ai/screen-pattern.md §3). 바뀌면 사업장의 AI 에이전트 목록을 다시 읽는다.
|
|
146
141
|
*/
|
|
147
142
|
subject?: string;
|
|
148
143
|
/**
|
|
@@ -365,9 +360,6 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
365
360
|
*/
|
|
366
361
|
private modeView?;
|
|
367
362
|
private modeViewToken;
|
|
368
|
-
/** 이 subject 에 설 수 있는 대화들 — 기본이 먼저. 하나뿐이면 고르개를 그리지 않는다. */
|
|
369
|
-
private modeChoices;
|
|
370
|
-
private modeChoicesToken;
|
|
371
363
|
/** The AI agents this domain lets a person pick (design/ai/agent-settings.md §5). One or none draws no picker. */
|
|
372
364
|
private agentChoices;
|
|
373
365
|
private agentId?;
|
|
@@ -379,35 +371,6 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
379
371
|
*/
|
|
380
372
|
private loadAgentChoices;
|
|
381
373
|
private onAgentPick;
|
|
382
|
-
/**
|
|
383
|
-
* 이 화면에 설 수 있는 대화를 서버에 묻는다.
|
|
384
|
-
*
|
|
385
|
-
* 화면이 종류 목록을 손에 들고 있으면, 대화를 하나 더 세운 날 그 화면만 모른 채로 남는다. 목록은
|
|
386
|
-
* 앱 서버의 선언에서 나온다(ADR-0072 A17 ①).
|
|
387
|
-
*/
|
|
388
|
-
private loadModeChoices;
|
|
389
|
-
/**
|
|
390
|
-
* 사람이 다른 대화를 골랐다.
|
|
391
|
-
*
|
|
392
|
-
* 고른 것은 **이 subject 종류에 대해** 기억한다 — 보드를 열 때마다 저작 대화로 돌아가면, 현장을
|
|
393
|
-
* 물으려던 사람은 화면을 열 때마다 같은 손질을 다시 해야 한다. 기억은 이 브라우저에만 남는다
|
|
394
|
-
* (서버에 칸을 만들지 않는다 — A17 ①). 세션 자체는 자기가 어느 대화였는지 이미 안다.
|
|
395
|
-
*/
|
|
396
|
-
private onModePick;
|
|
397
|
-
/**
|
|
398
|
-
* **고르는 것은 청하는 일이다** — `mode` 를 여기서 바꾸지 않는다.
|
|
399
|
-
*
|
|
400
|
-
* host 는 렌더마다 `mode` 를 다시 내려 준다. 패널이 자기 손으로 고쳐 두면 다음 렌더가 그것을 덮고,
|
|
401
|
-
* 더 나쁜 것은 host 가 **자기가 무엇을 열었는지 모르는 채로 남는다**는 것이다. host 는 대화의 세션을
|
|
402
|
-
* 대상별로 들고 있으므로, 대화가 바뀐 것을 모르면 앞 대화의 세션을 그대로 넘긴다 — 그러면 새 말이 앞
|
|
403
|
-
* 대화에 붙는다(2026-09-18 Figure 레인이 :3300 에서 본 것이 이것이다).
|
|
404
|
-
*
|
|
405
|
-
* 그래서 주인은 하나다: host 가 `mode` 를 갖고, 패널은 「이것으로 바꿔 달라」고 말한다. 기억은 패널이
|
|
406
|
-
* 진다(브라우저마다 다른 값이라 host 가 질 것이 아니다) — 기억한 것을 되살릴 때도 같은 청으로 나간다.
|
|
407
|
-
*/
|
|
408
|
-
private askHostForMode;
|
|
409
|
-
/** 이 subject 에서 지난번에 고른 대화 — 없으면 undefined. */
|
|
410
|
-
private rememberedMode;
|
|
411
374
|
private loadModeView;
|
|
412
375
|
private cachedCatalogCandidates;
|
|
413
376
|
private cachedSlashCandidates;
|
|
@@ -419,12 +382,7 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
419
382
|
private userFetchToken;
|
|
420
383
|
static styles: import("lit").CSSResult[];
|
|
421
384
|
connectedCallback(): void;
|
|
422
|
-
/**
|
|
423
|
-
* 화면이 보여 주는 것의 종류가 바뀌었다 — 설 수 있는 대화를 다시 묻고, 지난번 고른 것을 되살린다.
|
|
424
|
-
*
|
|
425
|
-
* 되살리는 것은 **고른 대화**이지 세션이 아니다. 세션은 자기가 어느 대화였는지 이미 알고, 다른
|
|
426
|
-
* 대화로 가면 그때 새로 난다(ADR-0072 A17 ①).
|
|
427
|
-
*/
|
|
385
|
+
/** 화면이 보여 주는 것의 종류가 바뀌었다 — 사업장의 AI 에이전트 목록을 다시 읽는다. */
|
|
428
386
|
private onSubjectChanged;
|
|
429
387
|
updated(changed: Map<string, any>): void;
|
|
430
388
|
disconnectedCallback(): void;
|
|
@@ -478,6 +436,8 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
478
436
|
private onKeyDown;
|
|
479
437
|
/** The request a Tab writes into the empty box - `composerSuggestion` decides it. */
|
|
480
438
|
private get suggestion();
|
|
439
|
+
/** The next request the last answer named - gone once the person sends or the conversation changes. */
|
|
440
|
+
private nextRequest?;
|
|
481
441
|
/** input 이벤트 — value 즉시 반영 + popup 갱신은 150ms 디바운스 (F-Finder 와 동일).
|
|
482
442
|
* big board 에서 매 keystroke filter 비용 절감 + 한국어 IME 조합 중 깜빡임 완화. */
|
|
483
443
|
private onTextareaInput;
|
|
@@ -525,36 +485,12 @@ export declare class OxAssistantChat extends LitElement {
|
|
|
525
485
|
private static readonly EXAMPLE_GROUPS;
|
|
526
486
|
/** 보드의 세션 탭 — 다중 세션 활성화 시 패널 상단. host 가 sessions 비워두면 표시 X.
|
|
527
487
|
* 레이블은 session.name 우선, 없으면 fallback "세션 N". */
|
|
528
|
-
/**
|
|
529
|
-
* 이 대화의 **주어** — 같은 화면 위에 두 대화가 설 때만 나온다(ADR-0072 A17 ①).
|
|
530
|
-
*
|
|
531
|
-
* 하나뿐이면 아무것도 그리지 않는다. 고를 것이 없는 고르개는 자리만 차지하고, 사람에게 「무언가
|
|
532
|
-
* 고를 것이 있다」는 잘못된 신호를 준다.
|
|
533
|
-
*
|
|
534
|
-
* ── 왜 탭 줄이 아닌가 (UX 레인, 2026-09-18) ───────────────────────────────
|
|
535
|
-
* 처음에는 칩 줄에 `role="tablist"` 를 달았다. 바로 아래가 세션 탭이고 그쪽도 tablist 라서, 화면
|
|
536
|
-
* 낭독기로 들으면 「탭 목록, 대화」 다음에 「탭 목록, Conversation history」 가 이어졌다 — 고르는
|
|
537
|
-
* 축이 둘인데 둘 다 「탭」이라고 말한다. 게다가 ARIA 탭은 좌우 화살표 이동과 `tabpanel` 이 함께
|
|
538
|
-
* 있어야 성립하고, 둘 다 없으면 낭독기는 「2 중 1번째 탭」이라 말하고 화살표는 아무 일도 안 한다.
|
|
539
|
-
*
|
|
540
|
-
* 무게도 다르다. 세션은 「어느 지난 대화냐」이고 모드는 「이 대화가 무엇에 대한 것이냐」이다. 같은
|
|
541
|
-
* 무게로 그리면 어느 쪽이 화제를 바꾸고 어느 쪽이 실타래를 바꾸는지 구별되지 않는다. 그래서 지금
|
|
542
|
-
* 대화가 제목 굵기로 서고, 바꿀 자리는 그 옆에 붙는다.
|
|
543
|
-
*
|
|
544
|
-
* ── 왜 도크 제목 줄로 안 올리나 (UX 레인 판정, 2026-09-18) ────────────────
|
|
545
|
-
* 도크 제목은 「지금 어디에 있나」(이 도형 · 이 현장 · 이 스테이션 화면)이고 이 줄은 「이 대화가
|
|
546
|
-
* 무엇에 대한 것이냐」이다. 대화의 주어는 대화 안에 있다. 그리고 세 도크가 각자 그리면 세 벌이 되고
|
|
547
|
-
* 그 세 벌은 갈린다 — `modeChoices` 를 아는 것도 여기다. 올리면 목록과 고른 것이 세 자리로 나갔다
|
|
548
|
-
* 돌아온다.
|
|
549
|
-
*/
|
|
550
|
-
private renderModeSubject;
|
|
551
488
|
/**
|
|
552
489
|
* Which AI answers the next turn - at the end of the row above the box, where the words about writing stand. A row of
|
|
553
490
|
* its own spent a line of the dock on one control. It is a property of the turn, not of the conversation: switching
|
|
554
491
|
* keeps the conversation and only the next answer comes from the other agent. Drawn only when there is a choice.
|
|
555
492
|
*/
|
|
556
493
|
private renderAgentPicker;
|
|
557
|
-
private renderModePicker;
|
|
558
494
|
private renderSessionTabs;
|
|
559
495
|
/** C4 roster — 세션 참여자 + online 표시. 참여자 2명 미만이면 숨김(혼자면 의미 없음). */
|
|
560
496
|
private renderRoster;
|
|
@@ -52,7 +52,7 @@ import { buildMentionCandidates, buildCatalogCandidates, buildSlashCandidates, b
|
|
|
52
52
|
* 뜨지 않는 것이 맞다.
|
|
53
53
|
*/
|
|
54
54
|
import { chatDefaults } from './chat-defaults.js';
|
|
55
|
-
import { isDifferentConversation, resolveConversation
|
|
55
|
+
import { isDifferentConversation, resolveConversation } from './assistant-mode.js';
|
|
56
56
|
import { parseProposalOutcomeLine, parseProposalRequestLine, proposalCardState } from './proposal-outcome-line.js';
|
|
57
57
|
import './entry-review.js';
|
|
58
58
|
import { entryGroupOf, entryReadyCount, entryTitle, isChangeProposal, isEntryProposal, latestEntryKeys } from '../utils/entry-proposal.js';
|
|
@@ -69,6 +69,7 @@ const BOARD_AI_CHAT_MUTATION = gql `
|
|
|
69
69
|
mutation BoardAIChat($input: BoardAIChatInput!) {
|
|
70
70
|
boardAIChat(input: $input) {
|
|
71
71
|
reply
|
|
72
|
+
suggestion
|
|
72
73
|
patch
|
|
73
74
|
followUp
|
|
74
75
|
clientId
|
|
@@ -107,6 +108,7 @@ const ASSISTANT_CHAT_MUTATION = gql `
|
|
|
107
108
|
mutation AssistantChat($input: AssistantChatInput!) {
|
|
108
109
|
assistantChat(input: $input) {
|
|
109
110
|
reply
|
|
111
|
+
suggestion
|
|
110
112
|
clientId
|
|
111
113
|
sessionId
|
|
112
114
|
userMessageId
|
|
@@ -126,20 +128,6 @@ const ASSISTANT_CHAT_MUTATION = gql `
|
|
|
126
128
|
* (ADR-0061 decision 7) and translated there, so the panel and the door cannot describe different
|
|
127
129
|
* conversations, and a host does not carry a second copy of the words.
|
|
128
130
|
*/
|
|
129
|
-
/*
|
|
130
|
-
* 이 화면에 설 수 있는 대화들 — ADR-0072 A17 ①.
|
|
131
|
-
*
|
|
132
|
-
* 화면은 자기가 무엇을 보여 주는지만 말하고, 그 위에 어느 대화가 서는지는 앱 서버가 답한다.
|
|
133
|
-
*/
|
|
134
|
-
const ASSISTANT_MODES_QUERY = gql `
|
|
135
|
-
query AssistantModes($subject: String!) {
|
|
136
|
-
assistantModes(subject: $subject) {
|
|
137
|
-
kind
|
|
138
|
-
label
|
|
139
|
-
isDefault
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
`;
|
|
143
131
|
const AI_AGENT_CHOICES_QUERY = gql `
|
|
144
132
|
query AiAgentChoices {
|
|
145
133
|
aiAgentChoices {
|
|
@@ -354,9 +342,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
354
342
|
* send 시 mutation input.userMentions 로 함께 전송. */
|
|
355
343
|
this.pickedUserMentions = new Map();
|
|
356
344
|
this.modeViewToken = 0;
|
|
357
|
-
/** 이 subject 에 설 수 있는 대화들 — 기본이 먼저. 하나뿐이면 고르개를 그리지 않는다. */
|
|
358
|
-
this.modeChoices = [];
|
|
359
|
-
this.modeChoicesToken = 0;
|
|
360
345
|
/** The AI agents this domain lets a person pick (design/ai/agent-settings.md §5). One or none draws no picker. */
|
|
361
346
|
this.agentChoices = [];
|
|
362
347
|
this.agentChoicesToken = 0;
|
|
@@ -745,81 +730,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
745
730
|
this.agentId = agentId;
|
|
746
731
|
rememberAgent(this.contextPath, agentId);
|
|
747
732
|
}
|
|
748
|
-
/**
|
|
749
|
-
* 이 화면에 설 수 있는 대화를 서버에 묻는다.
|
|
750
|
-
*
|
|
751
|
-
* 화면이 종류 목록을 손에 들고 있으면, 대화를 하나 더 세운 날 그 화면만 모른 채로 남는다. 목록은
|
|
752
|
-
* 앱 서버의 선언에서 나온다(ADR-0072 A17 ①).
|
|
753
|
-
*/
|
|
754
|
-
async loadModeChoices() {
|
|
755
|
-
const subject = this.subject;
|
|
756
|
-
if (!subject) {
|
|
757
|
-
this.modeChoices = [];
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
const token = ++this.modeChoicesToken;
|
|
761
|
-
try {
|
|
762
|
-
const result = await client.query({
|
|
763
|
-
query: ASSISTANT_MODES_QUERY,
|
|
764
|
-
variables: { subject },
|
|
765
|
-
fetchPolicy: 'cache-first'
|
|
766
|
-
});
|
|
767
|
-
if (token !== this.modeChoicesToken)
|
|
768
|
-
return;
|
|
769
|
-
this.modeChoices = result.data?.assistantModes ?? [];
|
|
770
|
-
}
|
|
771
|
-
catch {
|
|
772
|
-
/*
|
|
773
|
-
* 못 물어봤다고 화면을 막지 않는다 — host 가 준 대화는 그대로 산다. 고르개만 안 나온다.
|
|
774
|
-
* 여기서 오류 문구를 띄우면 고를 것이 하나뿐인 화면에서도 빨간 줄이 뜬다.
|
|
775
|
-
*/
|
|
776
|
-
if (token !== this.modeChoicesToken)
|
|
777
|
-
return;
|
|
778
|
-
this.modeChoices = [];
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* 사람이 다른 대화를 골랐다.
|
|
783
|
-
*
|
|
784
|
-
* 고른 것은 **이 subject 종류에 대해** 기억한다 — 보드를 열 때마다 저작 대화로 돌아가면, 현장을
|
|
785
|
-
* 물으려던 사람은 화면을 열 때마다 같은 손질을 다시 해야 한다. 기억은 이 브라우저에만 남는다
|
|
786
|
-
* (서버에 칸을 만들지 않는다 — A17 ①). 세션 자체는 자기가 어느 대화였는지 이미 안다.
|
|
787
|
-
*/
|
|
788
|
-
onModePick(kind) {
|
|
789
|
-
if (!this.mode || kind === this.mode.kind)
|
|
790
|
-
return;
|
|
791
|
-
try {
|
|
792
|
-
if (this.subject)
|
|
793
|
-
localStorage.setItem(`assistant-mode:${this.subject}`, kind);
|
|
794
|
-
}
|
|
795
|
-
catch {
|
|
796
|
-
/* 저장이 막힌 브라우저에서도 고르기 자체는 된다 — 다음에 다시 고르면 될 뿐이다. */
|
|
797
|
-
}
|
|
798
|
-
this.askHostForMode(kind);
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* **고르는 것은 청하는 일이다** — `mode` 를 여기서 바꾸지 않는다.
|
|
802
|
-
*
|
|
803
|
-
* host 는 렌더마다 `mode` 를 다시 내려 준다. 패널이 자기 손으로 고쳐 두면 다음 렌더가 그것을 덮고,
|
|
804
|
-
* 더 나쁜 것은 host 가 **자기가 무엇을 열었는지 모르는 채로 남는다**는 것이다. host 는 대화의 세션을
|
|
805
|
-
* 대상별로 들고 있으므로, 대화가 바뀐 것을 모르면 앞 대화의 세션을 그대로 넘긴다 — 그러면 새 말이 앞
|
|
806
|
-
* 대화에 붙는다(2026-09-18 Figure 레인이 :3300 에서 본 것이 이것이다).
|
|
807
|
-
*
|
|
808
|
-
* 그래서 주인은 하나다: host 가 `mode` 를 갖고, 패널은 「이것으로 바꿔 달라」고 말한다. 기억은 패널이
|
|
809
|
-
* 진다(브라우저마다 다른 값이라 host 가 질 것이 아니다) — 기억한 것을 되살릴 때도 같은 청으로 나간다.
|
|
810
|
-
*/
|
|
811
|
-
askHostForMode(kind) {
|
|
812
|
-
this.dispatchEvent(new CustomEvent('assistant-mode-change', { detail: { kind, subject: this.subject }, bubbles: true, composed: true }));
|
|
813
|
-
}
|
|
814
|
-
/** 이 subject 에서 지난번에 고른 대화 — 없으면 undefined. */
|
|
815
|
-
rememberedMode(subject) {
|
|
816
|
-
try {
|
|
817
|
-
return localStorage.getItem(`assistant-mode:${subject}`) ?? undefined;
|
|
818
|
-
}
|
|
819
|
-
catch {
|
|
820
|
-
return undefined;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
733
|
async loadModeView() {
|
|
824
734
|
const mode = this.mode;
|
|
825
735
|
if (!mode?.kind) {
|
|
@@ -877,31 +787,8 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
877
787
|
background: var(--md-sys-color-surface, #ffffff);
|
|
878
788
|
}
|
|
879
789
|
|
|
880
|
-
/*
|
|
881
|
-
|
|
882
|
-
지금 대화가 제목 굵기로 서고, 바꿀 자리가 그 옆에 붙는다. 세션 탭과 같은 무게로 그리지 않는다
|
|
883
|
-
(UX 레인, 2026-09-18) — 한쪽은 화제를 바꾸고 한쪽은 실타래를 바꾼다. */
|
|
884
|
-
.mode-subject {
|
|
885
|
-
display: flex;
|
|
886
|
-
align-items: baseline;
|
|
887
|
-
gap: 8px;
|
|
888
|
-
padding: 6px 12px;
|
|
889
|
-
border-bottom: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
|
|
890
|
-
}
|
|
891
|
-
.mode-now {
|
|
892
|
-
/* 고른 상태의 색은 세션 탭과 같은 계열이다 — 한 패널 안에서 「골랐다」가 두 색으로 나오면 읽는
|
|
893
|
-
사람은 두 색이 서로 다른 뜻인 줄 안다.
|
|
894
|
-
|
|
895
|
-
12px 이었다가 한 단 내렸다. 도크 제목(figure 의 .scope-title 이 12.5px / 650)과 0.5px ·
|
|
896
|
-
굵기 50 차이는 눈에 닿지 않아서, 거의 같은 제목 줄이 둘 포개졌다. 도크 제목은 그 도크 전체를
|
|
897
|
-
거느리고 이 줄은 그 안 대화 하나의 성질이다 — 11px 이면 이 패널의 다른 조작부와 같은 단이고,
|
|
898
|
-
「이것이 주어다」는 색이 계속 진다(UX 레인, 2026-09-18). */
|
|
899
|
-
color: var(--primary-color, #007f87);
|
|
900
|
-
font-size: 11px;
|
|
901
|
-
font-weight: 600;
|
|
902
|
-
}
|
|
903
|
-
.mode-switch,
|
|
904
|
-
.mode-select {
|
|
790
|
+
/* The agent sits at the end of the row above the box. */
|
|
791
|
+
.agent-select {
|
|
905
792
|
appearance: none;
|
|
906
793
|
background: transparent;
|
|
907
794
|
border: 1px solid var(--md-sys-color-outline-variant, #cbd5e1);
|
|
@@ -912,24 +799,12 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
912
799
|
font-size: 11px;
|
|
913
800
|
padding: 2px 10px;
|
|
914
801
|
white-space: nowrap;
|
|
915
|
-
}
|
|
916
|
-
/* The agent sits at the end of the row above the box. */
|
|
917
|
-
.agent-select {
|
|
918
802
|
margin-left: auto;
|
|
919
803
|
max-width: 50%;
|
|
920
804
|
text-overflow: ellipsis;
|
|
921
805
|
}
|
|
922
|
-
.mode-switch:hover:not([disabled]) {
|
|
923
|
-
background: color-mix(in srgb, var(--primary-color, #007f87) 7%, transparent);
|
|
924
|
-
color: var(--primary-color, #007f87);
|
|
925
|
-
}
|
|
926
|
-
/* 답하는 동안에도 「내가 지금 무엇에 대해 말하고 있나」는 또렷해야 한다 — 흐려지는 것은 바꿀
|
|
927
|
-
자리뿐이다. 지금 대화까지 흐리면 그 한 줄이 가장 안 보인다. */
|
|
928
|
-
.mode-subject [disabled] {
|
|
929
|
-
cursor: default;
|
|
930
|
-
opacity: 0.5;
|
|
931
|
-
}
|
|
932
806
|
|
|
807
|
+
/* ── C4 Roster (participants + presence) ───────────── */
|
|
933
808
|
.roster {
|
|
934
809
|
display: flex;
|
|
935
810
|
align-items: center;
|
|
@@ -2623,22 +2498,14 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2623
2498
|
this._startMessageSubscription();
|
|
2624
2499
|
}
|
|
2625
2500
|
}
|
|
2626
|
-
/**
|
|
2627
|
-
|
|
2628
|
-
*
|
|
2629
|
-
* 되살리는 것은 **고른 대화**이지 세션이 아니다. 세션은 자기가 어느 대화였는지 이미 알고, 다른
|
|
2630
|
-
* 대화로 가면 그때 새로 난다(ADR-0072 A17 ①).
|
|
2631
|
-
*/
|
|
2632
|
-
async onSubjectChanged() {
|
|
2501
|
+
/** 화면이 보여 주는 것의 종류가 바뀌었다 — 사업장의 AI 에이전트 목록을 다시 읽는다. */
|
|
2502
|
+
onSubjectChanged() {
|
|
2633
2503
|
void this.loadAgentChoices();
|
|
2634
|
-
await this.loadModeChoices();
|
|
2635
|
-
if (this.modeChoices.length < 2 || !this.subject || !this.mode)
|
|
2636
|
-
return;
|
|
2637
|
-
const kind = pickInitialMode(this.mode.kind, this.modeChoices, this.rememberedMode(this.subject));
|
|
2638
|
-
if (kind && kind !== this.mode.kind)
|
|
2639
|
-
this.askHostForMode(kind);
|
|
2640
2504
|
}
|
|
2641
2505
|
updated(changed) {
|
|
2506
|
+
/* Another conversation has no answer of this one to follow. */
|
|
2507
|
+
if (changed.has('sessionId') && changed.get('sessionId') !== undefined)
|
|
2508
|
+
this.nextRequest = undefined;
|
|
2642
2509
|
if (changed.has('input') || changed.has('composerWide') || changed.has('previewAttachmentId') || changed.has('attachments'))
|
|
2643
2510
|
this.fitComposer();
|
|
2644
2511
|
/*
|
|
@@ -2873,7 +2740,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2873
2740
|
${this.toastMessage
|
|
2874
2741
|
? html `<div class="toast show">${this.toastMessage}</div>`
|
|
2875
2742
|
: nothing}
|
|
2876
|
-
${this.renderModeSubject()}
|
|
2877
2743
|
${this.renderSessionTabs()}
|
|
2878
2744
|
${this.renderRoster()}
|
|
2879
2745
|
<div class="messages ${this.composerWide || this.previewedTable ? 'covered' : ''}" @click=${this.onBubbleClick}>
|
|
@@ -3413,7 +3279,12 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3413
3279
|
}
|
|
3414
3280
|
/** The request a Tab writes into the empty box - `composerSuggestion` decides it. */
|
|
3415
3281
|
get suggestion() {
|
|
3416
|
-
return composerSuggestion({
|
|
3282
|
+
return composerSuggestion({
|
|
3283
|
+
started: this.lines.length > 0,
|
|
3284
|
+
modeExamples: this.modeView?.examples,
|
|
3285
|
+
hostExamples: this.examples,
|
|
3286
|
+
answered: this.nextRequest
|
|
3287
|
+
});
|
|
3417
3288
|
}
|
|
3418
3289
|
/** input 이벤트 — value 즉시 반영 + popup 갱신은 150ms 디바운스 (F-Finder 와 동일).
|
|
3419
3290
|
* big board 에서 매 keystroke filter 비용 절감 + 한국어 IME 조합 중 깜빡임 완화. */
|
|
@@ -3691,33 +3562,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3691
3562
|
]; }
|
|
3692
3563
|
/** 보드의 세션 탭 — 다중 세션 활성화 시 패널 상단. host 가 sessions 비워두면 표시 X.
|
|
3693
3564
|
* 레이블은 session.name 우선, 없으면 fallback "세션 N". */
|
|
3694
|
-
/**
|
|
3695
|
-
* 이 대화의 **주어** — 같은 화면 위에 두 대화가 설 때만 나온다(ADR-0072 A17 ①).
|
|
3696
|
-
*
|
|
3697
|
-
* 하나뿐이면 아무것도 그리지 않는다. 고를 것이 없는 고르개는 자리만 차지하고, 사람에게 「무언가
|
|
3698
|
-
* 고를 것이 있다」는 잘못된 신호를 준다.
|
|
3699
|
-
*
|
|
3700
|
-
* ── 왜 탭 줄이 아닌가 (UX 레인, 2026-09-18) ───────────────────────────────
|
|
3701
|
-
* 처음에는 칩 줄에 `role="tablist"` 를 달았다. 바로 아래가 세션 탭이고 그쪽도 tablist 라서, 화면
|
|
3702
|
-
* 낭독기로 들으면 「탭 목록, 대화」 다음에 「탭 목록, Conversation history」 가 이어졌다 — 고르는
|
|
3703
|
-
* 축이 둘인데 둘 다 「탭」이라고 말한다. 게다가 ARIA 탭은 좌우 화살표 이동과 `tabpanel` 이 함께
|
|
3704
|
-
* 있어야 성립하고, 둘 다 없으면 낭독기는 「2 중 1번째 탭」이라 말하고 화살표는 아무 일도 안 한다.
|
|
3705
|
-
*
|
|
3706
|
-
* 무게도 다르다. 세션은 「어느 지난 대화냐」이고 모드는 「이 대화가 무엇에 대한 것이냐」이다. 같은
|
|
3707
|
-
* 무게로 그리면 어느 쪽이 화제를 바꾸고 어느 쪽이 실타래를 바꾸는지 구별되지 않는다. 그래서 지금
|
|
3708
|
-
* 대화가 제목 굵기로 서고, 바꿀 자리는 그 옆에 붙는다.
|
|
3709
|
-
*
|
|
3710
|
-
* ── 왜 도크 제목 줄로 안 올리나 (UX 레인 판정, 2026-09-18) ────────────────
|
|
3711
|
-
* 도크 제목은 「지금 어디에 있나」(이 도형 · 이 현장 · 이 스테이션 화면)이고 이 줄은 「이 대화가
|
|
3712
|
-
* 무엇에 대한 것이냐」이다. 대화의 주어는 대화 안에 있다. 그리고 세 도크가 각자 그리면 세 벌이 되고
|
|
3713
|
-
* 그 세 벌은 갈린다 — `modeChoices` 를 아는 것도 여기다. 올리면 목록과 고른 것이 세 자리로 나갔다
|
|
3714
|
-
* 돌아온다.
|
|
3715
|
-
*/
|
|
3716
|
-
renderModeSubject() {
|
|
3717
|
-
if (this.modeChoices.length < 2)
|
|
3718
|
-
return nothing;
|
|
3719
|
-
return html `<div class="mode-subject">${this.renderModePicker()}</div>`;
|
|
3720
|
-
}
|
|
3721
3565
|
/**
|
|
3722
3566
|
* Which AI answers the next turn - at the end of the row above the box, where the words about writing stand. A row of
|
|
3723
3567
|
* its own spent a line of the dock on one control. It is a property of the turn, not of the conversation: switching
|
|
@@ -3729,7 +3573,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3729
3573
|
const current = this.agentChoices.find(one => one.id === this.agentId);
|
|
3730
3574
|
return html `
|
|
3731
3575
|
<select
|
|
3732
|
-
class="
|
|
3576
|
+
class="agent-select"
|
|
3733
3577
|
aria-label=${i18next.t('ai-assistant.label.ai-agent', { defaultValue: 'AI agent' })}
|
|
3734
3578
|
title=${current ? agentDetail(current) : ''}
|
|
3735
3579
|
?disabled=${this.busy}
|
|
@@ -3738,33 +3582,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3738
3582
|
</select>
|
|
3739
3583
|
`;
|
|
3740
3584
|
}
|
|
3741
|
-
renderModePicker() {
|
|
3742
|
-
const current = this.modeChoices.find(choice => choice.kind === this.mode?.kind);
|
|
3743
|
-
const others = this.modeChoices.filter(choice => choice.kind !== this.mode?.kind);
|
|
3744
|
-
return html `
|
|
3745
|
-
<span class="mode-now">${current?.label ?? this.mode?.kind ?? ''}</span>
|
|
3746
|
-
${others.length === 1
|
|
3747
|
-
? html `
|
|
3748
|
-
<button
|
|
3749
|
-
class="mode-switch"
|
|
3750
|
-
title=${i18next.t('ai-assistant.text.switch-conversation', { name: others[0].label })}
|
|
3751
|
-
aria-label=${i18next.t('ai-assistant.text.switch-conversation', { name: others[0].label })}
|
|
3752
|
-
?disabled=${this.busy}
|
|
3753
|
-
@click=${() => this.onModePick(others[0].kind)}>
|
|
3754
|
-
${others[0].label}
|
|
3755
|
-
</button>
|
|
3756
|
-
`
|
|
3757
|
-
: html `
|
|
3758
|
-
<select
|
|
3759
|
-
class="mode-select"
|
|
3760
|
-
aria-label=${i18next.t('ai-assistant.label.conversation')}
|
|
3761
|
-
?disabled=${this.busy}
|
|
3762
|
-
@change=${(event) => this.onModePick(event.target.value)}>
|
|
3763
|
-
${this.modeChoices.map(choice => html `<option value=${choice.kind} ?selected=${choice.kind === this.mode?.kind}>${choice.label}</option>`)}
|
|
3764
|
-
</select>
|
|
3765
|
-
`}
|
|
3766
|
-
`;
|
|
3767
|
-
}
|
|
3768
3585
|
renderSessionTabs() {
|
|
3769
3586
|
if (!this.sessions || this.sessions.length === 0)
|
|
3770
3587
|
return nothing;
|
|
@@ -4572,6 +4389,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4572
4389
|
const attachmentsToSend = [...this.attachments];
|
|
4573
4390
|
if ((!text && attachmentsToSend.length === 0) || this.busy)
|
|
4574
4391
|
return;
|
|
4392
|
+
this.nextRequest = undefined;
|
|
4575
4393
|
this.attachments = [];
|
|
4576
4394
|
/* What was written wide is sent to be answered - the conversation comes back to show it. */
|
|
4577
4395
|
this.composerWide = false;
|
|
@@ -4752,6 +4570,8 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4752
4570
|
throw new Error(i18next.t('ai-assistant.text.empty-response'));
|
|
4753
4571
|
/* 대화가 길어졌다는 서버 보고 — 닫지 않았다면 안내를 띄운다. */
|
|
4754
4572
|
this.historyFolded = out.historyFolded ?? undefined;
|
|
4573
|
+
/* The answer's next request - the input shows it and Tab writes it in (design/ai/screen-pattern.md §4-1). */
|
|
4574
|
+
this.nextRequest = typeof out.suggestion === 'string' && out.suggestion.trim() ? out.suggestion.trim() : undefined;
|
|
4755
4575
|
// 전송 성공 — picks 정리 (다음 메시지부터는 새 picks 만)
|
|
4756
4576
|
this.pickedMentions.clear();
|
|
4757
4577
|
this.pickedUserMentions.clear();
|
|
@@ -5043,10 +4863,6 @@ __decorate([
|
|
|
5043
4863
|
state(),
|
|
5044
4864
|
__metadata("design:type", Object)
|
|
5045
4865
|
], OxAssistantChat.prototype, "modeView", void 0);
|
|
5046
|
-
__decorate([
|
|
5047
|
-
state(),
|
|
5048
|
-
__metadata("design:type", Array)
|
|
5049
|
-
], OxAssistantChat.prototype, "modeChoices", void 0);
|
|
5050
4866
|
__decorate([
|
|
5051
4867
|
state(),
|
|
5052
4868
|
__metadata("design:type", Array)
|
|
@@ -5055,6 +4871,10 @@ __decorate([
|
|
|
5055
4871
|
state(),
|
|
5056
4872
|
__metadata("design:type", String)
|
|
5057
4873
|
], OxAssistantChat.prototype, "agentId", void 0);
|
|
4874
|
+
__decorate([
|
|
4875
|
+
state(),
|
|
4876
|
+
__metadata("design:type", String)
|
|
4877
|
+
], OxAssistantChat.prototype, "nextRequest", void 0);
|
|
5058
4878
|
OxAssistantChat = OxAssistantChat_1 = __decorate([
|
|
5059
4879
|
customElement('ox-assistant-chat')
|
|
5060
4880
|
], OxAssistantChat);
|