@things-factory/board-ai 10.0.1 → 10.0.3
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/board-ai-chat.ts +565 -21
- package/client/components/chat-echo-dedup.test.ts +59 -3
- package/client/components/chat-echo-dedup.ts +32 -0
- package/client/components/chat-input-builder.ts +6 -0
- package/dist-client/client/components/board-ai-chat.d.ts +80 -0
- package/dist-client/client/components/board-ai-chat.js +540 -18
- package/dist-client/client/components/board-ai-chat.js.map +1 -1
- package/dist-client/client/components/chat-echo-dedup.d.ts +2 -0
- package/dist-client/client/components/chat-echo-dedup.js +29 -0
- package/dist-client/client/components/chat-echo-dedup.js.map +1 -1
- package/dist-client/client/components/chat-echo-dedup.test.js +53 -3
- package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -1
- package/dist-client/client/components/chat-input-builder.d.ts +5 -0
- package/dist-client/client/components/chat-input-builder.js +1 -0
- package/dist-client/client/components/chat-input-builder.js.map +1 -1
- package/dist-client/server/service/agentic-loop.d.ts +33 -0
- package/dist-client/server/service/agentic-loop.js +80 -10
- package/dist-client/server/service/agentic-loop.js.map +1 -1
- package/dist-client/server/service/assistant.js +32 -5
- package/dist-client/server/service/assistant.js.map +1 -1
- package/dist-client/server/service/grounding.d.ts +17 -0
- package/dist-client/server/service/grounding.js +42 -0
- package/dist-client/server/service/grounding.js.map +1 -0
- package/dist-client/server/service/types.d.ts +39 -0
- package/dist-client/server/service/types.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/agentic-loop.d.ts +33 -0
- package/dist-server/service/agentic-loop.js +81 -10
- package/dist-server/service/agentic-loop.js.map +1 -1
- package/dist-server/service/assistant.js +31 -4
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.d.ts +15 -0
- package/dist-server/service/board-ai-resolver.js +121 -2
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/chat-message/chat-message.d.ts +12 -0
- package/dist-server/service/chat-message/chat-message.js +23 -0
- package/dist-server/service/chat-message/chat-message.js.map +1 -1
- package/dist-server/service/chat-message/fold-history.d.ts +30 -0
- package/dist-server/service/chat-message/fold-history.js +29 -0
- package/dist-server/service/chat-message/fold-history.js.map +1 -0
- package/dist-server/service/chat-message/history-summary.d.ts +43 -0
- package/dist-server/service/chat-message/history-summary.js +77 -0
- package/dist-server/service/chat-message/history-summary.js.map +1 -0
- package/dist-server/service/chat-message/llm-history.d.ts +19 -0
- package/dist-server/service/chat-message/llm-history.js +31 -1
- package/dist-server/service/chat-message/llm-history.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.d.ts +8 -0
- package/dist-server/service/chat-session/chat-session.js +5 -0
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-inbox.d.ts +26 -0
- package/dist-server/service/chat-session/session-inbox.js +41 -0
- package/dist-server/service/chat-session/session-inbox.js.map +1 -1
- package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +11 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js +17 -1
- package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -1
- package/dist-server/service/chat-session-resolver.d.ts +44 -1
- package/dist-server/service/chat-session-resolver.js +306 -6
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/grounding.d.ts +17 -0
- package/dist-server/service/grounding.js +46 -0
- package/dist-server/service/grounding.js.map +1 -0
- package/dist-server/service/types.d.ts +39 -0
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/service/agentic-loop.test.ts +154 -0
- package/server/service/agentic-loop.ts +108 -10
- package/server/service/assistant.ts +36 -5
- package/server/service/board-ai-resolver.ts +131 -2
- package/server/service/chat-message/chat-message.ts +26 -0
- package/server/service/chat-message/fold-history.test.ts +98 -0
- package/server/service/chat-message/fold-history.ts +60 -0
- package/server/service/chat-message/history-summary.test.ts +127 -0
- package/server/service/chat-message/history-summary.ts +100 -0
- package/server/service/chat-message/llm-history.test.ts +65 -0
- package/server/service/chat-message/llm-history.ts +48 -1
- package/server/service/chat-session/chat-session.ts +11 -0
- package/server/service/chat-session/session-inbox.test.ts +69 -1
- package/server/service/chat-session/session-inbox.ts +45 -0
- package/server/service/chat-session-participant/chat-session-participant.ts +14 -0
- package/server/service/chat-session-resolver.ts +297 -5
- package/server/service/dock-contract.test.ts +305 -0
- package/server/service/grounding.test.ts +55 -0
- package/server/service/grounding.ts +53 -0
- package/server/service/types.ts +39 -0
- package/translations/en.json +16 -1
- package/translations/ja.json +16 -1
- package/translations/ko.json +15 -0
- package/translations/ms.json +16 -1
- package/translations/zh.json +16 -1
|
@@ -44,6 +44,9 @@ const BOARD_AI_CHAT_MUTATION = gql `
|
|
|
44
44
|
patchId
|
|
45
45
|
toolUsages
|
|
46
46
|
actions
|
|
47
|
+
proposals
|
|
48
|
+
groundingWarnings
|
|
49
|
+
historyFolded
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
`;
|
|
@@ -55,6 +58,7 @@ const CHAT_MESSAGES_QUERY = gql `
|
|
|
55
58
|
content
|
|
56
59
|
relatedPatchId
|
|
57
60
|
toolUsagesJson
|
|
61
|
+
groundingWarningsJson
|
|
58
62
|
createdAt
|
|
59
63
|
creator {
|
|
60
64
|
name
|
|
@@ -73,6 +77,7 @@ const CHAT_MESSAGE_SUBSCRIPTION = gql `
|
|
|
73
77
|
content
|
|
74
78
|
relatedPatchId
|
|
75
79
|
toolUsagesJson
|
|
80
|
+
groundingWarningsJson
|
|
76
81
|
createdAt
|
|
77
82
|
creator {
|
|
78
83
|
name
|
|
@@ -136,6 +141,13 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
136
141
|
this.selectedRefids = [];
|
|
137
142
|
/** 코어 보드 편집 도구 사용 여부. 기본 true. 보드를 고칠 이유가 없는 면에서 false. */
|
|
138
143
|
this.boardTools = true;
|
|
144
|
+
/**
|
|
145
|
+
* 라이브 상태를 다루는 대화면인가 — true 면 **첫 턴에 도구 호출을 강제**한다.
|
|
146
|
+
*
|
|
147
|
+
* 접지 가드는 지어낸 식별자를 잡지만 식별자 없는 상황 서술("바쁘게 움직이고 있다")은 잡지 못한다.
|
|
148
|
+
* 그건 도구를 부르지 않고 답한 경우이므로, 조회를 먼저 하게 만들어 원천을 막는다.
|
|
149
|
+
*/
|
|
150
|
+
this.requireGroundingTools = false;
|
|
139
151
|
this.placeholder = '자연어로 명령하세요 (Shift+Enter 줄바꿈, Enter 전송)';
|
|
140
152
|
this.lines = [];
|
|
141
153
|
this.input = '';
|
|
@@ -143,6 +155,12 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
143
155
|
this.revertedPatchIds = new Set();
|
|
144
156
|
/** mini action — 인라인 예시 토글 */
|
|
145
157
|
this.examplesOpen = false;
|
|
158
|
+
/** 원본 JSON 을 펼친 단계(라인:단계) — 목록은 한 줄 요약으로 훑고 필요한 것만 펼친다. */
|
|
159
|
+
this.openToolSteps = new Set();
|
|
160
|
+
/** 이미 실행을 요청한 제안 키 — 같은 카드를 두 번 눌러 되돌릴 수 없는 명령이 두 번 나가지 않게. */
|
|
161
|
+
this.sentProposals = new Set();
|
|
162
|
+
/** 안내를 닫았는가 — 한 번 닫으면 이 세션에서는 다시 띄우지 않는다(잔소리 금지). */
|
|
163
|
+
this.foldNoticeDismissed = false;
|
|
146
164
|
this.busy = false;
|
|
147
165
|
/** C4 presence/roster — 세션 참여자 목록. lastSeenAt 으로 online 판정. */
|
|
148
166
|
this.participants = [];
|
|
@@ -219,6 +237,14 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
219
237
|
composed: true
|
|
220
238
|
}));
|
|
221
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* 새 대화로 — **맥락을 옮기지 않는다.** 주제를 바꾸려고 새로 시작하는 것이므로, 옮기면 그 이유가
|
|
242
|
+
* 사라진다(승격은 이어가려는 것이고 이건 끊으려는 것이다). 세션 생성은 호스트의 몫이다.
|
|
243
|
+
*/
|
|
244
|
+
this.startNewFromNotice = () => {
|
|
245
|
+
this.foldNoticeDismissed = true;
|
|
246
|
+
this.dispatchEvent(new CustomEvent('board-ai-start-new-session', { bubbles: true, composed: true }));
|
|
247
|
+
};
|
|
222
248
|
}
|
|
223
249
|
static { OxBoardAIChat_1 = this; }
|
|
224
250
|
/** 현재 로그인 사용자 email — 그룹챗에서 내 메시지 vs 타인 메시지 구분.
|
|
@@ -789,6 +815,21 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
789
815
|
margin: 0;
|
|
790
816
|
border: 0;
|
|
791
817
|
}
|
|
818
|
+
/* 시스템 기록은 **곁줄**이다 — 길면 화면을 잡아먹어 대화가 안 보인다(옮겨온 대화를 한 덩어리로
|
|
819
|
+
넣었을 때 실제로 그랬다). 세 줄로 접고 눌러서 펼친다. */
|
|
820
|
+
.msg.system .sys-text {
|
|
821
|
+
display: -webkit-box;
|
|
822
|
+
-webkit-box-orient: vertical;
|
|
823
|
+
-webkit-line-clamp: 3;
|
|
824
|
+
overflow: hidden;
|
|
825
|
+
white-space: pre-wrap;
|
|
826
|
+
cursor: pointer;
|
|
827
|
+
}
|
|
828
|
+
.msg.system.open .sys-text {
|
|
829
|
+
display: block;
|
|
830
|
+
-webkit-line-clamp: unset;
|
|
831
|
+
overflow: visible;
|
|
832
|
+
}
|
|
792
833
|
|
|
793
834
|
.msg.pending {
|
|
794
835
|
opacity: 0.5;
|
|
@@ -877,6 +918,150 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
877
918
|
line-height: 1.45;
|
|
878
919
|
}
|
|
879
920
|
|
|
921
|
+
/* ── "대화가 길어졌습니다" 안내 — 조용한 한 줄, 강조하지 않는다 ───── */
|
|
922
|
+
.fold-notice {
|
|
923
|
+
display: flex;
|
|
924
|
+
align-items: center;
|
|
925
|
+
gap: 6px;
|
|
926
|
+
margin: 0 0 4px;
|
|
927
|
+
padding: 5px 8px;
|
|
928
|
+
border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
|
|
929
|
+
border-radius: 8px;
|
|
930
|
+
background: var(--md-sys-color-surface-container-low, #f8fafc);
|
|
931
|
+
color: var(--md-sys-color-on-surface-variant, #475569);
|
|
932
|
+
font-size: 10.5px;
|
|
933
|
+
line-height: 1.45;
|
|
934
|
+
}
|
|
935
|
+
.fold-notice md-icon {
|
|
936
|
+
--md-icon-size: 14px;
|
|
937
|
+
flex-shrink: 0;
|
|
938
|
+
}
|
|
939
|
+
.fold-notice .fn-text {
|
|
940
|
+
flex: 1;
|
|
941
|
+
min-width: 0;
|
|
942
|
+
}
|
|
943
|
+
/* 새 대화 — 권유이므로 강조색 글자만(채운 버튼은 지시처럼 읽힌다). */
|
|
944
|
+
.fold-notice .fn-new {
|
|
945
|
+
flex-shrink: 0;
|
|
946
|
+
border: 0;
|
|
947
|
+
background: none;
|
|
948
|
+
color: var(--md-sys-color-primary, #0f766e);
|
|
949
|
+
font-family: inherit;
|
|
950
|
+
font-size: 10.5px;
|
|
951
|
+
font-weight: 650;
|
|
952
|
+
cursor: pointer;
|
|
953
|
+
padding: 2px 4px;
|
|
954
|
+
border-radius: 5px;
|
|
955
|
+
white-space: nowrap;
|
|
956
|
+
}
|
|
957
|
+
.fold-notice .fn-new:hover {
|
|
958
|
+
background: var(--md-sys-color-surface-container-high, #e2e8f0);
|
|
959
|
+
}
|
|
960
|
+
.fold-notice .fn-close {
|
|
961
|
+
flex-shrink: 0;
|
|
962
|
+
border: 0;
|
|
963
|
+
background: none;
|
|
964
|
+
color: var(--md-sys-color-outline, #94a3b8);
|
|
965
|
+
cursor: pointer;
|
|
966
|
+
padding: 0;
|
|
967
|
+
display: inline-flex;
|
|
968
|
+
}
|
|
969
|
+
.fold-notice .fn-close md-icon {
|
|
970
|
+
--md-icon-size: 13px;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/* ── 조치 제안 — AI 는 제안까지, 실행은 사용자 ───── */
|
|
974
|
+
.proposals {
|
|
975
|
+
display: flex;
|
|
976
|
+
flex-direction: column;
|
|
977
|
+
gap: 4px;
|
|
978
|
+
margin-top: 6px;
|
|
979
|
+
}
|
|
980
|
+
.proposal {
|
|
981
|
+
display: flex;
|
|
982
|
+
align-items: center;
|
|
983
|
+
gap: 8px;
|
|
984
|
+
padding: 6px 8px 6px 7px;
|
|
985
|
+
border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);
|
|
986
|
+
/* 왼쪽 강조선 — 되돌릴 수 없는 행동임을 조용히 구분한다(색만으로 소리치지 않는다). */
|
|
987
|
+
border-left: 3px solid var(--md-sys-color-primary, #0f766e);
|
|
988
|
+
border-radius: 8px;
|
|
989
|
+
background: var(--md-sys-color-surface-container-low, #f8fafc);
|
|
990
|
+
}
|
|
991
|
+
.proposal md-icon {
|
|
992
|
+
--md-icon-size: 16px;
|
|
993
|
+
color: var(--md-sys-color-primary, #0f766e);
|
|
994
|
+
flex-shrink: 0;
|
|
995
|
+
}
|
|
996
|
+
.proposal .pbody {
|
|
997
|
+
display: flex;
|
|
998
|
+
flex-direction: column;
|
|
999
|
+
gap: 1px;
|
|
1000
|
+
min-width: 0;
|
|
1001
|
+
flex: 1;
|
|
1002
|
+
}
|
|
1003
|
+
.proposal .plabel {
|
|
1004
|
+
font-size: 11.5px;
|
|
1005
|
+
font-weight: 650;
|
|
1006
|
+
color: var(--md-sys-color-on-surface, #0f172a);
|
|
1007
|
+
overflow-wrap: anywhere;
|
|
1008
|
+
}
|
|
1009
|
+
.proposal .preason {
|
|
1010
|
+
font-size: 10.5px;
|
|
1011
|
+
color: var(--md-sys-color-on-surface-variant, #475569);
|
|
1012
|
+
overflow-wrap: anywhere;
|
|
1013
|
+
}
|
|
1014
|
+
/* 실행 버튼 — 이 대화의 유일한 "되돌릴 수 없는" 행동이므로 채운 버튼 하나로 분명하게. */
|
|
1015
|
+
.proposal .run {
|
|
1016
|
+
flex-shrink: 0;
|
|
1017
|
+
padding: 4px 12px;
|
|
1018
|
+
border: 0;
|
|
1019
|
+
border-radius: 999px;
|
|
1020
|
+
background: var(--md-sys-color-primary, #0f766e);
|
|
1021
|
+
color: var(--md-sys-color-on-primary, #ffffff);
|
|
1022
|
+
font-family: inherit;
|
|
1023
|
+
font-size: 11px;
|
|
1024
|
+
font-weight: 650;
|
|
1025
|
+
letter-spacing: 0.01em;
|
|
1026
|
+
cursor: pointer;
|
|
1027
|
+
transition: filter 0.12s;
|
|
1028
|
+
}
|
|
1029
|
+
.proposal .run:hover:not(:disabled) {
|
|
1030
|
+
filter: brightness(1.08);
|
|
1031
|
+
}
|
|
1032
|
+
.proposal .run:disabled {
|
|
1033
|
+
background: var(--md-sys-color-surface-container-high, #e2e8f0);
|
|
1034
|
+
color: var(--md-sys-color-on-surface-variant, #475569);
|
|
1035
|
+
cursor: default;
|
|
1036
|
+
}
|
|
1037
|
+
.proposal.sent {
|
|
1038
|
+
border-left-color: var(--md-sys-color-outline, #94a3b8);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/* ── 접지 경고 — 근거에 없는 대상을 지목한 답 ───── */
|
|
1042
|
+
.grounding-warning {
|
|
1043
|
+
display: flex;
|
|
1044
|
+
align-items: flex-start;
|
|
1045
|
+
gap: 6px;
|
|
1046
|
+
margin-top: 4px;
|
|
1047
|
+
padding: 5px 8px;
|
|
1048
|
+
border-radius: 6px;
|
|
1049
|
+
border: 1px solid var(--md-sys-color-error, #dc2626);
|
|
1050
|
+
background: var(--md-sys-color-error-container, #fef2f2);
|
|
1051
|
+
color: var(--md-sys-color-on-error-container, #7f1d1d);
|
|
1052
|
+
font-size: 10.5px;
|
|
1053
|
+
line-height: 1.45;
|
|
1054
|
+
}
|
|
1055
|
+
.grounding-warning md-icon {
|
|
1056
|
+
--md-icon-size: 14px;
|
|
1057
|
+
flex-shrink: 0;
|
|
1058
|
+
}
|
|
1059
|
+
.grounding-warning .ids {
|
|
1060
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1061
|
+
font-weight: 600;
|
|
1062
|
+
word-break: break-all;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
880
1065
|
/* ── Tool usages fold-able 박스 — AI 도구 호출 trace ───── */
|
|
881
1066
|
.tool-usages {
|
|
882
1067
|
margin-top: 4px;
|
|
@@ -977,6 +1162,76 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
977
1162
|
color: var(--md-sys-color-on-error, #ffffff);
|
|
978
1163
|
background: var(--md-sys-color-error, #b91c1c);
|
|
979
1164
|
}
|
|
1165
|
+
/* 판단 파이프라인 — 훑어 읽는 목록. 배지로 결과를 구분하고 원본은 눌러서 본다. */
|
|
1166
|
+
.tool-usages.has-problem {
|
|
1167
|
+
border-color: var(--md-sys-color-error, #dc2626);
|
|
1168
|
+
}
|
|
1169
|
+
.tool-usages-counts .tu-flag {
|
|
1170
|
+
font-weight: 650;
|
|
1171
|
+
}
|
|
1172
|
+
.tool-usages-counts .tu-flag.bad {
|
|
1173
|
+
color: var(--md-sys-color-error, #dc2626);
|
|
1174
|
+
}
|
|
1175
|
+
.tu-turn {
|
|
1176
|
+
list-style: none;
|
|
1177
|
+
margin: 4px 0 2px;
|
|
1178
|
+
padding: 0 8px;
|
|
1179
|
+
font-size: 9.5px;
|
|
1180
|
+
font-weight: 650;
|
|
1181
|
+
letter-spacing: 0.04em;
|
|
1182
|
+
color: var(--md-sys-color-outline, #94a3b8);
|
|
1183
|
+
text-transform: uppercase;
|
|
1184
|
+
}
|
|
1185
|
+
.tool-usage-head {
|
|
1186
|
+
cursor: pointer;
|
|
1187
|
+
align-items: center;
|
|
1188
|
+
}
|
|
1189
|
+
.tool-usage-item.outcome-rejected,
|
|
1190
|
+
.tool-usage-item.outcome-error {
|
|
1191
|
+
border-left-color: var(--md-sys-color-error, #dc2626);
|
|
1192
|
+
}
|
|
1193
|
+
.tool-usage-item.outcome-proposed {
|
|
1194
|
+
border-left-color: var(--md-sys-color-primary, #0f766e);
|
|
1195
|
+
}
|
|
1196
|
+
.tool-usage-item.outcome-folded {
|
|
1197
|
+
opacity: 0.7;
|
|
1198
|
+
}
|
|
1199
|
+
.tu-badge {
|
|
1200
|
+
flex-shrink: 0;
|
|
1201
|
+
padding: 0 5px;
|
|
1202
|
+
border-radius: 999px;
|
|
1203
|
+
font-size: 9px;
|
|
1204
|
+
font-weight: 700;
|
|
1205
|
+
line-height: 15px;
|
|
1206
|
+
background: var(--md-sys-color-surface-container-high, #e2e8f0);
|
|
1207
|
+
color: var(--md-sys-color-on-surface-variant, #475569);
|
|
1208
|
+
}
|
|
1209
|
+
.tu-badge.rejected,
|
|
1210
|
+
.tu-badge.error {
|
|
1211
|
+
background: var(--md-sys-color-error-container, #fef2f2);
|
|
1212
|
+
color: var(--md-sys-color-error, #dc2626);
|
|
1213
|
+
}
|
|
1214
|
+
.tu-badge.proposed {
|
|
1215
|
+
background: var(--md-sys-color-primary, #0f766e);
|
|
1216
|
+
color: var(--md-sys-color-on-primary, #fff);
|
|
1217
|
+
}
|
|
1218
|
+
/* 한 줄 요약 — 넘치면 자른다. 목록은 훑는 곳이고 읽는 곳이 아니다. */
|
|
1219
|
+
.tu-line {
|
|
1220
|
+
flex: 1;
|
|
1221
|
+
min-width: 0;
|
|
1222
|
+
overflow: hidden;
|
|
1223
|
+
text-overflow: ellipsis;
|
|
1224
|
+
white-space: nowrap;
|
|
1225
|
+
color: var(--md-sys-color-on-surface-variant, #475569);
|
|
1226
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1227
|
+
font-size: 9.5px;
|
|
1228
|
+
}
|
|
1229
|
+
.tu-more {
|
|
1230
|
+
--md-icon-size: 13px;
|
|
1231
|
+
flex-shrink: 0;
|
|
1232
|
+
color: var(--md-sys-color-outline, #94a3b8);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
980
1235
|
.tool-usage-args,
|
|
981
1236
|
.tool-usage-result {
|
|
982
1237
|
margin: 2px 0 0;
|
|
@@ -1482,12 +1737,22 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
1482
1737
|
? html `<div class="msg-label sender">${line.senderName || i18next.t('board-ai.label.participant')}</div>`
|
|
1483
1738
|
: nothing}
|
|
1484
1739
|
<div class="msg-wrap ${line.role} ${isOther ? 'other' : ''}">
|
|
1485
|
-
<div
|
|
1740
|
+
<div
|
|
1741
|
+
class="msg ${line.role} ${isOther ? 'other' : ''} ${line.pending ? 'pending' : ''} ${line.systemOpen
|
|
1742
|
+
? 'open'
|
|
1743
|
+
: ''}">
|
|
1486
1744
|
${line.pending
|
|
1487
1745
|
? html `<span class="typing-dots"><span></span><span></span><span></span></span>`
|
|
1488
1746
|
: isMarkdown
|
|
1489
1747
|
? html `<div class="md-body">${unsafeHTML(renderMarkdown(content))}</div>`
|
|
1490
|
-
:
|
|
1748
|
+
: line.role === 'system'
|
|
1749
|
+
? html `<span
|
|
1750
|
+
class="sys-text"
|
|
1751
|
+
title=${i18next.t('board-ai.text.toggle-system-note', { defaultValue: '눌러서 펼치기' })}
|
|
1752
|
+
@click=${() => this.toggleSystemNote(idx)}
|
|
1753
|
+
>${content}</span
|
|
1754
|
+
>`
|
|
1755
|
+
: content}
|
|
1491
1756
|
${line.patchId
|
|
1492
1757
|
? html `
|
|
1493
1758
|
<div class="summary ${this.revertedPatchIds.has(line.patchId) ? 'reverted' : ''}">
|
|
@@ -1509,6 +1774,10 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
1509
1774
|
${line.followUp
|
|
1510
1775
|
? html `<div class="followup">${unsafeHTML(renderMarkdown(line.followUp.trim()))}</div>`
|
|
1511
1776
|
: nothing}
|
|
1777
|
+
${line.proposals && line.proposals.length > 0 ? this.renderProposals(line) : nothing}
|
|
1778
|
+
${line.groundingWarnings && line.groundingWarnings.length > 0
|
|
1779
|
+
? this.renderGroundingWarning(line)
|
|
1780
|
+
: nothing}
|
|
1512
1781
|
${line.toolUsages && line.toolUsages.length > 0
|
|
1513
1782
|
? this.renderToolUsages(idx, line)
|
|
1514
1783
|
: nothing}
|
|
@@ -1533,6 +1802,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
1533
1802
|
</div>
|
|
1534
1803
|
`
|
|
1535
1804
|
: nothing}
|
|
1805
|
+
${this.renderFoldNotice()}
|
|
1536
1806
|
<div class="composer ${hasMessages ? '' : 'no-actions'}">
|
|
1537
1807
|
${hasMessages
|
|
1538
1808
|
? html `
|
|
@@ -2086,6 +2356,138 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2086
2356
|
</div>
|
|
2087
2357
|
`;
|
|
2088
2358
|
}
|
|
2359
|
+
/**
|
|
2360
|
+
* "대화가 길어졌습니다" 안내 — **사실만** 알리고 결정은 사용자에게 맡긴다.
|
|
2361
|
+
*
|
|
2362
|
+
* 주제가 바뀌었는지 판정하지 않는다: 판정하면 오탐이 생기고, 오탐이 생기면 사용자는 안내 자체를
|
|
2363
|
+
* 무시한다(접지 경고에서 배운 것과 같다). 근거는 "앞부분이 프롬프트에서 접혔다" 는 객관적 사실뿐이다.
|
|
2364
|
+
*
|
|
2365
|
+
* 왜 알려야 하는가: 한 대화에 주제가 여럿 섞이면 (1) 옛 맥락이 새 답을 오염시키고 (2) 요약이
|
|
2366
|
+
* 뭉개지고 (3) 목록의 대화 이름이 거짓말이 된다. 그걸 아는 사람은 사용자뿐이다.
|
|
2367
|
+
*/
|
|
2368
|
+
renderFoldNotice() {
|
|
2369
|
+
const folded = this.historyFolded;
|
|
2370
|
+
if (!folded || this.foldNoticeDismissed)
|
|
2371
|
+
return nothing;
|
|
2372
|
+
return html `
|
|
2373
|
+
<div class="fold-notice">
|
|
2374
|
+
<md-icon>unfold_less</md-icon>
|
|
2375
|
+
<span class="fn-text">
|
|
2376
|
+
${folded.summarized
|
|
2377
|
+
? i18next.t('board-ai.text.history-folded-summarized', {
|
|
2378
|
+
count: folded.omitted,
|
|
2379
|
+
defaultValue: '대화가 길어져 앞부분 {count}개가 요약으로 접혔습니다. 다른 주제라면 새 대화가 낫습니다.'
|
|
2380
|
+
})
|
|
2381
|
+
: i18next.t('board-ai.text.history-folded', {
|
|
2382
|
+
count: folded.omitted,
|
|
2383
|
+
defaultValue: '대화가 길어져 앞부분 {count}개가 생략되었습니다. 다른 주제라면 새 대화가 낫습니다.'
|
|
2384
|
+
})}
|
|
2385
|
+
</span>
|
|
2386
|
+
<button class="fn-new" @click=${this.startNewFromNotice}>
|
|
2387
|
+
${i18next.t('board-ai.button.new-chat')}
|
|
2388
|
+
</button>
|
|
2389
|
+
<button class="fn-close" title=${i18next.t('board-ai.button.close')} @click=${() => (this.foldNoticeDismissed = true)}>
|
|
2390
|
+
<md-icon>close</md-icon>
|
|
2391
|
+
</button>
|
|
2392
|
+
</div>
|
|
2393
|
+
`;
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* 조치 제안 카드 — AI 는 제안까지, **실행은 사용자**.
|
|
2397
|
+
*
|
|
2398
|
+
* 왜 카드인가: 되돌릴 수 없는 현장 명령을 문장 속 링크로 흘리면 실수로 눌린다. 무엇을·왜 하는지
|
|
2399
|
+
* 한 줄로 보이고 실행이 **분명한 한 번의 행동**이어야 한다.
|
|
2400
|
+
* 실행 결과는 호스트가 **대화에 기록**한다 — 누가 무엇을 실행했는지가 협의 이력에 남아야 한다.
|
|
2401
|
+
*/
|
|
2402
|
+
renderProposals(line) {
|
|
2403
|
+
const items = line.proposals ?? [];
|
|
2404
|
+
if (items.length === 0)
|
|
2405
|
+
return nothing;
|
|
2406
|
+
return html `
|
|
2407
|
+
<div class="proposals">
|
|
2408
|
+
${items.map(p => {
|
|
2409
|
+
const key = this.proposalKey(p);
|
|
2410
|
+
const sent = this.sentProposals.has(key);
|
|
2411
|
+
return html `
|
|
2412
|
+
<div class="proposal ${sent ? 'sent' : ''}">
|
|
2413
|
+
<md-icon>bolt</md-icon>
|
|
2414
|
+
<span class="pbody">
|
|
2415
|
+
<span class="plabel">${p.label || p.command}</span>
|
|
2416
|
+
${p.reason ? html `<span class="preason">${p.reason}</span>` : nothing}
|
|
2417
|
+
</span>
|
|
2418
|
+
<button
|
|
2419
|
+
class="run"
|
|
2420
|
+
?disabled=${sent}
|
|
2421
|
+
@click=${() => this.executeProposal(p, key)}>
|
|
2422
|
+
${sent ? i18next.t('board-ai.text.proposal-sent', { defaultValue: '실행 요청됨' })
|
|
2423
|
+
: i18next.t('board-ai.button.run-proposal', { defaultValue: '실행' })}
|
|
2424
|
+
</button>
|
|
2425
|
+
</div>
|
|
2426
|
+
`;
|
|
2427
|
+
})}
|
|
2428
|
+
</div>
|
|
2429
|
+
`;
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* 제안 식별 키 — 같은 조치를 두 번 실행하지 않도록 **효과**(명령·대상·인자)로만 만든다.
|
|
2433
|
+
*
|
|
2434
|
+
* 인자는 **키 순서를 정렬**해 직렬화한다: 같은 인자를 다른 순서로 받았을 뿐인데 키가 달라지면
|
|
2435
|
+
* 한 카드를 실행한 뒤에도 다른 카드가 살아 있어 **같은 명령이 두 번 나간다**(실제로 그랬다).
|
|
2436
|
+
* 설명 문구(label·reason)는 키에 넣지 않는다 — 문구가 달라도 같은 조치다.
|
|
2437
|
+
*/
|
|
2438
|
+
proposalKey(p) {
|
|
2439
|
+
const stable = (v) => {
|
|
2440
|
+
if (v === null || typeof v !== 'object')
|
|
2441
|
+
return JSON.stringify(v ?? null);
|
|
2442
|
+
if (Array.isArray(v))
|
|
2443
|
+
return `[${v.map(stable).join(',')}]`;
|
|
2444
|
+
return `{${Object.keys(v)
|
|
2445
|
+
.sort()
|
|
2446
|
+
.map(k => `${JSON.stringify(k)}:${stable(v[k])}`)
|
|
2447
|
+
.join(',')}}`;
|
|
2448
|
+
};
|
|
2449
|
+
try {
|
|
2450
|
+
return `${p?.command ?? ''}|${p?.instanceId ?? ''}|${stable(p?.args)}`;
|
|
2451
|
+
}
|
|
2452
|
+
catch {
|
|
2453
|
+
return `${p?.command ?? ''}|${p?.instanceId ?? ''}`;
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* 실행은 호스트가 한다 — 이 컴포넌트는 도메인(트윈 명령 채널)을 모른다.
|
|
2458
|
+
* 확인 대화·명령 전송·결과 기록은 호스트의 몫이고, 여기서는 중복 클릭만 막는다.
|
|
2459
|
+
*/
|
|
2460
|
+
executeProposal(p, key) {
|
|
2461
|
+
this.sentProposals = new Set(this.sentProposals).add(key);
|
|
2462
|
+
this.dispatchEvent(new CustomEvent('board-ai-proposal-execute', {
|
|
2463
|
+
detail: { proposal: p, sessionId: this.sessionId },
|
|
2464
|
+
bubbles: true,
|
|
2465
|
+
composed: true
|
|
2466
|
+
}));
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* 접지 경고 — 답이 언급했지만 **근거에 없던** 대상을 알린다.
|
|
2470
|
+
*
|
|
2471
|
+
* 왜 답을 지우지 않는가: 판정은 식별자 대조라 확정이 아니다(예: 사용자가 화면에서 본 이름을
|
|
2472
|
+
* 대화 밖에서 알고 말한 경우). 그래서 답은 그대로 보여주고, 검증되지 않았다는 사실만 붙인다 —
|
|
2473
|
+
* 사용자가 그 대상을 실제 작업의 근거로 삼기 전에 알아야 하는 정보다.
|
|
2474
|
+
*/
|
|
2475
|
+
renderGroundingWarning(line) {
|
|
2476
|
+
const ids = line.groundingWarnings ?? [];
|
|
2477
|
+
if (ids.length === 0)
|
|
2478
|
+
return nothing;
|
|
2479
|
+
return html `
|
|
2480
|
+
<div class="grounding-warning" role="note">
|
|
2481
|
+
<md-icon>report</md-icon>
|
|
2482
|
+
<span>
|
|
2483
|
+
${i18next.t('board-ai.text.ungrounded-mention', {
|
|
2484
|
+
defaultValue: '확인되지 않은 대상을 언급했습니다 — 실제 데이터에 없습니다:'
|
|
2485
|
+
})}
|
|
2486
|
+
<span class="ids">${ids.join(', ')}</span>
|
|
2487
|
+
</span>
|
|
2488
|
+
</div>
|
|
2489
|
+
`;
|
|
2490
|
+
}
|
|
2089
2491
|
/**
|
|
2090
2492
|
* "AI 가 이런 도구를 사용했습니다" fold-able 박스.
|
|
2091
2493
|
*
|
|
@@ -2101,41 +2503,136 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2101
2503
|
const readCount = usages.filter(u => u.kind === 'read').length;
|
|
2102
2504
|
const writeCount = usages.filter(u => u.kind === 'write').length;
|
|
2103
2505
|
const toolsLabel = i18next.t('board-ai.text.tools-used', { defaultValue: '도구 사용' });
|
|
2506
|
+
/* 눈에 걸려야 하는 것을 머리줄에 올린다 — 거절·접힘이 있으면 펼치기 전에 보인다.
|
|
2507
|
+
* (이번 사고들이 전부 그것이었다: 인자 빠진 호출이 거절됐고, 중복 제안이 접혔다.) */
|
|
2508
|
+
const problems = usages.filter(u => u.outcome === 'rejected' || u.outcome === 'error').length;
|
|
2509
|
+
const folded = usages.filter(u => u.outcome === 'folded').length;
|
|
2104
2510
|
return html `
|
|
2105
|
-
<div class="tool-usages ${open ? 'open' : ''}">
|
|
2511
|
+
<div class="tool-usages ${open ? 'open' : ''} ${problems ? 'has-problem' : ''}">
|
|
2106
2512
|
<button
|
|
2107
2513
|
class="tool-usages-header"
|
|
2108
2514
|
aria-expanded=${open}
|
|
2109
2515
|
@click=${() => this.toggleToolUsages(idx)}>
|
|
2110
|
-
<md-icon class="tool-usages-icon">${open ? 'expand_less' : '
|
|
2516
|
+
<md-icon class="tool-usages-icon">${open ? 'expand_less' : 'account_tree'}</md-icon>
|
|
2111
2517
|
<span class="tool-usages-summary">
|
|
2112
2518
|
<strong>${usages.length}</strong> ${toolsLabel}
|
|
2113
|
-
<span class="tool-usages-counts"
|
|
2519
|
+
<span class="tool-usages-counts">
|
|
2520
|
+
· read ${readCount} · write ${writeCount}${problems
|
|
2521
|
+
? html ` · <span class="tu-flag bad">${i18next.t('board-ai.text.tool-rejected-count', {
|
|
2522
|
+
count: problems,
|
|
2523
|
+
defaultValue: '거절 {count}'
|
|
2524
|
+
})}</span>`
|
|
2525
|
+
: nothing}${folded
|
|
2526
|
+
? html ` · <span class="tu-flag">${i18next.t('board-ai.text.tool-folded-count', {
|
|
2527
|
+
count: folded,
|
|
2528
|
+
defaultValue: '접힘 {count}'
|
|
2529
|
+
})}</span>`
|
|
2530
|
+
: nothing}
|
|
2531
|
+
</span>
|
|
2114
2532
|
</span>
|
|
2115
2533
|
<md-icon class="tool-usages-chevron">${open ? 'expand_less' : 'expand_more'}</md-icon>
|
|
2116
2534
|
</button>
|
|
2117
2535
|
${open
|
|
2118
2536
|
? html `
|
|
2119
2537
|
<ol class="tool-usages-list">
|
|
2120
|
-
${usages.map((u, i) =>
|
|
2121
|
-
<li class="tool-usage-item kind-${u.kind}">
|
|
2122
|
-
<div class="tool-usage-head">
|
|
2123
|
-
<span class="tool-usage-step">${i + 1}.</span>
|
|
2124
|
-
<span class="tool-usage-name">${u.name}</span>
|
|
2125
|
-
<span class="tool-usage-kind kind-${u.kind}">${u.kind}</span>
|
|
2126
|
-
</div>
|
|
2127
|
-
${u.arguments && Object.keys(u.arguments).length > 0
|
|
2128
|
-
? html `<pre class="tool-usage-args">${this.formatJson(u.arguments)}</pre>`
|
|
2129
|
-
: nothing}
|
|
2130
|
-
<pre class="tool-usage-result">${this.formatJson(u.result)}</pre>
|
|
2131
|
-
</li>
|
|
2132
|
-
`)}
|
|
2538
|
+
${usages.map((u, i) => this.renderToolStep(idx, u, i, usages[i - 1]))}
|
|
2133
2539
|
</ol>
|
|
2134
2540
|
`
|
|
2135
2541
|
: nothing}
|
|
2136
2542
|
</div>
|
|
2137
2543
|
`;
|
|
2138
2544
|
}
|
|
2545
|
+
/** 시스템 기록 펼치기/접기 — 곁줄이 길어도 대화가 가려지지 않게. */
|
|
2546
|
+
toggleSystemNote(idx) {
|
|
2547
|
+
const next = [...this.lines];
|
|
2548
|
+
const line = next[idx];
|
|
2549
|
+
if (!line)
|
|
2550
|
+
return;
|
|
2551
|
+
next[idx] = { ...line, systemOpen: !line.systemOpen };
|
|
2552
|
+
this.lines = next;
|
|
2553
|
+
}
|
|
2554
|
+
/**
|
|
2555
|
+
* 판단 과정의 한 단계 — 턴 구분 · 도구 · 배지 · **한 줄 요약**.
|
|
2556
|
+
*
|
|
2557
|
+
* 원본 JSON 을 처음부터 펼쳐 두면 눈이 흐려져 정작 중요한 것(무엇이 거절됐는지, 무엇이 접혔는지)이
|
|
2558
|
+
* 안 보인다. 한 줄로 줄이고 원본은 항목별로 눌러서 본다.
|
|
2559
|
+
*/
|
|
2560
|
+
renderToolStep(lineIdx, u, i, prev) {
|
|
2561
|
+
const outcome = u.outcome ?? 'ok';
|
|
2562
|
+
const openKey = `${lineIdx}:${i}`;
|
|
2563
|
+
const open = this.openToolSteps.has(openKey);
|
|
2564
|
+
/* 턴이 바뀌면 구분선 — "한 턴 안에서 정정했는지, 다음 턴에서 했는지" 가 진단의 절반이다. */
|
|
2565
|
+
const newTurn = typeof u.iter === 'number' && (!prev || prev.iter !== u.iter);
|
|
2566
|
+
return html `
|
|
2567
|
+
${newTurn
|
|
2568
|
+
? html `<li class="tu-turn">
|
|
2569
|
+
${i18next.t('board-ai.text.tool-turn', { n: (u.iter ?? 0) + 1, defaultValue: '{n}번째 판단' })}
|
|
2570
|
+
</li>`
|
|
2571
|
+
: nothing}
|
|
2572
|
+
<li class="tool-usage-item kind-${u.kind} outcome-${outcome}">
|
|
2573
|
+
<div class="tool-usage-head" @click=${() => this.toggleToolStep(openKey)}>
|
|
2574
|
+
<span class="tool-usage-step">${i + 1}.</span>
|
|
2575
|
+
<span class="tool-usage-name">${u.name}</span>
|
|
2576
|
+
<span class="tu-badge ${outcome}">${this.outcomeLabel(outcome)}</span>
|
|
2577
|
+
<span class="tu-line" title=${this.stepSummary(u)}>${this.stepSummary(u)}</span>
|
|
2578
|
+
<md-icon class="tu-more">${open ? 'expand_less' : 'expand_more'}</md-icon>
|
|
2579
|
+
</div>
|
|
2580
|
+
${open
|
|
2581
|
+
? html `
|
|
2582
|
+
${u.arguments && Object.keys(u.arguments).length > 0
|
|
2583
|
+
? html `<pre class="tool-usage-args">${this.formatJson(u.arguments)}</pre>`
|
|
2584
|
+
: nothing}
|
|
2585
|
+
<pre class="tool-usage-result">${this.formatJson(u.result)}</pre>
|
|
2586
|
+
`
|
|
2587
|
+
: nothing}
|
|
2588
|
+
</li>
|
|
2589
|
+
`;
|
|
2590
|
+
}
|
|
2591
|
+
/** 배지 문구 — 코드가 아니라 사람 말로. */
|
|
2592
|
+
outcomeLabel(outcome) {
|
|
2593
|
+
const map = {
|
|
2594
|
+
ok: ['board-ai.text.outcome-ok', '조회'],
|
|
2595
|
+
rejected: ['board-ai.text.outcome-rejected', '거절'],
|
|
2596
|
+
queued: ['board-ai.text.outcome-queued', '적용 예정'],
|
|
2597
|
+
proposed: ['board-ai.text.outcome-proposed', '제안'],
|
|
2598
|
+
folded: ['board-ai.text.outcome-folded', '중복 접힘'],
|
|
2599
|
+
error: ['board-ai.text.outcome-error', '오류']
|
|
2600
|
+
};
|
|
2601
|
+
const [key, fallback] = map[outcome] ?? map.ok;
|
|
2602
|
+
return i18next.t(key, { defaultValue: fallback });
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* 결과 한 줄 — 무엇을 얻었는지/왜 막혔는지만. 목록에서 훑어 읽을 수 있어야 한다.
|
|
2606
|
+
* 거절이면 사유를, 조회면 대표적인 개수를, 제안이면 무엇을 제안했는지.
|
|
2607
|
+
*/
|
|
2608
|
+
stepSummary(u) {
|
|
2609
|
+
const r = u?.result;
|
|
2610
|
+
if (!r || typeof r !== 'object')
|
|
2611
|
+
return typeof r === 'string' ? r.slice(0, 80) : '';
|
|
2612
|
+
if (u.outcome === 'rejected' || u.outcome === 'error') {
|
|
2613
|
+
return String(r.errorCode || r.error || r.issues?.[0]?.message || '').slice(0, 80);
|
|
2614
|
+
}
|
|
2615
|
+
if (u.outcome === 'proposed' || u.outcome === 'folded') {
|
|
2616
|
+
return [r.command, r.args && Object.keys(r.args).length ? this.formatJson(r.args) : '']
|
|
2617
|
+
.filter(Boolean)
|
|
2618
|
+
.join(' ')
|
|
2619
|
+
.slice(0, 80);
|
|
2620
|
+
}
|
|
2621
|
+
/* 조회 결과 — 배열 길이가 있는 필드를 골라 "무엇이 몇 개" 로 요약한다(전체 덤프 금지). */
|
|
2622
|
+
const counts = Object.keys(r)
|
|
2623
|
+
.filter(k => Array.isArray(r[k]) && r[k].length > 0)
|
|
2624
|
+
.slice(0, 2)
|
|
2625
|
+
.map(k => `${k} ${r[k].length}`);
|
|
2626
|
+
if (counts.length)
|
|
2627
|
+
return counts.join(' · ');
|
|
2628
|
+
const keys = Object.keys(r).slice(0, 4);
|
|
2629
|
+
return keys.map(k => (typeof r[k] === 'object' ? k : `${k} ${r[k]}`)).join(' · ').slice(0, 80);
|
|
2630
|
+
}
|
|
2631
|
+
toggleToolStep(key) {
|
|
2632
|
+
const next = new Set(this.openToolSteps);
|
|
2633
|
+
next.has(key) ? next.delete(key) : next.add(key);
|
|
2634
|
+
this.openToolSteps = next;
|
|
2635
|
+
}
|
|
2139
2636
|
toggleToolUsages(idx) {
|
|
2140
2637
|
const next = [...this.lines];
|
|
2141
2638
|
const line = next[idx];
|
|
@@ -2277,6 +2774,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2277
2774
|
hostContext: this.hostContext,
|
|
2278
2775
|
toolCategories: this.toolCategories,
|
|
2279
2776
|
boardTools: this.boardTools,
|
|
2777
|
+
requireGroundingTools: this.requireGroundingTools,
|
|
2280
2778
|
knownTypes: this.knownTypes,
|
|
2281
2779
|
categories: this.categories,
|
|
2282
2780
|
componentSchemas: this.componentSchemas,
|
|
@@ -2288,6 +2786,8 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2288
2786
|
const out = result.data?.boardAIChat;
|
|
2289
2787
|
if (!out)
|
|
2290
2788
|
throw new Error(i18next.t('board-ai.text.empty-response'));
|
|
2789
|
+
/* 대화가 길어졌다는 서버 보고 — 닫지 않았다면 안내를 띄운다. */
|
|
2790
|
+
this.historyFolded = out.historyFolded ?? undefined;
|
|
2291
2791
|
// 전송 성공 — picks 정리 (다음 메시지부터는 새 picks 만)
|
|
2292
2792
|
this.pickedMentions.clear();
|
|
2293
2793
|
this.pickedUserMentions.clear();
|
|
@@ -2301,6 +2801,8 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2301
2801
|
followUp: out.followUp || undefined,
|
|
2302
2802
|
patchId: out.patchId || undefined,
|
|
2303
2803
|
toolUsages: Array.isArray(out.toolUsages) ? out.toolUsages : undefined,
|
|
2804
|
+
groundingWarnings: Array.isArray(out.groundingWarnings) ? out.groundingWarnings : undefined,
|
|
2805
|
+
proposals: Array.isArray(out.proposals) ? out.proposals : undefined,
|
|
2304
2806
|
pending: false
|
|
2305
2807
|
});
|
|
2306
2808
|
// 호스트로 patch 이벤트 전파
|
|
@@ -2384,6 +2886,10 @@ __decorate([
|
|
|
2384
2886
|
property({ type: Boolean, attribute: 'board-tools' }),
|
|
2385
2887
|
__metadata("design:type", Object)
|
|
2386
2888
|
], OxBoardAIChat.prototype, "boardTools", void 0);
|
|
2889
|
+
__decorate([
|
|
2890
|
+
property({ type: Boolean, attribute: 'require-grounding-tools' }),
|
|
2891
|
+
__metadata("design:type", Object)
|
|
2892
|
+
], OxBoardAIChat.prototype, "requireGroundingTools", void 0);
|
|
2387
2893
|
__decorate([
|
|
2388
2894
|
property({ type: Array }),
|
|
2389
2895
|
__metadata("design:type", Array)
|
|
@@ -2432,6 +2938,22 @@ __decorate([
|
|
|
2432
2938
|
state(),
|
|
2433
2939
|
__metadata("design:type", Number)
|
|
2434
2940
|
], OxBoardAIChat.prototype, "copiedIdx", void 0);
|
|
2941
|
+
__decorate([
|
|
2942
|
+
state(),
|
|
2943
|
+
__metadata("design:type", Object)
|
|
2944
|
+
], OxBoardAIChat.prototype, "openToolSteps", void 0);
|
|
2945
|
+
__decorate([
|
|
2946
|
+
state(),
|
|
2947
|
+
__metadata("design:type", Object)
|
|
2948
|
+
], OxBoardAIChat.prototype, "sentProposals", void 0);
|
|
2949
|
+
__decorate([
|
|
2950
|
+
state(),
|
|
2951
|
+
__metadata("design:type", Object)
|
|
2952
|
+
], OxBoardAIChat.prototype, "historyFolded", void 0);
|
|
2953
|
+
__decorate([
|
|
2954
|
+
state(),
|
|
2955
|
+
__metadata("design:type", Object)
|
|
2956
|
+
], OxBoardAIChat.prototype, "foldNoticeDismissed", void 0);
|
|
2435
2957
|
__decorate([
|
|
2436
2958
|
state(),
|
|
2437
2959
|
__metadata("design:type", Object)
|