Spaces:
Running
Community tools (#1250)
Browse files* wip community tools
* push
* fix
* bugfix
* pass inputs to gradio correctly
* full inputs for imagegen
* lint
* update gradio
* fix call to gradio
* fix imagegen tool
* fix name
* fix image display
* Community tools (#1297)
* work on tools
* wip
* icon
* page
* use IDs for every tools
* improve tools page
* wip
* add optimized deps
* refacto 1tool=1function
* Add preview page
* fix active indicator
* fix text alignment
* fix populate script
* tooledit
* fixes
* better inputs
* fix console error & cancel button
* upload new tools
* edit
* improve lint
* working community tools
* fix active featured check
* Simplify outputs in user form
* fix non file outputs
* fix imagegen default tool
* add community tools popup
* redirect gradio API calls through chat-ui backend
* better usecount
* Better loading & output element
* better redirects & auto activate
* bugfixes
* fix migration changes
* Add feature flag for tools
* fix tools menu
* Keep track of output component index
* fix null check
* catch error for spaces config
* wip: MIME types
* wip: rework tools to use filenames
* fix broken tool calling
* fix input tools
* Working files
* fix(form): bind on name
* fix(prompt): double system prompt
* fix(prompt): file list
* fix(tools): make sure tools are off by default
* Add description to ToolEdit
* Display runs
* feat(tools): add back document parser
* feat(tools): bring back image editing tool
* fix MIME types
* Fix file passing prompts
* fix lint
* Set tools to default in migration
* Revert "Set tools to default in migration"
This reverts commit 87fdd094b73e2427af1a272e6697dafd4d466f1c.
* Use correct space for official image generation
* Remove debug logs
* fix(redirect): working redirect when no `APP_BASE`
* sveltekit 2 migration
* fix types with gradio update
* fix name based searching
* fix cut titles
* add min & max
* add error if no endpoint
* make form not submittable until it's filled
* fix return
* Better support for varied input types
* fix(update): hide null values for params that have not been called explicitly by the model
* add extra migration for transition
* fix document parser mime types
- .env +3 -0
- chart/env/prod.yaml +145 -0
- package-lock.json +0 -0
- package.json +3 -1
- scripts/populate.ts +75 -1
- src/lib/buildPrompt.ts +5 -1
- src/lib/components/NavMenu.svelte +10 -1
- src/lib/components/ToolLogo.svelte +100 -0
- src/lib/components/ToolsMenu.svelte +67 -22
- src/lib/components/chat/ChatWindow.svelte +2 -2
- src/lib/components/chat/ToolUpdate.svelte +12 -9
- src/lib/migrations/routines/03-add-tools-in-settings.ts +1 -1
- src/lib/migrations/routines/07-reset-tools-in-settings.ts +1 -1
- src/lib/migrations/routines/08-reset-tools-in-settings-2.ts +19 -0
- src/lib/migrations/routines/index.ts +2 -0
- src/lib/server/database.ts +9 -0
- src/lib/server/metrics.ts +3 -4
- src/lib/server/models.ts +32 -11
- src/lib/server/textGeneration/index.ts +3 -4
- src/lib/server/textGeneration/tools.ts +91 -41
- src/lib/server/textGeneration/types.ts +1 -1
- src/lib/server/tools/calculator.ts +23 -14
- src/lib/server/tools/directlyAnswer.ts +16 -6
- src/lib/server/tools/documentParser.ts +0 -60
- src/lib/server/tools/images/editing.ts +0 -95
- src/lib/server/tools/images/generation.ts +0 -84
- src/lib/server/tools/index.ts +281 -20
- src/lib/server/tools/outputs.ts +32 -0
- src/lib/server/tools/utils.ts +7 -2
- src/lib/server/tools/web/search.ts +19 -10
- src/lib/server/tools/web/url.ts +24 -14
- src/lib/server/usageLimits.ts +1 -0
- src/lib/stores/settings.ts +1 -1
- src/lib/types/Report.ts +2 -1
- src/lib/types/Settings.ts +2 -2
- src/lib/types/Tool.ts +145 -26
- src/lib/utils/getGradioApi.ts +11 -0
- src/lib/utils/messageUpdates.ts +1 -1
- src/lib/utils/tools.ts +16 -0
- src/routes/+layout.server.ts +49 -15
- src/routes/api/spaces-config/+server.ts +31 -0
- src/routes/assistants/+page.server.ts +2 -2
- src/routes/conversation/[id]/+server.ts +14 -3
- src/routes/settings/(nav)/+server.ts +10 -1
- src/routes/settings/(nav)/assistants/[assistantId]/+page.server.ts +4 -2
- src/routes/settings/(nav)/assistants/[assistantId]/ReportModal.svelte +2 -2
- src/routes/settings/+layout.server.ts +5 -2
- src/routes/tools/+layout.svelte +1 -0
- src/routes/tools/+layout.ts +15 -0
- src/routes/tools/+page.server.ts +94 -0
@@ -163,6 +163,9 @@ ALLOW_INSECURE_COOKIES=false # recommended to keep this to false but set to true
|
|
163 |
METRICS_ENABLED=false
|
164 |
METRICS_PORT=5565
|
165 |
LOG_LEVEL=info
|
|
|
|
|
|
|
166 |
BODY_SIZE_LIMIT=15728640
|
167 |
|
168 |
HF_ORG_ADMIN=
|
|
|
163 |
METRICS_ENABLED=false
|
164 |
METRICS_PORT=5565
|
165 |
LOG_LEVEL=info
|
166 |
+
|
167 |
+
|
168 |
+
TOOLS=`[]`
|
169 |
BODY_SIZE_LIMIT=15728640
|
170 |
|
171 |
HF_ORG_ADMIN=
|
@@ -328,6 +328,151 @@ envVars:
|
|
328 |
}]
|
329 |
WEBSEARCH_BLOCKLIST: '["youtube.com", "twitter.com"]'
|
330 |
XFF_DEPTH: '2'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
331 |
HF_ORG_ADMIN: 'huggingchat'
|
332 |
HF_ORG_EARLY_ACCESS: 'huggingface'
|
333 |
|
|
|
328 |
}]
|
329 |
WEBSEARCH_BLOCKLIST: '["youtube.com", "twitter.com"]'
|
330 |
XFF_DEPTH: '2'
|
331 |
+
TOOLS: >
|
332 |
+
[
|
333 |
+
{
|
334 |
+
"_id": "000000000000000000000001",
|
335 |
+
"displayName": "Image Generation",
|
336 |
+
"description": "Use this tool to generate images based on a prompt.",
|
337 |
+
"color": "yellow",
|
338 |
+
"icon": "camera",
|
339 |
+
"baseUrl": "stabilityai/stable-diffusion-3-medium",
|
340 |
+
"name": "image_generation",
|
341 |
+
"endpoint": "/infer",
|
342 |
+
"inputs": [
|
343 |
+
{
|
344 |
+
"name": "prompt",
|
345 |
+
"description": "A prompt to generate an image from",
|
346 |
+
"paramType": "required",
|
347 |
+
"type": "str"
|
348 |
+
},
|
349 |
+
{
|
350 |
+
"name": "negative_prompt",
|
351 |
+
"description": "A prompt for things that should not be in the image.",
|
352 |
+
"paramType": "optional",
|
353 |
+
"default": "",
|
354 |
+
"type": "str"
|
355 |
+
},
|
356 |
+
{
|
357 |
+
"name": "seed",
|
358 |
+
"paramType": "fixed",
|
359 |
+
"value": "0",
|
360 |
+
"type": "float"
|
361 |
+
},
|
362 |
+
{
|
363 |
+
"name": "randomize_seed",
|
364 |
+
"paramType": "fixed",
|
365 |
+
"value": "true",
|
366 |
+
"type": "bool"
|
367 |
+
},
|
368 |
+
{
|
369 |
+
"name": "width",
|
370 |
+
"description": "numeric value between 256 and 1344",
|
371 |
+
"paramType": "optional",
|
372 |
+
"default": 1024,
|
373 |
+
"type": "float"
|
374 |
+
},
|
375 |
+
{
|
376 |
+
"name": "height",
|
377 |
+
"description": "numeric value between 256 and 1344",
|
378 |
+
"paramType": "optional",
|
379 |
+
"default": 1024,
|
380 |
+
"type": "float"
|
381 |
+
},
|
382 |
+
{
|
383 |
+
"name": "guidance_scale",
|
384 |
+
"description": "numeric value between 0.0 and 10.0",
|
385 |
+
"paramType": "optional",
|
386 |
+
"default": 5,
|
387 |
+
"type": "float"
|
388 |
+
},
|
389 |
+
{
|
390 |
+
"name": "num_inference_steps",
|
391 |
+
"description": "numeric value between 1 and 50",
|
392 |
+
"paramType": "optional",
|
393 |
+
"default": 28,
|
394 |
+
"type": "float"
|
395 |
+
}
|
396 |
+
],
|
397 |
+
"outputComponent": "image",
|
398 |
+
"outputComponentIdx": 0,
|
399 |
+
"showOutput": true
|
400 |
+
},
|
401 |
+
{
|
402 |
+
"_id": "000000000000000000000002",
|
403 |
+
"displayName": "Document parser",
|
404 |
+
"description": "Use this tool to parse any document and get its content in markdown format.",
|
405 |
+
"color": "yellow",
|
406 |
+
"icon": "cloud",
|
407 |
+
"baseUrl": "huggingchat/document-parser",
|
408 |
+
"name": "document_parser",
|
409 |
+
"endpoint": "/predict",
|
410 |
+
"inputs": [
|
411 |
+
{
|
412 |
+
"name": "document",
|
413 |
+
"description": "Filename of the document to parse",
|
414 |
+
"paramType": "required",
|
415 |
+
"type": "file",
|
416 |
+
"mimeTypes": 'application/*'
|
417 |
+
},
|
418 |
+
{
|
419 |
+
"name": "filename",
|
420 |
+
"paramType": "fixed",
|
421 |
+
"value": "document.pdf",
|
422 |
+
"type": "str"
|
423 |
+
}
|
424 |
+
],
|
425 |
+
"outputComponent": "textbox",
|
426 |
+
"outputComponentIdx": 0,
|
427 |
+
"showOutput": false
|
428 |
+
},
|
429 |
+
{
|
430 |
+
"_id": "000000000000000000000003",
|
431 |
+
"name": "edit_image",
|
432 |
+
"baseUrl": "multimodalart/cosxl",
|
433 |
+
"endpoint": "/run_edit",
|
434 |
+
"inputs": [
|
435 |
+
{
|
436 |
+
"name": "image",
|
437 |
+
"description": "The image path to be edited",
|
438 |
+
"paramType": "required",
|
439 |
+
"type": "file",
|
440 |
+
"mimeTypes": 'image/*'
|
441 |
+
},
|
442 |
+
{
|
443 |
+
"name": "prompt",
|
444 |
+
"description": "The prompt with which to edit the image",
|
445 |
+
"paramType": "required",
|
446 |
+
"type": "str"
|
447 |
+
},
|
448 |
+
{
|
449 |
+
"name": "negative_prompt",
|
450 |
+
"paramType": "fixed",
|
451 |
+
"value": "",
|
452 |
+
"type": "str"
|
453 |
+
},
|
454 |
+
{
|
455 |
+
"name": "guidance_scale",
|
456 |
+
"paramType": "fixed",
|
457 |
+
"value": 6.5,
|
458 |
+
"type": "float"
|
459 |
+
},
|
460 |
+
{
|
461 |
+
"name": "steps",
|
462 |
+
"paramType": "fixed",
|
463 |
+
"value": 30,
|
464 |
+
"type": "float"
|
465 |
+
}
|
466 |
+
],
|
467 |
+
"outputComponent": "image",
|
468 |
+
"showOutput": true,
|
469 |
+
"displayName": "Image editor",
|
470 |
+
"color": "green",
|
471 |
+
"icon": "camera",
|
472 |
+
"description": "This tool lets you edit images",
|
473 |
+
"outputComponentIdx": 0
|
474 |
+
}
|
475 |
+
]
|
476 |
HF_ORG_ADMIN: 'huggingchat'
|
477 |
HF_ORG_EARLY_ACCESS: 'huggingface'
|
478 |
|
The diff for this file is too large to render.
See raw diff
|
|
@@ -27,6 +27,7 @@
|
|
27 |
"@types/express": "^4.17.21",
|
28 |
"@types/js-yaml": "^4.0.9",
|
29 |
"@types/jsdom": "^21.1.1",
|
|
|
30 |
"@types/minimist": "^1.2.5",
|
31 |
"@types/node": "^22.1.0",
|
32 |
"@types/parquetjs": "^0.10.3",
|
@@ -59,7 +60,7 @@
|
|
59 |
"dependencies": {
|
60 |
"@aws-sdk/credential-providers": "^3.592.0",
|
61 |
"@cliqz/adblocker-playwright": "^1.27.2",
|
62 |
-
"@gradio/client": "^
|
63 |
"@huggingface/hub": "^0.5.1",
|
64 |
"@huggingface/inference": "^2.7.0",
|
65 |
"@iconify-json/bi": "^1.1.21",
|
@@ -82,6 +83,7 @@
|
|
82 |
"jose": "^5.3.0",
|
83 |
"jsdom": "^22.0.0",
|
84 |
"json5": "^2.2.3",
|
|
|
85 |
"lint-staged": "^15.2.7",
|
86 |
"marked": "^12.0.1",
|
87 |
"marked-katex-extension": "^5.0.1",
|
|
|
27 |
"@types/express": "^4.17.21",
|
28 |
"@types/js-yaml": "^4.0.9",
|
29 |
"@types/jsdom": "^21.1.1",
|
30 |
+
"@types/jsonpath": "^0.2.4",
|
31 |
"@types/minimist": "^1.2.5",
|
32 |
"@types/node": "^22.1.0",
|
33 |
"@types/parquetjs": "^0.10.3",
|
|
|
60 |
"dependencies": {
|
61 |
"@aws-sdk/credential-providers": "^3.592.0",
|
62 |
"@cliqz/adblocker-playwright": "^1.27.2",
|
63 |
+
"@gradio/client": "^1.1.1",
|
64 |
"@huggingface/hub": "^0.5.1",
|
65 |
"@huggingface/inference": "^2.7.0",
|
66 |
"@iconify-json/bi": "^1.1.21",
|
|
|
83 |
"jose": "^5.3.0",
|
84 |
"jsdom": "^22.0.0",
|
85 |
"json5": "^2.2.3",
|
86 |
+
"jsonpath": "^1.1.1",
|
87 |
"lint-staged": "^15.2.7",
|
88 |
"marked": "^12.0.1",
|
89 |
"marked-katex-extension": "^5.0.1",
|
@@ -14,6 +14,7 @@ import type { User } from "../src/lib/types/User";
|
|
14 |
import type { Assistant } from "../src/lib/types/Assistant";
|
15 |
import type { Conversation } from "../src/lib/types/Conversation";
|
16 |
import type { Settings } from "../src/lib/types/Settings";
|
|
|
17 |
import { defaultEmbeddingModel } from "../src/lib/server/embeddingModels.ts";
|
18 |
import { Message } from "../src/lib/types/Message.ts";
|
19 |
|
@@ -29,7 +30,7 @@ rl.on("close", function () {
|
|
29 |
process.exit(0);
|
30 |
});
|
31 |
|
32 |
-
const possibleFlags = ["reset", "all", "users", "settings", "assistants", "conversations"];
|
33 |
const argv = minimist(process.argv.slice(2));
|
34 |
const flags = argv["_"].filter((flag) => possibleFlags.includes(flag));
|
35 |
|
@@ -113,6 +114,7 @@ async function seed() {
|
|
113 |
await collections.settings.deleteMany({});
|
114 |
await collections.assistants.deleteMany({});
|
115 |
await collections.conversations.deleteMany({});
|
|
|
116 |
console.log("Reset done");
|
117 |
}
|
118 |
|
@@ -239,6 +241,78 @@ async function seed() {
|
|
239 |
);
|
240 |
console.log("Done creating conversations.");
|
241 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
242 |
}
|
243 |
|
244 |
// run seed
|
|
|
14 |
import type { Assistant } from "../src/lib/types/Assistant";
|
15 |
import type { Conversation } from "../src/lib/types/Conversation";
|
16 |
import type { Settings } from "../src/lib/types/Settings";
|
17 |
+
import type { CommunityToolDB, ToolLogoColor, ToolLogoIcon } from "../src/lib/types/Tool";
|
18 |
import { defaultEmbeddingModel } from "../src/lib/server/embeddingModels.ts";
|
19 |
import { Message } from "../src/lib/types/Message.ts";
|
20 |
|
|
|
30 |
process.exit(0);
|
31 |
});
|
32 |
|
33 |
+
const possibleFlags = ["reset", "all", "users", "settings", "assistants", "conversations", "tools"];
|
34 |
const argv = minimist(process.argv.slice(2));
|
35 |
const flags = argv["_"].filter((flag) => possibleFlags.includes(flag));
|
36 |
|
|
|
114 |
await collections.settings.deleteMany({});
|
115 |
await collections.assistants.deleteMany({});
|
116 |
await collections.conversations.deleteMany({});
|
117 |
+
await collections.tools.deleteMany({});
|
118 |
console.log("Reset done");
|
119 |
}
|
120 |
|
|
|
241 |
);
|
242 |
console.log("Done creating conversations.");
|
243 |
}
|
244 |
+
|
245 |
+
// generate Community Tools
|
246 |
+
if (flags.includes("tools") || flags.includes("all")) {
|
247 |
+
const tools = await Promise.all(
|
248 |
+
faker.helpers.multiple(
|
249 |
+
() => {
|
250 |
+
const _id = new ObjectId();
|
251 |
+
const displayName = faker.company.catchPhrase();
|
252 |
+
const description = faker.company.catchPhrase();
|
253 |
+
const color = faker.helpers.arrayElement([
|
254 |
+
"purple",
|
255 |
+
"blue",
|
256 |
+
"green",
|
257 |
+
"yellow",
|
258 |
+
"red",
|
259 |
+
]) satisfies ToolLogoColor;
|
260 |
+
const icon = faker.helpers.arrayElement([
|
261 |
+
"wikis",
|
262 |
+
"tools",
|
263 |
+
"camera",
|
264 |
+
"code",
|
265 |
+
"email",
|
266 |
+
"cloud",
|
267 |
+
"terminal",
|
268 |
+
"game",
|
269 |
+
"chat",
|
270 |
+
"speaker",
|
271 |
+
"video",
|
272 |
+
]) satisfies ToolLogoIcon;
|
273 |
+
const baseUrl = faker.helpers.arrayElement([
|
274 |
+
"stabilityai/stable-diffusion-3-medium",
|
275 |
+
"multimodalart/cosxl",
|
276 |
+
"gokaygokay/SD3-Long-Captioner",
|
277 |
+
"xichenhku/MimicBrush",
|
278 |
+
]);
|
279 |
+
|
280 |
+
// keep empty for populate for now
|
281 |
+
|
282 |
+
const user: User = faker.helpers.arrayElement(users);
|
283 |
+
const createdById = user._id;
|
284 |
+
const createdByName = user.username ?? user.name;
|
285 |
+
|
286 |
+
return {
|
287 |
+
type: "community" as const,
|
288 |
+
_id,
|
289 |
+
createdById,
|
290 |
+
createdByName,
|
291 |
+
displayName,
|
292 |
+
name: displayName.toLowerCase().replace(" ", "_"),
|
293 |
+
endpoint: "/test",
|
294 |
+
description,
|
295 |
+
color,
|
296 |
+
icon,
|
297 |
+
baseUrl,
|
298 |
+
inputs: [],
|
299 |
+
outputPath: null,
|
300 |
+
outputType: "str" as const,
|
301 |
+
showOutput: false,
|
302 |
+
useCount: faker.number.int({ min: 0, max: 100000 }),
|
303 |
+
last24HoursUseCount: faker.number.int({ min: 0, max: 1000 }),
|
304 |
+
createdAt: faker.date.recent({ days: 30 }),
|
305 |
+
updatedAt: faker.date.recent({ days: 30 }),
|
306 |
+
searchTokens: generateSearchTokens(displayName),
|
307 |
+
featured: faker.datatype.boolean(),
|
308 |
+
};
|
309 |
+
},
|
310 |
+
{ count: faker.number.int({ min: 10, max: 200 }) }
|
311 |
+
)
|
312 |
+
);
|
313 |
+
|
314 |
+
await collections.tools.insertMany(tools satisfies CommunityToolDB[]);
|
315 |
+
}
|
316 |
}
|
317 |
|
318 |
// run seed
|
@@ -16,7 +16,11 @@ export async function buildPrompt({
|
|
16 |
tools,
|
17 |
toolResults,
|
18 |
}: buildPromptOptions): Promise<string> {
|
19 |
-
const filteredMessages = messages
|
|
|
|
|
|
|
|
|
20 |
|
21 |
let prompt = model
|
22 |
.chatPromptRender({
|
|
|
16 |
tools,
|
17 |
toolResults,
|
18 |
}: buildPromptOptions): Promise<string> {
|
19 |
+
const filteredMessages = messages;
|
20 |
+
|
21 |
+
if (filteredMessages[0].from === "system" && preprompt) {
|
22 |
+
filteredMessages[0].content = preprompt;
|
23 |
+
}
|
24 |
|
25 |
let prompt = model
|
26 |
.chatPromptRender({
|
@@ -134,8 +134,17 @@
|
|
134 |
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
135 |
>
|
136 |
Assistants
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
<span
|
138 |
-
class="ml-auto rounded-full border border-
|
139 |
>New</span
|
140 |
>
|
141 |
</a>
|
|
|
134 |
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
135 |
>
|
136 |
Assistants
|
137 |
+
</a>
|
138 |
+
{/if}
|
139 |
+
<!-- XXX: feature_flag_tools -->
|
140 |
+
{#if $page.data.user?.isEarlyAccess}
|
141 |
+
<a
|
142 |
+
href="{base}/tools"
|
143 |
+
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
144 |
+
>
|
145 |
+
Tools
|
146 |
<span
|
147 |
+
class="ml-auto rounded-full border border-purple-300 px-2 py-0.5 text-xs text-purple-500 dark:border-purple-500 dark:text-purple-400"
|
148 |
>New</span
|
149 |
>
|
150 |
</a>
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import CarbonWikis from "~icons/carbon/wikis";
|
3 |
+
import CarbonTools from "~icons/carbon/tools";
|
4 |
+
import CarbonCamera from "~icons/carbon/camera";
|
5 |
+
import CarbonCode from "~icons/carbon/code";
|
6 |
+
import CarbonEmail from "~icons/carbon/email";
|
7 |
+
import CarbonCloud from "~icons/carbon/cloud-upload";
|
8 |
+
import CarbonTerminal from "~icons/carbon/terminal";
|
9 |
+
import CarbonGame from "~icons/carbon/game-console";
|
10 |
+
import CarbonChat from "~icons/carbon/chat-bot";
|
11 |
+
import CarbonSpeaker from "~icons/carbon/volume-up";
|
12 |
+
import CarbonVideo from "~icons/carbon/video";
|
13 |
+
|
14 |
+
export let color: string;
|
15 |
+
export let icon: string;
|
16 |
+
export let size: "md" | "lg" = "md";
|
17 |
+
|
18 |
+
$: gradientColor = (() => {
|
19 |
+
switch (color) {
|
20 |
+
case "purple":
|
21 |
+
return "#653789";
|
22 |
+
case "blue":
|
23 |
+
return "#375889";
|
24 |
+
case "green":
|
25 |
+
return "#37894E";
|
26 |
+
case "yellow":
|
27 |
+
return "#897C37";
|
28 |
+
case "red":
|
29 |
+
return "#893737";
|
30 |
+
default:
|
31 |
+
return "#FFF";
|
32 |
+
}
|
33 |
+
})();
|
34 |
+
|
35 |
+
let iconEl = CarbonWikis;
|
36 |
+
|
37 |
+
switch (icon) {
|
38 |
+
case "wikis":
|
39 |
+
iconEl = CarbonWikis;
|
40 |
+
break;
|
41 |
+
case "tools":
|
42 |
+
iconEl = CarbonTools;
|
43 |
+
break;
|
44 |
+
case "camera":
|
45 |
+
iconEl = CarbonCamera;
|
46 |
+
break;
|
47 |
+
case "code":
|
48 |
+
iconEl = CarbonCode;
|
49 |
+
break;
|
50 |
+
case "email":
|
51 |
+
iconEl = CarbonEmail;
|
52 |
+
break;
|
53 |
+
case "cloud":
|
54 |
+
iconEl = CarbonCloud;
|
55 |
+
break;
|
56 |
+
case "terminal":
|
57 |
+
iconEl = CarbonTerminal;
|
58 |
+
break;
|
59 |
+
case "game":
|
60 |
+
iconEl = CarbonGame;
|
61 |
+
break;
|
62 |
+
case "chat":
|
63 |
+
iconEl = CarbonChat;
|
64 |
+
break;
|
65 |
+
case "speaker":
|
66 |
+
iconEl = CarbonSpeaker;
|
67 |
+
break;
|
68 |
+
case "video":
|
69 |
+
iconEl = CarbonVideo;
|
70 |
+
break;
|
71 |
+
}
|
72 |
+
|
73 |
+
$: sizeClass = (() => {
|
74 |
+
switch (size) {
|
75 |
+
case "md":
|
76 |
+
return "size-14";
|
77 |
+
case "lg":
|
78 |
+
return "size-24";
|
79 |
+
}
|
80 |
+
})();
|
81 |
+
</script>
|
82 |
+
|
83 |
+
<div class="flex {sizeClass} items-center justify-center">
|
84 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="absolute {sizeClass} h-full" viewBox="0 0 52 58">
|
85 |
+
<defs>
|
86 |
+
<linearGradient id="gradient-{gradientColor}" gradientTransform="rotate(90)">
|
87 |
+
<stop offset="0%" stop-color="#0E1523" />
|
88 |
+
<stop offset="100%" stop-color={gradientColor} />
|
89 |
+
</linearGradient>
|
90 |
+
<mask id="mask">
|
91 |
+
<path
|
92 |
+
d="M22.3043 1.2486C23.4279 0.603043 24.7025 0.263184 26 0.263184C27.2975 0.263184 28.5721 0.603043 29.6957 1.2486L48.3043 11.9373C49.4279 12.5828 50.361 13.5113 51.0097 14.6294C51.6584 15.7475 52 17.0158 52 18.3069V39.6902C52 40.9813 51.6584 42.2496 51.0097 43.3677C50.361 44.4858 49.4279 45.4143 48.3043 46.0598L29.6957 56.7514C28.5721 57.397 27.2975 57.7369 26 57.7369C24.7025 57.7369 23.4279 57.397 22.3043 56.7514L3.6957 46.0598C2.57209 45.4143 1.63904 44.4858 0.990308 43.3677C0.341578 42.2496 3.34785e-05 40.9813 5.18628e-07 39.6902V18.3099C-0.000485629 17.0183 0.340813 15.7494 0.989568 14.6307C1.63832 13.512 2.57166 12.5831 3.6957 11.9373L22.3043 1.2486Z"
|
93 |
+
fill="white"
|
94 |
+
/>
|
95 |
+
</mask>
|
96 |
+
</defs>
|
97 |
+
<rect width="100%" height="100%" fill="url(#gradient-{gradientColor})" mask="url(#mask)" />
|
98 |
+
</svg>
|
99 |
+
<svelte:component this={iconEl} class="relative {sizeClass} scale-50 text-clip text-gray-200" />
|
100 |
+
</div>
|
@@ -1,4 +1,5 @@
|
|
1 |
<script lang="ts">
|
|
|
2 |
import { page } from "$app/stores";
|
3 |
import { clickOutside } from "$lib/actions/clickOutside";
|
4 |
import { useSettingsStore } from "$lib/stores/settings";
|
@@ -14,15 +15,30 @@
|
|
14 |
|
15 |
// active tools are all the checked tools, either from settings or on by default
|
16 |
$: activeToolCount = $page.data.tools.filter(
|
17 |
-
(tool: ToolFront) =>
|
|
|
|
|
18 |
).length;
|
19 |
|
20 |
-
function setAllTools(value: boolean) {
|
21 |
-
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
}
|
|
|
25 |
$: allToolsEnabled = activeToolCount === $page.data.tools.length;
|
|
|
|
|
26 |
</script>
|
27 |
|
28 |
<details
|
@@ -67,24 +83,53 @@
|
|
67 |
{/if}
|
68 |
</button>
|
69 |
</div>
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
<div class="flex items-center gap-1.5">
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
</div>
|
89 |
{/each}
|
90 |
</div>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { base } from "$app/paths";
|
3 |
import { page } from "$app/stores";
|
4 |
import { clickOutside } from "$lib/actions/clickOutside";
|
5 |
import { useSettingsStore } from "$lib/stores/settings";
|
|
|
15 |
|
16 |
// active tools are all the checked tools, either from settings or on by default
|
17 |
$: activeToolCount = $page.data.tools.filter(
|
18 |
+
(tool: ToolFront) =>
|
19 |
+
// community tools are always on by default
|
20 |
+
tool.type === "community" || $settings?.tools?.includes(tool._id)
|
21 |
).length;
|
22 |
|
23 |
+
async function setAllTools(value: boolean) {
|
24 |
+
const configToolsIds = $page.data.tools
|
25 |
+
.filter((t: ToolFront) => t.type === "config")
|
26 |
+
.map((t: ToolFront) => t._id);
|
27 |
+
|
28 |
+
if (value) {
|
29 |
+
await settings.instantSet({
|
30 |
+
tools: Array.from(new Set([...configToolsIds, ...($settings?.tools ?? [])])),
|
31 |
+
});
|
32 |
+
} else {
|
33 |
+
await settings.instantSet({
|
34 |
+
tools: [],
|
35 |
+
});
|
36 |
+
}
|
37 |
}
|
38 |
+
|
39 |
$: allToolsEnabled = activeToolCount === $page.data.tools.length;
|
40 |
+
|
41 |
+
$: tools = $page.data.tools;
|
42 |
</script>
|
43 |
|
44 |
<details
|
|
|
83 |
{/if}
|
84 |
</button>
|
85 |
</div>
|
86 |
+
<!-- XXX: feature_flag_tools -->
|
87 |
+
{#if $page.data.user?.isEarlyAccess}
|
88 |
+
<a
|
89 |
+
href="{base}/tools"
|
90 |
+
class="col-span-2 my-1 h-fit w-fit items-center justify-center rounded-full bg-purple-500/20 px-2.5 py-1.5 text-sm hover:bg-purple-500/30"
|
91 |
+
>
|
92 |
+
<span class="mr-1 rounded-full bg-purple-700 px-1.5 py-1 text-xs font-bold uppercase">
|
93 |
+
new
|
94 |
+
</span>
|
95 |
+
Browse community tools ({$page.data.communityToolCount ?? 0})
|
96 |
+
</a>
|
97 |
+
{/if}
|
98 |
+
{#each tools as tool}
|
99 |
+
{@const isChecked = $settings?.tools?.includes(tool._id)}
|
100 |
<div class="flex items-center gap-1.5">
|
101 |
+
{#if tool.type === "community"}
|
102 |
+
<input
|
103 |
+
type="checkbox"
|
104 |
+
id={tool._id}
|
105 |
+
checked={true}
|
106 |
+
class="rounded-xs font-semibold accent-purple-500 hover:accent-purple-600"
|
107 |
+
on:click|stopPropagation|preventDefault={async () => {
|
108 |
+
await settings.instantSet({
|
109 |
+
tools: $settings?.tools?.filter((t) => t !== tool._id) ?? [],
|
110 |
+
});
|
111 |
+
}}
|
112 |
+
/>
|
113 |
+
{:else}
|
114 |
+
<input
|
115 |
+
type="checkbox"
|
116 |
+
id={tool._id}
|
117 |
+
checked={isChecked}
|
118 |
+
disabled={loading}
|
119 |
+
on:click|stopPropagation={async () => {
|
120 |
+
if (isChecked) {
|
121 |
+
await settings.instantSet({
|
122 |
+
tools: ($settings?.tools ?? []).filter((t) => t !== tool._id),
|
123 |
+
});
|
124 |
+
} else {
|
125 |
+
await settings.instantSet({
|
126 |
+
tools: [...($settings?.tools ?? []), tool._id],
|
127 |
+
});
|
128 |
+
}
|
129 |
+
}}
|
130 |
+
/>
|
131 |
+
{/if}
|
132 |
+
<label class="cursor-pointer" for={tool._id}>{tool.displayName} </label>
|
133 |
</div>
|
134 |
{/each}
|
135 |
</div>
|
@@ -155,8 +155,8 @@
|
|
155 |
const settings = useSettingsStore();
|
156 |
|
157 |
// active tools are all the checked tools, either from settings or on by default
|
158 |
-
$: activeTools = $page.data.tools.filter(
|
159 |
-
|
160 |
);
|
161 |
$: activeMimeTypes = [
|
162 |
...(!$page.data?.assistant && currentModel.tools
|
|
|
155 |
const settings = useSettingsStore();
|
156 |
|
157 |
// active tools are all the checked tools, either from settings or on by default
|
158 |
+
$: activeTools = $page.data.tools.filter((tool: ToolFront) =>
|
159 |
+
$settings?.tools?.includes(tool._id)
|
160 |
);
|
161 |
$: activeMimeTypes = [
|
162 |
...(!$page.data?.assistant && currentModel.tools
|
@@ -7,7 +7,6 @@
|
|
7 |
} from "$lib/utils/messageUpdates";
|
8 |
|
9 |
import CarbonTools from "~icons/carbon/tools";
|
10 |
-
import { toolHasName } from "$lib/utils/tools";
|
11 |
import type { ToolFront } from "$lib/types/Tool";
|
12 |
import { page } from "$app/stores";
|
13 |
import { onMount } from "svelte";
|
@@ -16,7 +15,7 @@
|
|
16 |
export let tool: MessageToolUpdate[];
|
17 |
export let loading: boolean = false;
|
18 |
|
19 |
-
const
|
20 |
$: toolError = tool.some(isMessageToolErrorUpdate);
|
21 |
$: toolDone = tool.some(isMessageToolResultUpdate);
|
22 |
|
@@ -26,12 +25,13 @@
|
|
26 |
let animation: Animation | undefined = undefined;
|
27 |
|
28 |
let isShowingLoadingBar = false;
|
|
|
29 |
onMount(() => {
|
30 |
if (!toolError && !toolDone && loading && loadingBarEl) {
|
31 |
loadingBarEl.classList.remove("hidden");
|
32 |
isShowingLoadingBar = true;
|
33 |
animation = loadingBarEl.animate([{ width: "0%" }, { width: "calc(100%+1rem)" }], {
|
34 |
-
duration: availableTools.find((tool) => tool.name ===
|
35 |
fill: "forwards",
|
36 |
});
|
37 |
}
|
@@ -63,7 +63,7 @@
|
|
63 |
})();
|
64 |
</script>
|
65 |
|
66 |
-
{#if
|
67 |
<details
|
68 |
class="group/tool my-2.5 w-fit cursor-pointer rounded-lg border border-gray-200 bg-white pl-1 pr-2.5 text-sm shadow-sm transition-all open:mb-3
|
69 |
open:border-purple-500/10 open:bg-purple-600/5 open:shadow-sm dark:border-gray-800 dark:bg-gray-900 open:dark:border-purple-800/40 open:dark:bg-purple-800/10"
|
@@ -103,7 +103,8 @@
|
|
103 |
<span>
|
104 |
{toolError ? "Error calling" : toolDone ? "Called" : "Calling"} tool
|
105 |
<span class="font-semibold"
|
106 |
-
>{availableTools.find((
|
|
|
107 |
>
|
108 |
</span>
|
109 |
</summary>
|
@@ -115,10 +116,12 @@
|
|
115 |
</div>
|
116 |
<ul class="py-1 text-sm">
|
117 |
{#each Object.entries(toolUpdate.call.parameters ?? {}) as [k, v]}
|
118 |
-
|
119 |
-
<
|
120 |
-
|
121 |
-
|
|
|
|
|
122 |
{/each}
|
123 |
</ul>
|
124 |
{:else if toolUpdate.subtype === MessageToolUpdateType.Error}
|
|
|
7 |
} from "$lib/utils/messageUpdates";
|
8 |
|
9 |
import CarbonTools from "~icons/carbon/tools";
|
|
|
10 |
import type { ToolFront } from "$lib/types/Tool";
|
11 |
import { page } from "$app/stores";
|
12 |
import { onMount } from "svelte";
|
|
|
15 |
export let tool: MessageToolUpdate[];
|
16 |
export let loading: boolean = false;
|
17 |
|
18 |
+
const toolFnName = tool.find(isMessageToolCallUpdate)?.call.name;
|
19 |
$: toolError = tool.some(isMessageToolErrorUpdate);
|
20 |
$: toolDone = tool.some(isMessageToolResultUpdate);
|
21 |
|
|
|
25 |
let animation: Animation | undefined = undefined;
|
26 |
|
27 |
let isShowingLoadingBar = false;
|
28 |
+
|
29 |
onMount(() => {
|
30 |
if (!toolError && !toolDone && loading && loadingBarEl) {
|
31 |
loadingBarEl.classList.remove("hidden");
|
32 |
isShowingLoadingBar = true;
|
33 |
animation = loadingBarEl.animate([{ width: "0%" }, { width: "calc(100%+1rem)" }], {
|
34 |
+
duration: availableTools.find((tool) => tool.name === toolFnName)?.timeToUseMS,
|
35 |
fill: "forwards",
|
36 |
});
|
37 |
}
|
|
|
63 |
})();
|
64 |
</script>
|
65 |
|
66 |
+
{#if toolFnName && toolFnName !== "websearch"}
|
67 |
<details
|
68 |
class="group/tool my-2.5 w-fit cursor-pointer rounded-lg border border-gray-200 bg-white pl-1 pr-2.5 text-sm shadow-sm transition-all open:mb-3
|
69 |
open:border-purple-500/10 open:bg-purple-600/5 open:shadow-sm dark:border-gray-800 dark:bg-gray-900 open:dark:border-purple-800/40 open:dark:bg-purple-800/10"
|
|
|
103 |
<span>
|
104 |
{toolError ? "Error calling" : toolDone ? "Called" : "Calling"} tool
|
105 |
<span class="font-semibold"
|
106 |
+
>{availableTools.find((tool) => tool.name === toolFnName)?.displayName ??
|
107 |
+
toolFnName}</span
|
108 |
>
|
109 |
</span>
|
110 |
</summary>
|
|
|
116 |
</div>
|
117 |
<ul class="py-1 text-sm">
|
118 |
{#each Object.entries(toolUpdate.call.parameters ?? {}) as [k, v]}
|
119 |
+
{#if v !== null}
|
120 |
+
<li>
|
121 |
+
<span class="font-semibold">{k}</span>:
|
122 |
+
<span>{v}</span>
|
123 |
+
</li>
|
124 |
+
{/if}
|
125 |
{/each}
|
126 |
</ul>
|
127 |
{:else if toolUpdate.subtype === MessageToolUpdateType.Error}
|
@@ -14,7 +14,7 @@ const addToolsToSettings: Migration = {
|
|
14 |
{
|
15 |
tools: { $exists: false },
|
16 |
},
|
17 |
-
{ $set: { tools:
|
18 |
);
|
19 |
|
20 |
settings
|
|
|
14 |
{
|
15 |
tools: { $exists: false },
|
16 |
},
|
17 |
+
{ $set: { tools: [] } }
|
18 |
);
|
19 |
|
20 |
settings
|
@@ -8,7 +8,7 @@ const resetTools: Migration = {
|
|
8 |
up: async () => {
|
9 |
const { settings } = collections;
|
10 |
|
11 |
-
await settings.updateMany({}, { $set: { tools:
|
12 |
|
13 |
return true;
|
14 |
},
|
|
|
8 |
up: async () => {
|
9 |
const { settings } = collections;
|
10 |
|
11 |
+
await settings.updateMany({}, { $set: { tools: [] } });
|
12 |
|
13 |
return true;
|
14 |
},
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { Migration } from ".";
|
2 |
+
import { collections } from "$lib/server/database";
|
3 |
+
import { ObjectId } from "mongodb";
|
4 |
+
|
5 |
+
const resetTools2: Migration = {
|
6 |
+
_id: new ObjectId("000000000008"),
|
7 |
+
name: "Reset tools to empty",
|
8 |
+
up: async () => {
|
9 |
+
const { settings } = collections;
|
10 |
+
|
11 |
+
await settings.updateMany({}, { $set: { tools: [] } });
|
12 |
+
|
13 |
+
return true;
|
14 |
+
},
|
15 |
+
runEveryTime: false,
|
16 |
+
runForHuggingChat: "only",
|
17 |
+
};
|
18 |
+
|
19 |
+
export default resetTools2;
|
@@ -8,6 +8,7 @@ import updateMessageUpdates from "./04-update-message-updates";
|
|
8 |
import updateMessageFiles from "./05-update-message-files";
|
9 |
import trimMessageUpdates from "./06-trim-message-updates";
|
10 |
import resetTools from "./07-reset-tools-in-settings";
|
|
|
11 |
|
12 |
export interface Migration {
|
13 |
_id: ObjectId;
|
@@ -27,4 +28,5 @@ export const migrations: Migration[] = [
|
|
27 |
updateMessageFiles,
|
28 |
trimMessageUpdates,
|
29 |
resetTools,
|
|
|
30 |
];
|
|
|
8 |
import updateMessageFiles from "./05-update-message-files";
|
9 |
import trimMessageUpdates from "./06-trim-message-updates";
|
10 |
import resetTools from "./07-reset-tools-in-settings";
|
11 |
+
import resetTools2 from "./08-reset-tools-in-settings-2";
|
12 |
|
13 |
export interface Migration {
|
14 |
_id: ObjectId;
|
|
|
28 |
updateMessageFiles,
|
29 |
trimMessageUpdates,
|
30 |
resetTools,
|
31 |
+
resetTools2,
|
32 |
];
|
@@ -13,6 +13,8 @@ import type { ConversationStats } from "$lib/types/ConversationStats";
|
|
13 |
import type { MigrationResult } from "$lib/types/MigrationResult";
|
14 |
import type { Semaphore } from "$lib/types/Semaphore";
|
15 |
import type { AssistantStats } from "$lib/types/AssistantStats";
|
|
|
|
|
16 |
import { logger } from "$lib/server/logger";
|
17 |
import { building } from "$app/environment";
|
18 |
import { onExit } from "./exitHandler";
|
@@ -83,6 +85,7 @@ export class Database {
|
|
83 |
const bucket = new GridFSBucket(db, { bucketName: "files" });
|
84 |
const migrationResults = db.collection<MigrationResult>("migrationResults");
|
85 |
const semaphores = db.collection<Semaphore>("semaphores");
|
|
|
86 |
|
87 |
return {
|
88 |
conversations,
|
@@ -99,6 +102,7 @@ export class Database {
|
|
99 |
bucket,
|
100 |
migrationResults,
|
101 |
semaphores,
|
|
|
102 |
};
|
103 |
}
|
104 |
|
@@ -120,6 +124,7 @@ export class Database {
|
|
120 |
sessions,
|
121 |
messageEvents,
|
122 |
semaphores,
|
|
|
123 |
} = this.getCollections();
|
124 |
|
125 |
conversations
|
@@ -209,6 +214,10 @@ export class Database {
|
|
209 |
semaphores
|
210 |
.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 })
|
211 |
.catch((e) => logger.error(e));
|
|
|
|
|
|
|
|
|
212 |
}
|
213 |
}
|
214 |
|
|
|
13 |
import type { MigrationResult } from "$lib/types/MigrationResult";
|
14 |
import type { Semaphore } from "$lib/types/Semaphore";
|
15 |
import type { AssistantStats } from "$lib/types/AssistantStats";
|
16 |
+
import type { CommunityToolDB } from "$lib/types/Tool";
|
17 |
+
|
18 |
import { logger } from "$lib/server/logger";
|
19 |
import { building } from "$app/environment";
|
20 |
import { onExit } from "./exitHandler";
|
|
|
85 |
const bucket = new GridFSBucket(db, { bucketName: "files" });
|
86 |
const migrationResults = db.collection<MigrationResult>("migrationResults");
|
87 |
const semaphores = db.collection<Semaphore>("semaphores");
|
88 |
+
const tools = db.collection<CommunityToolDB>("tools");
|
89 |
|
90 |
return {
|
91 |
conversations,
|
|
|
102 |
bucket,
|
103 |
migrationResults,
|
104 |
semaphores,
|
105 |
+
tools,
|
106 |
};
|
107 |
}
|
108 |
|
|
|
124 |
sessions,
|
125 |
messageEvents,
|
126 |
semaphores,
|
127 |
+
tools,
|
128 |
} = this.getCollections();
|
129 |
|
130 |
conversations
|
|
|
214 |
semaphores
|
215 |
.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 })
|
216 |
.catch((e) => logger.error(e));
|
217 |
+
|
218 |
+
tools.createIndex({ createdById: 1, userCount: -1 }).catch((e) => logger.error(e));
|
219 |
+
tools.createIndex({ userCount: 1 }).catch((e) => logger.error(e));
|
220 |
+
tools.createIndex({ last24HoursCount: 1 }).catch((e) => logger.error(e));
|
221 |
}
|
222 |
}
|
223 |
|
@@ -3,7 +3,6 @@ import express from "express";
|
|
3 |
import { logger } from "$lib/server/logger";
|
4 |
import { env } from "$env/dynamic/private";
|
5 |
import type { Model } from "$lib/types/Model";
|
6 |
-
import type { Tool } from "$lib/types/Tool";
|
7 |
import { onExit } from "./exitHandler";
|
8 |
import { promisify } from "util";
|
9 |
|
@@ -26,9 +25,9 @@ interface Metrics {
|
|
26 |
};
|
27 |
|
28 |
tool: {
|
29 |
-
toolUseCount: Counter<
|
30 |
-
toolUseCountError: Counter<
|
31 |
-
toolUseDuration: Summary<
|
32 |
timeToChooseTools: Summary;
|
33 |
};
|
34 |
}
|
|
|
3 |
import { logger } from "$lib/server/logger";
|
4 |
import { env } from "$env/dynamic/private";
|
5 |
import type { Model } from "$lib/types/Model";
|
|
|
6 |
import { onExit } from "./exitHandler";
|
7 |
import { promisify } from "util";
|
8 |
|
|
|
25 |
};
|
26 |
|
27 |
tool: {
|
28 |
+
toolUseCount: Counter<string>;
|
29 |
+
toolUseCountError: Counter<string>;
|
30 |
+
toolUseDuration: Summary<string>;
|
31 |
timeToChooseTools: Summary;
|
32 |
};
|
33 |
}
|
@@ -12,7 +12,7 @@ import type { PreTrainedTokenizer } from "@xenova/transformers";
|
|
12 |
import JSON5 from "json5";
|
13 |
import { getTokenizer } from "$lib/utils/getTokenizer";
|
14 |
import { logger } from "$lib/server/logger";
|
15 |
-
import { ToolResultStatus } from "$lib/types/Tool";
|
16 |
|
17 |
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
18 |
|
@@ -96,14 +96,11 @@ async function getChatPromptRender(
|
|
96 |
|
97 |
const renderTemplate = ({ messages, preprompt, tools, toolResults }: ChatTemplateInput) => {
|
98 |
let formattedMessages: { role: string; content: string }[] = messages.map((message) => ({
|
99 |
-
content:
|
100 |
-
message.files?.length && !tools?.length
|
101 |
-
? message.content + `\n This message has ${message.files.length} files attached`
|
102 |
-
: message.content,
|
103 |
role: message.from,
|
104 |
}));
|
105 |
|
106 |
-
if (preprompt) {
|
107 |
formattedMessages = [
|
108 |
{
|
109 |
role: "system",
|
@@ -195,17 +192,41 @@ async function getChatPromptRender(
|
|
195 |
);
|
196 |
});
|
197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
const output = tokenizer.apply_chat_template(formattedMessages, {
|
199 |
tokenize: false,
|
200 |
add_generation_prompt: true,
|
201 |
chat_template: chatTemplate,
|
202 |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
203 |
// @ts-ignore
|
204 |
-
tools:
|
205 |
-
tools?.map(({ parameterDefinitions, ...tool }) => ({
|
206 |
-
parameter_definitions: parameterDefinitions,
|
207 |
-
...tool,
|
208 |
-
})) ?? [],
|
209 |
documents,
|
210 |
});
|
211 |
|
|
|
12 |
import JSON5 from "json5";
|
13 |
import { getTokenizer } from "$lib/utils/getTokenizer";
|
14 |
import { logger } from "$lib/server/logger";
|
15 |
+
import { ToolResultStatus, type ToolInput } from "$lib/types/Tool";
|
16 |
|
17 |
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
18 |
|
|
|
96 |
|
97 |
const renderTemplate = ({ messages, preprompt, tools, toolResults }: ChatTemplateInput) => {
|
98 |
let formattedMessages: { role: string; content: string }[] = messages.map((message) => ({
|
99 |
+
content: message.content,
|
|
|
|
|
|
|
100 |
role: message.from,
|
101 |
}));
|
102 |
|
103 |
+
if (preprompt && formattedMessages[0].role !== "system") {
|
104 |
formattedMessages = [
|
105 |
{
|
106 |
role: "system",
|
|
|
192 |
);
|
193 |
});
|
194 |
|
195 |
+
const mappedTools =
|
196 |
+
tools?.map((tool) => {
|
197 |
+
const inputs: Record<
|
198 |
+
string,
|
199 |
+
{
|
200 |
+
type: ToolInput["type"];
|
201 |
+
description: string;
|
202 |
+
required: boolean;
|
203 |
+
}
|
204 |
+
> = {};
|
205 |
+
|
206 |
+
for (const value of tool.inputs) {
|
207 |
+
if (value.paramType !== "fixed") {
|
208 |
+
inputs[value.name] = {
|
209 |
+
type: value.type,
|
210 |
+
description: value.description ?? "",
|
211 |
+
required: value.paramType === "required",
|
212 |
+
};
|
213 |
+
}
|
214 |
+
}
|
215 |
+
|
216 |
+
return {
|
217 |
+
name: tool.name,
|
218 |
+
description: tool.description,
|
219 |
+
parameter_definitions: inputs,
|
220 |
+
};
|
221 |
+
}) ?? [];
|
222 |
+
|
223 |
const output = tokenizer.apply_chat_template(formattedMessages, {
|
224 |
tokenize: false,
|
225 |
add_generation_prompt: true,
|
226 |
chat_template: chatTemplate,
|
227 |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
228 |
// @ts-ignore
|
229 |
+
tools: mappedTools,
|
|
|
|
|
|
|
|
|
230 |
documents,
|
231 |
});
|
232 |
|
@@ -8,7 +8,7 @@ import {
|
|
8 |
getAssistantById,
|
9 |
processPreprompt,
|
10 |
} from "./assistant";
|
11 |
-
import {
|
12 |
import type { WebSearch } from "$lib/types/WebSearch";
|
13 |
import {
|
14 |
type MessageUpdate,
|
@@ -20,7 +20,6 @@ import { mergeAsyncGenerators } from "$lib/utils/mergeAsyncGenerators";
|
|
20 |
import type { TextGenerationContext } from "./types";
|
21 |
import type { ToolResult } from "$lib/types/Tool";
|
22 |
import { toolHasName } from "../tools/utils";
|
23 |
-
import directlyAnswer from "../tools/directlyAnswer";
|
24 |
|
25 |
export async function* textGeneration(ctx: TextGenerationContext) {
|
26 |
yield* mergeAsyncGenerators([
|
@@ -63,8 +62,8 @@ async function* textGenerationWithoutTitle(
|
|
63 |
let toolResults: ToolResult[] = [];
|
64 |
|
65 |
if (model.tools && !conv.assistantId) {
|
66 |
-
const tools =
|
67 |
-
const toolCallsRequired = tools.some((tool) => !toolHasName(
|
68 |
if (toolCallsRequired) toolResults = yield* runTools(ctx, tools, preprompt);
|
69 |
}
|
70 |
|
|
|
8 |
getAssistantById,
|
9 |
processPreprompt,
|
10 |
} from "./assistant";
|
11 |
+
import { filterToolsOnPreferences, runTools } from "./tools";
|
12 |
import type { WebSearch } from "$lib/types/WebSearch";
|
13 |
import {
|
14 |
type MessageUpdate,
|
|
|
20 |
import type { TextGenerationContext } from "./types";
|
21 |
import type { ToolResult } from "$lib/types/Tool";
|
22 |
import { toolHasName } from "../tools/utils";
|
|
|
23 |
|
24 |
export async function* textGeneration(ctx: TextGenerationContext) {
|
25 |
yield* mergeAsyncGenerators([
|
|
|
62 |
let toolResults: ToolResult[] = [];
|
63 |
|
64 |
if (model.tools && !conv.assistantId) {
|
65 |
+
const tools = await filterToolsOnPreferences(toolsPreference, Boolean(assistant));
|
66 |
+
const toolCallsRequired = tools.some((tool) => !toolHasName("directly_answer", tool));
|
67 |
if (toolCallsRequired) toolResults = yield* runTools(ctx, tools, preprompt);
|
68 |
}
|
69 |
|
@@ -1,6 +1,6 @@
|
|
1 |
-
import { ToolResultStatus, type ToolCall, type ToolResult } from "$lib/types/Tool";
|
2 |
import { v4 as uuidV4 } from "uuid";
|
3 |
-
import
|
4 |
import {
|
5 |
MessageToolUpdateType,
|
6 |
MessageUpdateStatus,
|
@@ -9,48 +9,44 @@ import {
|
|
9 |
} from "$lib/types/MessageUpdate";
|
10 |
import type { TextGenerationContext } from "./types";
|
11 |
|
12 |
-
import { allTools } from "../tools";
|
13 |
import directlyAnswer from "../tools/directlyAnswer";
|
14 |
import websearch from "../tools/web/search";
|
15 |
import { z } from "zod";
|
16 |
import { logger } from "../logger";
|
17 |
import { extractJson, toolHasName } from "../tools/utils";
|
18 |
-
import type { MessageFile } from "$lib/types/Message";
|
19 |
import { mergeAsyncGenerators } from "$lib/utils/mergeAsyncGenerators";
|
20 |
import { MetricsServer } from "../metrics";
|
21 |
import { stringifyError } from "$lib/utils/stringifyError";
|
|
|
|
|
|
|
22 |
|
23 |
-
function
|
24 |
-
|
25 |
-
return "The user has not uploaded any files. Do not attempt to use any tools that require files";
|
26 |
-
}
|
27 |
-
|
28 |
-
const stringifiedFiles = files
|
29 |
-
.map(
|
30 |
-
(file, fileIndex) =>
|
31 |
-
` - fileMessageIndex ${fileMessageIndex} | fileIndex ${fileIndex} | ${file.name} (${file.mime})`
|
32 |
-
)
|
33 |
-
.join("\n");
|
34 |
-
return `Attached ${files.length} file${files.length === 1 ? "" : "s"}:\n${stringifiedFiles}`;
|
35 |
-
}
|
36 |
-
|
37 |
-
export function pickTools(
|
38 |
-
toolsPreference: Record<string, boolean>,
|
39 |
isAssistant: boolean
|
40 |
-
):
|
41 |
// if it's an assistant, only support websearch for now
|
42 |
if (isAssistant) return [directlyAnswer, websearch];
|
43 |
|
44 |
// filter based on tool preferences, add the tools that are on by default
|
45 |
-
|
46 |
if (el.isLocked && el.isOnByDefault) return true;
|
47 |
-
return toolsPreference?.
|
48 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
}
|
50 |
|
51 |
async function* callTool(
|
52 |
ctx: BackendToolContext,
|
53 |
-
tools:
|
54 |
call: ToolCall
|
55 |
): AsyncGenerator<MessageUpdate, ToolResult | undefined, undefined> {
|
56 |
const uuid = uuidV4();
|
@@ -88,6 +84,8 @@ async function* callTool(
|
|
88 |
Date.now() - startTime
|
89 |
);
|
90 |
|
|
|
|
|
91 |
return { ...toolResult, call } as ToolResult;
|
92 |
} catch (error) {
|
93 |
MetricsServer.getMetrics().tool.toolUseCountError.inc({ tool: call.name });
|
@@ -110,28 +108,75 @@ async function* callTool(
|
|
110 |
|
111 |
export async function* runTools(
|
112 |
ctx: TextGenerationContext,
|
113 |
-
tools:
|
114 |
preprompt?: string
|
115 |
): AsyncGenerator<MessageUpdate, ToolResult[], undefined> {
|
116 |
const { endpoint, conv, messages, assistant, ip, username } = ctx;
|
117 |
const calls: ToolCall[] = [];
|
118 |
|
119 |
-
const
|
120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
return {
|
122 |
...message,
|
123 |
-
content
|
124 |
-
};
|
125 |
});
|
126 |
|
127 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
|
129 |
// do the function calling bits here
|
130 |
for await (const output of await endpoint({
|
131 |
-
messages:
|
132 |
preprompt,
|
133 |
generateSettings: assistant?.generateSettings,
|
134 |
-
tools
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
})) {
|
136 |
// model natively supports tool calls
|
137 |
if (output.token.toolCalls) {
|
@@ -146,7 +191,7 @@ export async function* runTools(
|
|
146 |
const rawCalls = await extractJson(output.generated_text);
|
147 |
const newCalls = rawCalls
|
148 |
.filter(isExternalToolCall)
|
149 |
-
.map(externalToToolCall)
|
150 |
.filter((call) => call !== undefined) as ToolCall[];
|
151 |
|
152 |
calls.push(...newCalls);
|
@@ -185,9 +230,10 @@ function isExternalToolCall(call: unknown): call is ExternalToolCall {
|
|
185 |
return externalToolCall.safeParse(call).success;
|
186 |
}
|
187 |
|
188 |
-
function externalToToolCall(call: ExternalToolCall): ToolCall | undefined {
|
189 |
// Convert - to _ since some models insist on using _ instead of -
|
190 |
-
const tool =
|
|
|
191 |
if (!tool) {
|
192 |
logger.debug(`Model requested tool that does not exist: "${call.tool_name}". Skipping tool...`);
|
193 |
return;
|
@@ -195,23 +241,27 @@ function externalToToolCall(call: ExternalToolCall): ToolCall | undefined {
|
|
195 |
|
196 |
const parametersWithDefaults: Record<string, string> = {};
|
197 |
|
198 |
-
for (const
|
199 |
-
const value = call.parameters[
|
200 |
|
201 |
// Required so ensure it's there, otherwise return undefined
|
202 |
-
if (
|
203 |
if (value === undefined) {
|
204 |
logger.debug(
|
205 |
-
`Model requested tool "${call.tool_name}" but was missing required parameter "${
|
206 |
);
|
207 |
return;
|
208 |
}
|
209 |
-
parametersWithDefaults[
|
210 |
continue;
|
211 |
}
|
212 |
|
213 |
// Optional so use default if not there
|
214 |
-
parametersWithDefaults[
|
|
|
|
|
|
|
|
|
215 |
}
|
216 |
|
217 |
return {
|
|
|
1 |
+
import { ToolResultStatus, type ToolCall, type Tool, type ToolResult } from "$lib/types/Tool";
|
2 |
import { v4 as uuidV4 } from "uuid";
|
3 |
+
import { getCallMethod, toolFromConfigs, type BackendToolContext } from "../tools";
|
4 |
import {
|
5 |
MessageToolUpdateType,
|
6 |
MessageUpdateStatus,
|
|
|
9 |
} from "$lib/types/MessageUpdate";
|
10 |
import type { TextGenerationContext } from "./types";
|
11 |
|
|
|
12 |
import directlyAnswer from "../tools/directlyAnswer";
|
13 |
import websearch from "../tools/web/search";
|
14 |
import { z } from "zod";
|
15 |
import { logger } from "../logger";
|
16 |
import { extractJson, toolHasName } from "../tools/utils";
|
|
|
17 |
import { mergeAsyncGenerators } from "$lib/utils/mergeAsyncGenerators";
|
18 |
import { MetricsServer } from "../metrics";
|
19 |
import { stringifyError } from "$lib/utils/stringifyError";
|
20 |
+
import { collections } from "../database";
|
21 |
+
import { ObjectId } from "mongodb";
|
22 |
+
import type { Message } from "$lib/types/Message";
|
23 |
|
24 |
+
export async function filterToolsOnPreferences(
|
25 |
+
toolsPreference: Array<string>,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
isAssistant: boolean
|
27 |
+
): Promise<Tool[]> {
|
28 |
// if it's an assistant, only support websearch for now
|
29 |
if (isAssistant) return [directlyAnswer, websearch];
|
30 |
|
31 |
// filter based on tool preferences, add the tools that are on by default
|
32 |
+
const activeConfigTools = toolFromConfigs.filter((el) => {
|
33 |
if (el.isLocked && el.isOnByDefault) return true;
|
34 |
+
return toolsPreference?.includes(el._id.toString()) ?? el.isOnByDefault;
|
35 |
});
|
36 |
+
|
37 |
+
// find tool where the id is in toolsPreference
|
38 |
+
const activeCommunityTools = await collections.tools
|
39 |
+
.find({
|
40 |
+
_id: { $in: toolsPreference.map((el) => new ObjectId(el)) },
|
41 |
+
})
|
42 |
+
.toArray()
|
43 |
+
.then((el) => el.map((el) => ({ ...el, call: getCallMethod(el) })));
|
44 |
+
return [...activeConfigTools, ...activeCommunityTools];
|
45 |
}
|
46 |
|
47 |
async function* callTool(
|
48 |
ctx: BackendToolContext,
|
49 |
+
tools: Tool[],
|
50 |
call: ToolCall
|
51 |
): AsyncGenerator<MessageUpdate, ToolResult | undefined, undefined> {
|
52 |
const uuid = uuidV4();
|
|
|
84 |
Date.now() - startTime
|
85 |
);
|
86 |
|
87 |
+
await collections.tools.findOneAndUpdate({ _id: tool._id }, { $inc: { useCount: 1 } });
|
88 |
+
|
89 |
return { ...toolResult, call } as ToolResult;
|
90 |
} catch (error) {
|
91 |
MetricsServer.getMetrics().tool.toolUseCountError.inc({ tool: call.name });
|
|
|
108 |
|
109 |
export async function* runTools(
|
110 |
ctx: TextGenerationContext,
|
111 |
+
tools: Tool[],
|
112 |
preprompt?: string
|
113 |
): AsyncGenerator<MessageUpdate, ToolResult[], undefined> {
|
114 |
const { endpoint, conv, messages, assistant, ip, username } = ctx;
|
115 |
const calls: ToolCall[] = [];
|
116 |
|
117 |
+
const pickToolStartTime = Date.now();
|
118 |
+
// append a message with the list of all available files
|
119 |
+
|
120 |
+
const files = messages.reduce((acc, curr, idx) => {
|
121 |
+
if (curr.files) {
|
122 |
+
const prefix = (curr.from === "user" ? "input" : "ouput") + "_" + idx;
|
123 |
+
acc.push(
|
124 |
+
...curr.files.map(
|
125 |
+
(file, fileIdx) => `${prefix}_${fileIdx}.${file?.name?.split(".")?.pop()?.toLowerCase()}`
|
126 |
+
)
|
127 |
+
);
|
128 |
+
}
|
129 |
+
return acc;
|
130 |
+
}, [] as string[]);
|
131 |
+
|
132 |
+
let formattedMessages = messages.map((message, msgIdx) => {
|
133 |
+
let content = message.content;
|
134 |
+
|
135 |
+
if (message.files && message.files.length > 0) {
|
136 |
+
content +=
|
137 |
+
"\n\nAdded files: \n - " +
|
138 |
+
message.files
|
139 |
+
.map((file, fileIdx) => {
|
140 |
+
const prefix = message.from === "user" ? "input" : "output";
|
141 |
+
const fileName = file.name.split(".").pop()?.toLowerCase();
|
142 |
+
|
143 |
+
return `${prefix}_${msgIdx}_${fileIdx}.${fileName}`;
|
144 |
+
})
|
145 |
+
.join("\n - ");
|
146 |
+
}
|
147 |
+
|
148 |
return {
|
149 |
...message,
|
150 |
+
content,
|
151 |
+
} satisfies Message;
|
152 |
});
|
153 |
|
154 |
+
const fileMsg = {
|
155 |
+
id: crypto.randomUUID(),
|
156 |
+
from: "system",
|
157 |
+
content:
|
158 |
+
"Here is the list of available filenames that can be used as input for tools. Use the filenames that are in this list. \n The filename structure is as follows : {input for user|output for tool}_{message index in the conversation}_{file index in the list of files}.{file extension} \n - " +
|
159 |
+
files.join("\n - ") +
|
160 |
+
"\n\n\n",
|
161 |
+
} satisfies Message;
|
162 |
+
|
163 |
+
// put fileMsg before last if files.length > 0
|
164 |
+
formattedMessages = files.length
|
165 |
+
? [...formattedMessages.slice(0, -1), fileMsg, ...formattedMessages.slice(-1)]
|
166 |
+
: messages;
|
167 |
|
168 |
// do the function calling bits here
|
169 |
for await (const output of await endpoint({
|
170 |
+
messages: formattedMessages,
|
171 |
preprompt,
|
172 |
generateSettings: assistant?.generateSettings,
|
173 |
+
tools: tools.map((tool) => ({
|
174 |
+
...tool,
|
175 |
+
inputs: tool.inputs.map((input) => ({
|
176 |
+
...input,
|
177 |
+
type: input.type === "file" ? "str" : input.type,
|
178 |
+
})),
|
179 |
+
})),
|
180 |
})) {
|
181 |
// model natively supports tool calls
|
182 |
if (output.token.toolCalls) {
|
|
|
191 |
const rawCalls = await extractJson(output.generated_text);
|
192 |
const newCalls = rawCalls
|
193 |
.filter(isExternalToolCall)
|
194 |
+
.map((call) => externalToToolCall(call, tools))
|
195 |
.filter((call) => call !== undefined) as ToolCall[];
|
196 |
|
197 |
calls.push(...newCalls);
|
|
|
230 |
return externalToolCall.safeParse(call).success;
|
231 |
}
|
232 |
|
233 |
+
function externalToToolCall(call: ExternalToolCall, tools: Tool[]): ToolCall | undefined {
|
234 |
// Convert - to _ since some models insist on using _ instead of -
|
235 |
+
const tool = tools.find((tool) => toolHasName(call.tool_name, tool));
|
236 |
+
|
237 |
if (!tool) {
|
238 |
logger.debug(`Model requested tool that does not exist: "${call.tool_name}". Skipping tool...`);
|
239 |
return;
|
|
|
241 |
|
242 |
const parametersWithDefaults: Record<string, string> = {};
|
243 |
|
244 |
+
for (const input of tool.inputs) {
|
245 |
+
const value = call.parameters[input.name];
|
246 |
|
247 |
// Required so ensure it's there, otherwise return undefined
|
248 |
+
if (input.paramType === "required") {
|
249 |
if (value === undefined) {
|
250 |
logger.debug(
|
251 |
+
`Model requested tool "${call.tool_name}" but was missing required parameter "${input.name}". Skipping tool...`
|
252 |
);
|
253 |
return;
|
254 |
}
|
255 |
+
parametersWithDefaults[input.name] = value;
|
256 |
continue;
|
257 |
}
|
258 |
|
259 |
// Optional so use default if not there
|
260 |
+
parametersWithDefaults[input.name] = value;
|
261 |
+
|
262 |
+
if (input.paramType === "optional") {
|
263 |
+
parametersWithDefaults[input.name] ??= input.default.toString();
|
264 |
+
}
|
265 |
}
|
266 |
|
267 |
return {
|
@@ -12,7 +12,7 @@ export interface TextGenerationContext {
|
|
12 |
assistant?: Pick<Assistant, "rag" | "dynamicPrompt" | "generateSettings">;
|
13 |
isContinue: boolean;
|
14 |
webSearch: boolean;
|
15 |
-
toolsPreference:
|
16 |
promptedAt: Date;
|
17 |
ip: string;
|
18 |
username?: string;
|
|
|
12 |
assistant?: Pick<Assistant, "rag" | "dynamicPrompt" | "generateSettings">;
|
13 |
isContinue: boolean;
|
14 |
webSearch: boolean;
|
15 |
+
toolsPreference: Array<string>;
|
16 |
promptedAt: Date;
|
17 |
ip: string;
|
18 |
username?: string;
|
@@ -1,29 +1,38 @@
|
|
1 |
-
import type {
|
|
|
2 |
import vm from "node:vm";
|
3 |
|
4 |
-
const calculator:
|
5 |
-
|
|
|
|
|
|
|
|
|
6 |
displayName: "Calculator",
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
11 |
description:
|
12 |
-
"
|
13 |
-
|
14 |
-
required: true,
|
15 |
},
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
18 |
try {
|
19 |
-
const blocks = String(
|
20 |
const query = blocks[blocks.length - 1].replace(/[^-()\d/*+.]/g, "");
|
21 |
|
22 |
return {
|
23 |
outputs: [{ calculator: `${query} = ${vm.runInNewContext(query)}` }],
|
24 |
};
|
25 |
} catch (cause) {
|
26 |
-
throw Error("Invalid expression", { cause });
|
27 |
}
|
28 |
},
|
29 |
};
|
|
|
1 |
+
import type { ConfigTool } from "$lib/types/Tool";
|
2 |
+
import { ObjectId } from "mongodb";
|
3 |
import vm from "node:vm";
|
4 |
|
5 |
+
const calculator: ConfigTool = {
|
6 |
+
_id: new ObjectId("00000000000000000000000C"),
|
7 |
+
type: "config",
|
8 |
+
description: "Calculate the result of a mathematical expression",
|
9 |
+
color: "blue",
|
10 |
+
icon: "code",
|
11 |
displayName: "Calculator",
|
12 |
+
name: "calculator",
|
13 |
+
endpoint: null,
|
14 |
+
inputs: [
|
15 |
+
{
|
16 |
+
name: "equation",
|
17 |
+
type: "str",
|
18 |
description:
|
19 |
+
"A mathematical expression to be evaluated. The result of the expression will be returned.",
|
20 |
+
paramType: "required",
|
|
|
21 |
},
|
22 |
+
],
|
23 |
+
outputComponent: null,
|
24 |
+
outputComponentIdx: null,
|
25 |
+
showOutput: false,
|
26 |
+
async *call({ equation }) {
|
27 |
try {
|
28 |
+
const blocks = String(equation).split("\n");
|
29 |
const query = blocks[blocks.length - 1].replace(/[^-()\d/*+.]/g, "");
|
30 |
|
31 |
return {
|
32 |
outputs: [{ calculator: `${query} = ${vm.runInNewContext(query)}` }],
|
33 |
};
|
34 |
} catch (cause) {
|
35 |
+
throw new Error("Invalid expression", { cause });
|
36 |
}
|
37 |
},
|
38 |
};
|
@@ -1,12 +1,22 @@
|
|
1 |
-
import type {
|
|
|
2 |
|
3 |
-
const directlyAnswer:
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
5 |
isOnByDefault: true,
|
6 |
-
isHidden: true,
|
7 |
isLocked: true,
|
8 |
-
|
9 |
-
|
|
|
|
|
|
|
|
|
|
|
10 |
async *call() {
|
11 |
return {
|
12 |
outputs: [],
|
|
|
1 |
+
import type { ConfigTool } from "$lib/types/Tool";
|
2 |
+
import { ObjectId } from "mongodb";
|
3 |
|
4 |
+
const directlyAnswer: ConfigTool = {
|
5 |
+
_id: new ObjectId("00000000000000000000000D"),
|
6 |
+
type: "config",
|
7 |
+
description: "Answer the user's query directly",
|
8 |
+
color: "blue",
|
9 |
+
icon: "chat",
|
10 |
+
displayName: "Directly Answer",
|
11 |
isOnByDefault: true,
|
|
|
12 |
isLocked: true,
|
13 |
+
isHidden: true,
|
14 |
+
name: "directlyAnswer",
|
15 |
+
endpoint: null,
|
16 |
+
inputs: [],
|
17 |
+
outputComponent: null,
|
18 |
+
outputComponentIdx: null,
|
19 |
+
showOutput: false,
|
20 |
async *call() {
|
21 |
return {
|
22 |
outputs: [],
|
@@ -1,60 +0,0 @@
|
|
1 |
-
import type { BackendTool } from ".";
|
2 |
-
import { callSpace, getIpToken } from "./utils";
|
3 |
-
import { downloadFile } from "$lib/server/files/downloadFile";
|
4 |
-
|
5 |
-
type PdfParserInput = [Blob /* pdf */, string /* filename */];
|
6 |
-
type PdfParserOutput = [string /* markdown */, Record<string, unknown> /* metadata */];
|
7 |
-
|
8 |
-
const documentParser: BackendTool = {
|
9 |
-
name: "document_parser",
|
10 |
-
displayName: "Document Parser",
|
11 |
-
description: "Use this tool to parse any document and get its content in markdown format.",
|
12 |
-
mimeTypes: ["application/*", "text/*"],
|
13 |
-
parameterDefinitions: {
|
14 |
-
fileMessageIndex: {
|
15 |
-
description: "Index of the message containing the document file to parse",
|
16 |
-
type: "number",
|
17 |
-
required: true,
|
18 |
-
},
|
19 |
-
fileIndex: {
|
20 |
-
description: "Index of the document file to parse",
|
21 |
-
type: "number",
|
22 |
-
required: true,
|
23 |
-
},
|
24 |
-
},
|
25 |
-
async *call({ fileMessageIndex, fileIndex }, { conv, messages, ip, username }) {
|
26 |
-
fileMessageIndex = Number(fileMessageIndex);
|
27 |
-
fileIndex = Number(fileIndex);
|
28 |
-
|
29 |
-
const message = messages[fileMessageIndex];
|
30 |
-
const files = message?.files ?? [];
|
31 |
-
if (!files || files.length === 0) throw Error("User did not provide a pdf to parse");
|
32 |
-
if (fileIndex >= files.length) throw Error("Model provided an invalid file index");
|
33 |
-
|
34 |
-
const file = files[fileIndex];
|
35 |
-
const fileBlob = await downloadFile(files[fileIndex].value, conv._id)
|
36 |
-
.then((file) => fetch(`data:${file.mime};base64,${file.value}`))
|
37 |
-
.then((res) => res.blob());
|
38 |
-
|
39 |
-
const ipToken = await getIpToken(ip, username);
|
40 |
-
|
41 |
-
const outputs = await callSpace<PdfParserInput, PdfParserOutput>(
|
42 |
-
"huggingchat/document-parser",
|
43 |
-
"predict",
|
44 |
-
[fileBlob, file.name],
|
45 |
-
ipToken
|
46 |
-
);
|
47 |
-
|
48 |
-
let documentMarkdown = outputs[0];
|
49 |
-
// TODO: quick fix for avoiding context limit. eventually should use the tokenizer
|
50 |
-
if (documentMarkdown.length > 30_000) {
|
51 |
-
documentMarkdown = documentMarkdown.slice(0, 30_000) + "\n\n... (truncated)";
|
52 |
-
}
|
53 |
-
return {
|
54 |
-
outputs: [{ [file.name]: documentMarkdown }],
|
55 |
-
display: false,
|
56 |
-
};
|
57 |
-
},
|
58 |
-
};
|
59 |
-
|
60 |
-
export default documentParser;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,95 +0,0 @@
|
|
1 |
-
import type { BackendTool } from "..";
|
2 |
-
import { uploadFile } from "../../files/uploadFile";
|
3 |
-
import { MessageUpdateType } from "$lib/types/MessageUpdate";
|
4 |
-
import { callSpace, getIpToken, type GradioImage } from "../utils";
|
5 |
-
import { downloadFile } from "$lib/server/files/downloadFile";
|
6 |
-
|
7 |
-
type ImageEditingInput = [
|
8 |
-
Blob /* image */,
|
9 |
-
string /* prompt */,
|
10 |
-
string /* negative prompt */,
|
11 |
-
number /* guidance scale */,
|
12 |
-
number /* steps */
|
13 |
-
];
|
14 |
-
type ImageEditingOutput = [GradioImage];
|
15 |
-
|
16 |
-
const imageEditing: BackendTool = {
|
17 |
-
name: "image_editing",
|
18 |
-
displayName: "Image Editing",
|
19 |
-
description: "Use this tool to edit an image from a prompt.",
|
20 |
-
mimeTypes: ["image/*"],
|
21 |
-
parameterDefinitions: {
|
22 |
-
prompt: {
|
23 |
-
description:
|
24 |
-
"A prompt to generate an image from. Describe the image visually in simple terms, separate terms with a comma.",
|
25 |
-
type: "string",
|
26 |
-
required: true,
|
27 |
-
},
|
28 |
-
fileMessageIndex: {
|
29 |
-
description: "Index of the message containing the file to edit",
|
30 |
-
type: "number",
|
31 |
-
required: true,
|
32 |
-
},
|
33 |
-
fileIndex: {
|
34 |
-
description: "Index of the file to edit",
|
35 |
-
type: "number",
|
36 |
-
required: true,
|
37 |
-
},
|
38 |
-
},
|
39 |
-
async *call({ prompt, fileMessageIndex, fileIndex }, { conv, messages, ip, username }) {
|
40 |
-
prompt = String(prompt);
|
41 |
-
fileMessageIndex = Number(fileMessageIndex);
|
42 |
-
fileIndex = Number(fileIndex);
|
43 |
-
|
44 |
-
const message = messages[fileMessageIndex];
|
45 |
-
const images = message?.files ?? [];
|
46 |
-
if (!images || images.length === 0) throw Error("User did not provide an image to edit");
|
47 |
-
if (fileIndex >= images.length) throw Error("Model provided an invalid file index");
|
48 |
-
if (!images[fileIndex].mime.startsWith("image/")) {
|
49 |
-
throw Error("Model provided a file idex which is not an image");
|
50 |
-
}
|
51 |
-
|
52 |
-
// todo: should handle multiple images
|
53 |
-
const image = await downloadFile(images[fileIndex].value, conv._id)
|
54 |
-
.then((file) => fetch(`data:${file.mime};base64,${file.value}`))
|
55 |
-
.then((res) => res.blob());
|
56 |
-
|
57 |
-
const ipToken = await getIpToken(ip, username);
|
58 |
-
|
59 |
-
const outputs = await callSpace<ImageEditingInput, ImageEditingOutput>(
|
60 |
-
"multimodalart/cosxl",
|
61 |
-
"run_edit",
|
62 |
-
[
|
63 |
-
image,
|
64 |
-
prompt,
|
65 |
-
"", // negative prompt
|
66 |
-
7, // guidance scale
|
67 |
-
20, // steps
|
68 |
-
],
|
69 |
-
ipToken
|
70 |
-
);
|
71 |
-
|
72 |
-
const outputImage = await fetch(outputs[0].url)
|
73 |
-
.then((res) => res.blob())
|
74 |
-
.then((blob) => new File([blob], outputs[0].orig_name, { type: blob.type }))
|
75 |
-
.then((file) => uploadFile(file, conv));
|
76 |
-
|
77 |
-
yield {
|
78 |
-
type: MessageUpdateType.File,
|
79 |
-
name: outputImage.name,
|
80 |
-
sha: outputImage.value,
|
81 |
-
mime: outputImage.mime,
|
82 |
-
};
|
83 |
-
|
84 |
-
return {
|
85 |
-
outputs: [
|
86 |
-
{
|
87 |
-
imageEditing: `An image has been generated for the following prompt: "${prompt}". Answer as if the user can already see the image. Do not try to insert the image or to add space for it. The user can already see the image. Do not try to describe the image as you the model cannot see it. Be concise.`,
|
88 |
-
},
|
89 |
-
],
|
90 |
-
display: false,
|
91 |
-
};
|
92 |
-
},
|
93 |
-
};
|
94 |
-
|
95 |
-
export default imageEditing;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,84 +0,0 @@
|
|
1 |
-
import type { BackendTool } from "..";
|
2 |
-
import { uploadFile } from "../../files/uploadFile";
|
3 |
-
import { MessageUpdateType } from "$lib/types/MessageUpdate";
|
4 |
-
import { callSpace, getIpToken, type GradioImage } from "../utils";
|
5 |
-
|
6 |
-
type ImageGenerationInput = [string, string, number, boolean, number, number, number, number];
|
7 |
-
type ImageGenerationOutput = [GradioImage, unknown];
|
8 |
-
|
9 |
-
const imageGeneration: BackendTool = {
|
10 |
-
name: "image_generation",
|
11 |
-
displayName: "Image Generation",
|
12 |
-
description: "Use this tool to generate an image from a prompt.",
|
13 |
-
parameterDefinitions: {
|
14 |
-
prompt: {
|
15 |
-
description:
|
16 |
-
"A prompt to generate an image from. Describe the image visually in simple terms, separate terms with a comma.",
|
17 |
-
type: "string",
|
18 |
-
required: true,
|
19 |
-
},
|
20 |
-
negativePrompt: {
|
21 |
-
description:
|
22 |
-
"A prompt for things that should not be in the image. Simple terms, separate terms with a comma.",
|
23 |
-
type: "string",
|
24 |
-
required: false,
|
25 |
-
default: "",
|
26 |
-
},
|
27 |
-
width: {
|
28 |
-
description: "Width of the generated image.",
|
29 |
-
type: "number",
|
30 |
-
required: false,
|
31 |
-
default: 1024,
|
32 |
-
},
|
33 |
-
height: {
|
34 |
-
description: "Height of the generated image.",
|
35 |
-
type: "number",
|
36 |
-
required: false,
|
37 |
-
default: 1024,
|
38 |
-
},
|
39 |
-
},
|
40 |
-
async *call({ prompt, negativePrompt, width, height }, { conv, ip, username }) {
|
41 |
-
const ipToken = await getIpToken(ip, username);
|
42 |
-
|
43 |
-
const outputs = await callSpace<ImageGenerationInput, ImageGenerationOutput>(
|
44 |
-
"stabilityai/stable-diffusion-3-medium",
|
45 |
-
"/infer",
|
46 |
-
[
|
47 |
-
String(prompt), // prompt
|
48 |
-
String(negativePrompt), // negative prompt
|
49 |
-
Math.floor(Math.random() * 1000), // seed random
|
50 |
-
true, // randomize seed
|
51 |
-
Number(width), // number in 'Image Width' Number component
|
52 |
-
Number(height), // number in 'Image Height' Number component
|
53 |
-
5, // guidance scale
|
54 |
-
28, // steps
|
55 |
-
],
|
56 |
-
ipToken
|
57 |
-
);
|
58 |
-
const image = await fetch(outputs[0].url)
|
59 |
-
.then((res) => res.blob())
|
60 |
-
.then(
|
61 |
-
(blob) =>
|
62 |
-
new File([blob], `${prompt}.${blob.type.split("/")[1] ?? "png"}`, { type: blob.type })
|
63 |
-
)
|
64 |
-
.then((file) => uploadFile(file, conv));
|
65 |
-
|
66 |
-
yield {
|
67 |
-
type: MessageUpdateType.File,
|
68 |
-
name: image.name,
|
69 |
-
sha: image.value,
|
70 |
-
mime: image.mime,
|
71 |
-
};
|
72 |
-
|
73 |
-
return {
|
74 |
-
outputs: [
|
75 |
-
{
|
76 |
-
imageGeneration: `An image has been generated for the following prompt: "${prompt}". Answer as if the user can already see the image. Do not try to insert the image or to add space for it. The user can already see the image. Do not try to describe the image as you the model cannot see it. Be concise.`,
|
77 |
-
},
|
78 |
-
],
|
79 |
-
display: false,
|
80 |
-
};
|
81 |
-
},
|
82 |
-
};
|
83 |
-
|
84 |
-
export default imageGeneration;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,33 +1,294 @@
|
|
1 |
-
import
|
2 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
|
|
4 |
import calculator from "./calculator";
|
5 |
import directlyAnswer from "./directlyAnswer";
|
6 |
-
import imageEditing from "./images/editing";
|
7 |
-
import imageGeneration from "./images/generation";
|
8 |
-
import documentParser from "./documentParser";
|
9 |
import fetchUrl from "./web/url";
|
10 |
import websearch from "./web/search";
|
11 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
export type BackendToolContext = Pick<
|
14 |
TextGenerationContext,
|
15 |
"conv" | "messages" | "assistant" | "ip" | "username"
|
16 |
> & { preprompt?: string };
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
}
|
24 |
|
25 |
-
export const
|
26 |
-
directlyAnswer,
|
27 |
-
websearch,
|
28 |
-
imageGeneration,
|
29 |
-
fetchUrl,
|
30 |
-
imageEditing,
|
31 |
-
documentParser,
|
32 |
-
calculator,
|
33 |
-
];
|
|
|
1 |
+
import { MessageUpdateType } from "$lib/types/MessageUpdate";
|
2 |
+
import {
|
3 |
+
ToolColor,
|
4 |
+
ToolIcon,
|
5 |
+
ToolOutputComponents,
|
6 |
+
type BackendCall,
|
7 |
+
type BaseTool,
|
8 |
+
type ConfigTool,
|
9 |
+
type ToolInput,
|
10 |
+
} from "$lib/types/Tool";
|
11 |
+
import type { TextGenerationContext } from "../textGeneration/types";
|
12 |
+
|
13 |
+
import { z } from "zod";
|
14 |
+
import JSON5 from "json5";
|
15 |
+
import { env } from "$env/dynamic/private";
|
16 |
|
17 |
+
import jp from "jsonpath";
|
18 |
import calculator from "./calculator";
|
19 |
import directlyAnswer from "./directlyAnswer";
|
|
|
|
|
|
|
20 |
import fetchUrl from "./web/url";
|
21 |
import websearch from "./web/search";
|
22 |
+
import { callSpace, getIpToken } from "./utils";
|
23 |
+
import { uploadFile } from "../files/uploadFile";
|
24 |
+
import type { MessageFile } from "$lib/types/Message";
|
25 |
+
import { sha256 } from "$lib/utils/sha256";
|
26 |
+
import { ObjectId } from "mongodb";
|
27 |
+
import { isValidOutputComponent, ToolOutputPaths } from "./outputs";
|
28 |
+
import { downloadFile } from "../files/downloadFile";
|
29 |
|
30 |
export type BackendToolContext = Pick<
|
31 |
TextGenerationContext,
|
32 |
"conv" | "messages" | "assistant" | "ip" | "username"
|
33 |
> & { preprompt?: string };
|
34 |
|
35 |
+
const IOType = z.union([z.literal("str"), z.literal("int"), z.literal("float"), z.literal("bool")]);
|
36 |
+
|
37 |
+
const toolInputBaseSchema = z.union([
|
38 |
+
z.object({
|
39 |
+
name: z.string().min(1).max(40),
|
40 |
+
description: z.string().max(100).optional(),
|
41 |
+
paramType: z.literal("required"),
|
42 |
+
}),
|
43 |
+
z.object({
|
44 |
+
name: z.string().min(1).max(40),
|
45 |
+
description: z.string().max(100).optional(),
|
46 |
+
paramType: z.literal("optional"),
|
47 |
+
default: z
|
48 |
+
.union([z.string().max(40), z.number(), z.boolean(), z.undefined()])
|
49 |
+
.transform((val) => (val === undefined ? "" : val)),
|
50 |
+
}),
|
51 |
+
z.object({
|
52 |
+
name: z.string().min(1).max(40),
|
53 |
+
paramType: z.literal("fixed"),
|
54 |
+
value: z
|
55 |
+
.union([z.string().max(40), z.number(), z.boolean(), z.undefined()])
|
56 |
+
.transform((val) => (val === undefined ? "" : val)),
|
57 |
+
}),
|
58 |
+
]);
|
59 |
+
|
60 |
+
const toolInputSchema = toolInputBaseSchema.and(
|
61 |
+
z.object({ type: IOType }).or(
|
62 |
+
z.object({
|
63 |
+
type: z.literal("file"),
|
64 |
+
mimeTypes: z.string().nonempty(),
|
65 |
+
})
|
66 |
+
)
|
67 |
+
);
|
68 |
+
|
69 |
+
export const editableToolSchema = z
|
70 |
+
.object({
|
71 |
+
name: z.string().min(1).max(40),
|
72 |
+
baseUrl: z.string().min(1).max(100),
|
73 |
+
endpoint: z.string().min(1).max(100),
|
74 |
+
inputs: z.array(toolInputSchema),
|
75 |
+
outputComponent: z.string().min(1).max(100),
|
76 |
+
showOutput: z.boolean(),
|
77 |
+
displayName: z.string().min(1).max(40),
|
78 |
+
color: ToolColor,
|
79 |
+
icon: ToolIcon,
|
80 |
+
description: z.string().min(1).max(100),
|
81 |
+
})
|
82 |
+
.transform((tool) => ({
|
83 |
+
...tool,
|
84 |
+
outputComponentIdx: parseInt(tool.outputComponent.split(";")[0]),
|
85 |
+
outputComponent: ToolOutputComponents.parse(tool.outputComponent.split(";")[1]),
|
86 |
+
}));
|
87 |
+
|
88 |
+
export const configTools = z
|
89 |
+
.array(
|
90 |
+
z
|
91 |
+
.object({
|
92 |
+
name: z.string(),
|
93 |
+
description: z.string(),
|
94 |
+
endpoint: z.union([z.string(), z.null()]),
|
95 |
+
inputs: z.array(toolInputSchema),
|
96 |
+
outputComponent: ToolOutputComponents.or(z.null()),
|
97 |
+
outputComponentIdx: z.number().int().default(0),
|
98 |
+
showOutput: z.boolean(),
|
99 |
+
_id: z
|
100 |
+
.string()
|
101 |
+
.length(24)
|
102 |
+
.regex(/^[0-9a-fA-F]{24}$/)
|
103 |
+
.transform((val) => new ObjectId(val)),
|
104 |
+
baseUrl: z.string().optional(),
|
105 |
+
displayName: z.string(),
|
106 |
+
color: ToolColor,
|
107 |
+
icon: ToolIcon,
|
108 |
+
isOnByDefault: z.optional(z.literal(true)),
|
109 |
+
isLocked: z.optional(z.literal(true)),
|
110 |
+
isHidden: z.optional(z.literal(true)),
|
111 |
+
})
|
112 |
+
.transform((val) => ({
|
113 |
+
type: "config" as const,
|
114 |
+
...val,
|
115 |
+
call: getCallMethod(val),
|
116 |
+
}))
|
117 |
+
)
|
118 |
+
// add the extra hardcoded tools
|
119 |
+
.transform((val) => [...val, calculator, directlyAnswer, fetchUrl, websearch]);
|
120 |
+
|
121 |
+
export function getCallMethod(tool: Omit<BaseTool, "call">): BackendCall {
|
122 |
+
return async function* (params, ctx) {
|
123 |
+
if (
|
124 |
+
tool.endpoint === null ||
|
125 |
+
!tool.baseUrl ||
|
126 |
+
!tool.outputComponent ||
|
127 |
+
tool.outputComponentIdx === null
|
128 |
+
) {
|
129 |
+
throw new Error(`Tool function ${tool.name} has no endpoint`);
|
130 |
+
}
|
131 |
+
|
132 |
+
const ipToken = await getIpToken(ctx.ip, ctx.username);
|
133 |
+
|
134 |
+
function coerceInput(value: unknown, type: ToolInput["type"]) {
|
135 |
+
const valueStr = String(value);
|
136 |
+
switch (type) {
|
137 |
+
case "str":
|
138 |
+
return valueStr;
|
139 |
+
case "int":
|
140 |
+
return parseInt(valueStr);
|
141 |
+
case "float":
|
142 |
+
return parseFloat(valueStr);
|
143 |
+
case "bool":
|
144 |
+
return valueStr === "true";
|
145 |
+
default:
|
146 |
+
throw new Error(`Unsupported type ${type}`);
|
147 |
+
}
|
148 |
+
}
|
149 |
+
const inputs = tool.inputs.map(async (input) => {
|
150 |
+
if (input.type === "file" && input.paramType !== "required") {
|
151 |
+
throw new Error("File inputs are always required and cannot be optional or fixed");
|
152 |
+
}
|
153 |
+
|
154 |
+
if (input.paramType === "fixed") {
|
155 |
+
return coerceInput(input.value, input.type);
|
156 |
+
} else if (input.paramType === "optional") {
|
157 |
+
return coerceInput(params[input.name] ?? input.default, input.type);
|
158 |
+
} else if (input.paramType === "required") {
|
159 |
+
if (params[input.name] === undefined) {
|
160 |
+
throw new Error(`Missing required input ${input.name}`);
|
161 |
+
}
|
162 |
+
|
163 |
+
if (input.type === "file") {
|
164 |
+
// todo: parse file here !
|
165 |
+
// structure is {input|output}-{msgIdx}-{fileIdx}-{filename}
|
166 |
+
|
167 |
+
const filename = params[input.name];
|
168 |
+
|
169 |
+
if (!filename || typeof filename !== "string") {
|
170 |
+
throw new Error(`Filename is not a string`);
|
171 |
+
}
|
172 |
+
|
173 |
+
const messages = ctx.messages;
|
174 |
+
|
175 |
+
const msgIdx = parseInt(filename.split("_")[1]);
|
176 |
+
const fileIdx = parseInt(filename.split("_")[2]);
|
177 |
+
|
178 |
+
if (Number.isNaN(msgIdx) || Number.isNaN(fileIdx)) {
|
179 |
+
throw Error(`Message index or file index is missing`);
|
180 |
+
}
|
181 |
+
|
182 |
+
if (msgIdx >= messages.length) {
|
183 |
+
throw Error(`Message index ${msgIdx} is out of bounds`);
|
184 |
+
}
|
185 |
+
|
186 |
+
const file = messages[msgIdx].files?.[fileIdx];
|
187 |
+
|
188 |
+
if (!file) {
|
189 |
+
throw Error(`File index ${fileIdx} is out of bounds`);
|
190 |
+
}
|
191 |
+
|
192 |
+
const blob = await downloadFile(file.value, ctx.conv._id)
|
193 |
+
.then((file) => fetch(`data:${file.mime};base64,${file.value}`))
|
194 |
+
.then((res) => res.blob())
|
195 |
+
.catch((err) => {
|
196 |
+
throw Error("Failed to download file", { cause: err });
|
197 |
+
});
|
198 |
+
|
199 |
+
return blob;
|
200 |
+
} else {
|
201 |
+
return coerceInput(params[input.name], input.type);
|
202 |
+
}
|
203 |
+
}
|
204 |
+
});
|
205 |
+
|
206 |
+
const outputs = await callSpace(
|
207 |
+
tool.baseUrl,
|
208 |
+
tool.endpoint,
|
209 |
+
await Promise.all(inputs),
|
210 |
+
ipToken
|
211 |
+
);
|
212 |
+
|
213 |
+
if (!isValidOutputComponent(tool.outputComponent)) {
|
214 |
+
throw new Error(`Tool output component is not defined`);
|
215 |
+
}
|
216 |
+
|
217 |
+
const { type, path } = ToolOutputPaths[tool.outputComponent];
|
218 |
+
|
219 |
+
if (!path || !type) {
|
220 |
+
throw new Error(`Tool output type ${tool.outputComponent} is not supported`);
|
221 |
+
}
|
222 |
+
|
223 |
+
const files: MessageFile[] = [];
|
224 |
+
|
225 |
+
const toolOutputs: Array<Record<string, string>> = [];
|
226 |
+
|
227 |
+
if (outputs.length <= tool.outputComponentIdx) {
|
228 |
+
throw new Error(`Tool output component index is out of bounds`);
|
229 |
+
}
|
230 |
+
|
231 |
+
// if its not an object, return directly
|
232 |
+
if (
|
233 |
+
outputs[tool.outputComponentIdx] !== undefined &&
|
234 |
+
typeof outputs[tool.outputComponentIdx] !== "object"
|
235 |
+
) {
|
236 |
+
return { outputs: [{ [tool.name + "-0"]: outputs[tool.outputComponentIdx] }] };
|
237 |
+
}
|
238 |
+
|
239 |
+
await Promise.all(
|
240 |
+
jp
|
241 |
+
.query(outputs[tool.outputComponentIdx], path)
|
242 |
+
.map(async (output: string | string[], idx) => {
|
243 |
+
const arrayedOutput = Array.isArray(output) ? output : [output];
|
244 |
+
if (type === "file") {
|
245 |
+
// output files are actually URLs
|
246 |
+
|
247 |
+
await Promise.all(
|
248 |
+
arrayedOutput.map(async (output, idx) => {
|
249 |
+
await fetch(output)
|
250 |
+
.then((res) => res.blob())
|
251 |
+
.then(async (blob) => {
|
252 |
+
const mimeType = blob.type;
|
253 |
+
const fileType = blob.type.split("/")[1] ?? mimeType?.split("/")[1];
|
254 |
+
return new File(
|
255 |
+
[blob],
|
256 |
+
`${idx}-${await sha256(JSON.stringify(params))}.${fileType}`,
|
257 |
+
{
|
258 |
+
type: fileType,
|
259 |
+
}
|
260 |
+
);
|
261 |
+
})
|
262 |
+
.then((file) => uploadFile(file, ctx.conv))
|
263 |
+
.then((file) => files.push(file));
|
264 |
+
})
|
265 |
+
);
|
266 |
+
|
267 |
+
toolOutputs.push({
|
268 |
+
[tool.name + "-" + idx.toString()]:
|
269 |
+
"A file has been generated. Answer as if the user can already see the file. Do not try to insert the file. The user can already see the file. Do not try to describe the file as the model cannot interact with it. Be concise.",
|
270 |
+
});
|
271 |
+
} else {
|
272 |
+
for (const output of arrayedOutput) {
|
273 |
+
toolOutputs.push({
|
274 |
+
[tool.name + "-" + idx.toString()]: output,
|
275 |
+
});
|
276 |
+
}
|
277 |
+
}
|
278 |
+
})
|
279 |
+
);
|
280 |
+
|
281 |
+
for (const file of files) {
|
282 |
+
yield {
|
283 |
+
type: MessageUpdateType.File,
|
284 |
+
name: file.name,
|
285 |
+
sha: file.value,
|
286 |
+
mime: file.mime,
|
287 |
+
};
|
288 |
+
}
|
289 |
+
|
290 |
+
return { outputs: toolOutputs, display: tool.showOutput };
|
291 |
+
};
|
292 |
}
|
293 |
|
294 |
+
export const toolFromConfigs = configTools.parse(JSON5.parse(env.TOOLS)) satisfies ConfigTool[];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { ToolIOType, ToolOutputComponents } from "$lib/types/Tool";
|
2 |
+
|
3 |
+
export const ToolOutputPaths: Record<
|
4 |
+
ToolOutputComponents,
|
5 |
+
{
|
6 |
+
type: ToolIOType;
|
7 |
+
path: string;
|
8 |
+
}
|
9 |
+
> = {
|
10 |
+
textbox: {
|
11 |
+
type: "str",
|
12 |
+
path: "$",
|
13 |
+
},
|
14 |
+
markdown: {
|
15 |
+
type: "str",
|
16 |
+
path: "$",
|
17 |
+
},
|
18 |
+
image: {
|
19 |
+
type: "file",
|
20 |
+
path: "$.url",
|
21 |
+
},
|
22 |
+
gallery: {
|
23 |
+
type: "file",
|
24 |
+
path: "$[*].image.url",
|
25 |
+
},
|
26 |
+
};
|
27 |
+
|
28 |
+
export const isValidOutputComponent = (
|
29 |
+
outputComponent: string
|
30 |
+
): outputComponent is keyof typeof ToolOutputPaths => {
|
31 |
+
return Object.keys(ToolOutputPaths).includes(outputComponent);
|
32 |
+
};
|
@@ -1,6 +1,7 @@
|
|
1 |
import { env } from "$env/dynamic/private";
|
2 |
import { Client } from "@gradio/client";
|
3 |
import { SignJWT } from "jose";
|
|
|
4 |
import JSON5 from "json5";
|
5 |
|
6 |
export type GradioImage = {
|
@@ -31,13 +32,17 @@ export async function callSpace<TInput extends unknown[], TOutput extends unknow
|
|
31 |
return super.fetch(input, init);
|
32 |
}
|
33 |
}
|
34 |
-
|
35 |
const client = await CustomClient.connect(name, {
|
36 |
hf_token: (env.HF_TOKEN ?? env.HF_ACCESS_TOKEN) as unknown as `hf_${string}`,
|
37 |
});
|
|
|
38 |
return await client
|
39 |
.predict(func, parameters)
|
40 |
-
.then((res) => (res as unknown as GradioResponse).data as TOutput)
|
|
|
|
|
|
|
|
|
41 |
}
|
42 |
|
43 |
export async function getIpToken(ip: string, username?: string) {
|
|
|
1 |
import { env } from "$env/dynamic/private";
|
2 |
import { Client } from "@gradio/client";
|
3 |
import { SignJWT } from "jose";
|
4 |
+
import { logger } from "../logger";
|
5 |
import JSON5 from "json5";
|
6 |
|
7 |
export type GradioImage = {
|
|
|
32 |
return super.fetch(input, init);
|
33 |
}
|
34 |
}
|
|
|
35 |
const client = await CustomClient.connect(name, {
|
36 |
hf_token: (env.HF_TOKEN ?? env.HF_ACCESS_TOKEN) as unknown as `hf_${string}`,
|
37 |
});
|
38 |
+
|
39 |
return await client
|
40 |
.predict(func, parameters)
|
41 |
+
.then((res) => (res as unknown as GradioResponse).data as TOutput)
|
42 |
+
.catch((e) => {
|
43 |
+
logger.error(e);
|
44 |
+
throw e;
|
45 |
+
});
|
46 |
}
|
47 |
|
48 |
export async function getIpToken(ip: string, username?: string) {
|
@@ -1,19 +1,28 @@
|
|
1 |
-
import type {
|
|
|
2 |
import { runWebSearch } from "../../websearch/runWebSearch";
|
3 |
|
4 |
-
const websearch:
|
5 |
-
|
|
|
|
|
|
|
|
|
6 |
displayName: "Web Search",
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
type: "
|
13 |
description:
|
14 |
"A search query which will be used to fetch the most relevant snippets regarding the user's query",
|
|
|
15 |
},
|
16 |
-
|
|
|
|
|
|
|
17 |
async *call({ query }, { conv, assistant, messages }) {
|
18 |
const webSearchToolResults = yield* runWebSearch(conv, messages, assistant?.rag, String(query));
|
19 |
const chunks = webSearchToolResults?.contextSources
|
|
|
1 |
+
import type { ConfigTool } from "$lib/types/Tool";
|
2 |
+
import { ObjectId } from "mongodb";
|
3 |
import { runWebSearch } from "../../websearch/runWebSearch";
|
4 |
|
5 |
+
const websearch: ConfigTool = {
|
6 |
+
_id: new ObjectId("00000000000000000000000A"),
|
7 |
+
type: "config",
|
8 |
+
description: "Search the web for answers to the user's query",
|
9 |
+
color: "blue",
|
10 |
+
icon: "wikis",
|
11 |
displayName: "Web Search",
|
12 |
+
name: "websearch",
|
13 |
+
endpoint: null,
|
14 |
+
inputs: [
|
15 |
+
{
|
16 |
+
name: "query",
|
17 |
+
type: "str",
|
18 |
description:
|
19 |
"A search query which will be used to fetch the most relevant snippets regarding the user's query",
|
20 |
+
paramType: "required",
|
21 |
},
|
22 |
+
],
|
23 |
+
outputComponent: null,
|
24 |
+
outputComponentIdx: null,
|
25 |
+
showOutput: false,
|
26 |
async *call({ query }, { conv, assistant, messages }) {
|
27 |
const webSearchToolResults = yield* runWebSearch(conv, messages, assistant?.rag, String(query));
|
28 |
const chunks = webSearchToolResults?.contextSources
|
@@ -1,23 +1,33 @@
|
|
1 |
import { stringifyMarkdownElementTree } from "$lib/server/websearch/markdown/utils/stringify";
|
2 |
import { scrapeUrl } from "$lib/server/websearch/scrape/scrape";
|
3 |
-
import type {
|
|
|
4 |
|
5 |
-
const fetchUrl:
|
6 |
-
|
7 |
-
|
8 |
-
description: "
|
9 |
-
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
12 |
type: "str",
|
13 |
-
|
|
|
14 |
},
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
19 |
|
20 |
-
const { title, markdownTree } = await scrapeUrl(
|
21 |
|
22 |
return {
|
23 |
outputs: [{ title, text: stringifyMarkdownElementTree(markdownTree) }],
|
|
|
1 |
import { stringifyMarkdownElementTree } from "$lib/server/websearch/markdown/utils/stringify";
|
2 |
import { scrapeUrl } from "$lib/server/websearch/scrape/scrape";
|
3 |
+
import type { ConfigTool } from "$lib/types/Tool";
|
4 |
+
import { ObjectId } from "mongodb";
|
5 |
|
6 |
+
const fetchUrl: ConfigTool = {
|
7 |
+
_id: new ObjectId("00000000000000000000000B"),
|
8 |
+
type: "config",
|
9 |
+
description: "Fetch the contents of a URL",
|
10 |
+
color: "blue",
|
11 |
+
icon: "cloud",
|
12 |
+
displayName: "Fetch URL",
|
13 |
+
name: "fetchUrl",
|
14 |
+
endpoint: null,
|
15 |
+
inputs: [
|
16 |
+
{
|
17 |
+
name: "url",
|
18 |
type: "str",
|
19 |
+
description: "The URL of the webpage to fetch",
|
20 |
+
paramType: "required",
|
21 |
},
|
22 |
+
],
|
23 |
+
outputComponent: null,
|
24 |
+
outputComponentIdx: null,
|
25 |
+
showOutput: false,
|
26 |
+
async *call({ url }) {
|
27 |
+
const blocks = String(url).split("\n");
|
28 |
+
const urlStr = blocks[blocks.length - 1];
|
29 |
|
30 |
+
const { title, markdownTree } = await scrapeUrl(urlStr, Infinity);
|
31 |
|
32 |
return {
|
33 |
outputs: [{ title, text: stringifyMarkdownElementTree(markdownTree) }],
|
@@ -17,6 +17,7 @@ export const usageLimitsSchema = z
|
|
17 |
return val;
|
18 |
}, z.coerce.number().optional())
|
19 |
.optional(), // how many messages per minute
|
|
|
20 |
})
|
21 |
.optional();
|
22 |
|
|
|
17 |
return val;
|
18 |
}, z.coerce.number().optional())
|
19 |
.optional(), // how many messages per minute
|
20 |
+
tools: z.coerce.number().optional(), // how many tools
|
21 |
})
|
22 |
.optional();
|
23 |
|
@@ -15,7 +15,7 @@ type SettingsStore = {
|
|
15 |
customPrompts: Record<string, string>;
|
16 |
recentlySaved: boolean;
|
17 |
assistants: Array<ObjectId | string>;
|
18 |
-
tools?:
|
19 |
disableStream: boolean;
|
20 |
};
|
21 |
|
|
|
15 |
customPrompts: Record<string, string>;
|
16 |
recentlySaved: boolean;
|
17 |
assistants: Array<ObjectId | string>;
|
18 |
+
tools?: Array<string>;
|
19 |
disableStream: boolean;
|
20 |
};
|
21 |
|
@@ -6,6 +6,7 @@ import type { Timestamps } from "./Timestamps";
|
|
6 |
export interface Report extends Timestamps {
|
7 |
_id: ObjectId;
|
8 |
createdBy: User["_id"] | string;
|
9 |
-
|
|
|
10 |
reason?: string;
|
11 |
}
|
|
|
6 |
export interface Report extends Timestamps {
|
7 |
_id: ObjectId;
|
8 |
createdBy: User["_id"] | string;
|
9 |
+
object: "assistant" | "tool";
|
10 |
+
contentId: Assistant["_id"];
|
11 |
reason?: string;
|
12 |
}
|
@@ -21,7 +21,7 @@ export interface Settings extends Timestamps {
|
|
21 |
customPrompts?: Record<string, string>;
|
22 |
|
23 |
assistants?: Assistant["_id"][];
|
24 |
-
tools?:
|
25 |
disableStream: boolean;
|
26 |
}
|
27 |
|
@@ -33,6 +33,6 @@ export const DEFAULT_SETTINGS = {
|
|
33 |
hideEmojiOnSidebar: false,
|
34 |
customPrompts: {},
|
35 |
assistants: [],
|
36 |
-
tools:
|
37 |
disableStream: false,
|
38 |
} satisfies SettingsEditable;
|
|
|
21 |
customPrompts?: Record<string, string>;
|
22 |
|
23 |
assistants?: Assistant["_id"][];
|
24 |
+
tools?: string[];
|
25 |
disableStream: boolean;
|
26 |
}
|
27 |
|
|
|
33 |
hideEmojiOnSidebar: false,
|
34 |
customPrompts: {},
|
35 |
assistants: [],
|
36 |
+
tools: [],
|
37 |
disableStream: false,
|
38 |
} satisfies SettingsEditable;
|
@@ -1,33 +1,147 @@
|
|
1 |
-
type
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
name: string;
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
description: string;
|
18 |
-
/** List of mime types that tool accepts */
|
19 |
-
mimeTypes?: string[];
|
20 |
-
parameterDefinitions: Record<string, ToolInput>;
|
21 |
-
spec?: string;
|
22 |
-
isOnByDefault?: true; // will it be toggled if the user hasn't tweaked it in settings ?
|
23 |
-
isLocked?: true; // can the user enable/disable it ?
|
24 |
-
isHidden?: true; // should it be hidden from the user ?
|
25 |
}
|
26 |
|
27 |
-
export
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
export enum ToolResultStatus {
|
33 |
Success = "success",
|
@@ -51,3 +165,8 @@ export interface ToolCall {
|
|
51 |
name: string;
|
52 |
parameters: Record<string, string | number | boolean>;
|
53 |
}
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { ObjectId } from "mongodb";
|
2 |
+
import type { User } from "./User";
|
3 |
+
import type { Timestamps } from "./Timestamps";
|
4 |
+
import type { BackendToolContext } from "$lib/server/tools";
|
5 |
+
import type { MessageUpdate } from "./MessageUpdate";
|
6 |
+
import { z } from "zod";
|
7 |
+
|
8 |
+
export const ToolColor = z.union([
|
9 |
+
z.literal("purple"),
|
10 |
+
z.literal("blue"),
|
11 |
+
z.literal("green"),
|
12 |
+
z.literal("yellow"),
|
13 |
+
z.literal("red"),
|
14 |
+
]);
|
15 |
+
|
16 |
+
export const ToolIcon = z.union([
|
17 |
+
z.literal("wikis"),
|
18 |
+
z.literal("tools"),
|
19 |
+
z.literal("camera"),
|
20 |
+
z.literal("code"),
|
21 |
+
z.literal("email"),
|
22 |
+
z.literal("cloud"),
|
23 |
+
z.literal("terminal"),
|
24 |
+
z.literal("game"),
|
25 |
+
z.literal("chat"),
|
26 |
+
z.literal("speaker"),
|
27 |
+
z.literal("video"),
|
28 |
+
]);
|
29 |
+
|
30 |
+
export const ToolOutputComponents = z
|
31 |
+
.string()
|
32 |
+
.toLowerCase()
|
33 |
+
.pipe(
|
34 |
+
z.union([z.literal("textbox"), z.literal("markdown"), z.literal("image"), z.literal("gallery")])
|
35 |
+
);
|
36 |
+
|
37 |
+
export type ToolOutputComponents = z.infer<typeof ToolOutputComponents>;
|
38 |
+
|
39 |
+
export type ToolLogoColor = z.infer<typeof ToolColor>;
|
40 |
+
export type ToolLogoIcon = z.infer<typeof ToolIcon>;
|
41 |
+
|
42 |
+
export type ToolIOType = "str" | "int" | "float" | "bool" | "file";
|
43 |
+
|
44 |
+
export type ToolInputRequired = {
|
45 |
+
paramType: "required";
|
46 |
+
name: string;
|
47 |
+
description?: string;
|
48 |
+
};
|
49 |
+
|
50 |
+
export type ToolInputOptional = {
|
51 |
+
paramType: "optional";
|
52 |
+
name: string;
|
53 |
+
description?: string;
|
54 |
+
default: string | number | boolean;
|
55 |
+
};
|
56 |
+
|
57 |
+
export type ToolInputFixed = {
|
58 |
+
paramType: "fixed";
|
59 |
name: string;
|
60 |
+
value: string | number | boolean;
|
61 |
+
};
|
62 |
+
|
63 |
+
type ToolInputBase = ToolInputRequired | ToolInputOptional | ToolInputFixed;
|
64 |
+
|
65 |
+
export type ToolInputFile = ToolInputBase & {
|
66 |
+
type: "file";
|
67 |
+
mimeTypes: string;
|
68 |
+
};
|
69 |
+
|
70 |
+
export type ToolInputSimple = ToolInputBase & {
|
71 |
+
type: Exclude<ToolIOType, "file">;
|
72 |
+
};
|
73 |
+
|
74 |
+
export type ToolInput = ToolInputFile | ToolInputSimple;
|
75 |
+
|
76 |
+
export interface BaseTool {
|
77 |
+
_id: ObjectId;
|
78 |
+
|
79 |
+
name: string; // name that will be shown to the AI
|
80 |
+
|
81 |
+
baseUrl?: string; // namespace for the tool
|
82 |
+
endpoint: string | null; // endpoint to call in gradio, if null we expect to override this function in code
|
83 |
+
outputComponent: string | null; // Gradio component type to use for the output
|
84 |
+
outputComponentIdx: number | null; // index of the output component
|
85 |
+
|
86 |
+
inputs: Array<ToolInput>;
|
87 |
+
showOutput: boolean; // show output in chat or not
|
88 |
+
|
89 |
+
call: BackendCall;
|
90 |
+
|
91 |
+
// for displaying in the UI
|
92 |
+
displayName: string;
|
93 |
+
color: ToolLogoColor;
|
94 |
+
icon: ToolLogoIcon;
|
95 |
description: string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
}
|
97 |
|
98 |
+
export interface ConfigTool extends BaseTool {
|
99 |
+
type: "config";
|
100 |
+
isOnByDefault?: true;
|
101 |
+
isLocked?: true;
|
102 |
+
isHidden?: true;
|
103 |
+
}
|
104 |
+
|
105 |
+
export interface CommunityTool extends BaseTool, Timestamps {
|
106 |
+
type: "community";
|
107 |
+
|
108 |
+
createdById: User["_id"] | string; // user id or session
|
109 |
+
createdByName?: User["username"];
|
110 |
+
|
111 |
+
// used to compute popular & trending
|
112 |
+
useCount: number;
|
113 |
+
last24HoursUseCount: number;
|
114 |
+
|
115 |
+
featured: boolean;
|
116 |
+
searchTokens: string[];
|
117 |
+
}
|
118 |
+
|
119 |
+
// no call function in db
|
120 |
+
export type CommunityToolDB = Omit<CommunityTool, "call">;
|
121 |
+
|
122 |
+
export type CommunityToolEditable = Omit<
|
123 |
+
CommunityToolDB,
|
124 |
+
| "_id"
|
125 |
+
| "useCount"
|
126 |
+
| "last24HoursUseCount"
|
127 |
+
| "createdById"
|
128 |
+
| "createdByName"
|
129 |
+
| "featured"
|
130 |
+
| "searchTokens"
|
131 |
+
| "type"
|
132 |
+
| "createdAt"
|
133 |
+
| "updatedAt"
|
134 |
+
>;
|
135 |
+
|
136 |
+
export type Tool = ConfigTool | CommunityTool;
|
137 |
+
|
138 |
+
export type ToolFront = Pick<Tool, "type" | "name" | "displayName" | "description"> & {
|
139 |
+
_id: string;
|
140 |
+
isOnByDefault: boolean;
|
141 |
+
isLocked: boolean;
|
142 |
+
mimeTypes: string[];
|
143 |
+
timeToUseMS?: number;
|
144 |
+
};
|
145 |
|
146 |
export enum ToolResultStatus {
|
147 |
Success = "success",
|
|
|
165 |
name: string;
|
166 |
parameters: Record<string, string | number | boolean>;
|
167 |
}
|
168 |
+
|
169 |
+
export type BackendCall = (
|
170 |
+
params: Record<string, string | number | boolean>,
|
171 |
+
context: BackendToolContext
|
172 |
+
) => AsyncGenerator<MessageUpdate, Omit<ToolResultSuccess, "status" | "call" | "type">, undefined>;
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { base } from "$app/paths";
|
2 |
+
import type { Client } from "@gradio/client";
|
3 |
+
|
4 |
+
export type ApiReturnType = Awaited<ReturnType<typeof Client.prototype.view_api>>;
|
5 |
+
|
6 |
+
export async function getGradioApi(space: string) {
|
7 |
+
const api: ApiReturnType = await fetch(`${base}/api/spaces-config?space=${space}`).then((res) =>
|
8 |
+
res.json()
|
9 |
+
);
|
10 |
+
return api;
|
11 |
+
}
|
@@ -48,7 +48,7 @@ type MessageUpdateRequestOptions = {
|
|
48 |
isRetry: boolean;
|
49 |
isContinue: boolean;
|
50 |
webSearch: boolean;
|
51 |
-
tools?:
|
52 |
files?: MessageFile[];
|
53 |
};
|
54 |
export async function fetchMessageUpdates(
|
|
|
48 |
isRetry: boolean;
|
49 |
isContinue: boolean;
|
50 |
webSearch: boolean;
|
51 |
+
tools?: Array<string>;
|
52 |
files?: MessageFile[];
|
53 |
};
|
54 |
export async function fetchMessageUpdates(
|
@@ -7,3 +7,19 @@ import type { Tool } from "$lib/types/Tool";
|
|
7 |
export function toolHasName(name: string, tool: Pick<Tool, "name">): boolean {
|
8 |
return tool.name.replaceAll("-", "_") === name.replaceAll("-", "_");
|
9 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
export function toolHasName(name: string, tool: Pick<Tool, "name">): boolean {
|
8 |
return tool.name.replaceAll("-", "_") === name.replaceAll("-", "_");
|
9 |
}
|
10 |
+
|
11 |
+
export const colors = ["purple", "blue", "green", "yellow", "red"] as const;
|
12 |
+
|
13 |
+
export const icons = [
|
14 |
+
"wikis",
|
15 |
+
"tools",
|
16 |
+
"camera",
|
17 |
+
"code",
|
18 |
+
"email",
|
19 |
+
"cloud",
|
20 |
+
"terminal",
|
21 |
+
"game",
|
22 |
+
"chat",
|
23 |
+
"speaker",
|
24 |
+
"video",
|
25 |
+
] as const;
|
@@ -8,8 +8,9 @@ import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
|
8 |
import { env } from "$env/dynamic/private";
|
9 |
import { ObjectId } from "mongodb";
|
10 |
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
11 |
-
import {
|
12 |
import { MetricsServer } from "$lib/server/metrics";
|
|
|
13 |
|
14 |
export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
|
15 |
depends(UrlDependency.ConversationList);
|
@@ -107,6 +108,25 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
|
|
107 |
}
|
108 |
|
109 |
const toolUseDuration = (await MetricsServer.getMetrics().tool.toolUseDuration.get()).values;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
return {
|
111 |
conversations: conversations.map((conv) => {
|
112 |
if (settings?.hideEmojiOnSidebar) {
|
@@ -146,7 +166,11 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
|
|
146 |
DEFAULT_SETTINGS.shareConversationsWithModelAuthors,
|
147 |
customPrompts: settings?.customPrompts ?? {},
|
148 |
assistants: userAssistants,
|
149 |
-
tools:
|
|
|
|
|
|
|
|
|
150 |
disableStream: settings?.disableStream ?? DEFAULT_SETTINGS.disableStream,
|
151 |
},
|
152 |
models: models.map((model) => ({
|
@@ -171,19 +195,29 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
|
|
171 |
unlisted: model.unlisted,
|
172 |
})),
|
173 |
oldModels,
|
174 |
-
tools:
|
175 |
-
.filter((tool) => !tool
|
176 |
-
.map(
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
assistants: assistants
|
188 |
.filter((el) => userAssistantsSet.has(el._id.toString()))
|
189 |
.map((el) => ({
|
|
|
8 |
import { env } from "$env/dynamic/private";
|
9 |
import { ObjectId } from "mongodb";
|
10 |
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
11 |
+
import { toolFromConfigs } from "$lib/server/tools";
|
12 |
import { MetricsServer } from "$lib/server/metrics";
|
13 |
+
import type { ToolFront, ToolInputFile } from "$lib/types/Tool";
|
14 |
|
15 |
export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
|
16 |
depends(UrlDependency.ConversationList);
|
|
|
108 |
}
|
109 |
|
110 |
const toolUseDuration = (await MetricsServer.getMetrics().tool.toolUseDuration.get()).values;
|
111 |
+
|
112 |
+
const configToolIds = toolFromConfigs.map((el) => el._id.toString());
|
113 |
+
|
114 |
+
const activeCommunityToolIds = (settings?.tools ?? []).filter(
|
115 |
+
(key) => !configToolIds.includes(key)
|
116 |
+
);
|
117 |
+
|
118 |
+
const communityTools = await collections.tools
|
119 |
+
.find({ _id: { $in: activeCommunityToolIds.map((el) => new ObjectId(el)) } })
|
120 |
+
.toArray()
|
121 |
+
.then((tools) =>
|
122 |
+
tools.map((tool) => ({
|
123 |
+
...tool,
|
124 |
+
isHidden: false,
|
125 |
+
isOnByDefault: true,
|
126 |
+
isLocked: true,
|
127 |
+
}))
|
128 |
+
);
|
129 |
+
|
130 |
return {
|
131 |
conversations: conversations.map((conv) => {
|
132 |
if (settings?.hideEmojiOnSidebar) {
|
|
|
166 |
DEFAULT_SETTINGS.shareConversationsWithModelAuthors,
|
167 |
customPrompts: settings?.customPrompts ?? {},
|
168 |
assistants: userAssistants,
|
169 |
+
tools:
|
170 |
+
settings?.tools ??
|
171 |
+
toolFromConfigs
|
172 |
+
.filter((el) => !el.isHidden && el.isOnByDefault)
|
173 |
+
.map((el) => el._id.toString()),
|
174 |
disableStream: settings?.disableStream ?? DEFAULT_SETTINGS.disableStream,
|
175 |
},
|
176 |
models: models.map((model) => ({
|
|
|
195 |
unlisted: model.unlisted,
|
196 |
})),
|
197 |
oldModels,
|
198 |
+
tools: [...toolFromConfigs, ...communityTools]
|
199 |
+
.filter((tool) => !tool?.isHidden)
|
200 |
+
.map(
|
201 |
+
(tool) =>
|
202 |
+
({
|
203 |
+
_id: tool._id.toString(),
|
204 |
+
type: tool.type,
|
205 |
+
displayName: tool.displayName,
|
206 |
+
name: tool.name,
|
207 |
+
description: tool.description,
|
208 |
+
mimeTypes: (tool.inputs ?? [])
|
209 |
+
.filter((input): input is ToolInputFile => input.type === "file")
|
210 |
+
.map((input) => (input as ToolInputFile).mimeTypes)
|
211 |
+
.flat(),
|
212 |
+
isOnByDefault: tool.isOnByDefault ?? true,
|
213 |
+
isLocked: tool.isLocked ?? true,
|
214 |
+
timeToUseMS:
|
215 |
+
toolUseDuration.find(
|
216 |
+
(el) => el.labels.tool === tool._id.toString() && el.labels.quantile === 0.9
|
217 |
+
)?.value ?? 15_000,
|
218 |
+
} satisfies ToolFront)
|
219 |
+
),
|
220 |
+
communityToolCount: await collections.tools.countDocuments({ type: "community" }),
|
221 |
assistants: assistants
|
222 |
.filter((el) => userAssistantsSet.has(el._id.toString()))
|
223 |
.map((el) => ({
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Client } from "@gradio/client";
|
2 |
+
|
3 |
+
export async function GET({ url, locals }) {
|
4 |
+
// XXX: feature_flag_tools
|
5 |
+
if (!locals.user?.isEarlyAccess) {
|
6 |
+
return new Response("Not early access", { status: 403 });
|
7 |
+
}
|
8 |
+
|
9 |
+
const space = url.searchParams.get("space");
|
10 |
+
|
11 |
+
if (!space) {
|
12 |
+
return new Response("Missing space", { status: 400 });
|
13 |
+
}
|
14 |
+
|
15 |
+
try {
|
16 |
+
const api = await (await Client.connect(space)).view_api();
|
17 |
+
return new Response(JSON.stringify(api), {
|
18 |
+
status: 200,
|
19 |
+
headers: {
|
20 |
+
"Content-Type": "application/json",
|
21 |
+
},
|
22 |
+
});
|
23 |
+
} catch (e) {
|
24 |
+
return new Response(JSON.stringify({ error: true, message: "Failed to get space API" }), {
|
25 |
+
status: 400,
|
26 |
+
headers: {
|
27 |
+
"Content-Type": "application/json",
|
28 |
+
},
|
29 |
+
});
|
30 |
+
}
|
31 |
+
}
|
@@ -55,8 +55,8 @@ export const load = async ({ url, locals }) => {
|
|
55 |
.assistants.find(filter)
|
56 |
.skip(NUM_PER_PAGE * pageIndex)
|
57 |
.sort({
|
58 |
-
...(sort === SortKey.TRENDING && {
|
59 |
-
|
60 |
})
|
61 |
.limit(NUM_PER_PAGE)
|
62 |
.toArray();
|
|
|
55 |
.assistants.find(filter)
|
56 |
.skip(NUM_PER_PAGE * pageIndex)
|
57 |
.sort({
|
58 |
+
...(sort === SortKey.TRENDING && { last24HoursUseCount: -1 }),
|
59 |
+
useCount: -1,
|
60 |
})
|
61 |
.limit(NUM_PER_PAGE)
|
62 |
.toArray();
|
@@ -159,12 +159,23 @@ export async function POST({ request, locals, params, getClientAddress }) {
|
|
159 |
is_continue: z.optional(z.boolean()),
|
160 |
web_search: z.optional(z.boolean()),
|
161 |
tools: z
|
162 |
-
.
|
163 |
.optional()
|
164 |
.transform((tools) =>
|
165 |
// disable tools on huggingchat android app
|
166 |
-
request.headers.get("user-agent")?.includes("co.huggingface.chat_ui_android") ?
|
167 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
})
|
169 |
.parse(JSON.parse(json));
|
170 |
|
@@ -419,7 +430,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
|
|
419 |
assistant: undefined,
|
420 |
isContinue: isContinue ?? false,
|
421 |
webSearch: webSearch ?? false,
|
422 |
-
toolsPreference: toolsPreferences ??
|
423 |
promptedAt,
|
424 |
ip: getClientAddress(),
|
425 |
username: locals.user?.username,
|
|
|
159 |
is_continue: z.optional(z.boolean()),
|
160 |
web_search: z.optional(z.boolean()),
|
161 |
tools: z
|
162 |
+
.array(z.string())
|
163 |
.optional()
|
164 |
.transform((tools) =>
|
165 |
// disable tools on huggingchat android app
|
166 |
+
request.headers.get("user-agent")?.includes("co.huggingface.chat_ui_android") ? [] : tools
|
167 |
),
|
168 |
+
|
169 |
+
files: z.optional(
|
170 |
+
z.array(
|
171 |
+
z.object({
|
172 |
+
type: z.literal("base64").or(z.literal("hash")),
|
173 |
+
name: z.string(),
|
174 |
+
value: z.string(),
|
175 |
+
mime: z.string(),
|
176 |
+
})
|
177 |
+
)
|
178 |
+
),
|
179 |
})
|
180 |
.parse(JSON.parse(json));
|
181 |
|
|
|
430 |
assistant: undefined,
|
431 |
isContinue: isContinue ?? false,
|
432 |
webSearch: webSearch ?? false,
|
433 |
+
toolsPreference: toolsPreferences ?? [],
|
434 |
promptedAt,
|
435 |
ip: getClientAddress(),
|
436 |
username: locals.user?.username,
|
@@ -2,6 +2,7 @@ import { collections } from "$lib/server/database";
|
|
2 |
import { z } from "zod";
|
3 |
import { authCondition } from "$lib/server/auth";
|
4 |
import { DEFAULT_SETTINGS, type SettingsEditable } from "$lib/types/Settings";
|
|
|
5 |
|
6 |
export async function POST({ request, locals }) {
|
7 |
const body = await request.json();
|
@@ -15,11 +16,19 @@ export async function POST({ request, locals }) {
|
|
15 |
ethicsModalAccepted: z.boolean().optional(),
|
16 |
activeModel: z.string().default(DEFAULT_SETTINGS.activeModel),
|
17 |
customPrompts: z.record(z.string()).default({}),
|
18 |
-
tools: z.
|
19 |
disableStream: z.boolean().default(false),
|
20 |
})
|
21 |
.parse(body) satisfies SettingsEditable;
|
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
await collections.settings.updateOne(
|
24 |
authCondition(locals),
|
25 |
{
|
|
|
2 |
import { z } from "zod";
|
3 |
import { authCondition } from "$lib/server/auth";
|
4 |
import { DEFAULT_SETTINGS, type SettingsEditable } from "$lib/types/Settings";
|
5 |
+
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
6 |
|
7 |
export async function POST({ request, locals }) {
|
8 |
const body = await request.json();
|
|
|
16 |
ethicsModalAccepted: z.boolean().optional(),
|
17 |
activeModel: z.string().default(DEFAULT_SETTINGS.activeModel),
|
18 |
customPrompts: z.record(z.string()).default({}),
|
19 |
+
tools: z.array(z.string()).optional(),
|
20 |
disableStream: z.boolean().default(false),
|
21 |
})
|
22 |
.parse(body) satisfies SettingsEditable;
|
23 |
|
24 |
+
// only allow tools to be set to community tools if user is early access
|
25 |
+
// XXX: feature_flag_tools
|
26 |
+
if (!locals.user?.isEarlyAccess) {
|
27 |
+
settings.tools = settings.tools?.filter((toolId) => {
|
28 |
+
return toolFromConfigs.some((tool) => tool._id.toString() === toolId);
|
29 |
+
});
|
30 |
+
}
|
31 |
+
|
32 |
await collections.settings.updateOne(
|
33 |
authCondition(locals),
|
34 |
{
|
@@ -63,7 +63,8 @@ export const actions: Actions = {
|
|
63 |
// is there already a report from this user for this model ?
|
64 |
const report = await collections.reports.findOne({
|
65 |
createdBy: locals.user?._id ?? locals.sessionId,
|
66 |
-
|
|
|
67 |
});
|
68 |
|
69 |
if (report) {
|
@@ -79,7 +80,8 @@ export const actions: Actions = {
|
|
79 |
|
80 |
const { acknowledged } = await collections.reports.insertOne({
|
81 |
_id: new ObjectId(),
|
82 |
-
|
|
|
83 |
createdBy: locals.user?._id ?? locals.sessionId,
|
84 |
createdAt: new Date(),
|
85 |
updatedAt: new Date(),
|
|
|
63 |
// is there already a report from this user for this model ?
|
64 |
const report = await collections.reports.findOne({
|
65 |
createdBy: locals.user?._id ?? locals.sessionId,
|
66 |
+
object: "assistant",
|
67 |
+
contentId: new ObjectId(params.assistantId),
|
68 |
});
|
69 |
|
70 |
if (report) {
|
|
|
80 |
|
81 |
const { acknowledged } = await collections.reports.insertOne({
|
82 |
_id: new ObjectId(),
|
83 |
+
contentId: new ObjectId(params.assistantId),
|
84 |
+
object: "assistant",
|
85 |
createdBy: locals.user?._id ?? locals.sessionId,
|
86 |
createdAt: new Date(),
|
87 |
updatedAt: new Date(),
|
@@ -22,10 +22,10 @@
|
|
22 |
}}
|
23 |
class="w-full min-w-64 p-4"
|
24 |
>
|
25 |
-
<span class="mb-1 text-sm font-semibold">Report
|
26 |
|
27 |
<p class="text-sm text-gray-500">
|
28 |
-
Please provide a brief description of why you are reporting this
|
29 |
</p>
|
30 |
|
31 |
<textarea
|
|
|
22 |
}}
|
23 |
class="w-full min-w-64 p-4"
|
24 |
>
|
25 |
+
<span class="mb-1 text-sm font-semibold">Report content</span>
|
26 |
|
27 |
<p class="text-sm text-gray-500">
|
28 |
+
Please provide a brief description of why you are reporting this content.
|
29 |
</p>
|
30 |
|
31 |
<textarea
|
@@ -9,9 +9,12 @@ export const load = (async ({ locals, parent }) => {
|
|
9 |
const createdBy = locals.user?._id ?? locals.sessionId;
|
10 |
if (createdBy) {
|
11 |
const reports = await collections.reports
|
12 |
-
.find<Pick<Report, "
|
|
|
|
|
|
|
13 |
.toArray();
|
14 |
-
reportsByUser = reports.map((r) => r.
|
15 |
}
|
16 |
|
17 |
return {
|
|
|
9 |
const createdBy = locals.user?._id ?? locals.sessionId;
|
10 |
if (createdBy) {
|
11 |
const reports = await collections.reports
|
12 |
+
.find<Pick<Report, "contentId">>(
|
13 |
+
{ createdBy, object: "assistant" },
|
14 |
+
{ projection: { _id: 0, contentId: 1 } }
|
15 |
+
)
|
16 |
.toArray();
|
17 |
+
reportsByUser = reports.map((r) => r.contentId.toString());
|
18 |
}
|
19 |
|
20 |
return {
|
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
<slot />
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// check if user is earlyAccess else redirect to base
|
2 |
+
|
3 |
+
import { base } from "$app/paths";
|
4 |
+
import { redirect } from "@sveltejs/kit";
|
5 |
+
|
6 |
+
// XXX: feature_flag_tools
|
7 |
+
export async function load({ parent }) {
|
8 |
+
const { user } = await parent();
|
9 |
+
|
10 |
+
if (user?.isEarlyAccess) {
|
11 |
+
return {};
|
12 |
+
}
|
13 |
+
|
14 |
+
redirect(302, `${base}/`);
|
15 |
+
}
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { authCondition } from "$lib/server/auth.js";
|
2 |
+
import { Database, collections } from "$lib/server/database.js";
|
3 |
+
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
4 |
+
import { SortKey } from "$lib/types/Assistant.js";
|
5 |
+
import type { CommunityToolDB } from "$lib/types/Tool.js";
|
6 |
+
import type { User } from "$lib/types/User.js";
|
7 |
+
import { generateQueryTokens, generateSearchTokens } from "$lib/utils/searchTokens.js";
|
8 |
+
import { error } from "@sveltejs/kit";
|
9 |
+
import { ObjectId, type Filter } from "mongodb";
|
10 |
+
|
11 |
+
const NUM_PER_PAGE = 16;
|
12 |
+
|
13 |
+
export const load = async ({ url, locals }) => {
|
14 |
+
// XXX: feature_flag_tools
|
15 |
+
if (!locals.user?.isEarlyAccess) {
|
16 |
+
error(403, "You need to be an early access user to view tools");
|
17 |
+
}
|
18 |
+
|
19 |
+
const username = url.searchParams.get("user");
|
20 |
+
const query = url.searchParams.get("q")?.trim() ?? null;
|
21 |
+
|
22 |
+
const pageIndex = parseInt(url.searchParams.get("p") ?? "0");
|
23 |
+
const sort = url.searchParams.get("sort")?.trim() ?? SortKey.TRENDING;
|
24 |
+
const createdByCurrentUser = locals.user?.username && locals.user.username === username;
|
25 |
+
const activeOnly = url.searchParams.get("active") === "true";
|
26 |
+
|
27 |
+
let user: Pick<User, "_id"> | null = null;
|
28 |
+
if (username) {
|
29 |
+
user = await collections.users.findOne<Pick<User, "_id">>(
|
30 |
+
{ username },
|
31 |
+
{ projection: { _id: 1 } }
|
32 |
+
);
|
33 |
+
if (!user) {
|
34 |
+
error(404, `User "${username}" doesn't exist`);
|
35 |
+
}
|
36 |
+
}
|
37 |
+
|
38 |
+
const settings = await collections.settings.findOne(authCondition(locals));
|
39 |
+
|
40 |
+
if (!settings && activeOnly) {
|
41 |
+
error(404, "No user settings found");
|
42 |
+
}
|
43 |
+
|
44 |
+
const queryTokens = !!query && generateQueryTokens(query);
|
45 |
+
|
46 |
+
const filter: Filter<CommunityToolDB> = {
|
47 |
+
...(!createdByCurrentUser && !activeOnly && { featured: true }),
|
48 |
+
...(user && { createdById: user._id }),
|
49 |
+
...(queryTokens && { searchTokens: { $all: queryTokens } }),
|
50 |
+
...(activeOnly && {
|
51 |
+
_id: {
|
52 |
+
$in: (settings?.tools ?? []).map((key) => {
|
53 |
+
return new ObjectId(key);
|
54 |
+
}),
|
55 |
+
},
|
56 |
+
}),
|
57 |
+
};
|
58 |
+
|
59 |
+
const communityTools = await Database.getInstance()
|
60 |
+
.getCollections()
|
61 |
+
.tools.find(filter)
|
62 |
+
.skip(NUM_PER_PAGE * pageIndex)
|
63 |
+
.sort({
|
64 |
+
...(sort === SortKey.TRENDING && { last24HoursUseCount: -1 }),
|
65 |
+
useCount: -1,
|
66 |
+
})
|
67 |
+
.limit(NUM_PER_PAGE)
|
68 |
+
.toArray();
|
69 |
+
|
70 |
+
const configTools = toolFromConfigs
|
71 |
+
.filter((tool) => !tool?.isHidden)
|
72 |
+
.filter((tool) => {
|
73 |
+
if (queryTokens) {
|
74 |
+
return generateSearchTokens(tool.name).some((token) =>
|
75 |
+
queryTokens.some((queryToken) => queryToken.test(token))
|
76 |
+
);
|
77 |
+
}
|
78 |
+
return true;
|
79 |
+
});
|
80 |
+
|
81 |
+
const tools = [...(pageIndex == 0 && !username ? configTools : []), ...communityTools];
|
82 |
+
|
83 |
+
const numTotalItems =
|
84 |
+
(await Database.getInstance().getCollections().tools.countDocuments(filter)) +
|
85 |
+
toolFromConfigs.length;
|
86 |
+
|
87 |
+
return {
|
88 |
+
tools: JSON.parse(JSON.stringify(tools)) as CommunityToolDB[],
|
89 |
+
numTotalItems,
|
90 |
+
numItemsPerPage: NUM_PER_PAGE,
|
91 |
+
query,
|
92 |
+
sort,
|
93 |
+
};
|
94 |
+
};
|