@things-factory/ai-assistant 10.1.6 → 10.1.7

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.
Files changed (44) hide show
  1. package/client/components/board-ai-chat.ts +165 -100
  2. package/client/components/chat-defaults.ts +13 -0
  3. package/client/components/chat-input-builder.test.ts +81 -0
  4. package/client/components/chat-input-builder.ts +43 -0
  5. package/client/components/chat-triggers.test.ts +72 -0
  6. package/client/components/chat-triggers.ts +62 -0
  7. package/dist-client/components/board-ai-chat.d.ts +8 -0
  8. package/dist-client/components/board-ai-chat.js +160 -100
  9. package/dist-client/components/board-ai-chat.js.map +1 -1
  10. package/dist-client/components/chat-defaults.d.ts +12 -0
  11. package/dist-client/components/chat-defaults.js +2 -0
  12. package/dist-client/components/chat-defaults.js.map +1 -1
  13. package/dist-client/components/chat-input-builder.d.ts +29 -0
  14. package/dist-client/components/chat-input-builder.js +22 -0
  15. package/dist-client/components/chat-input-builder.js.map +1 -1
  16. package/dist-client/components/chat-input-builder.test.d.ts +1 -0
  17. package/dist-client/components/chat-input-builder.test.js +67 -0
  18. package/dist-client/components/chat-input-builder.test.js.map +1 -0
  19. package/dist-client/components/chat-triggers.d.ts +16 -0
  20. package/dist-client/components/chat-triggers.js +42 -0
  21. package/dist-client/components/chat-triggers.js.map +1 -0
  22. package/dist-client/components/chat-triggers.test.d.ts +1 -0
  23. package/dist-client/components/chat-triggers.test.js +61 -0
  24. package/dist-client/components/chat-triggers.test.js.map +1 -0
  25. package/dist-client/tsconfig.tsbuildinfo +1 -1
  26. package/dist-server/service/assistant-chat-resolver.d.ts +32 -0
  27. package/dist-server/service/assistant-chat-resolver.js +342 -0
  28. package/dist-server/service/assistant-chat-resolver.js.map +1 -0
  29. package/dist-server/service/chat-session-resolver.js +18 -18
  30. package/dist-server/service/chat-session-resolver.js.map +1 -1
  31. package/dist-server/service/index.d.ts +3 -1
  32. package/dist-server/service/index.js +4 -0
  33. package/dist-server/service/index.js.map +1 -1
  34. package/dist-server/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +5 -5
  36. package/server/service/assistant-chat-resolver.ts +326 -0
  37. package/server/service/chat-session-resolver.ts +18 -18
  38. package/server/service/index.ts +4 -0
  39. package/test/translations.test.ts +79 -0
  40. package/translations/en.json +59 -0
  41. package/translations/ja.json +58 -0
  42. package/translations/ko.json +59 -0
  43. package/translations/ms.json +58 -0
  44. package/translations/zh.json +58 -0
@@ -67,3 +67,46 @@ export function buildChatMutationInput(args: ChatMutationInputArgs): Record<stri
67
67
  ...(args.mentions && args.mentions.length > 0 && { mentions: args.mentions })
68
68
  }
69
69
  }
