@things-factory/ai-assistant 10.1.157 → 10.1.159
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 +24 -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 +24 -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';
|
|
@@ -107,6 +107,7 @@ const ASSISTANT_CHAT_MUTATION = gql `
|
|
|
107
107
|
mutation AssistantChat($input: AssistantChatInput!) {
|
|
108
108
|
assistantChat(input: $input) {
|
|
109
109
|
reply
|
|
110
|
+
suggestion
|
|
110
111
|
clientId
|
|
111
112
|
sessionId
|
|
112
113
|
userMessageId
|
|
@@ -126,20 +127,6 @@ const ASSISTANT_CHAT_MUTATION = gql `
|
|
|
126
127
|
* (ADR-0061 decision 7) and translated there, so the panel and the door cannot describe different
|
|
127
128
|
* conversations, and a host does not carry a second copy of the words.
|
|
128
129
|
*/
|
|
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
130
|
const AI_AGENT_CHOICES_QUERY = gql `
|
|
144
131
|
query AiAgentChoices {
|
|
145
132
|
aiAgentChoices {
|
|
@@ -354,9 +341,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
354
341
|
* send 시 mutation input.userMentions 로 함께 전송. */
|
|
355
342
|
this.pickedUserMentions = new Map();
|
|
356
343
|
this.modeViewToken = 0;
|
|
357
|
-
/** 이 subject 에 설 수 있는 대화들 — 기본이 먼저. 하나뿐이면 고르개를 그리지 않는다. */
|
|
358
|
-
this.modeChoices = [];
|
|
359
|
-
this.modeChoicesToken = 0;
|
|
360
344
|
/** The AI agents this domain lets a person pick (design/ai/agent-settings.md §5). One or none draws no picker. */
|
|
361
345
|
this.agentChoices = [];
|
|
362
346
|
this.agentChoicesToken = 0;
|
|
@@ -745,81 +729,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
745
729
|
this.agentId = agentId;
|
|
746
730
|
rememberAgent(this.contextPath, agentId);
|
|
747
731
|
}
|
|
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
732
|
async loadModeView() {
|
|
824
733
|
const mode = this.mode;
|
|
825
734
|
if (!mode?.kind) {
|
|
@@ -877,31 +786,8 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
877
786
|
background: var(--md-sys-color-surface, #ffffff);
|
|
878
787
|
}
|
|
879
788
|
|
|
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 {
|
|
789
|
+
/* The agent sits at the end of the row above the box. */
|
|
790
|
+
.agent-select {
|
|
905
791
|
appearance: none;
|
|
906
792
|
background: transparent;
|
|
907
793
|
border: 1px solid var(--md-sys-color-outline-variant, #cbd5e1);
|
|
@@ -912,24 +798,12 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
912
798
|
font-size: 11px;
|
|
913
799
|
padding: 2px 10px;
|
|
914
800
|
white-space: nowrap;
|
|
915
|
-
}
|
|
916
|
-
/* The agent sits at the end of the row above the box. */
|
|
917
|
-
.agent-select {
|
|
918
801
|
margin-left: auto;
|
|
919
802
|
max-width: 50%;
|
|
920
803
|
text-overflow: ellipsis;
|
|
921
804
|
}
|
|
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
805
|
|
|
806
|
+
/* ── C4 Roster (participants + presence) ───────────── */
|
|
933
807
|
.roster {
|
|
934
808
|
display: flex;
|
|
935
809
|
align-items: center;
|
|
@@ -2623,22 +2497,14 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2623
2497
|
this._startMessageSubscription();
|
|
2624
2498
|
}
|
|
2625
2499
|
}
|
|
2626
|
-
/**
|
|
2627
|
-
|
|
2628
|
-
*
|
|
2629
|
-
* 되살리는 것은 **고른 대화**이지 세션이 아니다. 세션은 자기가 어느 대화였는지 이미 알고, 다른
|
|
2630
|
-
* 대화로 가면 그때 새로 난다(ADR-0072 A17 ①).
|
|
2631
|
-
*/
|
|
2632
|
-
async onSubjectChanged() {
|
|
2500
|
+
/** 화면이 보여 주는 것의 종류가 바뀌었다 — 사업장의 AI 에이전트 목록을 다시 읽는다. */
|
|
2501
|
+
onSubjectChanged() {
|
|
2633
2502
|
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
2503
|
}
|
|
2641
2504
|
updated(changed) {
|
|
2505
|
+
/* Another conversation has no answer of this one to follow. */
|
|
2506
|
+
if (changed.has('sessionId') && changed.get('sessionId') !== undefined)
|
|
2507
|
+
this.nextRequest = undefined;
|
|
2642
2508
|
if (changed.has('input') || changed.has('composerWide') || changed.has('previewAttachmentId') || changed.has('attachments'))
|
|
2643
2509
|
this.fitComposer();
|
|
2644
2510
|
/*
|
|
@@ -2873,7 +2739,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2873
2739
|
${this.toastMessage
|
|
2874
2740
|
? html `<div class="toast show">${this.toastMessage}</div>`
|
|
2875
2741
|
: nothing}
|
|
2876
|
-
${this.renderModeSubject()}
|
|
2877
2742
|
${this.renderSessionTabs()}
|
|
2878
2743
|
${this.renderRoster()}
|
|
2879
2744
|
<div class="messages ${this.composerWide || this.previewedTable ? 'covered' : ''}" @click=${this.onBubbleClick}>
|
|
@@ -3413,7 +3278,12 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3413
3278
|
}
|
|
3414
3279
|
/** The request a Tab writes into the empty box - `composerSuggestion` decides it. */
|
|
3415
3280
|
get suggestion() {
|
|
3416
|
-
return composerSuggestion({
|
|
3281
|
+
return composerSuggestion({
|
|
3282
|
+
started: this.lines.length > 0,
|
|
3283
|
+
modeExamples: this.modeView?.examples,
|
|
3284
|
+
hostExamples: this.examples,
|
|
3285
|
+
answered: this.nextRequest
|
|
3286
|
+
});
|
|
3417
3287
|
}
|
|
3418
3288
|
/** input 이벤트 — value 즉시 반영 + popup 갱신은 150ms 디바운스 (F-Finder 와 동일).
|
|
3419
3289
|
* big board 에서 매 keystroke filter 비용 절감 + 한국어 IME 조합 중 깜빡임 완화. */
|
|
@@ -3691,33 +3561,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3691
3561
|
]; }
|
|
3692
3562
|
/** 보드의 세션 탭 — 다중 세션 활성화 시 패널 상단. host 가 sessions 비워두면 표시 X.
|
|
3693
3563
|
* 레이블은 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
3564
|
/**
|
|
3722
3565
|
* 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
3566
|
* 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 +3572,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3729
3572
|
const current = this.agentChoices.find(one => one.id === this.agentId);
|
|
3730
3573
|
return html `
|
|
3731
3574
|
<select
|
|
3732
|
-
class="
|
|
3575
|
+
class="agent-select"
|
|
3733
3576
|
aria-label=${i18next.t('ai-assistant.label.ai-agent', { defaultValue: 'AI agent' })}
|
|
3734
3577
|
title=${current ? agentDetail(current) : ''}
|
|
3735
3578
|
?disabled=${this.busy}
|
|
@@ -3738,33 +3581,6 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3738
3581
|
</select>
|
|
3739
3582
|
`;
|
|
3740
3583
|
}
|
|
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
3584
|
renderSessionTabs() {
|
|
3769
3585
|
if (!this.sessions || this.sessions.length === 0)
|
|
3770
3586
|
return nothing;
|
|
@@ -4572,6 +4388,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4572
4388
|
const attachmentsToSend = [...this.attachments];
|
|
4573
4389
|
if ((!text && attachmentsToSend.length === 0) || this.busy)
|
|
4574
4390
|
return;
|
|
4391
|
+
this.nextRequest = undefined;
|
|
4575
4392
|
this.attachments = [];
|
|
4576
4393
|
/* What was written wide is sent to be answered - the conversation comes back to show it. */
|
|
4577
4394
|
this.composerWide = false;
|
|
@@ -4752,6 +4569,8 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
4752
4569
|
throw new Error(i18next.t('ai-assistant.text.empty-response'));
|
|
4753
4570
|
/* 대화가 길어졌다는 서버 보고 — 닫지 않았다면 안내를 띄운다. */
|
|
4754
4571
|
this.historyFolded = out.historyFolded ?? undefined;
|
|
4572
|
+
/* The answer's next request - the input shows it and Tab writes it in (design/ai/screen-pattern.md §4-1). */
|
|
4573
|
+
this.nextRequest = typeof out.suggestion === 'string' && out.suggestion.trim() ? out.suggestion.trim() : undefined;
|
|
4755
4574
|
// 전송 성공 — picks 정리 (다음 메시지부터는 새 picks 만)
|
|
4756
4575
|
this.pickedMentions.clear();
|
|
4757
4576
|
this.pickedUserMentions.clear();
|
|
@@ -5043,10 +4862,6 @@ __decorate([
|
|
|
5043
4862
|
state(),
|
|
5044
4863
|
__metadata("design:type", Object)
|
|
5045
4864
|
], OxAssistantChat.prototype, "modeView", void 0);
|
|
5046
|
-
__decorate([
|
|
5047
|
-
state(),
|
|
5048
|
-
__metadata("design:type", Array)
|
|
5049
|
-
], OxAssistantChat.prototype, "modeChoices", void 0);
|
|
5050
4865
|
__decorate([
|
|
5051
4866
|
state(),
|
|
5052
4867
|
__metadata("design:type", Array)
|
|
@@ -5055,6 +4870,10 @@ __decorate([
|
|
|
5055
4870
|
state(),
|
|
5056
4871
|
__metadata("design:type", String)
|
|
5057
4872
|
], OxAssistantChat.prototype, "agentId", void 0);
|
|
4873
|
+
__decorate([
|
|
4874
|
+
state(),
|
|
4875
|
+
__metadata("design:type", String)
|
|
4876
|
+
], OxAssistantChat.prototype, "nextRequest", void 0);
|
|
5058
4877
|
OxAssistantChat = OxAssistantChat_1 = __decorate([
|
|
5059
4878
|
customElement('ox-assistant-chat')
|
|
5060
4879
|
], OxAssistantChat);
|