@jskit-ai/assistant 0.1.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/package.descriptor.mjs +284 -0
- package/package.json +31 -0
- package/src/client/components/AssistantClientElement.vue +1316 -0
- package/src/client/components/AssistantConsoleSettingsClientElement.vue +71 -0
- package/src/client/components/AssistantSettingsFormCard.vue +76 -0
- package/src/client/components/AssistantWorkspaceClientElement.vue +15 -0
- package/src/client/components/AssistantWorkspaceSettingsClientElement.vue +73 -0
- package/src/client/composables/useAssistantWorkspaceRuntime.js +789 -0
- package/src/client/index.js +12 -0
- package/src/client/lib/assistantApi.js +137 -0
- package/src/client/lib/assistantHttpClient.js +10 -0
- package/src/client/lib/markdownRenderer.js +31 -0
- package/src/client/providers/AssistantWebClientProvider.js +25 -0
- package/src/server/AssistantServiceProvider.js +179 -0
- package/src/server/actionIds.js +11 -0
- package/src/server/actions.js +191 -0
- package/src/server/diTokens.js +19 -0
- package/src/server/lib/aiClient.js +43 -0
- package/src/server/lib/ndjson.js +47 -0
- package/src/server/lib/providers/anthropicClient.js +375 -0
- package/src/server/lib/providers/common.js +158 -0
- package/src/server/lib/providers/deepSeekClient.js +22 -0
- package/src/server/lib/providers/openAiClient.js +13 -0
- package/src/server/lib/providers/openAiCompatibleClient.js +69 -0
- package/src/server/lib/resolveWorkspaceSlug.js +24 -0
- package/src/server/lib/serviceToolCatalog.js +459 -0
- package/src/server/registerRoutes.js +384 -0
- package/src/server/repositories/assistantSettingsRepository.js +100 -0
- package/src/server/repositories/conversationsRepository.js +244 -0
- package/src/server/repositories/messagesRepository.js +154 -0
- package/src/server/repositories/repositoryPersistenceUtils.js +63 -0
- package/src/server/services/assistantSettingsService.js +153 -0
- package/src/server/services/chatService.js +987 -0
- package/src/server/services/transcriptService.js +334 -0
- package/src/shared/assistantPaths.js +50 -0
- package/src/shared/assistantResource.js +323 -0
- package/src/shared/assistantSettingsResource.js +214 -0
- package/src/shared/index.js +39 -0
- package/src/shared/queryKeys.js +69 -0
- package/src/shared/settingsEvents.js +7 -0
- package/src/shared/streamEvents.js +31 -0
- package/src/shared/support/positiveInteger.js +9 -0
- package/templates/migrations/assistant_settings_initial.cjs +39 -0
- package/templates/migrations/assistant_transcripts_initial.cjs +51 -0
- package/templates/src/pages/admin/workspace/assistant/index.vue +7 -0
- package/test/aiConfigValidation.test.js +15 -0
- package/test/assistantApiSurfaceHeader.test.js +64 -0
- package/test/assistantResource.test.js +53 -0
- package/test/assistantSettingsResource.test.js +48 -0
- package/test/assistantSettingsService.test.js +133 -0
- package/test/chatService.test.js +841 -0
- package/test/descriptorSurfaceOption.test.js +35 -0
- package/test/queryKeys.test.js +41 -0
- package/test/resolveWorkspaceSlug.test.js +83 -0
- package/test/routeInputContracts.test.js +287 -0
- package/test/serviceToolCatalog.test.js +1235 -0
- package/test/transcriptService.test.js +175 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import {
|
|
4
|
+
parseJsonObject,
|
|
5
|
+
stringifyJsonObject,
|
|
6
|
+
toIso,
|
|
7
|
+
resolveInsertedId
|
|
8
|
+
} from "./repositoryPersistenceUtils.js";
|
|
9
|
+
|
|
10
|
+
function mapConversationRow(row = {}) {
|
|
11
|
+
return {
|
|
12
|
+
id: Number(row.id),
|
|
13
|
+
workspaceId: Number(row.workspace_id),
|
|
14
|
+
workspaceSlug: String(row.workspace_slug || ""),
|
|
15
|
+
workspaceName: String(row.workspace_name || ""),
|
|
16
|
+
title: String(row.title || "New conversation"),
|
|
17
|
+
createdByUserId: row.created_by_user_id == null ? null : Number(row.created_by_user_id),
|
|
18
|
+
createdByUserDisplayName: String(row.created_by_user_display_name || ""),
|
|
19
|
+
createdByUserEmail: String(row.created_by_user_email || ""),
|
|
20
|
+
status: String(row.status || "active"),
|
|
21
|
+
provider: String(row.provider || ""),
|
|
22
|
+
model: String(row.model || ""),
|
|
23
|
+
surfaceId: String(row.surface_id || ""),
|
|
24
|
+
startedAt: toIso(row.started_at),
|
|
25
|
+
endedAt: row.ended_at ? toIso(row.ended_at) : null,
|
|
26
|
+
messageCount: Number(row.message_count || 0),
|
|
27
|
+
metadata: parseJsonObject(row.metadata_json),
|
|
28
|
+
createdAt: toIso(row.created_at),
|
|
29
|
+
updatedAt: toIso(row.updated_at)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeCursorPagination(pagination = {}, { defaultLimit = 20, maxLimit = 200 } = {}) {
|
|
34
|
+
const cursor = parsePositiveInteger(pagination.cursor) || 0;
|
|
35
|
+
const limit = Math.max(1, Math.min(maxLimit, parsePositiveInteger(pagination.limit) || defaultLimit));
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
cursor,
|
|
39
|
+
limit
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createConversationBaseQuery(client) {
|
|
44
|
+
return client("ai_conversations as c")
|
|
45
|
+
.leftJoin("workspaces as w", "w.id", "c.workspace_id")
|
|
46
|
+
.leftJoin("user_profiles as u", "u.id", "c.created_by_user_id")
|
|
47
|
+
.select(
|
|
48
|
+
"c.*",
|
|
49
|
+
client.raw("COALESCE(w.slug, '') AS workspace_slug"),
|
|
50
|
+
client.raw("COALESCE(w.name, '') AS workspace_name"),
|
|
51
|
+
client.raw("COALESCE(u.display_name, '') AS created_by_user_display_name"),
|
|
52
|
+
client.raw("COALESCE(u.email, '') AS created_by_user_email")
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createConversationsRepository(knex) {
|
|
57
|
+
if (!knex || typeof knex !== "function") {
|
|
58
|
+
throw new Error("createConversationsRepository requires knex client.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function findById(conversationId, options = {}) {
|
|
62
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
63
|
+
if (!numericConversationId) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const client = options?.trx || knex;
|
|
68
|
+
const row = await createConversationBaseQuery(client)
|
|
69
|
+
.where("c.id", numericConversationId)
|
|
70
|
+
.first();
|
|
71
|
+
|
|
72
|
+
return row ? mapConversationRow(row) : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function findByIdForWorkspaceAndUser(conversationId, workspaceId, actorUserId, options = {}) {
|
|
76
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
77
|
+
const numericWorkspaceId = parsePositiveInteger(workspaceId);
|
|
78
|
+
const numericActorUserId = parsePositiveInteger(actorUserId);
|
|
79
|
+
if (!numericConversationId || !numericWorkspaceId || !numericActorUserId) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const client = options?.trx || knex;
|
|
84
|
+
const row = await createConversationBaseQuery(client)
|
|
85
|
+
.where("c.id", numericConversationId)
|
|
86
|
+
.where("c.workspace_id", numericWorkspaceId)
|
|
87
|
+
.where("c.created_by_user_id", numericActorUserId)
|
|
88
|
+
.first();
|
|
89
|
+
|
|
90
|
+
return row ? mapConversationRow(row) : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function create(payload = {}, options = {}) {
|
|
94
|
+
const client = options?.trx || knex;
|
|
95
|
+
const now = new Date();
|
|
96
|
+
const insertResult = await client("ai_conversations").insert({
|
|
97
|
+
workspace_id: parsePositiveInteger(payload.workspaceId),
|
|
98
|
+
created_by_user_id: parsePositiveInteger(payload.createdByUserId) || null,
|
|
99
|
+
title: normalizeText(payload.title) || "New conversation",
|
|
100
|
+
status: normalizeText(payload.status).toLowerCase() || "active",
|
|
101
|
+
provider: normalizeText(payload.provider),
|
|
102
|
+
model: normalizeText(payload.model),
|
|
103
|
+
surface_id: normalizeText(payload.surfaceId).toLowerCase() || "admin",
|
|
104
|
+
message_count: parsePositiveInteger(payload.messageCount) || 0,
|
|
105
|
+
metadata_json: stringifyJsonObject(payload.metadata),
|
|
106
|
+
started_at: payload.startedAt ? new Date(payload.startedAt) : now,
|
|
107
|
+
ended_at: payload.endedAt ? new Date(payload.endedAt) : null,
|
|
108
|
+
created_at: now,
|
|
109
|
+
updated_at: now
|
|
110
|
+
});
|
|
111
|
+
const id = resolveInsertedId(insertResult);
|
|
112
|
+
if (!id) {
|
|
113
|
+
throw new Error("conversationsRepository.create could not resolve inserted id.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return findById(id, {
|
|
117
|
+
trx: client
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function updateById(conversationId, patch = {}, options = {}) {
|
|
122
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
123
|
+
if (!numericConversationId) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const client = options?.trx || knex;
|
|
128
|
+
const updatePatch = {};
|
|
129
|
+
|
|
130
|
+
if (Object.hasOwn(patch, "title")) {
|
|
131
|
+
updatePatch.title = normalizeText(patch.title) || "New conversation";
|
|
132
|
+
}
|
|
133
|
+
if (Object.hasOwn(patch, "status")) {
|
|
134
|
+
updatePatch.status = normalizeText(patch.status).toLowerCase() || "active";
|
|
135
|
+
}
|
|
136
|
+
if (Object.hasOwn(patch, "provider")) {
|
|
137
|
+
updatePatch.provider = normalizeText(patch.provider);
|
|
138
|
+
}
|
|
139
|
+
if (Object.hasOwn(patch, "model")) {
|
|
140
|
+
updatePatch.model = normalizeText(patch.model);
|
|
141
|
+
}
|
|
142
|
+
if (Object.hasOwn(patch, "surfaceId")) {
|
|
143
|
+
updatePatch.surface_id = normalizeText(patch.surfaceId).toLowerCase() || "admin";
|
|
144
|
+
}
|
|
145
|
+
if (Object.hasOwn(patch, "messageCount")) {
|
|
146
|
+
updatePatch.message_count = Math.max(0, Number(patch.messageCount || 0));
|
|
147
|
+
}
|
|
148
|
+
if (Object.hasOwn(patch, "endedAt")) {
|
|
149
|
+
updatePatch.ended_at = patch.endedAt ? new Date(patch.endedAt) : null;
|
|
150
|
+
}
|
|
151
|
+
if (Object.hasOwn(patch, "metadata")) {
|
|
152
|
+
updatePatch.metadata_json = stringifyJsonObject(patch.metadata);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (Object.keys(updatePatch).length > 0) {
|
|
156
|
+
updatePatch.updated_at = new Date();
|
|
157
|
+
await client("ai_conversations").where({ id: numericConversationId }).update(updatePatch);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return findById(numericConversationId, {
|
|
161
|
+
trx: client
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function incrementMessageCount(conversationId, delta = 1, options = {}) {
|
|
166
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
167
|
+
if (!numericConversationId) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const client = options?.trx || knex;
|
|
172
|
+
const incrementBy = Number.isInteger(Number(delta)) ? Number(delta) : 1;
|
|
173
|
+
await client("ai_conversations")
|
|
174
|
+
.where({ id: numericConversationId })
|
|
175
|
+
.update({
|
|
176
|
+
message_count: client.raw("GREATEST(0, message_count + ?)", [incrementBy]),
|
|
177
|
+
updated_at: new Date()
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return findById(numericConversationId, {
|
|
181
|
+
trx: client
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function listForWorkspaceAndUser(workspaceId, actorUserId, pagination = {}, filters = {}, options = {}) {
|
|
186
|
+
const numericWorkspaceId = parsePositiveInteger(workspaceId);
|
|
187
|
+
const numericActorUserId = parsePositiveInteger(actorUserId);
|
|
188
|
+
if (!numericWorkspaceId || !numericActorUserId) {
|
|
189
|
+
return {
|
|
190
|
+
items: [],
|
|
191
|
+
nextCursor: null
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const client = options?.trx || knex;
|
|
196
|
+
const { cursor, limit } = normalizeCursorPagination(pagination);
|
|
197
|
+
|
|
198
|
+
let query = createConversationBaseQuery(client)
|
|
199
|
+
.where("c.workspace_id", numericWorkspaceId)
|
|
200
|
+
.where("c.created_by_user_id", numericActorUserId);
|
|
201
|
+
|
|
202
|
+
const normalizedStatus = normalizeText(filters.status).toLowerCase();
|
|
203
|
+
if (normalizedStatus) {
|
|
204
|
+
query = query.where("c.status", normalizedStatus);
|
|
205
|
+
}
|
|
206
|
+
if (cursor > 0) {
|
|
207
|
+
query = query.where("c.id", "<", cursor);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const rows = await query
|
|
211
|
+
.orderBy("c.id", "desc")
|
|
212
|
+
.limit(limit + 1);
|
|
213
|
+
|
|
214
|
+
const hasMore = rows.length > limit;
|
|
215
|
+
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
216
|
+
const items = pageRows.map(mapConversationRow);
|
|
217
|
+
const nextCursor = hasMore && pageRows.length > 0 ? String(pageRows[pageRows.length - 1].id) : null;
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
items,
|
|
221
|
+
nextCursor
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function transaction(callback) {
|
|
226
|
+
if (typeof knex.transaction !== "function") {
|
|
227
|
+
return callback(knex);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return knex.transaction(callback);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return Object.freeze({
|
|
234
|
+
findById,
|
|
235
|
+
findByIdForWorkspaceAndUser,
|
|
236
|
+
create,
|
|
237
|
+
updateById,
|
|
238
|
+
incrementMessageCount,
|
|
239
|
+
listForWorkspaceAndUser,
|
|
240
|
+
transaction
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export { createConversationsRepository as createRepository, createConversationsRepository };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import {
|
|
4
|
+
parseJsonObject,
|
|
5
|
+
stringifyJsonObject,
|
|
6
|
+
toIso,
|
|
7
|
+
resolveInsertedId
|
|
8
|
+
} from "./repositoryPersistenceUtils.js";
|
|
9
|
+
|
|
10
|
+
function mapMessageRow(row = {}) {
|
|
11
|
+
return {
|
|
12
|
+
id: Number(row.id),
|
|
13
|
+
conversationId: Number(row.conversation_id),
|
|
14
|
+
workspaceId: Number(row.workspace_id),
|
|
15
|
+
seq: Number(row.seq),
|
|
16
|
+
role: String(row.role || ""),
|
|
17
|
+
kind: String(row.kind || "chat"),
|
|
18
|
+
clientMessageId: String(row.client_message_id || ""),
|
|
19
|
+
actorUserId: row.actor_user_id == null ? null : Number(row.actor_user_id),
|
|
20
|
+
contentText: row.content_text == null ? null : String(row.content_text),
|
|
21
|
+
metadata: parseJsonObject(row.metadata_json),
|
|
22
|
+
createdAt: toIso(row.created_at)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizePagination(pagination = {}, { defaultPage = 1, defaultPageSize = 200, maxPageSize = 500 } = {}) {
|
|
27
|
+
const page = Math.max(1, parsePositiveInteger(pagination.page) || defaultPage);
|
|
28
|
+
const pageSize = Math.max(1, Math.min(maxPageSize, parsePositiveInteger(pagination.pageSize) || defaultPageSize));
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
page,
|
|
32
|
+
pageSize
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function resolveNextSequence(client, conversationId) {
|
|
37
|
+
const row = await client("ai_messages").where({ conversation_id: conversationId }).max({ maxSeq: "seq" }).first();
|
|
38
|
+
const maxSeq = Number(row?.maxSeq || 0);
|
|
39
|
+
if (!Number.isInteger(maxSeq) || maxSeq < 0) {
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return maxSeq + 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createMessagesRepository(knex) {
|
|
47
|
+
if (!knex || typeof knex !== "function") {
|
|
48
|
+
throw new Error("createMessagesRepository requires knex client.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function findById(messageId, options = {}) {
|
|
52
|
+
const numericMessageId = parsePositiveInteger(messageId);
|
|
53
|
+
if (!numericMessageId) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const client = options?.trx || knex;
|
|
58
|
+
const row = await client("ai_messages").where({ id: numericMessageId }).first();
|
|
59
|
+
return row ? mapMessageRow(row) : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function create(payload = {}, options = {}) {
|
|
63
|
+
const client = options?.trx || knex;
|
|
64
|
+
const conversationId = parsePositiveInteger(payload.conversationId);
|
|
65
|
+
const workspaceId = parsePositiveInteger(payload.workspaceId);
|
|
66
|
+
if (!conversationId || !workspaceId) {
|
|
67
|
+
throw new TypeError("messagesRepository.create requires conversationId and workspaceId.");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const seq = parsePositiveInteger(payload.seq) || (await resolveNextSequence(client, conversationId));
|
|
71
|
+
const insertResult = await client("ai_messages").insert({
|
|
72
|
+
conversation_id: conversationId,
|
|
73
|
+
workspace_id: workspaceId,
|
|
74
|
+
seq,
|
|
75
|
+
role: normalizeText(payload.role).toLowerCase(),
|
|
76
|
+
kind: normalizeText(payload.kind).toLowerCase() || "chat",
|
|
77
|
+
client_message_id: normalizeText(payload.clientMessageId),
|
|
78
|
+
actor_user_id: parsePositiveInteger(payload.actorUserId) || null,
|
|
79
|
+
content_text: payload.contentText == null ? null : String(payload.contentText),
|
|
80
|
+
metadata_json: stringifyJsonObject(payload.metadata),
|
|
81
|
+
created_at: payload.createdAt ? new Date(payload.createdAt) : new Date()
|
|
82
|
+
});
|
|
83
|
+
const id = resolveInsertedId(insertResult);
|
|
84
|
+
if (!id) {
|
|
85
|
+
throw new Error("messagesRepository.create could not resolve inserted id.");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return findById(id, {
|
|
89
|
+
trx: client
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function countByConversationForWorkspace(conversationId, workspaceId, options = {}) {
|
|
94
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
95
|
+
const numericWorkspaceId = parsePositiveInteger(workspaceId);
|
|
96
|
+
if (!numericConversationId || !numericWorkspaceId) {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const client = options?.trx || knex;
|
|
101
|
+
const row = await client("ai_messages")
|
|
102
|
+
.where({
|
|
103
|
+
conversation_id: numericConversationId,
|
|
104
|
+
workspace_id: numericWorkspaceId
|
|
105
|
+
})
|
|
106
|
+
.count({ total: "*" })
|
|
107
|
+
.first();
|
|
108
|
+
|
|
109
|
+
const total = Number(row?.total || 0);
|
|
110
|
+
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function listByConversationForWorkspace(conversationId, workspaceId, pagination = {}, options = {}) {
|
|
114
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
115
|
+
const numericWorkspaceId = parsePositiveInteger(workspaceId);
|
|
116
|
+
if (!numericConversationId || !numericWorkspaceId) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const client = options?.trx || knex;
|
|
121
|
+
const { page, pageSize } = normalizePagination(pagination);
|
|
122
|
+
const offset = (page - 1) * pageSize;
|
|
123
|
+
|
|
124
|
+
const rows = await client("ai_messages")
|
|
125
|
+
.where({
|
|
126
|
+
conversation_id: numericConversationId,
|
|
127
|
+
workspace_id: numericWorkspaceId
|
|
128
|
+
})
|
|
129
|
+
.orderBy("seq", "asc")
|
|
130
|
+
.orderBy("id", "asc")
|
|
131
|
+
.limit(pageSize)
|
|
132
|
+
.offset(offset);
|
|
133
|
+
|
|
134
|
+
return rows.map(mapMessageRow);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function transaction(callback) {
|
|
138
|
+
if (typeof knex.transaction !== "function") {
|
|
139
|
+
return callback(knex);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return knex.transaction(callback);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return Object.freeze({
|
|
146
|
+
findById,
|
|
147
|
+
create,
|
|
148
|
+
countByConversationForWorkspace,
|
|
149
|
+
listByConversationForWorkspace,
|
|
150
|
+
transaction
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export { createMessagesRepository as createRepository, createMessagesRepository };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
function parseJsonObject(value) {
|
|
2
|
+
if (value == null) {
|
|
3
|
+
return {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
8
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return parsed;
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function stringifyJsonObject(value) {
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return JSON.stringify(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function toIso(value) {
|
|
27
|
+
if (!value) {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
32
|
+
if (Number.isNaN(date.getTime())) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return date.toISOString();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveInsertedId(insertResult) {
|
|
40
|
+
if (Array.isArray(insertResult) && insertResult.length > 0) {
|
|
41
|
+
const first = insertResult[0];
|
|
42
|
+
if (first && typeof first === "object" && !Array.isArray(first)) {
|
|
43
|
+
const objectId = Number(first.id);
|
|
44
|
+
if (Number.isInteger(objectId) && objectId > 0) {
|
|
45
|
+
return objectId;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const scalarId = Number(first);
|
|
50
|
+
if (Number.isInteger(scalarId) && scalarId > 0) {
|
|
51
|
+
return scalarId;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const directId = Number(insertResult);
|
|
56
|
+
if (Number.isInteger(directId) && directId > 0) {
|
|
57
|
+
return directId;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { parseJsonObject, stringifyJsonObject, toIso, resolveInsertedId };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
|
|
5
|
+
import {
|
|
6
|
+
ASSISTANT_CONSOLE_SETTINGS_CHANGED_EVENT,
|
|
7
|
+
ASSISTANT_WORKSPACE_SETTINGS_CHANGED_EVENT
|
|
8
|
+
} from "../../shared/settingsEvents.js";
|
|
9
|
+
|
|
10
|
+
const serviceEvents = Object.freeze({
|
|
11
|
+
updateConsoleSettings: Object.freeze([
|
|
12
|
+
Object.freeze({
|
|
13
|
+
type: "entity.changed",
|
|
14
|
+
source: "assistant",
|
|
15
|
+
entity: "console.settings",
|
|
16
|
+
operation: "updated",
|
|
17
|
+
entityId: 1,
|
|
18
|
+
realtime: Object.freeze({
|
|
19
|
+
event: ASSISTANT_CONSOLE_SETTINGS_CHANGED_EVENT,
|
|
20
|
+
audience: "all_users"
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
]),
|
|
24
|
+
updateWorkspaceSettings: Object.freeze([
|
|
25
|
+
Object.freeze({
|
|
26
|
+
type: "entity.changed",
|
|
27
|
+
source: "assistant",
|
|
28
|
+
entity: "workspace.settings",
|
|
29
|
+
operation: "updated",
|
|
30
|
+
entityId: ({ args }) => args?.[0]?.id || null,
|
|
31
|
+
realtime: Object.freeze({
|
|
32
|
+
event: ASSISTANT_WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
33
|
+
audience: "event_scope",
|
|
34
|
+
payload: ({ args }) => ({
|
|
35
|
+
workspaceSlug: String(args?.[0]?.slug || "").trim()
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
])
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function normalizeSurface(value) {
|
|
43
|
+
return normalizeText(value).toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function mapConsoleResponse(record = {}) {
|
|
47
|
+
return {
|
|
48
|
+
settings: {
|
|
49
|
+
workspaceSurfacePrompt: String(record.workspaceSurfacePrompt || "")
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function mapWorkspaceResponse(record = {}) {
|
|
55
|
+
return {
|
|
56
|
+
settings: {
|
|
57
|
+
appSurfacePrompt: String(record.appSurfacePrompt || "")
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createService({ assistantSettingsRepository, consoleService } = {}) {
|
|
63
|
+
if (!assistantSettingsRepository || typeof assistantSettingsRepository.ensureConsoleSettings !== "function") {
|
|
64
|
+
throw new Error("assistantSettingsService requires assistantSettingsRepository.ensureConsoleSettings().");
|
|
65
|
+
}
|
|
66
|
+
if (!assistantSettingsRepository || typeof assistantSettingsRepository.ensureWorkspaceSettings !== "function") {
|
|
67
|
+
throw new Error("assistantSettingsService requires assistantSettingsRepository.ensureWorkspaceSettings().");
|
|
68
|
+
}
|
|
69
|
+
if (!assistantSettingsRepository || typeof assistantSettingsRepository.updateConsoleSettings !== "function") {
|
|
70
|
+
throw new Error("assistantSettingsService requires assistantSettingsRepository.updateConsoleSettings().");
|
|
71
|
+
}
|
|
72
|
+
if (!assistantSettingsRepository || typeof assistantSettingsRepository.updateWorkspaceSettings !== "function") {
|
|
73
|
+
throw new Error("assistantSettingsService requires assistantSettingsRepository.updateWorkspaceSettings().");
|
|
74
|
+
}
|
|
75
|
+
if (!consoleService || typeof consoleService.requireConsoleOwner !== "function") {
|
|
76
|
+
throw new Error("assistantSettingsService requires consoleService.requireConsoleOwner().");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function getConsoleSettings(options = {}) {
|
|
80
|
+
await consoleService.requireConsoleOwner(options?.context, options);
|
|
81
|
+
const settings = await assistantSettingsRepository.ensureConsoleSettings(options);
|
|
82
|
+
return mapConsoleResponse(settings);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function updateConsoleSettings(payload = {}, options = {}) {
|
|
86
|
+
await consoleService.requireConsoleOwner(options?.context, options);
|
|
87
|
+
const source = normalizeObjectInput(payload);
|
|
88
|
+
const patch = pickOwnProperties(source, ["workspaceSurfacePrompt"]);
|
|
89
|
+
if (Object.keys(patch).length < 1) {
|
|
90
|
+
const settings = await assistantSettingsRepository.ensureConsoleSettings(options);
|
|
91
|
+
return mapConsoleResponse(settings);
|
|
92
|
+
}
|
|
93
|
+
const settings = await assistantSettingsRepository.updateConsoleSettings({
|
|
94
|
+
workspaceSurfacePrompt: String(patch.workspaceSurfacePrompt || "")
|
|
95
|
+
}, options);
|
|
96
|
+
return mapConsoleResponse(settings);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function getWorkspaceSettings(workspace, options = {}) {
|
|
100
|
+
const workspaceId = parsePositiveInteger(workspace?.id);
|
|
101
|
+
if (!workspaceId) {
|
|
102
|
+
throw new Error("assistantSettingsService.getWorkspaceSettings requires workspace.id.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const settings = await assistantSettingsRepository.ensureWorkspaceSettings(workspaceId, options);
|
|
106
|
+
return mapWorkspaceResponse(settings);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function updateWorkspaceSettings(workspace, payload = {}, options = {}) {
|
|
110
|
+
const workspaceId = parsePositiveInteger(workspace?.id);
|
|
111
|
+
if (!workspaceId) {
|
|
112
|
+
throw new Error("assistantSettingsService.updateWorkspaceSettings requires workspace.id.");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const source = normalizeObjectInput(payload);
|
|
116
|
+
const patch = pickOwnProperties(source, ["appSurfacePrompt"]);
|
|
117
|
+
if (Object.keys(patch).length < 1) {
|
|
118
|
+
const settings = await assistantSettingsRepository.ensureWorkspaceSettings(workspaceId, options);
|
|
119
|
+
return mapWorkspaceResponse(settings);
|
|
120
|
+
}
|
|
121
|
+
const settings = await assistantSettingsRepository.updateWorkspaceSettings(
|
|
122
|
+
workspaceId,
|
|
123
|
+
{
|
|
124
|
+
appSurfacePrompt: String(patch.appSurfacePrompt || "")
|
|
125
|
+
},
|
|
126
|
+
options
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return mapWorkspaceResponse(settings);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function resolveSystemPrompt(workspace, { surface = "" } = {}, options = {}) {
|
|
133
|
+
const normalizedSurface = normalizeSurface(surface);
|
|
134
|
+
|
|
135
|
+
if (normalizedSurface === "app") {
|
|
136
|
+
const workspaceSettings = await getWorkspaceSettings(workspace, options);
|
|
137
|
+
return String(workspaceSettings?.settings?.appSurfacePrompt || "");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const consoleSettings = await assistantSettingsRepository.ensureConsoleSettings(options);
|
|
141
|
+
return String(consoleSettings.workspaceSurfacePrompt || "");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return Object.freeze({
|
|
145
|
+
getConsoleSettings,
|
|
146
|
+
updateConsoleSettings,
|
|
147
|
+
getWorkspaceSettings,
|
|
148
|
+
updateWorkspaceSettings,
|
|
149
|
+
resolveSystemPrompt
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { createService, serviceEvents };
|