dsh-cait 1.0.3 → 1.0.4
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/CHANGELOG.md +367 -352
- package/LICENSE +201 -201
- package/README.ja.md +4 -3
- package/README.md +424 -423
- package/README.zh-CN.md +391 -390
- package/client/components.js +2377 -2377
- package/client/helpers.js +103 -103
- package/client/locales.js +730 -730
- package/client/rpc.js +93 -93
- package/client/styles.js +258 -258
- package/client.js +3556 -3556
- package/icon.svg +8 -8
- package/index.js +45 -45
- package/lib/api.js +223 -223
- package/lib/docx.js +389 -389
- package/lib/errors.js +8 -8
- package/lib/formats.js +917 -917
- package/lib/glossary.js +178 -178
- package/lib/import-export.js +605 -605
- package/lib/libraries.js +183 -183
- package/lib/markdown.js +28 -28
- package/lib/mt.js +152 -152
- package/lib/persistence.js +321 -321
- package/lib/pptx.js +89 -89
- package/lib/segmenter.js +84 -84
- package/lib/similarity.js +113 -113
- package/lib/store-utils.js +339 -339
- package/lib/store.js +734 -734
- package/lib/tm.js +160 -160
- package/lib/tmx.js +85 -85
- package/lib/tools.js +472 -472
- package/lib/xlsx.js +218 -218
- package/lib/xml.js +31 -31
- package/locale/en.json +11 -11
- package/locale/zh.json +11 -11
- package/package.json +83 -83
- package/scripts/build-client.mjs +13 -13
package/lib/tools.js
CHANGED
|
@@ -1,472 +1,472 @@
|
|
|
1
|
-
import { CatError, canonicalLanguage } from './store.js'
|
|
2
|
-
|
|
3
|
-
function text(value) {
|
|
4
|
-
return [{ type: 'text', text: String(value ?? '') }]
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function failure(error) {
|
|
8
|
-
if (error instanceof CatError) return { ok: false, error: error.code, message: error.message }
|
|
9
|
-
return { ok: false, error: 'CAIT_ERROR', message: error?.message ?? String(error) }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const MATCH_LABELS = {
|
|
13
|
-
exact: 'exact',
|
|
14
|
-
near_exact: 'near-exact',
|
|
15
|
-
high_fuzzy: 'high fuzzy',
|
|
16
|
-
medium_fuzzy: 'medium fuzzy',
|
|
17
|
-
low_fuzzy: 'low fuzzy',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function matchLabel(match) {
|
|
21
|
-
return MATCH_LABELS[match?.matchType] || (match?.exact ? 'exact' : 'fuzzy')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function summarizeMatches(matches) {
|
|
25
|
-
if (!matches?.length) return 'TM matches: none'
|
|
26
|
-
const lines = ['TM matches:']
|
|
27
|
-
for (const match of matches.slice(0, 5)) {
|
|
28
|
-
lines.push(`- ${match.percent}% ${matchLabel(match)} · ${match.source} → ${match.target}`)
|
|
29
|
-
}
|
|
30
|
-
return lines.join('\n')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function summarizeTerms(hits) {
|
|
34
|
-
if (!hits?.length) return 'Glossary hits: none'
|
|
35
|
-
const lines = ['Glossary hits:']
|
|
36
|
-
for (const hit of hits.slice(0, 10)) {
|
|
37
|
-
lines.push(`- ${hit.term} → ${hit.target}${hit.note ? ` (${hit.note})` : ''}`)
|
|
38
|
-
}
|
|
39
|
-
return lines.join('\n')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function summarizeDocument(doc, activeDocumentId) {
|
|
43
|
-
const marker = doc.id === activeDocumentId ? '[active] ' : ''
|
|
44
|
-
const progress = doc.progress || { confirmed: 0, total: 0, percent: 0 }
|
|
45
|
-
return `${marker}${doc.name} (${doc.sourceLang} → ${doc.targetLang}) · ${progress.confirmed}/${progress.total} confirmed (${progress.percent}%) · cursor ${doc.cursor}`
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function registerCatTools(ctx, store) {
|
|
49
|
-
const disposers = [
|
|
50
|
-
ctx.tools.register({
|
|
51
|
-
name: 'cat_get_segment',
|
|
52
|
-
description:
|
|
53
|
-
'Get the current segment of the active CAIT translation document, together with translation-memory matches and glossary hits from the currently mounted libraries. Call this before translating. Returns ok:false when no document has been created in the CAIT workbench.',
|
|
54
|
-
parameters: {
|
|
55
|
-
type: 'object',
|
|
56
|
-
properties: {
|
|
57
|
-
documentId: {
|
|
58
|
-
type: 'string',
|
|
59
|
-
description: 'Optional CAIT document id. Defaults to the active document in the workbench.',
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
additionalProperties: false,
|
|
63
|
-
},
|
|
64
|
-
output: {
|
|
65
|
-
schema: { type: 'object', additionalProperties: true },
|
|
66
|
-
render(_args, value) {
|
|
67
|
-
if (!value || value.ok === false) {
|
|
68
|
-
return text(`CAIT: ${value?.message || 'No active document.'}`)
|
|
69
|
-
}
|
|
70
|
-
const doc = value.document
|
|
71
|
-
const segment = value.segment
|
|
72
|
-
const lines = [
|
|
73
|
-
`CAIT — ${doc.name} (${doc.sourceLang} → ${doc.targetLang})`,
|
|
74
|
-
`Segment ${segment.index + 1}/${doc.total} · ${segment.status} · ${value.progress.confirmed}/${doc.total} confirmed`,
|
|
75
|
-
'',
|
|
76
|
-
'SOURCE:',
|
|
77
|
-
segment.source,
|
|
78
|
-
'',
|
|
79
|
-
'TARGET:',
|
|
80
|
-
segment.target || '(not translated yet)',
|
|
81
|
-
'',
|
|
82
|
-
summarizeMatches(value.tmMatches),
|
|
83
|
-
'',
|
|
84
|
-
summarizeTerms(value.glossaryHits),
|
|
85
|
-
]
|
|
86
|
-
if (value.done) lines.push('', 'All segments are confirmed.')
|
|
87
|
-
return text(lines.join('\n'))
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
async execute(args) {
|
|
91
|
-
try {
|
|
92
|
-
return { ok: true, ...store.getSegmentForTool(args) }
|
|
93
|
-
} catch (error) {
|
|
94
|
-
return failure(error)
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
}),
|
|
98
|
-
|
|
99
|
-
ctx.tools.register({
|
|
100
|
-
name: 'cat_submit_translation',
|
|
101
|
-
description:
|
|
102
|
-
'Submit and confirm the translation of a CAIT segment. The source–target pair is written to the selected mounted translation-memory library immediately, and the workbench advances to the next unconfirmed segment. Always provide the complete translated segment text.',
|
|
103
|
-
parameters: {
|
|
104
|
-
type: 'object',
|
|
105
|
-
properties: {
|
|
106
|
-
target: {
|
|
107
|
-
type: 'string',
|
|
108
|
-
description: 'The complete translated text for the segment.',
|
|
109
|
-
},
|
|
110
|
-
documentId: {
|
|
111
|
-
type: 'string',
|
|
112
|
-
description: 'Optional CAIT document id. Defaults to the active document in the workbench.',
|
|
113
|
-
},
|
|
114
|
-
index: {
|
|
115
|
-
type: 'integer',
|
|
116
|
-
minimum: 0,
|
|
117
|
-
description: 'Optional zero-based segment index. Defaults to the current segment.',
|
|
118
|
-
},
|
|
119
|
-
addToTm: {
|
|
120
|
-
type: 'boolean',
|
|
121
|
-
description: 'Write the confirmed pair into translation memory. Defaults to true.',
|
|
122
|
-
},
|
|
123
|
-
targetTmId: {
|
|
124
|
-
type: 'string',
|
|
125
|
-
description:
|
|
126
|
-
'Optional mounted translation-memory library id. Defaults to the current default write library; a known but unmounted library is rejected with TM_NOT_MOUNTED.',
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
required: ['target'],
|
|
130
|
-
additionalProperties: false,
|
|
131
|
-
},
|
|
132
|
-
output: {
|
|
133
|
-
schema: { type: 'object', additionalProperties: true },
|
|
134
|
-
render(_args, value) {
|
|
135
|
-
if (!value || value.ok === false) {
|
|
136
|
-
return text(`CAIT: ${value?.message || 'Could not confirm the segment.'}`)
|
|
137
|
-
}
|
|
138
|
-
const confirmed = value.confirmed
|
|
139
|
-
const lines = [
|
|
140
|
-
`Confirmed segment ${confirmed.index + 1}.`,
|
|
141
|
-
'',
|
|
142
|
-
'SOURCE:',
|
|
143
|
-
confirmed.source,
|
|
144
|
-
'',
|
|
145
|
-
'TARGET:',
|
|
146
|
-
confirmed.target,
|
|
147
|
-
]
|
|
148
|
-
if (value.tm?.count !== undefined) {
|
|
149
|
-
lines.push('', `Translation memory now holds ${value.tm.count} segment pair(s).`)
|
|
150
|
-
}
|
|
151
|
-
if (value.next) {
|
|
152
|
-
lines.push(
|
|
153
|
-
'',
|
|
154
|
-
`Next segment ${value.next.index + 1}/${value.document.total}:`,
|
|
155
|
-
value.next.source,
|
|
156
|
-
`Progress: ${value.progress.confirmed}/${value.progress.total} (${value.progress.percent}%).`,
|
|
157
|
-
)
|
|
158
|
-
} else {
|
|
159
|
-
lines.push('', `All ${value.progress.total} segments are confirmed.`)
|
|
160
|
-
}
|
|
161
|
-
return text(lines.join('\n'))
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
async execute(args) {
|
|
165
|
-
try {
|
|
166
|
-
return { ok: true, ...store.submitTranslationForTool(args) }
|
|
167
|
-
} catch (error) {
|
|
168
|
-
return failure(error)
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
}),
|
|
172
|
-
|
|
173
|
-
ctx.tools.register({
|
|
174
|
-
name: 'cat_lookup_tm',
|
|
175
|
-
description:
|
|
176
|
-
'Search the currently mounted CAIT translation-memory libraries for a source sentence. Returns exact, near-exact and graded fuzzy matches sorted by score, with a 0–1 threshold (default from workbench settings). Each match carries matchType and the source tmId.',
|
|
177
|
-
parameters: {
|
|
178
|
-
type: 'object',
|
|
179
|
-
properties: {
|
|
180
|
-
query: {
|
|
181
|
-
type: 'string',
|
|
182
|
-
description: 'Source text to look up in translation memory.',
|
|
183
|
-
},
|
|
184
|
-
threshold: {
|
|
185
|
-
type: 'number',
|
|
186
|
-
minimum: 0,
|
|
187
|
-
maximum: 1,
|
|
188
|
-
description: 'Minimum fuzzy score. 1 returns exact matches only.',
|
|
189
|
-
},
|
|
190
|
-
limit: {
|
|
191
|
-
type: 'integer',
|
|
192
|
-
minimum: 1,
|
|
193
|
-
maximum: 30,
|
|
194
|
-
description: 'Maximum number of matches. Defaults to 5.',
|
|
195
|
-
},
|
|
196
|
-
sourceLang: {
|
|
197
|
-
type: 'string',
|
|
198
|
-
description: 'Optional source language filter, e.g. en.',
|
|
199
|
-
},
|
|
200
|
-
targetLang: {
|
|
201
|
-
type: 'string',
|
|
202
|
-
description: 'Optional target language filter, e.g. zh-CN.',
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
required: ['query'],
|
|
206
|
-
additionalProperties: false,
|
|
207
|
-
},
|
|
208
|
-
output: {
|
|
209
|
-
schema: { type: 'object', additionalProperties: true },
|
|
210
|
-
render(_args, value) {
|
|
211
|
-
if (!value || value.ok === false) {
|
|
212
|
-
return text(`CAIT: ${value?.message || 'Lookup failed.'}`)
|
|
213
|
-
}
|
|
214
|
-
if (!value.matches?.length) {
|
|
215
|
-
return text(`CAIT TM: no matches for “${value.query}”.`)
|
|
216
|
-
}
|
|
217
|
-
const lines = [`CAIT TM: ${value.matches.length} match(es) for “${value.query}”`, '']
|
|
218
|
-
for (const match of value.matches) {
|
|
219
|
-
lines.push(
|
|
220
|
-
`${match.percent}% ${matchLabel(match)} · ${match.sourceLang} → ${match.targetLang}`,
|
|
221
|
-
` ${match.source}`,
|
|
222
|
-
` → ${match.target}`,
|
|
223
|
-
'',
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
return text(lines.join('\n').trim())
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
async execute(args) {
|
|
230
|
-
try {
|
|
231
|
-
const result = store.lookupTM(args)
|
|
232
|
-
return { ok: true, ...result, count: result.matches.length }
|
|
233
|
-
} catch (error) {
|
|
234
|
-
return failure(error)
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
}),
|
|
238
|
-
|
|
239
|
-
ctx.tools.register({
|
|
240
|
-
name: 'cat_lookup_glossary',
|
|
241
|
-
description:
|
|
242
|
-
'Search the currently mounted CAIT glossary libraries by source language and optional source/target substring. Returns the same glossary entry fields as the workbench bootstrap state, including glossaryId.',
|
|
243
|
-
parameters: {
|
|
244
|
-
type: 'object',
|
|
245
|
-
properties: {
|
|
246
|
-
query: {
|
|
247
|
-
type: 'string',
|
|
248
|
-
description:
|
|
249
|
-
"Optional substring matched against the source or target term; each entry's caseSensitive flag controls case handling.",
|
|
250
|
-
},
|
|
251
|
-
sourceLang: {
|
|
252
|
-
type: 'string',
|
|
253
|
-
description: 'Optional source language filter, e.g. en. Omit to search every language.',
|
|
254
|
-
},
|
|
255
|
-
limit: {
|
|
256
|
-
type: 'integer',
|
|
257
|
-
minimum: 1,
|
|
258
|
-
maximum: 100,
|
|
259
|
-
description: 'Maximum number of entries to return. Defaults to 20.',
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
additionalProperties: false,
|
|
263
|
-
},
|
|
264
|
-
output: {
|
|
265
|
-
schema: { type: 'object', additionalProperties: true },
|
|
266
|
-
render(_args, value) {
|
|
267
|
-
if (!value || value.ok === false) {
|
|
268
|
-
return text(`CAIT: ${value?.message || 'Glossary lookup failed.'}`)
|
|
269
|
-
}
|
|
270
|
-
if (!value.entries?.length) {
|
|
271
|
-
const scope = value.sourceLang ? ` for ${value.sourceLang}` : ''
|
|
272
|
-
return text(`CAIT glossary: no entries matching “${value.query}”${scope}.`)
|
|
273
|
-
}
|
|
274
|
-
const suffix = value.total > value.entries.length
|
|
275
|
-
? `, showing ${value.entries.length} of ${value.total}`
|
|
276
|
-
: ''
|
|
277
|
-
const scope = value.sourceLang ? ` · ${value.sourceLang}` : ''
|
|
278
|
-
const lines = [`CAIT glossary: ${value.entries.length} entry(ies)${scope}${suffix}`, '']
|
|
279
|
-
for (const entry of value.entries) {
|
|
280
|
-
lines.push(
|
|
281
|
-
`- ${entry.source} → ${entry.target}${entry.note ? ` (${entry.note})` : ''}`,
|
|
282
|
-
` ${entry.sourceLang || 'any'} · ${entry.glossaryId || 'glossary_default'}`,
|
|
283
|
-
)
|
|
284
|
-
}
|
|
285
|
-
return text(lines.join('\n').trim())
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
async execute(args = {}) {
|
|
289
|
-
try {
|
|
290
|
-
const query = String(args?.query ?? '').trim()
|
|
291
|
-
const sourceLang = String(args?.sourceLang ?? '').trim()
|
|
292
|
-
const rawLimit = Number(args?.limit)
|
|
293
|
-
const limit = Number.isFinite(rawLimit)
|
|
294
|
-
? Math.min(100, Math.max(1, Math.trunc(rawLimit)))
|
|
295
|
-
: 20
|
|
296
|
-
const lang = sourceLang ? canonicalLanguage(sourceLang) : ''
|
|
297
|
-
const foldedQuery = query.toLocaleLowerCase()
|
|
298
|
-
const matches = store.bootstrap().glossary.filter((entry) => {
|
|
299
|
-
if (lang && (!entry.sourceLang || canonicalLanguage(entry.sourceLang) !== lang)) {
|
|
300
|
-
return false
|
|
301
|
-
}
|
|
302
|
-
if (!query) return true
|
|
303
|
-
const haystack = `${entry.source ?? ''}\n${entry.target ?? ''}`
|
|
304
|
-
return entry.caseSensitive
|
|
305
|
-
? haystack.includes(query)
|
|
306
|
-
: haystack.toLocaleLowerCase().includes(foldedQuery)
|
|
307
|
-
})
|
|
308
|
-
return {
|
|
309
|
-
ok: true,
|
|
310
|
-
query,
|
|
311
|
-
sourceLang: lang || null,
|
|
312
|
-
count: Math.min(matches.length, limit),
|
|
313
|
-
total: matches.length,
|
|
314
|
-
entries: matches.slice(0, limit),
|
|
315
|
-
}
|
|
316
|
-
} catch (error) {
|
|
317
|
-
return failure(error)
|
|
318
|
-
}
|
|
319
|
-
},
|
|
320
|
-
}),
|
|
321
|
-
|
|
322
|
-
ctx.tools.register({
|
|
323
|
-
name: 'cat_list_documents',
|
|
324
|
-
description:
|
|
325
|
-
'List every CAIT document with its translation progress, using the same document summary and stats fields as the workbench bootstrap state. The active document is marked by activeDocumentId.',
|
|
326
|
-
parameters: {
|
|
327
|
-
type: 'object',
|
|
328
|
-
properties: {},
|
|
329
|
-
additionalProperties: false,
|
|
330
|
-
},
|
|
331
|
-
output: {
|
|
332
|
-
schema: { type: 'object', additionalProperties: true },
|
|
333
|
-
render(_args, value) {
|
|
334
|
-
if (!value || value.ok === false) {
|
|
335
|
-
return text(`CAIT: ${value?.message || 'Could not list documents.'}`)
|
|
336
|
-
}
|
|
337
|
-
if (!value.documents?.length) return text('CAIT documents: none.')
|
|
338
|
-
const lines = [`CAIT documents (${value.documents.length}):`, '']
|
|
339
|
-
for (const doc of value.documents) {
|
|
340
|
-
lines.push(`- ${summarizeDocument(doc, value.activeDocumentId)}`)
|
|
341
|
-
}
|
|
342
|
-
lines.push(
|
|
343
|
-
'',
|
|
344
|
-
`Stats: ${value.stats?.segments ?? 0} segments · ${value.stats?.confirmed ?? 0} confirmed · ${value.stats?.tm ?? 0} TM entries · ${value.stats?.glossary ?? 0} glossary terms`,
|
|
345
|
-
)
|
|
346
|
-
return text(lines.join('\n'))
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
async execute() {
|
|
350
|
-
try {
|
|
351
|
-
const state = store.bootstrap()
|
|
352
|
-
return {
|
|
353
|
-
ok: true,
|
|
354
|
-
activeDocumentId: state.activeDocument?.id ?? null,
|
|
355
|
-
documents: state.documents,
|
|
356
|
-
stats: state.stats,
|
|
357
|
-
}
|
|
358
|
-
} catch (error) {
|
|
359
|
-
return failure(error)
|
|
360
|
-
}
|
|
361
|
-
},
|
|
362
|
-
}),
|
|
363
|
-
|
|
364
|
-
ctx.tools.register({
|
|
365
|
-
name: 'cat_switch_document',
|
|
366
|
-
description:
|
|
367
|
-
'Switch the active CAIT document. Subsequent cat_get_segment and cat_submit_translation calls use this document by default. Returns the selected document summary, including its progress.',
|
|
368
|
-
parameters: {
|
|
369
|
-
type: 'object',
|
|
370
|
-
properties: {
|
|
371
|
-
documentId: {
|
|
372
|
-
type: 'string',
|
|
373
|
-
description: 'CAIT document id returned by cat_list_documents.',
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
required: ['documentId'],
|
|
377
|
-
additionalProperties: false,
|
|
378
|
-
},
|
|
379
|
-
output: {
|
|
380
|
-
schema: { type: 'object', additionalProperties: true },
|
|
381
|
-
render(_args, value) {
|
|
382
|
-
if (!value || value.ok === false) {
|
|
383
|
-
return text(`CAIT: ${value?.message || 'Could not switch document.'}`)
|
|
384
|
-
}
|
|
385
|
-
const doc = value.document
|
|
386
|
-
const progress = doc.progress || { confirmed: 0, total: 0, percent: 0 }
|
|
387
|
-
return text(
|
|
388
|
-
[
|
|
389
|
-
`CAIT: switched to “${doc.name}” (${doc.sourceLang} → ${doc.targetLang}).`,
|
|
390
|
-
`Progress: ${progress.confirmed}/${progress.total} confirmed (${progress.percent}%).`,
|
|
391
|
-
`Current segment: ${doc.cursor + 1}/${progress.total}.`,
|
|
392
|
-
].join('\n'),
|
|
393
|
-
)
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
async execute(args = {}) {
|
|
397
|
-
try {
|
|
398
|
-
if (!args?.documentId || typeof args.documentId !== 'string') {
|
|
399
|
-
throw new CatError('MISSING_DOCUMENT_ID', 'documentId 不能为空。')
|
|
400
|
-
}
|
|
401
|
-
const result = store.openDocument({ documentId: args.documentId })
|
|
402
|
-
const state = store.bootstrap()
|
|
403
|
-
const document =
|
|
404
|
-
state.documents.find((item) => item.id === result.document.id) || result.document
|
|
405
|
-
return { ok: true, activeDocumentId: document.id, document }
|
|
406
|
-
} catch (error) {
|
|
407
|
-
return failure(error)
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
}),
|
|
411
|
-
|
|
412
|
-
ctx.tools.register({
|
|
413
|
-
name: 'cat_suggest_mt',
|
|
414
|
-
description:
|
|
415
|
-
'Generate a machine-translation suggestion through the DSH provider route selected in CAIT settings. Credentials are resolved by DSH; CAIT never receives an API key. If no usable DSH route is configured, falls back to offline glossary substitution and returns a note. Without text, the selected segment (or active document cursor) is used; an explicitly supplied unknown documentId or out-of-range index is an error.',
|
|
416
|
-
parameters: {
|
|
417
|
-
type: 'object',
|
|
418
|
-
properties: {
|
|
419
|
-
text: {
|
|
420
|
-
type: 'string',
|
|
421
|
-
description: 'Optional source text. Defaults to the current segment of the active document.',
|
|
422
|
-
},
|
|
423
|
-
documentId: {
|
|
424
|
-
type: 'string',
|
|
425
|
-
description: 'Optional CAIT document id. Defaults to the active document.',
|
|
426
|
-
},
|
|
427
|
-
index: {
|
|
428
|
-
type: 'integer',
|
|
429
|
-
minimum: 0,
|
|
430
|
-
description: 'Optional zero-based segment index. Defaults to the document cursor.',
|
|
431
|
-
},
|
|
432
|
-
sourceLang: {
|
|
433
|
-
type: 'string',
|
|
434
|
-
description: 'Optional source language override, e.g. en.',
|
|
435
|
-
},
|
|
436
|
-
targetLang: {
|
|
437
|
-
type: 'string',
|
|
438
|
-
description: 'Optional target language override, e.g. zh-CN.',
|
|
439
|
-
},
|
|
440
|
-
},
|
|
441
|
-
additionalProperties: false,
|
|
442
|
-
},
|
|
443
|
-
output: {
|
|
444
|
-
schema: { type: 'object', additionalProperties: true },
|
|
445
|
-
render(_args, value) {
|
|
446
|
-
if (!value || value.ok === false) {
|
|
447
|
-
return text(`CAIT: ${value?.message || 'Machine translation failed.'}`)
|
|
448
|
-
}
|
|
449
|
-
const lines = [
|
|
450
|
-
`CAIT MT (${value.provider} · ${value.model} · ${value.latencyMs} ms):`,
|
|
451
|
-
'',
|
|
452
|
-
value.text,
|
|
453
|
-
]
|
|
454
|
-
if (value.note) lines.push('', value.note)
|
|
455
|
-
return text(lines.join('\n'))
|
|
456
|
-
},
|
|
457
|
-
},
|
|
458
|
-
async execute(args = {}) {
|
|
459
|
-
try {
|
|
460
|
-
const result = await store.mtSuggest(args)
|
|
461
|
-
return { ok: true, ...result }
|
|
462
|
-
} catch (error) {
|
|
463
|
-
return failure(error)
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
}),
|
|
467
|
-
]
|
|
468
|
-
|
|
469
|
-
return () => {
|
|
470
|
-
for (const dispose of disposers) dispose()
|
|
471
|
-
}
|
|
472
|
-
}
|
|
1
|
+
import { CatError, canonicalLanguage } from './store.js'
|
|
2
|
+
|
|
3
|
+
function text(value) {
|
|
4
|
+
return [{ type: 'text', text: String(value ?? '') }]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function failure(error) {
|
|
8
|
+
if (error instanceof CatError) return { ok: false, error: error.code, message: error.message }
|
|
9
|
+
return { ok: false, error: 'CAIT_ERROR', message: error?.message ?? String(error) }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MATCH_LABELS = {
|
|
13
|
+
exact: 'exact',
|
|
14
|
+
near_exact: 'near-exact',
|
|
15
|
+
high_fuzzy: 'high fuzzy',
|
|
16
|
+
medium_fuzzy: 'medium fuzzy',
|
|
17
|
+
low_fuzzy: 'low fuzzy',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function matchLabel(match) {
|
|
21
|
+
return MATCH_LABELS[match?.matchType] || (match?.exact ? 'exact' : 'fuzzy')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function summarizeMatches(matches) {
|
|
25
|
+
if (!matches?.length) return 'TM matches: none'
|
|
26
|
+
const lines = ['TM matches:']
|
|
27
|
+
for (const match of matches.slice(0, 5)) {
|
|
28
|
+
lines.push(`- ${match.percent}% ${matchLabel(match)} · ${match.source} → ${match.target}`)
|
|
29
|
+
}
|
|
30
|
+
return lines.join('\n')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function summarizeTerms(hits) {
|
|
34
|
+
if (!hits?.length) return 'Glossary hits: none'
|
|
35
|
+
const lines = ['Glossary hits:']
|
|
36
|
+
for (const hit of hits.slice(0, 10)) {
|
|
37
|
+
lines.push(`- ${hit.term} → ${hit.target}${hit.note ? ` (${hit.note})` : ''}`)
|
|
38
|
+
}
|
|
39
|
+
return lines.join('\n')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function summarizeDocument(doc, activeDocumentId) {
|
|
43
|
+
const marker = doc.id === activeDocumentId ? '[active] ' : ''
|
|
44
|
+
const progress = doc.progress || { confirmed: 0, total: 0, percent: 0 }
|
|
45
|
+
return `${marker}${doc.name} (${doc.sourceLang} → ${doc.targetLang}) · ${progress.confirmed}/${progress.total} confirmed (${progress.percent}%) · cursor ${doc.cursor}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function registerCatTools(ctx, store) {
|
|
49
|
+
const disposers = [
|
|
50
|
+
ctx.tools.register({
|
|
51
|
+
name: 'cat_get_segment',
|
|
52
|
+
description:
|
|
53
|
+
'Get the current segment of the active CAIT translation document, together with translation-memory matches and glossary hits from the currently mounted libraries. Call this before translating. Returns ok:false when no document has been created in the CAIT workbench.',
|
|
54
|
+
parameters: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
documentId: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: 'Optional CAIT document id. Defaults to the active document in the workbench.',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
additionalProperties: false,
|
|
63
|
+
},
|
|
64
|
+
output: {
|
|
65
|
+
schema: { type: 'object', additionalProperties: true },
|
|
66
|
+
render(_args, value) {
|
|
67
|
+
if (!value || value.ok === false) {
|
|
68
|
+
return text(`CAIT: ${value?.message || 'No active document.'}`)
|
|
69
|
+
}
|
|
70
|
+
const doc = value.document
|
|
71
|
+
const segment = value.segment
|
|
72
|
+
const lines = [
|
|
73
|
+
`CAIT — ${doc.name} (${doc.sourceLang} → ${doc.targetLang})`,
|
|
74
|
+
`Segment ${segment.index + 1}/${doc.total} · ${segment.status} · ${value.progress.confirmed}/${doc.total} confirmed`,
|
|
75
|
+
'',
|
|
76
|
+
'SOURCE:',
|
|
77
|
+
segment.source,
|
|
78
|
+
'',
|
|
79
|
+
'TARGET:',
|
|
80
|
+
segment.target || '(not translated yet)',
|
|
81
|
+
'',
|
|
82
|
+
summarizeMatches(value.tmMatches),
|
|
83
|
+
'',
|
|
84
|
+
summarizeTerms(value.glossaryHits),
|
|
85
|
+
]
|
|
86
|
+
if (value.done) lines.push('', 'All segments are confirmed.')
|
|
87
|
+
return text(lines.join('\n'))
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
async execute(args) {
|
|
91
|
+
try {
|
|
92
|
+
return { ok: true, ...store.getSegmentForTool(args) }
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return failure(error)
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
ctx.tools.register({
|
|
100
|
+
name: 'cat_submit_translation',
|
|
101
|
+
description:
|
|
102
|
+
'Submit and confirm the translation of a CAIT segment. The source–target pair is written to the selected mounted translation-memory library immediately, and the workbench advances to the next unconfirmed segment. Always provide the complete translated segment text.',
|
|
103
|
+
parameters: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
target: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'The complete translated text for the segment.',
|
|
109
|
+
},
|
|
110
|
+
documentId: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description: 'Optional CAIT document id. Defaults to the active document in the workbench.',
|
|
113
|
+
},
|
|
114
|
+
index: {
|
|
115
|
+
type: 'integer',
|
|
116
|
+
minimum: 0,
|
|
117
|
+
description: 'Optional zero-based segment index. Defaults to the current segment.',
|
|
118
|
+
},
|
|
119
|
+
addToTm: {
|
|
120
|
+
type: 'boolean',
|
|
121
|
+
description: 'Write the confirmed pair into translation memory. Defaults to true.',
|
|
122
|
+
},
|
|
123
|
+
targetTmId: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description:
|
|
126
|
+
'Optional mounted translation-memory library id. Defaults to the current default write library; a known but unmounted library is rejected with TM_NOT_MOUNTED.',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
required: ['target'],
|
|
130
|
+
additionalProperties: false,
|
|
131
|
+
},
|
|
132
|
+
output: {
|
|
133
|
+
schema: { type: 'object', additionalProperties: true },
|
|
134
|
+
render(_args, value) {
|
|
135
|
+
if (!value || value.ok === false) {
|
|
136
|
+
return text(`CAIT: ${value?.message || 'Could not confirm the segment.'}`)
|
|
137
|
+
}
|
|
138
|
+
const confirmed = value.confirmed
|
|
139
|
+
const lines = [
|
|
140
|
+
`Confirmed segment ${confirmed.index + 1}.`,
|
|
141
|
+
'',
|
|
142
|
+
'SOURCE:',
|
|
143
|
+
confirmed.source,
|
|
144
|
+
'',
|
|
145
|
+
'TARGET:',
|
|
146
|
+
confirmed.target,
|
|
147
|
+
]
|
|
148
|
+
if (value.tm?.count !== undefined) {
|
|
149
|
+
lines.push('', `Translation memory now holds ${value.tm.count} segment pair(s).`)
|
|
150
|
+
}
|
|
151
|
+
if (value.next) {
|
|
152
|
+
lines.push(
|
|
153
|
+
'',
|
|
154
|
+
`Next segment ${value.next.index + 1}/${value.document.total}:`,
|
|
155
|
+
value.next.source,
|
|
156
|
+
`Progress: ${value.progress.confirmed}/${value.progress.total} (${value.progress.percent}%).`,
|
|
157
|
+
)
|
|
158
|
+
} else {
|
|
159
|
+
lines.push('', `All ${value.progress.total} segments are confirmed.`)
|
|
160
|
+
}
|
|
161
|
+
return text(lines.join('\n'))
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
async execute(args) {
|
|
165
|
+
try {
|
|
166
|
+
return { ok: true, ...store.submitTranslationForTool(args) }
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return failure(error)
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
|
|
173
|
+
ctx.tools.register({
|
|
174
|
+
name: 'cat_lookup_tm',
|
|
175
|
+
description:
|
|
176
|
+
'Search the currently mounted CAIT translation-memory libraries for a source sentence. Returns exact, near-exact and graded fuzzy matches sorted by score, with a 0–1 threshold (default from workbench settings). Each match carries matchType and the source tmId.',
|
|
177
|
+
parameters: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
properties: {
|
|
180
|
+
query: {
|
|
181
|
+
type: 'string',
|
|
182
|
+
description: 'Source text to look up in translation memory.',
|
|
183
|
+
},
|
|
184
|
+
threshold: {
|
|
185
|
+
type: 'number',
|
|
186
|
+
minimum: 0,
|
|
187
|
+
maximum: 1,
|
|
188
|
+
description: 'Minimum fuzzy score. 1 returns exact matches only.',
|
|
189
|
+
},
|
|
190
|
+
limit: {
|
|
191
|
+
type: 'integer',
|
|
192
|
+
minimum: 1,
|
|
193
|
+
maximum: 30,
|
|
194
|
+
description: 'Maximum number of matches. Defaults to 5.',
|
|
195
|
+
},
|
|
196
|
+
sourceLang: {
|
|
197
|
+
type: 'string',
|
|
198
|
+
description: 'Optional source language filter, e.g. en.',
|
|
199
|
+
},
|
|
200
|
+
targetLang: {
|
|
201
|
+
type: 'string',
|
|
202
|
+
description: 'Optional target language filter, e.g. zh-CN.',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
required: ['query'],
|
|
206
|
+
additionalProperties: false,
|
|
207
|
+
},
|
|
208
|
+
output: {
|
|
209
|
+
schema: { type: 'object', additionalProperties: true },
|
|
210
|
+
render(_args, value) {
|
|
211
|
+
if (!value || value.ok === false) {
|
|
212
|
+
return text(`CAIT: ${value?.message || 'Lookup failed.'}`)
|
|
213
|
+
}
|
|
214
|
+
if (!value.matches?.length) {
|
|
215
|
+
return text(`CAIT TM: no matches for “${value.query}”.`)
|
|
216
|
+
}
|
|
217
|
+
const lines = [`CAIT TM: ${value.matches.length} match(es) for “${value.query}”`, '']
|
|
218
|
+
for (const match of value.matches) {
|
|
219
|
+
lines.push(
|
|
220
|
+
`${match.percent}% ${matchLabel(match)} · ${match.sourceLang} → ${match.targetLang}`,
|
|
221
|
+
` ${match.source}`,
|
|
222
|
+
` → ${match.target}`,
|
|
223
|
+
'',
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
return text(lines.join('\n').trim())
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
async execute(args) {
|
|
230
|
+
try {
|
|
231
|
+
const result = store.lookupTM(args)
|
|
232
|
+
return { ok: true, ...result, count: result.matches.length }
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return failure(error)
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
|
|
239
|
+
ctx.tools.register({
|
|
240
|
+
name: 'cat_lookup_glossary',
|
|
241
|
+
description:
|
|
242
|
+
'Search the currently mounted CAIT glossary libraries by source language and optional source/target substring. Returns the same glossary entry fields as the workbench bootstrap state, including glossaryId.',
|
|
243
|
+
parameters: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
query: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
description:
|
|
249
|
+
"Optional substring matched against the source or target term; each entry's caseSensitive flag controls case handling.",
|
|
250
|
+
},
|
|
251
|
+
sourceLang: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: 'Optional source language filter, e.g. en. Omit to search every language.',
|
|
254
|
+
},
|
|
255
|
+
limit: {
|
|
256
|
+
type: 'integer',
|
|
257
|
+
minimum: 1,
|
|
258
|
+
maximum: 100,
|
|
259
|
+
description: 'Maximum number of entries to return. Defaults to 20.',
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
additionalProperties: false,
|
|
263
|
+
},
|
|
264
|
+
output: {
|
|
265
|
+
schema: { type: 'object', additionalProperties: true },
|
|
266
|
+
render(_args, value) {
|
|
267
|
+
if (!value || value.ok === false) {
|
|
268
|
+
return text(`CAIT: ${value?.message || 'Glossary lookup failed.'}`)
|
|
269
|
+
}
|
|
270
|
+
if (!value.entries?.length) {
|
|
271
|
+
const scope = value.sourceLang ? ` for ${value.sourceLang}` : ''
|
|
272
|
+
return text(`CAIT glossary: no entries matching “${value.query}”${scope}.`)
|
|
273
|
+
}
|
|
274
|
+
const suffix = value.total > value.entries.length
|
|
275
|
+
? `, showing ${value.entries.length} of ${value.total}`
|
|
276
|
+
: ''
|
|
277
|
+
const scope = value.sourceLang ? ` · ${value.sourceLang}` : ''
|
|
278
|
+
const lines = [`CAIT glossary: ${value.entries.length} entry(ies)${scope}${suffix}`, '']
|
|
279
|
+
for (const entry of value.entries) {
|
|
280
|
+
lines.push(
|
|
281
|
+
`- ${entry.source} → ${entry.target}${entry.note ? ` (${entry.note})` : ''}`,
|
|
282
|
+
` ${entry.sourceLang || 'any'} · ${entry.glossaryId || 'glossary_default'}`,
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
return text(lines.join('\n').trim())
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
async execute(args = {}) {
|
|
289
|
+
try {
|
|
290
|
+
const query = String(args?.query ?? '').trim()
|
|
291
|
+
const sourceLang = String(args?.sourceLang ?? '').trim()
|
|
292
|
+
const rawLimit = Number(args?.limit)
|
|
293
|
+
const limit = Number.isFinite(rawLimit)
|
|
294
|
+
? Math.min(100, Math.max(1, Math.trunc(rawLimit)))
|
|
295
|
+
: 20
|
|
296
|
+
const lang = sourceLang ? canonicalLanguage(sourceLang) : ''
|
|
297
|
+
const foldedQuery = query.toLocaleLowerCase()
|
|
298
|
+
const matches = store.bootstrap().glossary.filter((entry) => {
|
|
299
|
+
if (lang && (!entry.sourceLang || canonicalLanguage(entry.sourceLang) !== lang)) {
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
if (!query) return true
|
|
303
|
+
const haystack = `${entry.source ?? ''}\n${entry.target ?? ''}`
|
|
304
|
+
return entry.caseSensitive
|
|
305
|
+
? haystack.includes(query)
|
|
306
|
+
: haystack.toLocaleLowerCase().includes(foldedQuery)
|
|
307
|
+
})
|
|
308
|
+
return {
|
|
309
|
+
ok: true,
|
|
310
|
+
query,
|
|
311
|
+
sourceLang: lang || null,
|
|
312
|
+
count: Math.min(matches.length, limit),
|
|
313
|
+
total: matches.length,
|
|
314
|
+
entries: matches.slice(0, limit),
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
return failure(error)
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
|
|
322
|
+
ctx.tools.register({
|
|
323
|
+
name: 'cat_list_documents',
|
|
324
|
+
description:
|
|
325
|
+
'List every CAIT document with its translation progress, using the same document summary and stats fields as the workbench bootstrap state. The active document is marked by activeDocumentId.',
|
|
326
|
+
parameters: {
|
|
327
|
+
type: 'object',
|
|
328
|
+
properties: {},
|
|
329
|
+
additionalProperties: false,
|
|
330
|
+
},
|
|
331
|
+
output: {
|
|
332
|
+
schema: { type: 'object', additionalProperties: true },
|
|
333
|
+
render(_args, value) {
|
|
334
|
+
if (!value || value.ok === false) {
|
|
335
|
+
return text(`CAIT: ${value?.message || 'Could not list documents.'}`)
|
|
336
|
+
}
|
|
337
|
+
if (!value.documents?.length) return text('CAIT documents: none.')
|
|
338
|
+
const lines = [`CAIT documents (${value.documents.length}):`, '']
|
|
339
|
+
for (const doc of value.documents) {
|
|
340
|
+
lines.push(`- ${summarizeDocument(doc, value.activeDocumentId)}`)
|
|
341
|
+
}
|
|
342
|
+
lines.push(
|
|
343
|
+
'',
|
|
344
|
+
`Stats: ${value.stats?.segments ?? 0} segments · ${value.stats?.confirmed ?? 0} confirmed · ${value.stats?.tm ?? 0} TM entries · ${value.stats?.glossary ?? 0} glossary terms`,
|
|
345
|
+
)
|
|
346
|
+
return text(lines.join('\n'))
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
async execute() {
|
|
350
|
+
try {
|
|
351
|
+
const state = store.bootstrap()
|
|
352
|
+
return {
|
|
353
|
+
ok: true,
|
|
354
|
+
activeDocumentId: state.activeDocument?.id ?? null,
|
|
355
|
+
documents: state.documents,
|
|
356
|
+
stats: state.stats,
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
return failure(error)
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
}),
|
|
363
|
+
|
|
364
|
+
ctx.tools.register({
|
|
365
|
+
name: 'cat_switch_document',
|
|
366
|
+
description:
|
|
367
|
+
'Switch the active CAIT document. Subsequent cat_get_segment and cat_submit_translation calls use this document by default. Returns the selected document summary, including its progress.',
|
|
368
|
+
parameters: {
|
|
369
|
+
type: 'object',
|
|
370
|
+
properties: {
|
|
371
|
+
documentId: {
|
|
372
|
+
type: 'string',
|
|
373
|
+
description: 'CAIT document id returned by cat_list_documents.',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
required: ['documentId'],
|
|
377
|
+
additionalProperties: false,
|
|
378
|
+
},
|
|
379
|
+
output: {
|
|
380
|
+
schema: { type: 'object', additionalProperties: true },
|
|
381
|
+
render(_args, value) {
|
|
382
|
+
if (!value || value.ok === false) {
|
|
383
|
+
return text(`CAIT: ${value?.message || 'Could not switch document.'}`)
|
|
384
|
+
}
|
|
385
|
+
const doc = value.document
|
|
386
|
+
const progress = doc.progress || { confirmed: 0, total: 0, percent: 0 }
|
|
387
|
+
return text(
|
|
388
|
+
[
|
|
389
|
+
`CAIT: switched to “${doc.name}” (${doc.sourceLang} → ${doc.targetLang}).`,
|
|
390
|
+
`Progress: ${progress.confirmed}/${progress.total} confirmed (${progress.percent}%).`,
|
|
391
|
+
`Current segment: ${doc.cursor + 1}/${progress.total}.`,
|
|
392
|
+
].join('\n'),
|
|
393
|
+
)
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
async execute(args = {}) {
|
|
397
|
+
try {
|
|
398
|
+
if (!args?.documentId || typeof args.documentId !== 'string') {
|
|
399
|
+
throw new CatError('MISSING_DOCUMENT_ID', 'documentId 不能为空。')
|
|
400
|
+
}
|
|
401
|
+
const result = store.openDocument({ documentId: args.documentId })
|
|
402
|
+
const state = store.bootstrap()
|
|
403
|
+
const document =
|
|
404
|
+
state.documents.find((item) => item.id === result.document.id) || result.document
|
|
405
|
+
return { ok: true, activeDocumentId: document.id, document }
|
|
406
|
+
} catch (error) {
|
|
407
|
+
return failure(error)
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
}),
|
|
411
|
+
|
|
412
|
+
ctx.tools.register({
|
|
413
|
+
name: 'cat_suggest_mt',
|
|
414
|
+
description:
|
|
415
|
+
'Generate a machine-translation suggestion through the DSH provider route selected in CAIT settings. Credentials are resolved by DSH; CAIT never receives an API key. If no usable DSH route is configured, falls back to offline glossary substitution and returns a note. Without text, the selected segment (or active document cursor) is used; an explicitly supplied unknown documentId or out-of-range index is an error.',
|
|
416
|
+
parameters: {
|
|
417
|
+
type: 'object',
|
|
418
|
+
properties: {
|
|
419
|
+
text: {
|
|
420
|
+
type: 'string',
|
|
421
|
+
description: 'Optional source text. Defaults to the current segment of the active document.',
|
|
422
|
+
},
|
|
423
|
+
documentId: {
|
|
424
|
+
type: 'string',
|
|
425
|
+
description: 'Optional CAIT document id. Defaults to the active document.',
|
|
426
|
+
},
|
|
427
|
+
index: {
|
|
428
|
+
type: 'integer',
|
|
429
|
+
minimum: 0,
|
|
430
|
+
description: 'Optional zero-based segment index. Defaults to the document cursor.',
|
|
431
|
+
},
|
|
432
|
+
sourceLang: {
|
|
433
|
+
type: 'string',
|
|
434
|
+
description: 'Optional source language override, e.g. en.',
|
|
435
|
+
},
|
|
436
|
+
targetLang: {
|
|
437
|
+
type: 'string',
|
|
438
|
+
description: 'Optional target language override, e.g. zh-CN.',
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
additionalProperties: false,
|
|
442
|
+
},
|
|
443
|
+
output: {
|
|
444
|
+
schema: { type: 'object', additionalProperties: true },
|
|
445
|
+
render(_args, value) {
|
|
446
|
+
if (!value || value.ok === false) {
|
|
447
|
+
return text(`CAIT: ${value?.message || 'Machine translation failed.'}`)
|
|
448
|
+
}
|
|
449
|
+
const lines = [
|
|
450
|
+
`CAIT MT (${value.provider} · ${value.model} · ${value.latencyMs} ms):`,
|
|
451
|
+
'',
|
|
452
|
+
value.text,
|
|
453
|
+
]
|
|
454
|
+
if (value.note) lines.push('', value.note)
|
|
455
|
+
return text(lines.join('\n'))
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
async execute(args = {}) {
|
|
459
|
+
try {
|
|
460
|
+
const result = await store.mtSuggest(args)
|
|
461
|
+
return { ok: true, ...result }
|
|
462
|
+
} catch (error) {
|
|
463
|
+
return failure(error)
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
}),
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
return () => {
|
|
470
|
+
for (const dispose of disposers) dispose()
|
|
471
|
+
}
|
|
472
|
+
}
|