70
+
71
+ /**
72
+ * Input for `assistantChat` — the door for a surface that has no document of its own.
73
+ *
74
+ * Deliberately a separate builder rather than a flag on the one above. The two mutations take
75
+ * different inputs, and folding them together would mean a board field reaching a surface that
76
+ * has no board, or a required field going missing with nothing to notice it. The one thing they
77
+ * share is the conversation, and that is passed the same way.
78
+ */
79
+ export interface AssistantChatInputArgs {
80
+ sessionId?: string
81
+ history: Array<{ role: string; content: string }>
82
+ /** Who the assistant is on this surface. The host owns this prose; the server appends tool rules. */
83
+ systemPrompt: string
84
+ truncateAfterMessageId?: string
85
+ hostContext?: any
86
+ toolCategories?: string[]
87
+ requireGroundingTools?: boolean
88
+ }
89
+
90
+ /**
91
+ * Build the neutral input.
92
+ *
93
+ * Refuses an empty system prompt instead of sending one. A conversation with no persona still
94
+ * answers — it just answers as nobody, on a surface that never said what it is — and that reads
95
+ * as a bad model rather than a missing argument.
96
+ */
97
+ export function buildAssistantChatInput(args: AssistantChatInputArgs): Record<string, any> {
98
+ if (!args.systemPrompt?.trim()) {
99
+ throw new Error('[ox-board-ai-chat] systemPrompt is required when the chat talks to assistantChat')
100
+ }
101
+
102
+ return {
103
+ sessionId: args.sessionId ?? null,
104
+ messages: args.history,
105
+ systemPrompt: args.systemPrompt,
106
+ ...(args.truncateAfterMessageId ? { truncateAfterMessageId: args.truncateAfterMessageId } : {}),
107
+ ...(args.hostContext ? { hostContext: args.hostContext } : {}),
108
+ /* Absent means every registered category; an empty array means none. Both are meant, so pass it through. */
109
+ ...(args.toolCategories ? { toolCategories: args.toolCategories } : {}),
110
+ ...(args.requireGroundingTools ? { requireGroundingTools: true } : {})
111
+ }
112
+ }
@@ -0,0 +1,72 @@
1
+ /*
2
+ * What this guards: **the empty chat does not teach what this surface cannot do.**
3
+ *
4
+ * operato-figure mounted the chat and its empty screen offered all three triggers. `#` mentions
5
+ * components of a document figure has none of; `/` inserts templates figure registered none of,
6
+ * so the popup opens empty. Neither errors — they just do nothing, which reads as broken rather
7
+ * than not applicable.
8
+ */
9
+ import { availableTriggers } from './chat-triggers'
10
+
11
+ const chars = (availability: any) => availableTriggers(availability).map(chip => chip.char)
12
+
13
+ const board = { hasMentionableThings: true, hasSession: true, slashTemplateCount: 12 }
14
+ const figure = { hasMentionableThings: false, hasSession: true, slashTemplateCount: 0 }
15
+
16
+ describe('a board authoring surface', () => {
17
+ it('offers all three', () => {
18
+ expect(chars(board)).toEqual(['#', '@', '/'])
19
+ })
20
+ })
21
+
22
+ describe('a surface with no document of its own', () => {
23
+ it('★ does not offer # — there is nothing to mention', () => {
24
+ expect(chars(figure)).not.toContain('#')
25
+ })
26
+
27
+ it('★ does not offer / — nobody registered a template, so the popup would open empty', () => {
28
+ expect(chars(figure)).not.toContain('/')
29
+ })
30
+
31
+ it('still offers @ — mentioning a colleague works wherever a conversation is stored', () => {
32
+ expect(chars(figure)).toEqual(['@'])
33
+ })
34
+ })
35
+
36
+ describe('each chip is read off the one thing it needs', () => {
37
+ it('a declared type list is enough for # — a live document is not required', () => {
38
+ expect(chars({ ...figure, hasMentionableThings: true })).toContain('#')
39
+ })
40
+
41
+ it('an ad-hoc turn has no @ — a mention notifies a participant and there is no conversation to join', () => {
42
+ expect(chars({ ...board, hasSession: false })).not.toContain('@')
43
+ })
44
+
45
+ it('one registered template is enough for /', () => {
46
+ expect(chars({ ...figure, slashTemplateCount: 1 })).toContain('/')
47
+ })
48
+ })
49
+
50
+ describe('what the row looks like when nothing is available', () => {
51
+ it('★ empty, so the caller can leave the whole row out rather than draw a heading over nothing', () => {
52
+ expect(availableTriggers({ hasMentionableThings: false, hasSession: false, slashTemplateCount: 0 })).toEqual([])
53
+ })
54
+ })
55
+
56
+ describe('order', () => {
57
+ it('stays # @ / regardless of which ones survive — it is the order people learned', () => {
58
+ expect(chars({ hasMentionableThings: true, hasSession: false, slashTemplateCount: 3 })).toEqual(['#', '/'])
59
+ })
60
+ })
61
+
62
+ describe('every chip carries its own words', () => {
63
+ it('label and tooltip keys are distinct per chip — one shared key would caption them all alike', () => {
64
+ const chips = availableTriggers(board)
65
+ const labels = new Set(chips.map(chip => chip.labelKey))
66
+ const tooltips = new Set(chips.map(chip => chip.tooltipKey))
67
+
68
+ expect(labels.size).toBe(chips.length)
69
+ expect(tooltips.size).toBe(chips.length)
70
+ for (const chip of chips) expect(chip.labelKey.startsWith('ai-assistant.')).toBe(true)
71
+ })
72
+ })
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Which trigger characters a chat surface can actually use — **asked, not declared.**
3
+ *
4
+ * All three chips (`#` `@` `/`) were always drawn. On operato-figure that taught two things the
5
+ * surface cannot do: `#` mentions components of a document figure has none of, and `/` inserts
6
+ * templates figure registered none of, so the popup opens empty.
7
+ *
8
+ * A chip that opens nothing is worse than an absent one. The reader does not conclude "not
9
+ * applicable here" — they conclude the feature is broken, and they are half right.
10
+ *
11
+ * So each chip is read off the thing it needs. A host that gains a document, a stored session or
12
+ * a template set gets the chip with no extra declaration; one that never does never shows it.
13
+ * Same shape as lite-menu deciding whether the board menu type is selectable by asking whether
14
+ * its renderer is registered, rather than declaring that it is.
15
+ */
16
+
17
+ /** What the surface has to offer, as plain facts. The caller reads these off itself. */
18
+ export interface TriggerAvailability {
19
+ /** Something to mention: a live document, a static one, or a declared type list. */
20
+ hasMentionableThings: boolean
21
+ /** A stored conversation. A `@` mention notifies a participant, and an ad-hoc turn has none. */
22
+ hasSession: boolean
23
+ /** How many insertable templates the host registered. */
24
+ slashTemplateCount: number
25
+ }
26
+
27
+ export interface TriggerChip {
28
+ char: '#' | '@' | '/'
29
+ labelKey: string
30
+ tooltipKey: string
31
+ }
32
+
33
+ /** Order is fixed: `#` `@` `/`. It is the order people learned, so it does not follow availability. */
34
+ export function availableTriggers(availability: TriggerAvailability): TriggerChip[] {
35
+ const chips: TriggerChip[] = []
36
+
37
+ if (availability.hasMentionableThings) {
38
+ chips.push({
39
+ char: '#',
40
+ labelKey: 'ai-assistant.label.trigger-component',
41
+ tooltipKey: 'ai-assistant.text.trigger-component-tooltip'
42
+ })
43
+ }
44
+
45
+ if (availability.hasSession) {
46
+ chips.push({
47
+ char: '@',
48
+ labelKey: 'ai-assistant.label.trigger-user',
49
+ tooltipKey: 'ai-assistant.text.trigger-user-tooltip'
50
+ })
51
+ }
52
+
53
+ if (availability.slashTemplateCount > 0) {
54
+ chips.push({
55
+ char: '/',
56
+ labelKey: 'ai-assistant.label.trigger-insert',
57
+ tooltipKey: 'ai-assistant.text.trigger-insert-tooltip'
58
+ })
59
+ }
60
+
61
+ return chips
62
+ }
@@ -100,6 +100,12 @@ export declare class OxBoardAIChat extends LitElement {
100
100
  hostContext?: any;
101
101
  /** 코어 보드 편집 도구 사용 여부. 기본 true. 보드를 고칠 이유가 없는 면에서 false. */
102
102
  boardTools: boolean;
103
+ chatEndpoint: 'boardAIChat' | 'assistantChat';
104
+ /**
105
+ * Who the assistant is on this surface, in the host's words. Required by `assistantChat`; the
106
+ * board conversation carries its own persona on the server and ignores this.
107
+ */
108
+ systemPrompt?: string;
103
109
  /**
104
110
  * 라이브 상태를 다루는 대화면인가 — true 면 **첫 턴에 도구 호출을 강제**한다.
105
111
  *
@@ -306,6 +312,8 @@ export declare class OxBoardAIChat extends LitElement {
306
312
  * 읽었고, 그러면 호스트 값을 한쪽만 반영하는 상태가 만들어질 수 있었다.
307
313
  */
308
314
  private get exampleGroups();
315
+ /** 이 표면이 실제로 쓸 수 있는 트리거 — 판정은 `chat-triggers.ts` 가 하고 여기서는 사실만 읽는다. */
316
+ private get triggerChips();
309
317
  private static readonly EXAMPLE_GROUPS;
310
318
  /** 보드의 세션 탭 — 다중 세션 활성화 시 패널 상단. host 가 sessions 비워두면 표시 X.
311
319
  * 레이블은 session.name 우선, 없으면 fallback "세션 N". */