@things-factory/ai-assistant 10.1.8 → 10.1.13
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-session-toolbar.ts +70 -0
- package/client/components/board-ai-chat.ts +50 -41
- package/client/index.ts +4 -0
- package/client/utils/assistant-request-context.ts +21 -0
- package/client/utils/assistant-session-controller.ts +166 -0
- package/client/utils/assistant-session-transport.ts +34 -0
- package/dist-client/components/assistant-session-toolbar.d.ts +18 -0
- package/dist-client/components/assistant-session-toolbar.js +109 -0
- package/dist-client/components/assistant-session-toolbar.js.map +1 -0
- package/dist-client/components/board-ai-chat.d.ts +6 -0
- package/dist-client/components/board-ai-chat.js +62 -40
- package/dist-client/components/board-ai-chat.js.map +1 -1
- package/dist-client/index.d.ts +4 -0
- package/dist-client/index.js +4 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-request-context.d.ts +10 -0
- package/dist-client/utils/assistant-request-context.js +20 -0
- package/dist-client/utils/assistant-request-context.js.map +1 -0
- package/dist-client/utils/assistant-session-controller.d.ts +49 -0
- package/dist-client/utils/assistant-session-controller.js +165 -0
- package/dist-client/utils/assistant-session-controller.js.map +1 -0
- package/dist-client/utils/assistant-session-transport.d.ts +3 -0
- package/dist-client/utils/assistant-session-transport.js +39 -0
- package/dist-client/utils/assistant-session-transport.js.map +1 -0
- package/dist-server/service/assistant-chat-resolver.js +8 -0
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.js +1 -1
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-anchor.d.ts +1 -1
- package/dist-server/service/chat-session/session-anchor.js +3 -1
- package/dist-server/service/chat-session/session-anchor.js.map +1 -1
- package/dist-server/service/chat-session/station-session-guard.d.ts +12 -0
- package/dist-server/service/chat-session/station-session-guard.js +20 -0
- package/dist-server/service/chat-session/station-session-guard.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/docs/session-controller.md +52 -0
- package/package.json +2 -2
- package/server/service/assistant-chat-resolver.ts +9 -0
- package/server/service/chat-session/chat-session.ts +1 -1
- package/server/service/chat-session/session-anchor.ts +3 -1
- package/server/service/chat-session/station-session-guard.ts +17 -0
- package/server/service/session-anchor.test.ts +11 -0
- package/server/service/station-session-guard.test.ts +24 -0
- package/test/assistant-request-context.test.ts +30 -0
- package/test/assistant-session-controller.test.ts +88 -0
- package/test/assistant-session-toolbar.test.ts +29 -0
- package/translations/en.json +6 -0
- package/translations/ko.json +6 -0
|
@@ -14,12 +14,16 @@
|
|
|
14
14
|
* 모드 전환은 컨테이너 (워크스페이스) 의 책임. 이 컴포넌트는 자체로 풀 채팅 UX.
|
|
15
15
|
*/
|
|
16
16
|
import '@material/web/icon/icon.js';
|
|
17
|
+
import './assistant-session-toolbar.js';
|
|
18
|
+
import { type AssistantRequestContext } from '../utils/assistant-request-context.js';
|
|
17
19
|
import { LitElement } from 'lit';
|
|
18
20
|
import './mention-popup.js';
|
|
19
21
|
import { type FilterResult } from './mention-popup.js';
|
|
20
22
|
export declare class OxBoardAIChat extends LitElement {
|
|
21
23
|
/** 영속 세션 id. 없으면 ad-hoc (메시지 영속 안 됨). */
|
|
22
24
|
sessionId?: string;
|
|
25
|
+
prepareSession?: (adopt: (id: string) => void) => Promise<string | undefined>;
|
|
26
|
+
private adoptingSession;
|
|
23
27
|
/**
|
|
24
28
|
* 보드의 모든 ChatSession 리스트 — 다중 세션 활성화 시 탭 UI 로 표시.
|
|
25
29
|
*
|
|
@@ -138,6 +142,8 @@ export declare class OxBoardAIChat extends LitElement {
|
|
|
138
142
|
* 값이 들어오면 그 문장을 입력으로 삼아 한 번 전송하고 비운다(같은 질문이 반복 전송되지 않게).
|
|
139
143
|
*/
|
|
140
144
|
autoAsk?: string;
|
|
145
|
+
autoAskRequestId?: string;
|
|
146
|
+
contextProvider?: () => AssistantRequestContext;
|
|
141
147
|
/**
|
|
142
148
|
* 빈 화면의 제목·부제 — **호스트가 자기 말로 준다.**
|
|
143
149
|
*
|
|
@@ -16,6 +16,8 @@ import { __decorate, __metadata } from "tslib";
|
|
|
16
16
|
* 모드 전환은 컨테이너 (워크스페이스) 의 책임. 이 컴포넌트는 자체로 풀 채팅 UX.
|
|
17
17
|
*/
|
|
18
18
|
import '@material/web/icon/icon.js';
|
|
19
|
+
import './assistant-session-toolbar.js';
|
|
20
|
+
import { assistantAutoAskKey, assistantModelFingerprint } from '../utils/assistant-request-context.js';
|
|
19
21
|
import { LitElement, css, html, nothing } from 'lit';
|
|
20
22
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
21
23
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
@@ -155,6 +157,7 @@ function initials(name) {
|
|
|
155
157
|
let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
156
158
|
constructor() {
|
|
157
159
|
super(...arguments);
|
|
160
|
+
this.adoptingSession = false;
|
|
158
161
|
/**
|
|
159
162
|
* 보드의 모든 ChatSession 리스트 — 다중 세션 활성화 시 탭 UI 로 표시.
|
|
160
163
|
*
|
|
@@ -282,6 +285,8 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
282
285
|
}
|
|
283
286
|
};
|
|
284
287
|
this._onSessionCreateClick = () => {
|
|
288
|
+
if (this.busy || this.lines.length === 0)
|
|
289
|
+
return;
|
|
285
290
|
this.dispatchEvent(new CustomEvent('session-create', {
|
|
286
291
|
bubbles: true,
|
|
287
292
|
composed: true
|
|
@@ -1601,12 +1606,17 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
1601
1606
|
* sessionId 가 붙기 전에 보내면 대화에 남지 않으므로 세션이 준비될 때까지 기다린다.
|
|
1602
1607
|
* 같은 문장을 두 번 보내지 않도록 보낸 값을 기억한다 — 재렌더마다 updated 가 다시 돌기 때문이다.
|
|
1603
1608
|
*/
|
|
1604
|
-
if (this.autoAsk && this.autoAsk !== this._autoAsked && this.sessionId && !this.busy) {
|
|
1605
|
-
this._autoAsked = this.autoAsk;
|
|
1609
|
+
if (this.autoAsk && assistantAutoAskKey(this.autoAsk, this.autoAskRequestId) !== this._autoAsked && (this.sessionId || this.prepareSession) && !this.busy) {
|
|
1610
|
+
this._autoAsked = assistantAutoAskKey(this.autoAsk, this.autoAskRequestId);
|
|
1606
1611
|
this.input = this.autoAsk;
|
|
1607
1612
|
this.send();
|
|
1608
1613
|
}
|
|
1609
|
-
if (changed.has('sessionId')) {
|
|
1614
|
+
if (changed.has('sessionId') && this.adoptingSession) {
|
|
1615
|
+
this.adoptingSession = false;
|
|
1616
|
+
this._startPresence();
|
|
1617
|
+
this._startMessageSubscription();
|
|
1618
|
+
}
|
|
1619
|
+
else if (changed.has('sessionId')) {
|
|
1610
1620
|
// sessionId 가 바뀌면 (set / unset / 다른 보드 전환) 이전 세션의 모든 클라이언트
|
|
1611
1621
|
// 컨텍스트 정리 — lines / pickedMentions / errorMessage / input 모두 새지 않도록.
|
|
1612
1622
|
// 보드 단위 격리 (ChatSession 1:1 boardId) 가 깨지는 회귀 방지.
|
|
@@ -2232,38 +2242,11 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2232
2242
|
if (!this.sessions || this.sessions.length === 0)
|
|
2233
2243
|
return nothing;
|
|
2234
2244
|
return html `
|
|
2235
|
-
<
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
defaultValue: `세션 ${i + 1}`
|
|
2241
|
-
});
|
|
2242
|
-
const label = s.name?.trim() || fallback;
|
|
2243
|
-
const tooltip = s.createdAt
|
|
2244
|
-
? `${label} · ${new Date(s.createdAt).toLocaleString()}`
|
|
2245
|
-
: label;
|
|
2246
|
-
return html `
|
|
2247
|
-
<button
|
|
2248
|
-
class="session-tab ${isActive ? 'active' : ''}"
|
|
2249
|
-
role="tab"
|
|
2250
|
-
aria-selected=${isActive ? 'true' : 'false'}
|
|
2251
|
-
title=${tooltip}
|
|
2252
|
-
@click=${() => this._onSessionTabClick(s.id)}>
|
|
2253
|
-
${label}
|
|
2254
|
-
</button>
|
|
2255
|
-
`;
|
|
2256
|
-
})}
|
|
2257
|
-
<button
|
|
2258
|
-
class="session-tab-new"
|
|
2259
|
-
title=${i18next.t('ai-assistant.button.new-chat', {
|
|
2260
|
-
defaultValue: '새 대화'
|
|
2261
|
-
})}
|
|
2262
|
-
@click=${this._onSessionCreateClick}>
|
|
2263
|
-
<md-icon>add</md-icon>
|
|
2264
|
-
<span>${i18next.t('ai-assistant.button.new-chat', { defaultValue: '새 대화' })}</span>
|
|
2265
|
-
</button>
|
|
2266
|
-
</div>
|
|
2245
|
+
<ox-assistant-session-toolbar style="padding:8px 12px" .sessions=${this.sessions}
|
|
2246
|
+
.sessionId=${this.sessionId || ''} .disabled=${this.busy}
|
|
2247
|
+
@assistant-session-select=${(event) => { event.stopPropagation(); this._onSessionTabClick(event.detail.sessionId); }}
|
|
2248
|
+
@assistant-session-reset=${(event) => { event.stopPropagation(); this._onSessionCreateClick(); }}
|
|
2249
|
+
></ox-assistant-session-toolbar>
|
|
2267
2250
|
`;
|
|
2268
2251
|
}
|
|
2269
2252
|
/** C4 roster — 세션 참여자 + online 표시. 참여자 2명 미만이면 숨김(혼자면 의미 없음). */
|
|
@@ -2796,6 +2779,33 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2796
2779
|
const text = this.input.trim();
|
|
2797
2780
|
if (!text || this.busy)
|
|
2798
2781
|
return;
|
|
2782
|
+
if (!this.sessionId && this.prepareSession) {
|
|
2783
|
+
this.busy = true;
|
|
2784
|
+
try {
|
|
2785
|
+
await this.prepareSession(id => { this.adoptingSession = true; this.sessionId = id; });
|
|
2786
|
+
await this.updateComplete;
|
|
2787
|
+
}
|
|
2788
|
+
catch (error) {
|
|
2789
|
+
this.errorMessage = error instanceof Error ? error.message : String(error);
|
|
2790
|
+
this.busy = false;
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
this.busy = false;
|
|
2794
|
+
}
|
|
2795
|
+
let requestContext;
|
|
2796
|
+
let liveBoard;
|
|
2797
|
+
let requestBase;
|
|
2798
|
+
try {
|
|
2799
|
+
const provided = this.contextProvider?.() || {};
|
|
2800
|
+
requestContext = structuredClone({ systemPrompt: this.systemPrompt, hostContext: this.hostContext, ...provided });
|
|
2801
|
+
liveBoard = structuredClone(this.boardProvider ? this.boardProvider() : this.currentBoard);
|
|
2802
|
+
requestBase = { version: 1, sessionId: this.sessionId, hostContext: requestContext.hostContext,
|
|
2803
|
+
modelFingerprint: assistantModelFingerprint(liveBoard) };
|
|
2804
|
+
}
|
|
2805
|
+
catch (error) {
|
|
2806
|
+
this.errorMessage = error instanceof Error ? error.message : String(error);
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2799
2809
|
// pickedMentions / pickedUserMentions 에서 현재 텍스트에 실제 #/@ token 으로
|
|
2800
2810
|
// 등장하는 것만 추출 — 지운 토큰 제외. inline 마커로 content 에 주입.
|
|
2801
2811
|
const mentions = [];
|
|
@@ -2830,7 +2840,6 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2830
2840
|
.map(l => ({ role: l.role, content: l.content }));
|
|
2831
2841
|
// 라이브 보드 우선 — 호스트의 캔버스가 사용자 수작업 편집을 들고 있을 수 있음.
|
|
2832
2842
|
// boardProvider 가 있으면 send 시점에 그것을 pull, 없으면 정적 currentBoard.
|
|
2833
|
-
const liveBoard = this.boardProvider ? this.boardProvider() : this.currentBoard;
|
|
2834
2843
|
// mentions 는 위에서 이미 계산해 user line 에 thread — 그대로 mutation 입력으로 재사용.
|
|
2835
2844
|
const neutral = this.chatEndpoint === 'assistantChat';
|
|
2836
2845
|
const result = await client.mutate({
|
|
@@ -2840,9 +2849,9 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2840
2849
|
? buildAssistantChatInput({
|
|
2841
2850
|
sessionId: this.sessionId,
|
|
2842
2851
|
history,
|
|
2843
|
-
systemPrompt:
|
|
2852
|
+
systemPrompt: requestContext.systemPrompt,
|
|
2844
2853
|
truncateAfterMessageId: this._truncateAfterId,
|
|
2845
|
-
hostContext:
|
|
2854
|
+
hostContext: requestContext.hostContext,
|
|
2846
2855
|
toolCategories: this.toolCategories,
|
|
2847
2856
|
requireGroundingTools: this.requireGroundingTools
|
|
2848
2857
|
})
|
|
@@ -2852,7 +2861,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2852
2861
|
liveBoard,
|
|
2853
2862
|
scopes: this.scopes,
|
|
2854
2863
|
truncateAfterMessageId: this._truncateAfterId,
|
|
2855
|
-
hostContext:
|
|
2864
|
+
hostContext: requestContext.hostContext,
|
|
2856
2865
|
toolCategories: this.toolCategories,
|
|
2857
2866
|
boardTools: this.boardTools,
|
|
2858
2867
|
requireGroundingTools: this.requireGroundingTools,
|
|
@@ -2891,6 +2900,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2891
2900
|
this.dispatchEvent(new CustomEvent('board-edit-patch', {
|
|
2892
2901
|
detail: {
|
|
2893
2902
|
patch: out.patch,
|
|
2903
|
+
requestBase,
|
|
2894
2904
|
summary: out.patch.summary,
|
|
2895
2905
|
confidence: out.patch.confidence,
|
|
2896
2906
|
patchId: out.patchId,
|
|
@@ -2903,7 +2913,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2903
2913
|
// 호스트로 ephemeral scene 조작 actions 전파 (selection / view / mode)
|
|
2904
2914
|
if (Array.isArray(out.actions) && out.actions.length > 0) {
|
|
2905
2915
|
this.dispatchEvent(new CustomEvent('board-action-execute', {
|
|
2906
|
-
detail: { actions: out.actions, sessionId: out.sessionId },
|
|
2916
|
+
detail: { actions: out.actions, sessionId: out.sessionId, requestBase },
|
|
2907
2917
|
bubbles: true,
|
|
2908
2918
|
composed: true
|
|
2909
2919
|
}));
|
|
@@ -2931,6 +2941,10 @@ __decorate([
|
|
|
2931
2941
|
property({ type: String, attribute: 'session-id' }),
|
|
2932
2942
|
__metadata("design:type", String)
|
|
2933
2943
|
], OxBoardAIChat.prototype, "sessionId", void 0);
|
|
2944
|
+
__decorate([
|
|
2945
|
+
property({ attribute: false }),
|
|
2946
|
+
__metadata("design:type", Function)
|
|
2947
|
+
], OxBoardAIChat.prototype, "prepareSession", void 0);
|
|
2934
2948
|
__decorate([
|
|
2935
2949
|
property({ type: Array, attribute: false }),
|
|
2936
2950
|
__metadata("design:type", Array)
|
|
@@ -3011,6 +3025,14 @@ __decorate([
|
|
|
3011
3025
|
property({ attribute: false }),
|
|
3012
3026
|
__metadata("design:type", String)
|
|
3013
3027
|
], OxBoardAIChat.prototype, "autoAsk", void 0);
|
|
3028
|
+
__decorate([
|
|
3029
|
+
property(),
|
|
3030
|
+
__metadata("design:type", String)
|
|
3031
|
+
], OxBoardAIChat.prototype, "autoAskRequestId", void 0);
|
|
3032
|
+
__decorate([
|
|
3033
|
+
property({ attribute: false }),
|
|
3034
|
+
__metadata("design:type", Function)
|
|
3035
|
+
], OxBoardAIChat.prototype, "contextProvider", void 0);
|
|
3014
3036
|
__decorate([
|
|
3015
3037
|
property({ type: Object, attribute: false }),
|
|
3016
3038
|
__metadata("design:type", Object)
|