@things-factory/ai-assistant 10.1.109 → 10.1.111
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 +290 -18
- package/client/components/assistant-dock.ts +8 -3
- package/client/setup-assistant-dock.ts +9 -1
- package/client/utils/composer-frame.ts +49 -0
- package/client/utils/pasted-table.ts +2 -2
- package/dist-client/components/assistant-chat.d.ts +18 -0
- package/dist-client/components/assistant-chat.js +291 -18
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.js +9 -3
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/setup-assistant-dock.js +8 -1
- package/dist-client/setup-assistant-dock.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/composer-frame.d.ts +28 -0
- package/dist-client/utils/composer-frame.js +46 -0
- package/dist-client/utils/composer-frame.js.map +1 -0
- package/dist-client/utils/pasted-table.d.ts +1 -0
- package/dist-client/utils/pasted-table.js +1 -1
- package/dist-client/utils/pasted-table.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/assistant-dock.test.ts +28 -0
- package/test/composer-frame.test.ts +125 -0
- package/test/pasted-table.test.ts +1 -1
- package/translations/en.json +7 -1
- package/translations/ja.json +7 -1
- package/translations/ko.json +7 -1
- package/translations/ms.json +7 -1
- package/translations/zh.json +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/ai-assistant",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.111",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"things-factory": true,
|
|
6
6
|
"author": "heartyoh",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"@operato/styles": "^10.0.0",
|
|
36
36
|
"@things-factory/ai-client-base": "^10.1.81",
|
|
37
37
|
"@things-factory/apptool-base": "^10.1.106",
|
|
38
|
-
"@things-factory/attachment-base": "^10.1.
|
|
39
|
-
"@things-factory/auth-base": "^10.1.
|
|
38
|
+
"@things-factory/attachment-base": "^10.1.111",
|
|
39
|
+
"@things-factory/auth-base": "^10.1.111",
|
|
40
40
|
"@things-factory/env": "^10.1.20",
|
|
41
41
|
"@things-factory/shell": "^10.1.106",
|
|
42
42
|
"dompurify": "^3.0.0",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"copyfiles": "^2.4.1",
|
|
53
53
|
"rimraf": "^5.0.0"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "f33122c15e5b3e45393129e9418e2c367a273ade"
|
|
56
56
|
}
|
|
@@ -147,3 +147,31 @@ describe('what twin needed of the dock', () => {
|
|
|
147
147
|
assert.match(dock, /this\.startCarried\(ask\)\.catch\(error => \{\s*notify\(\{ level: 'error'/)
|
|
148
148
|
})
|
|
149
149
|
})
|
|
150
|
+
|
|
151
|
+
describe('closing and opening the dock again (design/ai/shared-dock.md §2-1, §2-7)', () => {
|
|
152
|
+
const setup = code('../client/setup-assistant-dock.ts')
|
|
153
|
+
const between = (text: string, from: string, to: string) => text.slice(text.indexOf(from), text.indexOf(to, text.indexOf(from)))
|
|
154
|
+
|
|
155
|
+
it('★ the viewpart puts back one dock - a template that makes the element makes a new dock on every opening', () => {
|
|
156
|
+
assert.match(setup, /const dock = document\.createElement\('ox-assistant-dock'\)/)
|
|
157
|
+
assert.match(setup, /template: html`\$\{dock\}`/)
|
|
158
|
+
assert.doesNotMatch(setup, /<ox-assistant-dock[\s>]/)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('★ closing keeps the conversation - no reset, no cancelled opening', () => {
|
|
162
|
+
const closing = between(dock, 'disconnectedCallback() {', 'super.disconnectedCallback()')
|
|
163
|
+
assert.doesNotMatch(closing, /controller\.clear\(\)/)
|
|
164
|
+
assert.doesNotMatch(closing, /this\.revision\+\+/)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('★ opening again follows what moved, it does not open the conversation anew', () => {
|
|
168
|
+
const opening = between(dock, 'connectedCallback() {', 'disconnectedCallback() {')
|
|
169
|
+
assert.match(opening, /if \(!this\.openedIdentity\) return this\.follow\(true\)/)
|
|
170
|
+
assert.match(opening, /setOpenSession\(this\.sessionId\)\s*this\.follow\(\)/)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('★ the chat put back reads its conversation again and starts its broadcast and presence again', () => {
|
|
174
|
+
const opening = between(chat, ' connectedCallback() {', ' private async onSubjectChanged()')
|
|
175
|
+
assert.match(opening, /if \(this\.hasUpdated && this\.sessionId\) \{\s*this\.loadHistory\(\)\s*this\._startPresence\(\)\s*this\._startMessageSubscription\(\)/)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The composer's frame (client/utils/composer-frame.ts, design/ai/shared-dock.md §10): it grows with its text and
|
|
3
|
+
* stops at 40% of the chat, Enter means a line in the wide box, and a pasted table can go back to words.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from 'fs'
|
|
6
|
+
import { join } from 'path'
|
|
7
|
+
|
|
8
|
+
import { appendWords, composerHeight, composerKeyAction, sendShortcut, tableAsWords } from '../client/utils/composer-frame'
|
|
9
|
+
|
|
10
|
+
const chat = readFileSync(join(__dirname, '../client/components/assistant-chat.ts'), 'utf8')
|
|
11
|
+
|
|
12
|
+
describe('the composer grows with its text', () => {
|
|
13
|
+
it('★ one line stands at the resting height', () => {
|
|
14
|
+
expect(composerHeight(35, 714, 54)).toEqual({ height: 54, scrolls: false })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('★ twelve lines show whole - the box was three lines high and hid the rest', () => {
|
|
18
|
+
expect(composerHeight(221, 714, 54)).toEqual({ height: 221, scrolls: false })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('★ past 40% of the chat it stops and scrolls', () => {
|
|
22
|
+
expect(composerHeight(900, 714, 54)).toEqual({ height: 285, scrolls: true })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('a chat too short for 40% to hold the resting height keeps the resting height', () => {
|
|
26
|
+
expect(composerHeight(200, 100, 54)).toEqual({ height: 54, scrolls: true })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('★ the chat sizes the box from the rule, and the dead 140px ceiling is gone', () => {
|
|
30
|
+
expect(chat).toMatch(/composerHeight\(/)
|
|
31
|
+
expect(chat).not.toMatch(/max-height: 140px/)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('Enter', () => {
|
|
36
|
+
const enter = { key: 'Enter' }
|
|
37
|
+
|
|
38
|
+
it('★ in the small box Enter sends and Shift+Enter breaks the line', () => {
|
|
39
|
+
expect(composerKeyAction(enter, false)).toBe('send')
|
|
40
|
+
expect(composerKeyAction({ ...enter, shiftKey: true }, false)).toBeNull()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('★ in the wide box Enter breaks the line', () => {
|
|
44
|
+
expect(composerKeyAction(enter, true)).toBeNull()
|
|
45
|
+
expect(composerKeyAction({ ...enter, shiftKey: true }, true)).toBeNull()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('★ Ctrl/Cmd+Enter sends in both boxes', () => {
|
|
49
|
+
for (const wide of [false, true]) {
|
|
50
|
+
expect(composerKeyAction({ ...enter, ctrlKey: true }, wide)).toBe('send')
|
|
51
|
+
expect(composerKeyAction({ ...enter, metaKey: true }, wide)).toBe('send')
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('Escape closes the wide box and leaves the small one alone', () => {
|
|
56
|
+
expect(composerKeyAction({ key: 'Escape' }, true)).toBe('narrow')
|
|
57
|
+
expect(composerKeyAction({ key: 'Escape' }, false)).toBeNull()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('other keys are the textarea\'s', () => {
|
|
61
|
+
expect(composerKeyAction({ key: 'a' }, true)).toBeNull()
|
|
62
|
+
expect(composerKeyAction({ ...enter, altKey: true }, false)).toBeNull()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('the shortcut is named in this machine\'s keys', () => {
|
|
66
|
+
expect(sendShortcut('MacIntel')).toBe('⌘ Enter')
|
|
67
|
+
expect(sendShortcut('Win32')).toBe('Ctrl+Enter')
|
|
68
|
+
expect(sendShortcut(undefined)).toBe('Ctrl+Enter')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('★ the chat decides the key by the rule', () => {
|
|
72
|
+
expect(chat).toMatch(/composerKeyAction\(e, this\.composerWide\)/)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('a pasted table back as words', () => {
|
|
77
|
+
const rows = [
|
|
78
|
+
['이용일', '가맹점'],
|
|
79
|
+
['2026-09-12', '카카오T']
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
it('★ the clipboard\'s own text comes back as it was copied', () => {
|
|
83
|
+
expect(tableAsWords({ rows, text: '이용일\t가맹점\r\n2026-09-12\t카카오T\r\n' })).toBe('이용일\t가맹점\n2026-09-12\t카카오T')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('★ a clipboard that held only HTML gives the rows tab-separated', () => {
|
|
87
|
+
expect(tableAsWords({ rows, text: '' })).toBe('이용일\t가맹점\n2026-09-12\t카카오T')
|
|
88
|
+
expect(tableAsWords({ rows })).toBe('이용일\t가맹점\n2026-09-12\t카카오T')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('the words go on a line of their own after what is written', () => {
|
|
92
|
+
expect(appendWords('', 'a\tb')).toBe('a\tb')
|
|
93
|
+
expect(appendWords('경비로 올려 줘 \n', 'a\tb')).toBe('경비로 올려 줘\na\tb')
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('the pasted table chip', () => {
|
|
98
|
+
it('★ the paste hands its rows and its words to the chip', () => {
|
|
99
|
+
expect(chat).toMatch(/onAttachFiles\(\[new File\(\[pasted\.text\], pasted\.name, \{ type: 'text\/csv' \}\)\], \{ \.\.\.pasted\.table, text: words \}\)/)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('★ the chip names rows × columns, opens the rows, and gives them back as words', () => {
|
|
103
|
+
const chip = chat.slice(chat.indexOf('${att.table'), chat.indexOf('formatFileSize(att.size)'))
|
|
104
|
+
expect(chip).toMatch(/table_chart/)
|
|
105
|
+
expect(chip).toMatch(/this\.tableShape\(att\.table\)/)
|
|
106
|
+
expect(chip).toMatch(/this\.previewAttachmentId = /)
|
|
107
|
+
expect(chat).toMatch(/@click=\$\{\(\) => this\.tableToWords\(att\.id\)\}/)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('found on the screen (office :4700, 10.1.110)', () => {
|
|
112
|
+
it('★ the Escape that narrows the wide box does not reach the floating dock, which closed on it', () => {
|
|
113
|
+
const branch = chat.slice(chat.indexOf("if (action === 'narrow')"), chat.indexOf("} else if (action === 'send')"))
|
|
114
|
+
expect(branch).toMatch(/e\.stopPropagation\(\)/)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('★ the send shortcut stands out of the row - it took 74px of the wide box\'s width', () => {
|
|
118
|
+
const rule = /\n \.send-shortcut \{([^}]*)\}/.exec(chat)?.[1] ?? ''
|
|
119
|
+
expect(rule).toMatch(/position: absolute/)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('★ a finger is not told a key it does not have - the phone showed the shortcut', () => {
|
|
123
|
+
expect(chat).toMatch(/@media \(pointer: coarse\) \{\s*\.send-shortcut \{\s*display: none;/)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -118,7 +118,7 @@ describe('a pasted table', () => {
|
|
|
118
118
|
const nameOf = (rows: number) => `붙여넣은 표 ${rows}행`
|
|
119
119
|
|
|
120
120
|
it('★ a conversation that takes text gets a CSV file - one that takes none keeps the paste as words', () => {
|
|
121
|
-
expect(pastedTableFile({ text: EXCEL_TEXT }, { kinds: ['image', 'text'] }, nameOf)).toEqual({ name: '붙여넣은 표 4행.csv', text: pastedTableText(pastedTable({ text: EXCEL_TEXT })!, '붙여넣은 표 4행') })
|
|
121
|
+
expect(pastedTableFile({ text: EXCEL_TEXT }, { kinds: ['image', 'text'] }, nameOf)).toEqual({ name: '붙여넣은 표 4행.csv', text: pastedTableText(pastedTable({ text: EXCEL_TEXT })!, '붙여넣은 표 4행'), table: pastedTable({ text: EXCEL_TEXT }) })
|
|
122
122
|
expect(pastedTableFile({ text: EXCEL_TEXT }, { kinds: ['spreadsheet'] }, nameOf)).toMatchObject({ name: '붙여넣은 표 4행.csv' })
|
|
123
123
|
expect(pastedTableFile({ text: EXCEL_TEXT }, { kinds: ['image'] }, nameOf)).toBeNull()
|
|
124
124
|
expect(pastedTableFile({ text: EXCEL_TEXT }, null, nameOf)).toBeNull()
|
package/translations/en.json
CHANGED
|
@@ -289,5 +289,11 @@
|
|
|
289
289
|
"ai-assistant.entry.review-stale": "this proposal changed after it was reviewed in the workspace - review it again before entering.",
|
|
290
290
|
"ai-assistant.workspace.entry-not-found": "there is no proposal here that you can see.",
|
|
291
291
|
"ai-assistant.workspace.entry-superseded": "a newer card replaced this one in the conversation - review that one.",
|
|
292
|
-
"ai-assistant.workspace.entry-press-in-dock": "this is a review. Enter the rows with the button on the card in the AI dock."
|
|
292
|
+
"ai-assistant.workspace.entry-press-in-dock": "this is a review. Enter the rows with the button on the card in the AI dock.",
|
|
293
|
+
"ai-assistant.button.write-wide": "Write wide",
|
|
294
|
+
"ai-assistant.button.write-narrow": "Narrow",
|
|
295
|
+
"ai-assistant.text.send-shortcut": "{key} to send",
|
|
296
|
+
"ai-assistant.text.send-tooltip-wide": "Send ({key})",
|
|
297
|
+
"ai-assistant.text.table-shape": "{rows} rows × {columns} columns",
|
|
298
|
+
"ai-assistant.button.table-to-words": "Put in as words"
|
|
293
299
|
}
|
package/translations/ja.json
CHANGED
|
@@ -289,5 +289,11 @@
|
|
|
289
289
|
"ai-assistant.entry.review-stale": "作業ボードで確認した後に入力案が変わりました。もう一度確認してから入力してください。",
|
|
290
290
|
"ai-assistant.workspace.entry-not-found": "表示できる入力案はありません。",
|
|
291
291
|
"ai-assistant.workspace.entry-superseded": "会話の中で新しいカードがこのカードに置き換わりました。そちらを確認してください。",
|
|
292
|
-
"ai-assistant.workspace.entry-press-in-dock": "これは確認画面です。入力は AI ドックのカードのボタンで行います。"
|
|
292
|
+
"ai-assistant.workspace.entry-press-in-dock": "これは確認画面です。入力は AI ドックのカードのボタンで行います。",
|
|
293
|
+
"ai-assistant.button.write-wide": "広く書く",
|
|
294
|
+
"ai-assistant.button.write-narrow": "縮める",
|
|
295
|
+
"ai-assistant.text.send-shortcut": "{key} で送信",
|
|
296
|
+
"ai-assistant.text.send-tooltip-wide": "送信 ({key})",
|
|
297
|
+
"ai-assistant.text.table-shape": "{rows}行 × {columns}列",
|
|
298
|
+
"ai-assistant.button.table-to-words": "文字で入れる"
|
|
293
299
|
}
|
package/translations/ko.json
CHANGED
|
@@ -289,5 +289,11 @@
|
|
|
289
289
|
"ai-assistant.entry.review-stale": "작업판에서 검토한 뒤 입력안이 바뀌었습니다. 다시 검토한 뒤 입력하십시오.",
|
|
290
290
|
"ai-assistant.workspace.entry-not-found": "볼 수 있는 입력안이 없습니다.",
|
|
291
291
|
"ai-assistant.workspace.entry-superseded": "대화에서 더 새로운 카드가 이 카드를 대신했습니다. 그 카드를 검토하십시오.",
|
|
292
|
-
"ai-assistant.workspace.entry-press-in-dock": "검토 화면입니다. 입력은 AI 도크의 카드에 있는 단추로 합니다."
|
|
292
|
+
"ai-assistant.workspace.entry-press-in-dock": "검토 화면입니다. 입력은 AI 도크의 카드에 있는 단추로 합니다.",
|
|
293
|
+
"ai-assistant.button.write-wide": "넓게 쓰기",
|
|
294
|
+
"ai-assistant.button.write-narrow": "줄이기",
|
|
295
|
+
"ai-assistant.text.send-shortcut": "{key} 보내기",
|
|
296
|
+
"ai-assistant.text.send-tooltip-wide": "전송 ({key})",
|
|
297
|
+
"ai-assistant.text.table-shape": "{rows}행 × {columns}열",
|
|
298
|
+
"ai-assistant.button.table-to-words": "글로 넣기"
|
|
293
299
|
}
|
package/translations/ms.json
CHANGED
|
@@ -289,5 +289,11 @@
|
|
|
289
289
|
"ai-assistant.entry.review-stale": "cadangan ini berubah selepas disemak di papan kerja - semak semula sebelum memasukkan.",
|
|
290
290
|
"ai-assistant.workspace.entry-not-found": "tiada cadangan di sini yang boleh anda lihat.",
|
|
291
291
|
"ai-assistant.workspace.entry-superseded": "kad yang lebih baharu telah menggantikan kad ini dalam perbualan - semak kad itu.",
|
|
292
|
-
"ai-assistant.workspace.entry-press-in-dock": "ini ialah semakan. Masukkan baris dengan butang pada kad di dok AI."
|
|
292
|
+
"ai-assistant.workspace.entry-press-in-dock": "ini ialah semakan. Masukkan baris dengan butang pada kad di dok AI.",
|
|
293
|
+
"ai-assistant.button.write-wide": "Tulis luas",
|
|
294
|
+
"ai-assistant.button.write-narrow": "Kecilkan",
|
|
295
|
+
"ai-assistant.text.send-shortcut": "{key} untuk hantar",
|
|
296
|
+
"ai-assistant.text.send-tooltip-wide": "Hantar ({key})",
|
|
297
|
+
"ai-assistant.text.table-shape": "{rows} baris × {columns} lajur",
|
|
298
|
+
"ai-assistant.button.table-to-words": "Masukkan sebagai teks"
|
|
293
299
|
}
|
package/translations/zh.json
CHANGED
|
@@ -289,5 +289,11 @@
|
|
|
289
289
|
"ai-assistant.entry.review-stale": "在工作板审阅后输入方案已变更。请重新审阅后再输入。",
|
|
290
290
|
"ai-assistant.workspace.entry-not-found": "这里没有您可查看的输入方案。",
|
|
291
291
|
"ai-assistant.workspace.entry-superseded": "对话中较新的卡片已取代此卡片,请审阅那一张。",
|
|
292
|
-
"ai-assistant.workspace.entry-press-in-dock": "这是审阅页面。请用 AI 停靠栏卡片上的按钮输入。"
|
|
292
|
+
"ai-assistant.workspace.entry-press-in-dock": "这是审阅页面。请用 AI 停靠栏卡片上的按钮输入。",
|
|
293
|
+
"ai-assistant.button.write-wide": "展开书写",
|
|
294
|
+
"ai-assistant.button.write-narrow": "收起",
|
|
295
|
+
"ai-assistant.text.send-shortcut": "{key} 发送",
|
|
296
|
+
"ai-assistant.text.send-tooltip-wide": "发送 ({key})",
|
|
297
|
+
"ai-assistant.text.table-shape": "{rows}行 × {columns}列",
|
|
298
|
+
"ai-assistant.button.table-to-words": "作为文字放入"
|
|
293
299
|
}
|