@things-factory/board-ai 10.0.0 → 10.0.2
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 +107 -2
- package/client/components/chat-echo-dedup.ts +3 -0
- package/client/components/chat-input-builder.ts +6 -0
- package/dist-client/client/components/board-ai-chat.d.ts +17 -0
- package/dist-client/client/components/board-ai-chat.js +102 -2
- 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 +1 -0
- package/dist-client/client/components/chat-echo-dedup.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 +18 -0
- package/dist-client/server/service/agentic-loop.js +10 -1
- package/dist-client/server/service/agentic-loop.js.map +1 -1
- package/dist-client/server/service/assistant.js +14 -2
- 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 +16 -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 +18 -0
- package/dist-server/service/agentic-loop.js +10 -1
- package/dist-server/service/agentic-loop.js.map +1 -1
- package/dist-server/service/assistant.js +14 -2
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.d.ts +2 -0
- package/dist-server/service/board-ai-resolver.js +22 -1
- 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-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 +16 -0
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/service/agentic-loop.test.ts +63 -0
- package/server/service/agentic-loop.ts +28 -1
- package/server/service/assistant.ts +15 -2
- package/server/service/board-ai-resolver.ts +23 -1
- package/server/service/chat-message/chat-message.ts +26 -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/grounding.test.ts +55 -0
- package/server/service/grounding.ts +53 -0
- package/server/service/types.ts +16 -0
- package/translations/en.json +3 -1
- package/translations/ja.json +3 -1
- package/translations/ko.json +2 -0
- package/translations/ms.json +3 -1
- package/translations/zh.json +3 -1
|
@@ -70,6 +70,13 @@ interface ChatLine {
|
|
|
70
70
|
toolUsages?: Array<{ name: string; arguments: any; result: any; kind: 'read' | 'write' | 'unknown' }>
|
|
71
71
|
/** UI 상태: tool usage 박스 펼침 여부 (assistant 메시지마다 독립). */
|
|
72
72
|
toolUsagesOpen?: boolean
|
|
73
|
+
/** UI 상태: 접힌 시스템 기록을 펼쳤는가(곁줄이 대화를 가리지 않게 기본은 3줄 접기). */
|
|
74
|
+
systemOpen?: boolean
|
|
75
|
+
/**
|
|
76
|
+
* 접지 경고 — 이 답이 언급했으나 모델이 받은 근거(프롬프트·보드 문맥·이력·도구 결과)에 없던 식별자.
|
|
77
|
+
* 있으면 그 대상은 만들어 낸 것일 수 있다는 주의를 답 아래에 표시한다(답 자체는 그대로 보여준다).
|
|
78
|
+
*/
|
|
79
|
+
groundingWarnings?: string[]
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
const BOARD_AI_CHAT_MUTATION = gql`
|
|
@@ -85,6 +92,7 @@ const BOARD_AI_CHAT_MUTATION = gql`
|
|
|
85
92
|
patchId
|
|
86
93
|
toolUsages
|
|
87
94
|
actions
|
|
95
|
+
groundingWarnings
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
`
|
|
@@ -97,6 +105,7 @@ const CHAT_MESSAGES_QUERY = gql`
|
|
|
97
105
|
content
|
|
98
106
|
relatedPatchId
|
|
99
107
|
toolUsagesJson
|
|
108
|
+
groundingWarningsJson
|
|
100
109
|
createdAt
|
|
101
110
|
creator {
|
|
102
111
|
name
|
|
@@ -116,6 +125,7 @@ const CHAT_MESSAGE_SUBSCRIPTION = gql`
|
|
|
116
125
|
content
|
|
117
126
|
relatedPatchId
|
|
118
127
|
toolUsagesJson
|
|
128
|
+
groundingWarningsJson
|
|
119
129
|
createdAt
|
|
120
130
|
creator {
|
|
121
131
|
name
|
|
@@ -253,6 +263,15 @@ export class OxBoardAIChat extends LitElement {
|
|
|
253
263
|
@property({ type: Boolean, attribute: 'board-tools' })
|
|
254
264
|
boardTools = true
|
|
255
265
|
|
|
266
|
+
/**
|
|
267
|
+
* 라이브 상태를 다루는 대화면인가 — true 면 **첫 턴에 도구 호출을 강제**한다.
|
|
268
|
+
*
|
|
269
|
+
* 접지 가드는 지어낸 식별자를 잡지만 식별자 없는 상황 서술("바쁘게 움직이고 있다")은 잡지 못한다.
|
|
270
|
+
* 그건 도구를 부르지 않고 답한 경우이므로, 조회를 먼저 하게 만들어 원천을 막는다.
|
|
271
|
+
*/
|
|
272
|
+
@property({ type: Boolean, attribute: 'require-grounding-tools' })
|
|
273
|
+
requireGroundingTools = false
|
|
274
|
+
|
|
256
275
|
/** board-import registry scopes. */
|
|
257
276
|
@property({ type: Array })
|
|
258
277
|
scopes?: string[]
|
|
@@ -951,6 +970,21 @@ export class OxBoardAIChat extends LitElement {
|
|
|
951
970
|
margin: 0;
|
|
952
971
|
border: 0;
|
|
953
972
|
}
|
|
973
|
+
/* 시스템 기록은 **곁줄**이다 — 길면 화면을 잡아먹어 대화가 안 보인다(옮겨온 대화를 한 덩어리로
|
|
974
|
+
넣었을 때 실제로 그랬다). 세 줄로 접고 눌러서 펼친다. */
|
|
975
|
+
.msg.system .sys-text {
|
|
976
|
+
display: -webkit-box;
|
|
977
|
+
-webkit-box-orient: vertical;
|
|
978
|
+
-webkit-line-clamp: 3;
|
|
979
|
+
overflow: hidden;
|
|
980
|
+
white-space: pre-wrap;
|
|
981
|
+
cursor: pointer;
|
|
982
|
+
}
|
|
983
|
+
.msg.system.open .sys-text {
|
|
984
|
+
display: block;
|
|
985
|
+
-webkit-line-clamp: unset;
|
|
986
|
+
overflow: visible;
|
|
987
|
+
}
|
|
954
988
|
|
|
955
989
|
.msg.pending {
|
|
956
990
|
opacity: 0.5;
|
|
@@ -1039,6 +1073,30 @@ export class OxBoardAIChat extends LitElement {
|
|
|
1039
1073
|
line-height: 1.45;
|
|
1040
1074
|
}
|
|
1041
1075
|
|
|
1076
|
+
/* ── 접지 경고 — 근거에 없는 대상을 지목한 답 ───── */
|
|
1077
|
+
.grounding-warning {
|
|
1078
|
+
display: flex;
|
|
1079
|
+
align-items: flex-start;
|
|
1080
|
+
gap: 6px;
|
|
1081
|
+
margin-top: 4px;
|
|
1082
|
+
padding: 5px 8px;
|
|
1083
|
+
border-radius: 6px;
|
|
1084
|
+
border: 1px solid var(--md-sys-color-error, #dc2626);
|
|
1085
|
+
background: var(--md-sys-color-error-container, #fef2f2);
|
|
1086
|
+
color: var(--md-sys-color-on-error-container, #7f1d1d);
|
|
1087
|
+
font-size: 10.5px;
|
|
1088
|
+
line-height: 1.45;
|
|
1089
|
+
}
|
|
1090
|
+
.grounding-warning md-icon {
|
|
1091
|
+
--md-icon-size: 14px;
|
|
1092
|
+
flex-shrink: 0;
|
|
1093
|
+
}
|
|
1094
|
+
.grounding-warning .ids {
|
|
1095
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1096
|
+
font-weight: 600;
|
|
1097
|
+
word-break: break-all;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1042
1100
|
/* ── Tool usages fold-able 박스 — AI 도구 호출 trace ───── */
|
|
1043
1101
|
.tool-usages {
|
|
1044
1102
|
margin-top: 4px;
|
|
@@ -1645,12 +1703,22 @@ export class OxBoardAIChat extends LitElement {
|
|
|
1645
1703
|
? html`<div class="msg-label sender">${line.senderName || i18next.t('board-ai.label.participant')}</div>`
|
|
1646
1704
|
: nothing}
|
|
1647
1705
|
<div class="msg-wrap ${line.role} ${isOther ? 'other' : ''}">
|
|
1648
|
-
<div
|
|
1706
|
+
<div
|
|
1707
|
+
class="msg ${line.role} ${isOther ? 'other' : ''} ${line.pending ? 'pending' : ''} ${line.systemOpen
|
|
1708
|
+
? 'open'
|
|
1709
|
+
: ''}">
|
|
1649
1710
|
${line.pending
|
|
1650
1711
|
? html`<span class="typing-dots"><span></span><span></span><span></span></span>`
|
|
1651
1712
|
: isMarkdown
|
|
1652
1713
|
? html`<div class="md-body">${unsafeHTML(renderMarkdown(content))}</div>`
|
|
1653
|
-
:
|
|
1714
|
+
: line.role === 'system'
|
|
1715
|
+
? html`<span
|
|
1716
|
+
class="sys-text"
|
|
1717
|
+
title=${i18next.t('board-ai.text.toggle-system-note', { defaultValue: '눌러서 펼치기' })}
|
|
1718
|
+
@click=${() => this.toggleSystemNote(idx)}
|
|
1719
|
+
>${content}</span
|
|
1720
|
+
>`
|
|
1721
|
+
: content}
|
|
1654
1722
|
${line.patchId
|
|
1655
1723
|
? html`
|
|
1656
1724
|
<div class="summary ${this.revertedPatchIds.has(line.patchId) ? 'reverted' : ''}">
|
|
@@ -1672,6 +1740,9 @@ export class OxBoardAIChat extends LitElement {
|
|
|
1672
1740
|
${line.followUp
|
|
1673
1741
|
? html`<div class="followup">${unsafeHTML(renderMarkdown(line.followUp.trim()))}</div>`
|
|
1674
1742
|
: nothing}
|
|
1743
|
+
${line.groundingWarnings && line.groundingWarnings.length > 0
|
|
1744
|
+
? this.renderGroundingWarning(line)
|
|
1745
|
+
: nothing}
|
|
1675
1746
|
${line.toolUsages && line.toolUsages.length > 0
|
|
1676
1747
|
? this.renderToolUsages(idx, line)
|
|
1677
1748
|
: nothing}
|
|
@@ -2327,6 +2398,29 @@ export class OxBoardAIChat extends LitElement {
|
|
|
2327
2398
|
`
|
|
2328
2399
|
}
|
|
2329
2400
|
|
|
2401
|
+
/**
|
|
2402
|
+
* 접지 경고 — 답이 언급했지만 **근거에 없던** 대상을 알린다.
|
|
2403
|
+
*
|
|
2404
|
+
* 왜 답을 지우지 않는가: 판정은 식별자 대조라 확정이 아니다(예: 사용자가 화면에서 본 이름을
|
|
2405
|
+
* 대화 밖에서 알고 말한 경우). 그래서 답은 그대로 보여주고, 검증되지 않았다는 사실만 붙인다 —
|
|
2406
|
+
* 사용자가 그 대상을 실제 작업의 근거로 삼기 전에 알아야 하는 정보다.
|
|
2407
|
+
*/
|
|
2408
|
+
private renderGroundingWarning(line: ChatLine) {
|
|
2409
|
+
const ids = line.groundingWarnings ?? []
|
|
2410
|
+
if (ids.length === 0) return nothing
|
|
2411
|
+
return html`
|
|
2412
|
+
<div class="grounding-warning" role="note">
|
|
2413
|
+
<md-icon>report</md-icon>
|
|
2414
|
+
<span>
|
|
2415
|
+
${i18next.t('board-ai.text.ungrounded-mention', {
|
|
2416
|
+
defaultValue: '확인되지 않은 대상을 언급했습니다 — 실제 데이터에 없습니다:'
|
|
2417
|
+
})}
|
|
2418
|
+
<span class="ids">${ids.join(', ')}</span>
|
|
2419
|
+
</span>
|
|
2420
|
+
</div>
|
|
2421
|
+
`
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2330
2424
|
/**
|
|
2331
2425
|
* "AI 가 이런 도구를 사용했습니다" fold-able 박스.
|
|
2332
2426
|
*
|
|
@@ -2379,6 +2473,15 @@ export class OxBoardAIChat extends LitElement {
|
|
|
2379
2473
|
`
|
|
2380
2474
|
}
|
|
2381
2475
|
|
|
2476
|
+
/** 시스템 기록 펼치기/접기 — 곁줄이 길어도 대화가 가려지지 않게. */
|
|
2477
|
+
private toggleSystemNote(idx: number) {
|
|
2478
|
+
const next = [...this.lines]
|
|
2479
|
+
const line = next[idx]
|
|
2480
|
+
if (!line) return
|
|
2481
|
+
next[idx] = { ...line, systemOpen: !line.systemOpen }
|
|
2482
|
+
this.lines = next
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2382
2485
|
private toggleToolUsages(idx: number) {
|
|
2383
2486
|
const next = [...this.lines]
|
|
2384
2487
|
const line = next[idx]
|
|
@@ -2534,6 +2637,7 @@ export class OxBoardAIChat extends LitElement {
|
|
|
2534
2637
|
hostContext: this.hostContext,
|
|
2535
2638
|
toolCategories: this.toolCategories,
|
|
2536
2639
|
boardTools: this.boardTools,
|
|
2640
|
+
requireGroundingTools: this.requireGroundingTools,
|
|
2537
2641
|
knownTypes: this.knownTypes,
|
|
2538
2642
|
categories: this.categories,
|
|
2539
2643
|
componentSchemas: this.componentSchemas,
|
|
@@ -2560,6 +2664,7 @@ export class OxBoardAIChat extends LitElement {
|
|
|
2560
2664
|
followUp: out.followUp || undefined,
|
|
2561
2665
|
patchId: out.patchId || undefined,
|
|
2562
2666
|
toolUsages: Array.isArray(out.toolUsages) ? out.toolUsages : undefined,
|
|
2667
|
+
groundingWarnings: Array.isArray(out.groundingWarnings) ? out.groundingWarnings : undefined,
|
|
2563
2668
|
pending: false
|
|
2564
2669
|
})
|
|
2565
2670
|
|
|
@@ -27,6 +27,8 @@ export interface RemoteMessage {
|
|
|
27
27
|
content: string
|
|
28
28
|
relatedPatchId?: string
|
|
29
29
|
toolUsagesJson?: any
|
|
30
|
+
/** 접지 경고(영속) — 답이 언급했으나 근거에 없던 식별자. 나중에 읽는 사람도 같은 주의를 봐야 한다. */
|
|
31
|
+
groundingWarningsJson?: any
|
|
30
32
|
/** 발신자 — 표시용 name + **자기 여부 판정용 email**(내부 관리키 id 는 쓰지 않는다). */
|
|
31
33
|
creator?: { name?: string; email?: string } | null
|
|
32
34
|
}
|
|
@@ -39,6 +41,7 @@ export function remoteMessageToLine(m: RemoteMessage): DedupLine {
|
|
|
39
41
|
content: m.content,
|
|
40
42
|
patchId: m.relatedPatchId ?? undefined,
|
|
41
43
|
toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,
|
|
44
|
+
groundingWarnings: Array.isArray(m.groundingWarningsJson) ? m.groundingWarningsJson : undefined,
|
|
42
45
|
senderEmail: m.creator?.email ?? undefined,
|
|
43
46
|
senderName: m.creator?.name ?? undefined
|
|
44
47
|
}
|
|
@@ -21,6 +21,11 @@ export interface ChatMutationInputArgs {
|
|
|
21
21
|
toolCategories?: string[]
|
|
22
22
|
/** 코어 보드 편집 도구(addComponent·스타일링·씬 조작) 사용 여부. 기본 true. */
|
|
23
23
|
boardTools?: boolean
|
|
24
|
+
/**
|
|
25
|
+
* 첫 턴에 도구 호출을 강제할지 — 라이브 상태를 다루는 대화면에서 켠다.
|
|
26
|
+
* 근거 없이 상태를 단언하는 답(도구를 아예 부르지 않은 답)을 원천에서 막는다.
|
|
27
|
+
*/
|
|
28
|
+
requireGroundingTools?: boolean
|
|
24
29
|
knownTypes?: string[]
|
|
25
30
|
categories?: string[]
|
|
26
31
|
componentSchemas?: any
|
|
@@ -54,6 +59,7 @@ export function buildChatMutationInput(args: ChatMutationInputArgs): Record<stri
|
|
|
54
59
|
/* 미지정은 서버 기본(전부 허용)에 맡긴다 — null 을 보내 굳이 덮어쓰지 않는다. */
|
|
55
60
|
...(args.toolCategories ? { toolCategories: args.toolCategories } : {}),
|
|
56
61
|
...(args.boardTools === false ? { boardTools: false } : {}),
|
|
62
|
+
...(args.requireGroundingTools ? { requireGroundingTools: true } : {}),
|
|
57
63
|
knownTypes: args.knownTypes,
|
|
58
64
|
categories: args.categories,
|
|
59
65
|
componentSchemas: args.componentSchemas,
|
|
@@ -100,6 +100,13 @@ export declare class OxBoardAIChat extends LitElement {
|
|
|
100
100
|
hostContext?: any;
|
|
101
101
|
/** 코어 보드 편집 도구 사용 여부. 기본 true. 보드를 고칠 이유가 없는 면에서 false. */
|
|
102
102
|
boardTools: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* 라이브 상태를 다루는 대화면인가 — true 면 **첫 턴에 도구 호출을 강제**한다.
|
|
105
|
+
*
|
|
106
|
+
* 접지 가드는 지어낸 식별자를 잡지만 식별자 없는 상황 서술("바쁘게 움직이고 있다")은 잡지 못한다.
|
|
107
|
+
* 그건 도구를 부르지 않고 답한 경우이므로, 조회를 먼저 하게 만들어 원천을 막는다.
|
|
108
|
+
*/
|
|
109
|
+
requireGroundingTools: boolean;
|
|
103
110
|
/** board-import registry scopes. */
|
|
104
111
|
scopes?: string[];
|
|
105
112
|
knownTypes?: string[];
|
|
@@ -261,6 +268,14 @@ export declare class OxBoardAIChat extends LitElement {
|
|
|
261
268
|
* system : 없음
|
|
262
269
|
*/
|
|
263
270
|
private renderMessageActions;
|
|
271
|
+
/**
|
|
272
|
+
* 접지 경고 — 답이 언급했지만 **근거에 없던** 대상을 알린다.
|
|
273
|
+
*
|
|
274
|
+
* 왜 답을 지우지 않는가: 판정은 식별자 대조라 확정이 아니다(예: 사용자가 화면에서 본 이름을
|
|
275
|
+
* 대화 밖에서 알고 말한 경우). 그래서 답은 그대로 보여주고, 검증되지 않았다는 사실만 붙인다 —
|
|
276
|
+
* 사용자가 그 대상을 실제 작업의 근거로 삼기 전에 알아야 하는 정보다.
|
|
277
|
+
*/
|
|
278
|
+
private renderGroundingWarning;
|
|
264
279
|
/**
|
|
265
280
|
* "AI 가 이런 도구를 사용했습니다" fold-able 박스.
|
|
266
281
|
*
|
|
@@ -269,6 +284,8 @@ export declare class OxBoardAIChat extends LitElement {
|
|
|
269
284
|
* - 디버그 / 신뢰도 향상용 — 사용자가 "왜 이렇게 답했지" 의문 가질 때 즉시 검증.
|
|
270
285
|
*/
|
|
271
286
|
private renderToolUsages;
|
|
287
|
+
/** 시스템 기록 펼치기/접기 — 곁줄이 길어도 대화가 가려지지 않게. */
|
|
288
|
+
private toggleSystemNote;
|
|
272
289
|
private toggleToolUsages;
|
|
273
290
|
private formatJson;
|
|
274
291
|
private copyMessage;
|
|
@@ -44,6 +44,7 @@ const BOARD_AI_CHAT_MUTATION = gql `
|
|
|
44
44
|
patchId
|
|
45
45
|
toolUsages
|
|
46
46
|
actions
|
|
47
|
+
groundingWarnings
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
`;
|
|
@@ -55,6 +56,7 @@ const CHAT_MESSAGES_QUERY = gql `
|
|
|
55
56
|
content
|
|
56
57
|
relatedPatchId
|
|
57
58
|
toolUsagesJson
|
|
59
|
+
groundingWarningsJson
|
|
58
60
|
createdAt
|
|
59
61
|
creator {
|
|
60
62
|
name
|
|
@@ -73,6 +75,7 @@ const CHAT_MESSAGE_SUBSCRIPTION = gql `
|
|
|
73
75
|
content
|
|
74
76
|
relatedPatchId
|
|
75
77
|
toolUsagesJson
|
|
78
|
+
groundingWarningsJson
|
|
76
79
|
createdAt
|
|
77
80
|
creator {
|
|
78
81
|
name
|
|
@@ -136,6 +139,13 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
136
139
|
this.selectedRefids = [];
|
|
137
140
|
/** 코어 보드 편집 도구 사용 여부. 기본 true. 보드를 고칠 이유가 없는 면에서 false. */
|
|
138
141
|
this.boardTools = true;
|
|
142
|
+
/**
|
|
143
|
+
* 라이브 상태를 다루는 대화면인가 — true 면 **첫 턴에 도구 호출을 강제**한다.
|
|
144
|
+
*
|
|
145
|
+
* 접지 가드는 지어낸 식별자를 잡지만 식별자 없는 상황 서술("바쁘게 움직이고 있다")은 잡지 못한다.
|
|
146
|
+
* 그건 도구를 부르지 않고 답한 경우이므로, 조회를 먼저 하게 만들어 원천을 막는다.
|
|
147
|
+
*/
|
|
148
|
+
this.requireGroundingTools = false;
|
|
139
149
|
this.placeholder = '자연어로 명령하세요 (Shift+Enter 줄바꿈, Enter 전송)';
|
|
140
150
|
this.lines = [];
|
|
141
151
|
this.input = '';
|
|
@@ -789,6 +799,21 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
789
799
|
margin: 0;
|
|
790
800
|
border: 0;
|
|
791
801
|
}
|
|
802
|
+
/* 시스템 기록은 **곁줄**이다 — 길면 화면을 잡아먹어 대화가 안 보인다(옮겨온 대화를 한 덩어리로
|
|
803
|
+
넣었을 때 실제로 그랬다). 세 줄로 접고 눌러서 펼친다. */
|
|
804
|
+
.msg.system .sys-text {
|
|
805
|
+
display: -webkit-box;
|
|
806
|
+
-webkit-box-orient: vertical;
|
|
807
|
+
-webkit-line-clamp: 3;
|
|
808
|
+
overflow: hidden;
|
|
809
|
+
white-space: pre-wrap;
|
|
810
|
+
cursor: pointer;
|
|
811
|
+
}
|
|
812
|
+
.msg.system.open .sys-text {
|
|
813
|
+
display: block;
|
|
814
|
+
-webkit-line-clamp: unset;
|
|
815
|
+
overflow: visible;
|
|
816
|
+
}
|
|
792
817
|
|
|
793
818
|
.msg.pending {
|
|
794
819
|
opacity: 0.5;
|
|
@@ -877,6 +902,30 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
877
902
|
line-height: 1.45;
|
|
878
903
|
}
|
|
879
904
|
|
|
905
|
+
/* ── 접지 경고 — 근거에 없는 대상을 지목한 답 ───── */
|
|
906
|
+
.grounding-warning {
|
|
907
|
+
display: flex;
|
|
908
|
+
align-items: flex-start;
|
|
909
|
+
gap: 6px;
|
|
910
|
+
margin-top: 4px;
|
|
911
|
+
padding: 5px 8px;
|
|
912
|
+
border-radius: 6px;
|
|
913
|
+
border: 1px solid var(--md-sys-color-error, #dc2626);
|
|
914
|
+
background: var(--md-sys-color-error-container, #fef2f2);
|
|
915
|
+
color: var(--md-sys-color-on-error-container, #7f1d1d);
|
|
916
|
+
font-size: 10.5px;
|
|
917
|
+
line-height: 1.45;
|
|
918
|
+
}
|
|
919
|
+
.grounding-warning md-icon {
|
|
920
|
+
--md-icon-size: 14px;
|
|
921
|
+
flex-shrink: 0;
|
|
922
|
+
}
|
|
923
|
+
.grounding-warning .ids {
|
|
924
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
925
|
+
font-weight: 600;
|
|
926
|
+
word-break: break-all;
|
|
927
|
+
}
|
|
928
|
+
|
|
880
929
|
/* ── Tool usages fold-able 박스 — AI 도구 호출 trace ───── */
|
|
881
930
|
.tool-usages {
|
|
882
931
|
margin-top: 4px;
|
|
@@ -1482,12 +1531,22 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
1482
1531
|
? html `<div class="msg-label sender">${line.senderName || i18next.t('board-ai.label.participant')}</div>`
|
|
1483
1532
|
: nothing}
|
|
1484
1533
|
<div class="msg-wrap ${line.role} ${isOther ? 'other' : ''}">
|
|
1485
|
-
<div
|
|
1534
|
+
<div
|
|
1535
|
+
class="msg ${line.role} ${isOther ? 'other' : ''} ${line.pending ? 'pending' : ''} ${line.systemOpen
|
|
1536
|
+
? 'open'
|
|
1537
|
+
: ''}">
|
|
1486
1538
|
${line.pending
|
|
1487
1539
|
? html `<span class="typing-dots"><span></span><span></span><span></span></span>`
|
|
1488
1540
|
: isMarkdown
|
|
1489
1541
|
? html `<div class="md-body">${unsafeHTML(renderMarkdown(content))}</div>`
|
|
1490
|
-
:
|
|
1542
|
+
: line.role === 'system'
|
|
1543
|
+
? html `<span
|
|
1544
|
+
class="sys-text"
|
|
1545
|
+
title=${i18next.t('board-ai.text.toggle-system-note', { defaultValue: '눌러서 펼치기' })}
|
|
1546
|
+
@click=${() => this.toggleSystemNote(idx)}
|
|
1547
|
+
>${content}</span
|
|
1548
|
+
>`
|
|
1549
|
+
: content}
|
|
1491
1550
|
${line.patchId
|
|
1492
1551
|
? html `
|
|
1493
1552
|
<div class="summary ${this.revertedPatchIds.has(line.patchId) ? 'reverted' : ''}">
|
|
@@ -1509,6 +1568,9 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
1509
1568
|
${line.followUp
|
|
1510
1569
|
? html `<div class="followup">${unsafeHTML(renderMarkdown(line.followUp.trim()))}</div>`
|
|
1511
1570
|
: nothing}
|
|
1571
|
+
${line.groundingWarnings && line.groundingWarnings.length > 0
|
|
1572
|
+
? this.renderGroundingWarning(line)
|
|
1573
|
+
: nothing}
|
|
1512
1574
|
${line.toolUsages && line.toolUsages.length > 0
|
|
1513
1575
|
? this.renderToolUsages(idx, line)
|
|
1514
1576
|
: nothing}
|
|
@@ -2086,6 +2148,29 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2086
2148
|
</div>
|
|
2087
2149
|
`;
|
|
2088
2150
|
}
|
|
2151
|
+
/**
|
|
2152
|
+
* 접지 경고 — 답이 언급했지만 **근거에 없던** 대상을 알린다.
|
|
2153
|
+
*
|
|
2154
|
+
* 왜 답을 지우지 않는가: 판정은 식별자 대조라 확정이 아니다(예: 사용자가 화면에서 본 이름을
|
|
2155
|
+
* 대화 밖에서 알고 말한 경우). 그래서 답은 그대로 보여주고, 검증되지 않았다는 사실만 붙인다 —
|
|
2156
|
+
* 사용자가 그 대상을 실제 작업의 근거로 삼기 전에 알아야 하는 정보다.
|
|
2157
|
+
*/
|
|
2158
|
+
renderGroundingWarning(line) {
|
|
2159
|
+
const ids = line.groundingWarnings ?? [];
|
|
2160
|
+
if (ids.length === 0)
|
|
2161
|
+
return nothing;
|
|
2162
|
+
return html `
|
|
2163
|
+
<div class="grounding-warning" role="note">
|
|
2164
|
+
<md-icon>report</md-icon>
|
|
2165
|
+
<span>
|
|
2166
|
+
${i18next.t('board-ai.text.ungrounded-mention', {
|
|
2167
|
+
defaultValue: '확인되지 않은 대상을 언급했습니다 — 실제 데이터에 없습니다:'
|
|
2168
|
+
})}
|
|
2169
|
+
<span class="ids">${ids.join(', ')}</span>
|
|
2170
|
+
</span>
|
|
2171
|
+
</div>
|
|
2172
|
+
`;
|
|
2173
|
+
}
|
|
2089
2174
|
/**
|
|
2090
2175
|
* "AI 가 이런 도구를 사용했습니다" fold-able 박스.
|
|
2091
2176
|
*
|
|
@@ -2136,6 +2221,15 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2136
2221
|
</div>
|
|
2137
2222
|
`;
|
|
2138
2223
|
}
|
|
2224
|
+
/** 시스템 기록 펼치기/접기 — 곁줄이 길어도 대화가 가려지지 않게. */
|
|
2225
|
+
toggleSystemNote(idx) {
|
|
2226
|
+
const next = [...this.lines];
|
|
2227
|
+
const line = next[idx];
|
|
2228
|
+
if (!line)
|
|
2229
|
+
return;
|
|
2230
|
+
next[idx] = { ...line, systemOpen: !line.systemOpen };
|
|
2231
|
+
this.lines = next;
|
|
2232
|
+
}
|
|
2139
2233
|
toggleToolUsages(idx) {
|
|
2140
2234
|
const next = [...this.lines];
|
|
2141
2235
|
const line = next[idx];
|
|
@@ -2277,6 +2371,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2277
2371
|
hostContext: this.hostContext,
|
|
2278
2372
|
toolCategories: this.toolCategories,
|
|
2279
2373
|
boardTools: this.boardTools,
|
|
2374
|
+
requireGroundingTools: this.requireGroundingTools,
|
|
2280
2375
|
knownTypes: this.knownTypes,
|
|
2281
2376
|
categories: this.categories,
|
|
2282
2377
|
componentSchemas: this.componentSchemas,
|
|
@@ -2301,6 +2396,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
|
|
|
2301
2396
|
followUp: out.followUp || undefined,
|
|
2302
2397
|
patchId: out.patchId || undefined,
|
|
2303
2398
|
toolUsages: Array.isArray(out.toolUsages) ? out.toolUsages : undefined,
|
|
2399
|
+
groundingWarnings: Array.isArray(out.groundingWarnings) ? out.groundingWarnings : undefined,
|
|
2304
2400
|
pending: false
|
|
2305
2401
|
});
|
|
2306
2402
|
// 호스트로 patch 이벤트 전파
|
|
@@ -2384,6 +2480,10 @@ __decorate([
|
|
|
2384
2480
|
property({ type: Boolean, attribute: 'board-tools' }),
|
|
2385
2481
|
__metadata("design:type", Object)
|
|
2386
2482
|
], OxBoardAIChat.prototype, "boardTools", void 0);
|
|
2483
|
+
__decorate([
|
|
2484
|
+
property({ type: Boolean, attribute: 'require-grounding-tools' }),
|
|
2485
|
+
__metadata("design:type", Object)
|
|
2486
|
+
], OxBoardAIChat.prototype, "requireGroundingTools", void 0);
|
|
2387
2487
|
__decorate([
|
|
2388
2488
|
property({ type: Array }),
|
|
2389
2489
|
__metadata("design:type", Array)
|