Spaces:
Running
Running
feat: Update featured workflow to allow user to submit featured requests (#1510)
Browse files* feat: Update featured workflow to allow user to submit featured requests
* fix: merge conflict
* fix: styling of buttons
- scripts/populate.ts +4 -2
- src/lib/migrations/routines/06-trim-message-updates.ts +1 -1
- src/lib/migrations/routines/07-reset-tools-in-settings.ts +1 -1
- src/lib/migrations/routines/08-update-featured-to-review.ts +32 -0
- src/lib/migrations/routines/index.ts +2 -0
- src/lib/server/database.ts +1 -1
- src/lib/server/sendSlack.ts +23 -0
- src/lib/types/Assistant.ts +2 -1
- src/lib/types/Review.ts +6 -0
- src/lib/types/Tool.ts +3 -2
- src/routes/+layout.server.ts +2 -1
- src/routes/api/assistants/+server.ts +2 -1
- src/routes/api/tools/[toolId]/+server.ts +3 -2
- src/routes/api/tools/search/+server.ts +2 -2
- src/routes/assistants/+page.server.ts +2 -2
- src/routes/assistants/+page.svelte +2 -1
- src/routes/settings/(nav)/assistants/[assistantId]/+page.server.ts +97 -55
- src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte +40 -11
- src/routes/settings/(nav)/assistants/new/+page.server.ts +2 -1
- src/routes/tools/+page.server.ts +2 -1
- src/routes/tools/+page.svelte +4 -2
- src/routes/tools/[toolId]/+layout.server.ts +2 -1
- src/routes/tools/[toolId]/+page.server.ts +97 -49
- src/routes/tools/[toolId]/+page.svelte +41 -12
- src/routes/tools/new/+page.server.ts +2 -1
scripts/populate.ts
CHANGED
@@ -20,6 +20,7 @@ import { Message } from "../src/lib/types/Message.ts";
|
|
20 |
|
21 |
import { addChildren } from "../src/lib/utils/tree/addChildren.ts";
|
22 |
import { generateSearchTokens } from "../src/lib/utils/searchTokens.ts";
|
|
|
23 |
|
24 |
const rl = readline.createInterface({
|
25 |
input: process.stdin,
|
@@ -151,6 +152,7 @@ async function seed() {
|
|
151 |
disableStream: faker.datatype.boolean(0.25),
|
152 |
customPrompts: {},
|
153 |
assistants: [],
|
|
|
154 |
};
|
155 |
await collections.settings.updateOne(
|
156 |
{ userId: user._id },
|
@@ -175,7 +177,7 @@ async function seed() {
|
|
175 |
createdAt: faker.date.recent({ days: 30 }),
|
176 |
updatedAt: faker.date.recent({ days: 30 }),
|
177 |
userCount: faker.number.int({ min: 1, max: 100000 }),
|
178 |
-
|
179 |
modelId: faker.helpers.arrayElement(modelIds),
|
180 |
description: faker.lorem.sentence(),
|
181 |
preprompt: faker.hacker.phrase(),
|
@@ -307,7 +309,7 @@ async function seed() {
|
|
307 |
createdAt: faker.date.recent({ days: 30 }),
|
308 |
updatedAt: faker.date.recent({ days: 30 }),
|
309 |
searchTokens: generateSearchTokens(displayName),
|
310 |
-
|
311 |
outputComponent: null,
|
312 |
outputComponentIdx: null,
|
313 |
};
|
|
|
20 |
|
21 |
import { addChildren } from "../src/lib/utils/tree/addChildren.ts";
|
22 |
import { generateSearchTokens } from "../src/lib/utils/searchTokens.ts";
|
23 |
+
import { ReviewStatus } from "../src/lib/types/Review.ts";
|
24 |
|
25 |
const rl = readline.createInterface({
|
26 |
input: process.stdin,
|
|
|
152 |
disableStream: faker.datatype.boolean(0.25),
|
153 |
customPrompts: {},
|
154 |
assistants: [],
|
155 |
+
disableStream: faker.datatype.boolean(0.25),
|
156 |
};
|
157 |
await collections.settings.updateOne(
|
158 |
{ userId: user._id },
|
|
|
177 |
createdAt: faker.date.recent({ days: 30 }),
|
178 |
updatedAt: faker.date.recent({ days: 30 }),
|
179 |
userCount: faker.number.int({ min: 1, max: 100000 }),
|
180 |
+
review: faker.helpers.enumValue(ReviewStatus),
|
181 |
modelId: faker.helpers.arrayElement(modelIds),
|
182 |
description: faker.lorem.sentence(),
|
183 |
preprompt: faker.hacker.phrase(),
|
|
|
309 |
createdAt: faker.date.recent({ days: 30 }),
|
310 |
updatedAt: faker.date.recent({ days: 30 }),
|
311 |
searchTokens: generateSearchTokens(displayName),
|
312 |
+
review: faker.helpers.enumValue(ReviewStatus),
|
313 |
outputComponent: null,
|
314 |
outputComponentIdx: null,
|
315 |
};
|
src/lib/migrations/routines/06-trim-message-updates.ts
CHANGED
@@ -41,7 +41,7 @@ function convertMessageUpdate(message: Message, update: MessageUpdate): MessageU
|
|
41 |
}
|
42 |
|
43 |
const trimMessageUpdates: Migration = {
|
44 |
-
_id: new ObjectId("
|
45 |
name: "Trim message updates to reduce stored size",
|
46 |
up: async () => {
|
47 |
const allConversations = collections.conversations.find({});
|
|
|
41 |
}
|
42 |
|
43 |
const trimMessageUpdates: Migration = {
|
44 |
+
_id: new ObjectId("000000000000000000000006"),
|
45 |
name: "Trim message updates to reduce stored size",
|
46 |
up: async () => {
|
47 |
const allConversations = collections.conversations.find({});
|
src/lib/migrations/routines/07-reset-tools-in-settings.ts
CHANGED
@@ -3,7 +3,7 @@ import { collections } from "$lib/server/database";
|
|
3 |
import { ObjectId } from "mongodb";
|
4 |
|
5 |
const resetTools: Migration = {
|
6 |
-
_id: new ObjectId("
|
7 |
name: "Reset tools to empty",
|
8 |
up: async () => {
|
9 |
const { settings } = collections;
|
|
|
3 |
import { ObjectId } from "mongodb";
|
4 |
|
5 |
const resetTools: Migration = {
|
6 |
+
_id: new ObjectId("000000000000000000000007"),
|
7 |
name: "Reset tools to empty",
|
8 |
up: async () => {
|
9 |
const { settings } = collections;
|
src/lib/migrations/routines/08-update-featured-to-review.ts
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { Migration } from ".";
|
2 |
+
import { collections } from "$lib/server/database";
|
3 |
+
import { ObjectId } from "mongodb";
|
4 |
+
import { ReviewStatus } from "$lib/types/Review";
|
5 |
+
|
6 |
+
const updateFeaturedToReview: Migration = {
|
7 |
+
_id: new ObjectId("000000000000000000000008"),
|
8 |
+
name: "Update featured to review",
|
9 |
+
up: async () => {
|
10 |
+
const { assistants, tools } = collections;
|
11 |
+
|
12 |
+
// Update assistants
|
13 |
+
await assistants.updateMany({ featured: true }, { $set: { review: ReviewStatus.APPROVED } });
|
14 |
+
await assistants.updateMany(
|
15 |
+
{ featured: { $ne: true } },
|
16 |
+
{ $set: { review: ReviewStatus.PRIVATE } }
|
17 |
+
);
|
18 |
+
|
19 |
+
await assistants.updateMany({}, { $unset: { featured: "" } });
|
20 |
+
|
21 |
+
// Update tools
|
22 |
+
await tools.updateMany({ featured: true }, { $set: { review: ReviewStatus.APPROVED } });
|
23 |
+
await tools.updateMany({ featured: { $ne: true } }, { $set: { review: ReviewStatus.PRIVATE } });
|
24 |
+
|
25 |
+
await tools.updateMany({}, { $unset: { featured: "" } });
|
26 |
+
|
27 |
+
return true;
|
28 |
+
},
|
29 |
+
runEveryTime: false,
|
30 |
+
};
|
31 |
+
|
32 |
+
export default updateFeaturedToReview;
|
src/lib/migrations/routines/index.ts
CHANGED
@@ -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 updateFeaturedToReview from "./08-update-featured-to-review";
|
12 |
|
13 |
export interface Migration {
|
14 |
_id: ObjectId;
|
|
|
28 |
updateMessageFiles,
|
29 |
trimMessageUpdates,
|
30 |
resetTools,
|
31 |
+
updateFeaturedToReview,
|
32 |
];
|
src/lib/server/database.ts
CHANGED
@@ -202,7 +202,7 @@ export class Database {
|
|
202 |
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch((e) => logger.error(e));
|
203 |
assistants.createIndex({ createdById: 1, userCount: -1 }).catch((e) => logger.error(e));
|
204 |
assistants.createIndex({ userCount: 1 }).catch((e) => logger.error(e));
|
205 |
-
assistants.createIndex({
|
206 |
assistants.createIndex({ modelId: 1, userCount: -1 }).catch((e) => logger.error(e));
|
207 |
assistants.createIndex({ searchTokens: 1 }).catch((e) => logger.error(e));
|
208 |
assistants.createIndex({ last24HoursCount: 1 }).catch((e) => logger.error(e));
|
|
|
202 |
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch((e) => logger.error(e));
|
203 |
assistants.createIndex({ createdById: 1, userCount: -1 }).catch((e) => logger.error(e));
|
204 |
assistants.createIndex({ userCount: 1 }).catch((e) => logger.error(e));
|
205 |
+
assistants.createIndex({ review: 1, userCount: -1 }).catch((e) => logger.error(e));
|
206 |
assistants.createIndex({ modelId: 1, userCount: -1 }).catch((e) => logger.error(e));
|
207 |
assistants.createIndex({ searchTokens: 1 }).catch((e) => logger.error(e));
|
208 |
assistants.createIndex({ last24HoursCount: 1 }).catch((e) => logger.error(e));
|
src/lib/server/sendSlack.ts
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { env } from "$env/dynamic/private";
|
2 |
+
import { logger } from "$lib/server/logger";
|
3 |
+
|
4 |
+
export async function sendSlack(text: string) {
|
5 |
+
if (!env.WEBHOOK_URL_REPORT_ASSISTANT) {
|
6 |
+
logger.warn("WEBHOOK_URL_REPORT_ASSISTANT is not set, tried to send a slack message.");
|
7 |
+
return;
|
8 |
+
}
|
9 |
+
|
10 |
+
const res = await fetch(env.WEBHOOK_URL_REPORT_ASSISTANT, {
|
11 |
+
method: "POST",
|
12 |
+
headers: {
|
13 |
+
"Content-type": "application/json",
|
14 |
+
},
|
15 |
+
body: JSON.stringify({
|
16 |
+
text,
|
17 |
+
}),
|
18 |
+
});
|
19 |
+
|
20 |
+
if (!res.ok) {
|
21 |
+
logger.error(`Webhook message failed. ${res.statusText} ${res.text}`);
|
22 |
+
}
|
23 |
+
}
|
src/lib/types/Assistant.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import type { ObjectId } from "mongodb";
|
2 |
import type { User } from "./User";
|
3 |
import type { Timestamps } from "./Timestamps";
|
|
|
4 |
|
5 |
export interface Assistant extends Timestamps {
|
6 |
_id: ObjectId;
|
@@ -13,7 +14,7 @@ export interface Assistant extends Timestamps {
|
|
13 |
exampleInputs: string[];
|
14 |
preprompt: string;
|
15 |
userCount?: number;
|
16 |
-
|
17 |
rag?: {
|
18 |
allowAllDomains: boolean;
|
19 |
allowedDomains: string[];
|
|
|
1 |
import type { ObjectId } from "mongodb";
|
2 |
import type { User } from "./User";
|
3 |
import type { Timestamps } from "./Timestamps";
|
4 |
+
import type { ReviewStatus } from "./Review";
|
5 |
|
6 |
export interface Assistant extends Timestamps {
|
7 |
_id: ObjectId;
|
|
|
14 |
exampleInputs: string[];
|
15 |
preprompt: string;
|
16 |
userCount?: number;
|
17 |
+
review: ReviewStatus;
|
18 |
rag?: {
|
19 |
allowAllDomains: boolean;
|
20 |
allowedDomains: string[];
|
src/lib/types/Review.ts
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export enum ReviewStatus {
|
2 |
+
PRIVATE = "PRIVATE",
|
3 |
+
PENDING = "PENDING",
|
4 |
+
APPROVED = "APPROVED",
|
5 |
+
DENIED = "DENIED",
|
6 |
+
}
|
src/lib/types/Tool.ts
CHANGED
@@ -4,6 +4,7 @@ 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"),
|
@@ -122,7 +123,7 @@ export interface CommunityTool extends BaseTool, Timestamps {
|
|
122 |
useCount: number;
|
123 |
last24HoursUseCount: number;
|
124 |
|
125 |
-
|
126 |
searchTokens: string[];
|
127 |
}
|
128 |
|
@@ -136,7 +137,7 @@ export type CommunityToolEditable = Omit<
|
|
136 |
| "last24HoursUseCount"
|
137 |
| "createdById"
|
138 |
| "createdByName"
|
139 |
-
| "
|
140 |
| "searchTokens"
|
141 |
| "type"
|
142 |
| "createdAt"
|
|
|
4 |
import type { BackendToolContext } from "$lib/server/tools";
|
5 |
import type { MessageUpdate } from "./MessageUpdate";
|
6 |
import { z } from "zod";
|
7 |
+
import type { ReviewStatus } from "./Review";
|
8 |
|
9 |
export const ToolColor = z.union([
|
10 |
z.literal("purple"),
|
|
|
123 |
useCount: number;
|
124 |
last24HoursUseCount: number;
|
125 |
|
126 |
+
review: ReviewStatus;
|
127 |
searchTokens: string[];
|
128 |
}
|
129 |
|
|
|
137 |
| "last24HoursUseCount"
|
138 |
| "createdById"
|
139 |
| "createdByName"
|
140 |
+
| "review"
|
141 |
| "searchTokens"
|
142 |
| "type"
|
143 |
| "createdAt"
|
src/routes/+layout.server.ts
CHANGED
@@ -11,6 +11,7 @@ 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);
|
@@ -221,7 +222,7 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
|
|
221 |
),
|
222 |
communityToolCount: await collections.tools.countDocuments({
|
223 |
type: "community",
|
224 |
-
|
225 |
}),
|
226 |
assistants: assistants
|
227 |
.filter((el) => userAssistantsSet.has(el._id.toString()))
|
|
|
11 |
import { toolFromConfigs } from "$lib/server/tools";
|
12 |
import { MetricsServer } from "$lib/server/metrics";
|
13 |
import type { ToolFront, ToolInputFile } from "$lib/types/Tool";
|
14 |
+
import { ReviewStatus } from "$lib/types/Review";
|
15 |
|
16 |
export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
|
17 |
depends(UrlDependency.ConversationList);
|
|
|
222 |
),
|
223 |
communityToolCount: await collections.tools.countDocuments({
|
224 |
type: "community",
|
225 |
+
review: ReviewStatus.APPROVED,
|
226 |
}),
|
227 |
assistants: assistants
|
228 |
.filter((el) => userAssistantsSet.has(el._id.toString()))
|
src/routes/api/assistants/+server.ts
CHANGED
@@ -4,6 +4,7 @@ import type { User } from "$lib/types/User";
|
|
4 |
import { generateQueryTokens } from "$lib/utils/searchTokens.js";
|
5 |
import type { Filter } from "mongodb";
|
6 |
import { env } from "$env/dynamic/private";
|
|
|
7 |
|
8 |
const NUM_PER_PAGE = 24;
|
9 |
|
@@ -27,7 +28,7 @@ export async function GET({ url, locals }) {
|
|
27 |
|
28 |
// if there is no user, we show community assistants, so only show featured assistants
|
29 |
const shouldBeFeatured =
|
30 |
-
env.REQUIRE_FEATURED_ASSISTANTS === "true" && !user ? {
|
31 |
|
32 |
// if the user queried is not the current user, only show "public" assistants that have been shared before
|
33 |
const shouldHaveBeenShared =
|
|
|
4 |
import { generateQueryTokens } from "$lib/utils/searchTokens.js";
|
5 |
import type { Filter } from "mongodb";
|
6 |
import { env } from "$env/dynamic/private";
|
7 |
+
import { ReviewStatus } from "$lib/types/Review";
|
8 |
|
9 |
const NUM_PER_PAGE = 24;
|
10 |
|
|
|
28 |
|
29 |
// if there is no user, we show community assistants, so only show featured assistants
|
30 |
const shouldBeFeatured =
|
31 |
+
env.REQUIRE_FEATURED_ASSISTANTS === "true" && !user ? { review: ReviewStatus.APPROVED } : {};
|
32 |
|
33 |
// if the user queried is not the current user, only show "public" assistants that have been shared before
|
34 |
const shouldHaveBeenShared =
|
src/routes/api/tools/[toolId]/+server.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import { env } from "$env/dynamic/private";
|
2 |
import { collections } from "$lib/server/database.js";
|
3 |
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
|
|
4 |
import type { CommunityToolDB } from "$lib/types/Tool.js";
|
5 |
import { ObjectId } from "mongodb";
|
6 |
|
@@ -33,12 +34,12 @@ export async function GET({ params }) {
|
|
33 |
color: tool.color,
|
34 |
icon: tool.icon,
|
35 |
createdByName: tool.createdByName,
|
36 |
-
|
37 |
}
|
38 |
: undefined
|
39 |
);
|
40 |
|
41 |
-
if (!tool ||
|
42 |
return new Response(`Tool "${toolId}" not found`, { status: 404 });
|
43 |
}
|
44 |
|
|
|
1 |
import { env } from "$env/dynamic/private";
|
2 |
import { collections } from "$lib/server/database.js";
|
3 |
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
4 |
+
import { ReviewStatus } from "$lib/types/Review";
|
5 |
import type { CommunityToolDB } from "$lib/types/Tool.js";
|
6 |
import { ObjectId } from "mongodb";
|
7 |
|
|
|
34 |
color: tool.color,
|
35 |
icon: tool.icon,
|
36 |
createdByName: tool.createdByName,
|
37 |
+
review: tool.review,
|
38 |
}
|
39 |
: undefined
|
40 |
);
|
41 |
|
42 |
+
if (!tool || tool.review !== ReviewStatus.APPROVED) {
|
43 |
return new Response(`Tool "${toolId}" not found`, { status: 404 });
|
44 |
}
|
45 |
|
src/routes/api/tools/search/+server.ts
CHANGED
@@ -4,7 +4,7 @@ import { toolFromConfigs } from "$lib/server/tools/index.js";
|
|
4 |
import type { BaseTool, CommunityToolDB } from "$lib/types/Tool.js";
|
5 |
import { generateQueryTokens, generateSearchTokens } from "$lib/utils/searchTokens.js";
|
6 |
import type { Filter } from "mongodb";
|
7 |
-
|
8 |
export async function GET({ url }) {
|
9 |
if (env.COMMUNITY_TOOLS !== "true") {
|
10 |
return new Response("Community tools are not enabled", { status: 403 });
|
@@ -15,7 +15,7 @@ export async function GET({ url }) {
|
|
15 |
|
16 |
const filter: Filter<CommunityToolDB> = {
|
17 |
...(queryTokens && { searchTokens: { $all: queryTokens } }),
|
18 |
-
|
19 |
};
|
20 |
|
21 |
const matchingCommunityTools = await collections.tools
|
|
|
4 |
import type { BaseTool, CommunityToolDB } from "$lib/types/Tool.js";
|
5 |
import { generateQueryTokens, generateSearchTokens } from "$lib/utils/searchTokens.js";
|
6 |
import type { Filter } from "mongodb";
|
7 |
+
import { ReviewStatus } from "$lib/types/Review";
|
8 |
export async function GET({ url }) {
|
9 |
if (env.COMMUNITY_TOOLS !== "true") {
|
10 |
return new Response("Community tools are not enabled", { status: 403 });
|
|
|
15 |
|
16 |
const filter: Filter<CommunityToolDB> = {
|
17 |
...(queryTokens && { searchTokens: { $all: queryTokens } }),
|
18 |
+
review: ReviewStatus.APPROVED,
|
19 |
};
|
20 |
|
21 |
const matchingCommunityTools = await collections.tools
|
src/routes/assistants/+page.server.ts
CHANGED
@@ -6,7 +6,7 @@ import type { User } from "$lib/types/User";
|
|
6 |
import { generateQueryTokens } from "$lib/utils/searchTokens.js";
|
7 |
import { error, redirect } from "@sveltejs/kit";
|
8 |
import type { Filter } from "mongodb";
|
9 |
-
|
10 |
const NUM_PER_PAGE = 24;
|
11 |
|
12 |
export const load = async ({ url, locals }) => {
|
@@ -36,7 +36,7 @@ export const load = async ({ url, locals }) => {
|
|
36 |
// if there is no user, we show community assistants, so only show featured assistants
|
37 |
const shouldBeFeatured =
|
38 |
env.REQUIRE_FEATURED_ASSISTANTS === "true" && !user && !(locals.user?.isAdmin && showUnfeatured)
|
39 |
-
? {
|
40 |
: {};
|
41 |
|
42 |
// if the user queried is not the current user, only show "public" assistants that have been shared before
|
|
|
6 |
import { generateQueryTokens } from "$lib/utils/searchTokens.js";
|
7 |
import { error, redirect } from "@sveltejs/kit";
|
8 |
import type { Filter } from "mongodb";
|
9 |
+
import { ReviewStatus } from "$lib/types/Review";
|
10 |
const NUM_PER_PAGE = 24;
|
11 |
|
12 |
export const load = async ({ url, locals }) => {
|
|
|
36 |
// if there is no user, we show community assistants, so only show featured assistants
|
37 |
const shouldBeFeatured =
|
38 |
env.REQUIRE_FEATURED_ASSISTANTS === "true" && !user && !(locals.user?.isAdmin && showUnfeatured)
|
39 |
+
? { review: ReviewStatus.APPROVED }
|
40 |
: {};
|
41 |
|
42 |
// if the user queried is not the current user, only show "public" assistants that have been shared before
|
src/routes/assistants/+page.svelte
CHANGED
@@ -25,6 +25,7 @@
|
|
25 |
import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
26 |
import { isDesktop } from "$lib/utils/isDesktop";
|
27 |
import { SortKey } from "$lib/types/Assistant";
|
|
|
28 |
|
29 |
export let data: PageData;
|
30 |
|
@@ -245,7 +246,7 @@
|
|
245 |
|
246 |
<button
|
247 |
class="relative flex flex-col items-center justify-center overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 py-6 text-center shadow hover:bg-gray-50 hover:shadow-inner dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40 max-sm:px-4 sm:h-64 sm:pb-4 xl:pt-8
|
248 |
-
{!assistant.
|
249 |
on:click={() => {
|
250 |
if (data.settings.assistants.includes(assistant._id.toString())) {
|
251 |
settings.instantSet({ activeModel: assistant._id.toString() });
|
|
|
25 |
import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
26 |
import { isDesktop } from "$lib/utils/isDesktop";
|
27 |
import { SortKey } from "$lib/types/Assistant";
|
28 |
+
import { ReviewStatus } from "$lib/types/Review";
|
29 |
|
30 |
export let data: PageData;
|
31 |
|
|
|
246 |
|
247 |
<button
|
248 |
class="relative flex flex-col items-center justify-center overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 py-6 text-center shadow hover:bg-gray-50 hover:shadow-inner dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40 max-sm:px-4 sm:h-64 sm:pb-4 xl:pt-8
|
249 |
+
{!(assistant.review === ReviewStatus.APPROVED) && !createdByMe ? 'border !border-red-500/30' : ''}"
|
250 |
on:click={() => {
|
251 |
if (data.settings.assistants.includes(assistant._id.toString())) {
|
252 |
settings.instantSet({ activeModel: assistant._id.toString() });
|
src/routes/settings/(nav)/assistants/[assistantId]/+page.server.ts
CHANGED
@@ -7,7 +7,8 @@ import { env as envPublic } from "$env/dynamic/public";
|
|
7 |
import { env } from "$env/dynamic/private";
|
8 |
import { z } from "zod";
|
9 |
import type { Assistant } from "$lib/types/Assistant";
|
10 |
-
import {
|
|
|
11 |
|
12 |
async function assistantOnlyIfAuthor(locals: App.Locals, assistantId?: string) {
|
13 |
const assistant = await collections.assistants.findOne({ _id: new ObjectId(assistantId) });
|
@@ -104,21 +105,11 @@ export const actions: Actions = {
|
|
104 |
|
105 |
const username = locals.user?.username;
|
106 |
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
body: JSON.stringify({
|
113 |
-
text: `Assistant <${assistantUrl}|${assistant?.name}> reported by ${
|
114 |
-
username ? `<http://hf.co/${username}|${username}>` : "non-logged in user"
|
115 |
-
}.\n\n> ${result.data}`,
|
116 |
-
}),
|
117 |
-
});
|
118 |
-
|
119 |
-
if (!res.ok) {
|
120 |
-
logger.error(`Webhook assistant report failed. ${res.statusText} ${res.text}`);
|
121 |
-
}
|
122 |
}
|
123 |
|
124 |
return { from: "report", ok: true, message: "Assistant reported" };
|
@@ -172,54 +163,105 @@ export const actions: Actions = {
|
|
172 |
|
173 |
redirect(302, `${base}/settings`);
|
174 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
|
|
|
|
193 |
|
194 |
-
|
195 |
-
|
196 |
-
}
|
197 |
|
198 |
-
|
199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
|
210 |
-
|
211 |
-
|
212 |
-
|
|
|
213 |
|
214 |
-
const
|
215 |
-
{ _id: assistant._id },
|
216 |
-
{ $set: { featured: true } }
|
217 |
-
);
|
218 |
|
219 |
-
|
220 |
-
|
221 |
-
|
|
|
|
|
|
|
222 |
|
223 |
-
|
224 |
-
|
225 |
-
};
|
|
|
7 |
import { env } from "$env/dynamic/private";
|
8 |
import { z } from "zod";
|
9 |
import type { Assistant } from "$lib/types/Assistant";
|
10 |
+
import { ReviewStatus } from "$lib/types/Review";
|
11 |
+
import { sendSlack } from "$lib/server/sendSlack";
|
12 |
|
13 |
async function assistantOnlyIfAuthor(locals: App.Locals, assistantId?: string) {
|
14 |
const assistant = await collections.assistants.findOne({ _id: new ObjectId(assistantId) });
|
|
|
105 |
|
106 |
const username = locals.user?.username;
|
107 |
|
108 |
+
await sendSlack(
|
109 |
+
`🔴 Assistant <${assistantUrl}|${assistant?.name}> reported by ${
|
110 |
+
username ? `<http://hf.co/${username}|${username}>` : "non-logged in user"
|
111 |
+
}.\n\n> ${result.data}`
|
112 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
}
|
114 |
|
115 |
return { from: "report", ok: true, message: "Assistant reported" };
|
|
|
163 |
|
164 |
redirect(302, `${base}/settings`);
|
165 |
},
|
166 |
+
deny: async ({ params, locals, url }) => {
|
167 |
+
return await setReviewStatus({
|
168 |
+
assistantId: params.assistantId,
|
169 |
+
locals,
|
170 |
+
status: ReviewStatus.DENIED,
|
171 |
+
url,
|
172 |
+
});
|
173 |
+
},
|
174 |
+
approve: async ({ params, locals, url }) => {
|
175 |
+
return await setReviewStatus({
|
176 |
+
assistantId: params.assistantId,
|
177 |
+
locals,
|
178 |
+
status: ReviewStatus.APPROVED,
|
179 |
+
url,
|
180 |
+
});
|
181 |
+
},
|
182 |
+
request: async ({ params, locals, url }) => {
|
183 |
+
return await setReviewStatus({
|
184 |
+
assistantId: params.assistantId,
|
185 |
+
locals,
|
186 |
+
status: ReviewStatus.PENDING,
|
187 |
+
url,
|
188 |
+
});
|
189 |
+
},
|
190 |
+
unrequest: async ({ params, locals, url }) => {
|
191 |
+
return await setReviewStatus({
|
192 |
+
assistantId: params.assistantId,
|
193 |
+
locals,
|
194 |
+
status: ReviewStatus.PRIVATE,
|
195 |
+
url,
|
196 |
+
});
|
197 |
+
},
|
198 |
+
};
|
199 |
|
200 |
+
async function setReviewStatus({
|
201 |
+
locals,
|
202 |
+
assistantId,
|
203 |
+
status,
|
204 |
+
url,
|
205 |
+
}: {
|
206 |
+
locals: App.Locals;
|
207 |
+
assistantId?: string;
|
208 |
+
status: ReviewStatus;
|
209 |
+
url: URL;
|
210 |
+
}) {
|
211 |
+
if (!assistantId) {
|
212 |
+
return fail(400, { error: true, message: "Assistant ID is required" });
|
213 |
+
}
|
214 |
|
215 |
+
const assistant = await collections.assistants.findOne({
|
216 |
+
_id: new ObjectId(assistantId),
|
217 |
+
});
|
218 |
|
219 |
+
if (!assistant) {
|
220 |
+
return fail(404, { error: true, message: "Assistant not found" });
|
221 |
+
}
|
222 |
|
223 |
+
if (
|
224 |
+
!locals.user ||
|
225 |
+
(!locals.user.isAdmin && assistant.createdById.toString() !== locals.user._id.toString())
|
226 |
+
) {
|
227 |
+
return fail(403, { error: true, message: "Permission denied" });
|
228 |
+
}
|
229 |
|
230 |
+
// only admins can set the status to APPROVED or DENIED
|
231 |
+
// if the status is already APPROVED or DENIED, only admins can change it
|
|
|
232 |
|
233 |
+
if (
|
234 |
+
(status === ReviewStatus.APPROVED ||
|
235 |
+
status === ReviewStatus.DENIED ||
|
236 |
+
assistant.review === ReviewStatus.APPROVED ||
|
237 |
+
assistant.review === ReviewStatus.DENIED) &&
|
238 |
+
!locals.user?.isAdmin
|
239 |
+
) {
|
240 |
+
return fail(403, { error: true, message: "Permission denied" });
|
241 |
+
}
|
242 |
|
243 |
+
const result = await collections.assistants.updateOne(
|
244 |
+
{ _id: assistant._id },
|
245 |
+
{ $set: { review: status } }
|
246 |
+
);
|
247 |
|
248 |
+
if (result.modifiedCount === 0) {
|
249 |
+
return fail(500, { error: true, message: "Failed to update review status" });
|
250 |
+
}
|
251 |
|
252 |
+
if (status === ReviewStatus.PENDING) {
|
253 |
+
const prefixUrl =
|
254 |
+
envPublic.PUBLIC_SHARE_PREFIX || `${envPublic.PUBLIC_ORIGIN || url.origin}${base}`;
|
255 |
+
const assistantUrl = `${prefixUrl}/assistant/${assistantId}`;
|
256 |
|
257 |
+
const username = locals.user?.username;
|
|
|
|
|
|
|
258 |
|
259 |
+
await sendSlack(
|
260 |
+
`🟢 Assistant <${assistantUrl}|${assistant?.name}> requested to be featured by ${
|
261 |
+
username ? `<http://hf.co/${username}|${username}>` : "non-logged in user"
|
262 |
+
}.`
|
263 |
+
);
|
264 |
+
}
|
265 |
|
266 |
+
return { from: "setReviewStatus", ok: true, message: "Review status updated" };
|
267 |
+
}
|
|
src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte
CHANGED
@@ -15,11 +15,13 @@
|
|
15 |
import CarbonChat from "~icons/carbon/chat";
|
16 |
import CarbonStar from "~icons/carbon/star";
|
17 |
import CarbonTools from "~icons/carbon/tools";
|
|
|
18 |
|
19 |
import CopyToClipBoardBtn from "$lib/components/CopyToClipBoardBtn.svelte";
|
20 |
import ReportModal from "./ReportModal.svelte";
|
21 |
import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
22 |
import ToolBadge from "$lib/components/ToolBadge.svelte";
|
|
|
23 |
|
24 |
export let data: PageData;
|
25 |
|
@@ -151,24 +153,51 @@
|
|
151 |
{/if}
|
152 |
{/if}
|
153 |
{#if data?.user?.isAdmin}
|
154 |
-
|
155 |
-
<
|
156 |
-
<CarbonTrash class="mr-1.5 inline text-xs" />Delete</button
|
157 |
-
>
|
158 |
-
</form>
|
159 |
-
{#if assistant?.featured}
|
160 |
-
<form method="POST" action="?/unfeature" use:enhance>
|
161 |
<button type="submit" class="flex items-center text-red-600 underline">
|
162 |
-
<CarbonTrash class="mr-1.5 inline text-xs" />
|
163 |
>
|
164 |
</form>
|
165 |
-
{
|
166 |
-
|
|
|
167 |
<button type="submit" class="flex items-center text-green-600 underline">
|
168 |
-
<CarbonStar class="mr-1.5 inline text-xs" />
|
169 |
>
|
170 |
</form>
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
{/if}
|
173 |
</div>
|
174 |
</div>
|
|
|
15 |
import CarbonChat from "~icons/carbon/chat";
|
16 |
import CarbonStar from "~icons/carbon/star";
|
17 |
import CarbonTools from "~icons/carbon/tools";
|
18 |
+
import CarbonLock from "~icons/carbon/locked";
|
19 |
|
20 |
import CopyToClipBoardBtn from "$lib/components/CopyToClipBoardBtn.svelte";
|
21 |
import ReportModal from "./ReportModal.svelte";
|
22 |
import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
23 |
import ToolBadge from "$lib/components/ToolBadge.svelte";
|
24 |
+
import { ReviewStatus } from "$lib/types/Review";
|
25 |
|
26 |
export let data: PageData;
|
27 |
|
|
|
153 |
{/if}
|
154 |
{/if}
|
155 |
{#if data?.user?.isAdmin}
|
156 |
+
{#if !assistant?.createdByMe}
|
157 |
+
<form method="POST" action="?/delete" use:enhance>
|
|
|
|
|
|
|
|
|
|
|
158 |
<button type="submit" class="flex items-center text-red-600 underline">
|
159 |
+
<CarbonTrash class="mr-1.5 inline text-xs" />Delete</button
|
160 |
>
|
161 |
</form>
|
162 |
+
{/if}
|
163 |
+
{#if assistant?.review === ReviewStatus.PENDING}
|
164 |
+
<form method="POST" action="?/approve" use:enhance>
|
165 |
<button type="submit" class="flex items-center text-green-600 underline">
|
166 |
+
<CarbonStar class="mr-1.5 inline text-xs" />Approve</button
|
167 |
>
|
168 |
</form>
|
169 |
+
<form method="POST" action="?/deny" use:enhance>
|
170 |
+
<button type="submit" class="flex items-center text-red-600">
|
171 |
+
<span class="mr-1.5 font-light no-underline">X</span>
|
172 |
+
<span class="underline">Deny</span>
|
173 |
+
</button>
|
174 |
+
</form>
|
175 |
{/if}
|
176 |
+
{#if assistant?.review === ReviewStatus.APPROVED || assistant?.review === ReviewStatus.DENIED}
|
177 |
+
<form method="POST" action="?/unrequest" use:enhance>
|
178 |
+
<button type="submit" class="flex items-center text-red-600 underline">
|
179 |
+
<CarbonLock class="mr-1.5 inline text-xs " />Make private</button
|
180 |
+
>
|
181 |
+
</form>
|
182 |
+
{/if}
|
183 |
+
{/if}
|
184 |
+
{#if assistant?.createdByMe && assistant?.review === ReviewStatus.PRIVATE}
|
185 |
+
<form
|
186 |
+
method="POST"
|
187 |
+
action="?/request"
|
188 |
+
use:enhance={async ({ cancel }) => {
|
189 |
+
const confirmed = confirm(
|
190 |
+
"Are you sure you want to request this assistant to be featured? Make sure you have tried the assistant and that it works as expected. "
|
191 |
+
);
|
192 |
+
if (!confirmed) {
|
193 |
+
cancel();
|
194 |
+
}
|
195 |
+
}}
|
196 |
+
>
|
197 |
+
<button type="submit" class="flex items-center underline">
|
198 |
+
<CarbonStar class="mr-1.5 inline text-xs" />Request to be featured</button
|
199 |
+
>
|
200 |
+
</form>
|
201 |
{/if}
|
202 |
</div>
|
203 |
</div>
|
src/routes/settings/(nav)/assistants/new/+page.server.ts
CHANGED
@@ -11,6 +11,7 @@ import { parseStringToList } from "$lib/utils/parseStringToList";
|
|
11 |
import { usageLimits } from "$lib/server/usageLimits";
|
12 |
import { generateSearchTokens } from "$lib/utils/searchTokens";
|
13 |
import { toolFromConfigs } from "$lib/server/tools";
|
|
|
14 |
|
15 |
const newAsssistantSchema = z.object({
|
16 |
name: z.string().min(1),
|
@@ -148,7 +149,7 @@ export const actions: Actions = {
|
|
148 |
createdAt: new Date(),
|
149 |
updatedAt: new Date(),
|
150 |
userCount: 1,
|
151 |
-
|
152 |
rag: {
|
153 |
allowedLinks: parse.data.ragLinkList,
|
154 |
allowedDomains: parse.data.ragDomainList,
|
|
|
11 |
import { usageLimits } from "$lib/server/usageLimits";
|
12 |
import { generateSearchTokens } from "$lib/utils/searchTokens";
|
13 |
import { toolFromConfigs } from "$lib/server/tools";
|
14 |
+
import { ReviewStatus } from "$lib/types/Review";
|
15 |
|
16 |
const newAsssistantSchema = z.object({
|
17 |
name: z.string().min(1),
|
|
|
149 |
createdAt: new Date(),
|
150 |
updatedAt: new Date(),
|
151 |
userCount: 1,
|
152 |
+
review: ReviewStatus.PRIVATE,
|
153 |
rag: {
|
154 |
allowedLinks: parse.data.ragLinkList,
|
155 |
allowedDomains: parse.data.ragDomainList,
|
src/routes/tools/+page.server.ts
CHANGED
@@ -3,6 +3,7 @@ import { authCondition } from "$lib/server/auth.js";
|
|
3 |
import { Database, collections } from "$lib/server/database.js";
|
4 |
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
5 |
import { SortKey } from "$lib/types/Assistant.js";
|
|
|
6 |
import type { CommunityToolDB } from "$lib/types/Tool.js";
|
7 |
import type { User } from "$lib/types/User.js";
|
8 |
import { generateQueryTokens, generateSearchTokens } from "$lib/utils/searchTokens.js";
|
@@ -47,7 +48,7 @@ export const load = async ({ url, locals }) => {
|
|
47 |
const filter: Filter<CommunityToolDB> = {
|
48 |
...(!createdByCurrentUser &&
|
49 |
!activeOnly &&
|
50 |
-
!(locals.user?.isAdmin && showUnfeatured) && {
|
51 |
...(user && { createdById: user._id }),
|
52 |
...(queryTokens && { searchTokens: { $all: queryTokens } }),
|
53 |
...(activeOnly && {
|
|
|
3 |
import { Database, collections } from "$lib/server/database.js";
|
4 |
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
5 |
import { SortKey } from "$lib/types/Assistant.js";
|
6 |
+
import { ReviewStatus } from "$lib/types/Review";
|
7 |
import type { CommunityToolDB } from "$lib/types/Tool.js";
|
8 |
import type { User } from "$lib/types/User.js";
|
9 |
import { generateQueryTokens, generateSearchTokens } from "$lib/utils/searchTokens.js";
|
|
|
48 |
const filter: Filter<CommunityToolDB> = {
|
49 |
...(!createdByCurrentUser &&
|
50 |
!activeOnly &&
|
51 |
+
!(locals.user?.isAdmin && showUnfeatured) && { review: ReviewStatus.APPROVED }),
|
52 |
...(user && { createdById: user._id }),
|
53 |
...(queryTokens && { searchTokens: { $all: queryTokens } }),
|
54 |
...(activeOnly && {
|
src/routes/tools/+page.svelte
CHANGED
@@ -19,6 +19,7 @@
|
|
19 |
import { isDesktop } from "$lib/utils/isDesktop";
|
20 |
import { SortKey } from "$lib/types/Assistant";
|
21 |
import ToolLogo from "$lib/components/ToolLogo.svelte";
|
|
|
22 |
|
23 |
export let data: PageData;
|
24 |
|
@@ -233,8 +234,9 @@
|
|
233 |
{@const isOfficial = !tool.createdByName}
|
234 |
<a
|
235 |
href="{base}/tools/{tool._id.toString()}"
|
236 |
-
class="relative flex flex-row items-center gap-4 overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 text-center shadow hover:bg-gray-50 hover:shadow-inner dark:bg-gray-950/20 dark:hover:bg-gray-950/40 max-sm:px-4 sm:h-24 {!
|
237 |
-
|
|
|
238 |
? ' border-red-500/30'
|
239 |
: 'dark:border-gray-800/70'}"
|
240 |
class:!border-blue-600={isActive}
|
|
|
19 |
import { isDesktop } from "$lib/utils/isDesktop";
|
20 |
import { SortKey } from "$lib/types/Assistant";
|
21 |
import ToolLogo from "$lib/components/ToolLogo.svelte";
|
22 |
+
import { ReviewStatus } from "$lib/types/Review";
|
23 |
|
24 |
export let data: PageData;
|
25 |
|
|
|
234 |
{@const isOfficial = !tool.createdByName}
|
235 |
<a
|
236 |
href="{base}/tools/{tool._id.toString()}"
|
237 |
+
class="relative flex flex-row items-center gap-4 overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 text-center shadow hover:bg-gray-50 hover:shadow-inner dark:bg-gray-950/20 dark:hover:bg-gray-950/40 max-sm:px-4 sm:h-24 {!(
|
238 |
+
tool.review === ReviewStatus.APPROVED
|
239 |
+
) && !isOfficial
|
240 |
? ' border-red-500/30'
|
241 |
: 'dark:border-gray-800/70'}"
|
242 |
class:!border-blue-600={isActive}
|
src/routes/tools/[toolId]/+layout.server.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import { base } from "$app/paths";
|
2 |
import { collections } from "$lib/server/database.js";
|
3 |
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
|
|
4 |
import { redirect } from "@sveltejs/kit";
|
5 |
import { ObjectId } from "mongodb";
|
6 |
|
@@ -21,7 +22,7 @@ export const load = async ({ params, locals }) => {
|
|
21 |
createdByName: null,
|
22 |
createdByMe: false,
|
23 |
reported: false,
|
24 |
-
|
25 |
},
|
26 |
};
|
27 |
}
|
|
|
1 |
import { base } from "$app/paths";
|
2 |
import { collections } from "$lib/server/database.js";
|
3 |
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
4 |
+
import { ReviewStatus } from "$lib/types/Review.js";
|
5 |
import { redirect } from "@sveltejs/kit";
|
6 |
import { ObjectId } from "mongodb";
|
7 |
|
|
|
22 |
createdByName: null,
|
23 |
createdByMe: false,
|
24 |
reported: false,
|
25 |
+
review: ReviewStatus.APPROVED,
|
26 |
},
|
27 |
};
|
28 |
}
|
src/routes/tools/[toolId]/+page.server.ts
CHANGED
@@ -2,7 +2,8 @@ import { base } from "$app/paths";
|
|
2 |
import { env } from "$env/dynamic/private";
|
3 |
import { env as envPublic } from "$env/dynamic/public";
|
4 |
import { collections } from "$lib/server/database";
|
5 |
-
import {
|
|
|
6 |
import type { Tool } from "$lib/types/Tool";
|
7 |
import { fail, redirect, type Actions } from "@sveltejs/kit";
|
8 |
import { ObjectId } from "mongodb";
|
@@ -103,64 +104,111 @@ export const actions: Actions = {
|
|
103 |
|
104 |
const username = locals.user?.username;
|
105 |
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
body: JSON.stringify({
|
112 |
-
text: `Tool <${toolUrl}|${tool?.displayName}> reported by ${
|
113 |
-
username ? `<http://hf.co/${username}|${username}>` : "non-logged in user"
|
114 |
-
}.\n\n> ${result.data}`,
|
115 |
-
}),
|
116 |
-
});
|
117 |
-
|
118 |
-
if (!res.ok) {
|
119 |
-
logger.error(`Webhook tool report failed. ${res.statusText} ${res.text}`);
|
120 |
-
}
|
121 |
}
|
122 |
|
123 |
return { from: "report", ok: true, message: "Tool reported" };
|
124 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
|
|
|
|
143 |
|
144 |
-
|
145 |
-
|
146 |
-
}
|
147 |
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
|
|
|
|
|
|
154 |
|
155 |
-
|
156 |
-
{ _id: new ObjectId(params.toolId) },
|
157 |
-
{ $set: { featured: true } }
|
158 |
-
);
|
159 |
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
|
164 |
-
|
165 |
-
|
166 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import { env } from "$env/dynamic/private";
|
3 |
import { env as envPublic } from "$env/dynamic/public";
|
4 |
import { collections } from "$lib/server/database";
|
5 |
+
import { sendSlack } from "$lib/server/sendSlack";
|
6 |
+
import { ReviewStatus } from "$lib/types/Review";
|
7 |
import type { Tool } from "$lib/types/Tool";
|
8 |
import { fail, redirect, type Actions } from "@sveltejs/kit";
|
9 |
import { ObjectId } from "mongodb";
|
|
|
104 |
|
105 |
const username = locals.user?.username;
|
106 |
|
107 |
+
await sendSlack(
|
108 |
+
`🔴 Tool <${toolUrl}|${tool?.displayName}> reported by ${
|
109 |
+
username ? `<http://hf.co/${username}|${username}>` : "non-logged in user"
|
110 |
+
}.\n\n> ${result.data}`
|
111 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
}
|
113 |
|
114 |
return { from: "report", ok: true, message: "Tool reported" };
|
115 |
},
|
116 |
+
deny: async ({ params, locals, url }) => {
|
117 |
+
return await setReviewStatus({
|
118 |
+
toolId: params.toolId,
|
119 |
+
locals,
|
120 |
+
status: ReviewStatus.DENIED,
|
121 |
+
url,
|
122 |
+
});
|
123 |
+
},
|
124 |
+
approve: async ({ params, locals, url }) => {
|
125 |
+
return await setReviewStatus({
|
126 |
+
toolId: params.toolId,
|
127 |
+
locals,
|
128 |
+
status: ReviewStatus.APPROVED,
|
129 |
+
url,
|
130 |
+
});
|
131 |
+
},
|
132 |
+
request: async ({ params, locals, url }) => {
|
133 |
+
return await setReviewStatus({
|
134 |
+
toolId: params.toolId,
|
135 |
+
locals,
|
136 |
+
status: ReviewStatus.PENDING,
|
137 |
+
url,
|
138 |
+
});
|
139 |
+
},
|
140 |
+
unrequest: async ({ params, locals, url }) => {
|
141 |
+
return await setReviewStatus({
|
142 |
+
toolId: params.toolId,
|
143 |
+
locals,
|
144 |
+
status: ReviewStatus.PRIVATE,
|
145 |
+
url,
|
146 |
+
});
|
147 |
+
},
|
148 |
+
};
|
149 |
|
150 |
+
async function setReviewStatus({
|
151 |
+
locals,
|
152 |
+
toolId,
|
153 |
+
status,
|
154 |
+
url,
|
155 |
+
}: {
|
156 |
+
locals: App.Locals;
|
157 |
+
toolId?: string;
|
158 |
+
status: ReviewStatus;
|
159 |
+
url: URL;
|
160 |
+
}) {
|
161 |
+
if (!toolId) {
|
162 |
+
return fail(400, { error: true, message: "Tool ID is required" });
|
163 |
+
}
|
164 |
|
165 |
+
const tool = await collections.tools.findOne({
|
166 |
+
_id: new ObjectId(toolId),
|
167 |
+
});
|
168 |
|
169 |
+
if (!tool) {
|
170 |
+
return fail(404, { error: true, message: "Tool not found" });
|
171 |
+
}
|
172 |
|
173 |
+
if (
|
174 |
+
!locals.user ||
|
175 |
+
(!locals.user.isAdmin && tool.createdById.toString() !== locals.user._id.toString())
|
176 |
+
) {
|
177 |
+
return fail(403, { error: true, message: "Permission denied" });
|
178 |
+
}
|
179 |
|
180 |
+
// only admins can set the status to APPROVED or DENIED
|
181 |
+
// if the status is already APPROVED or DENIED, only admins can change it
|
|
|
182 |
|
183 |
+
if (
|
184 |
+
(status === ReviewStatus.APPROVED ||
|
185 |
+
status === ReviewStatus.DENIED ||
|
186 |
+
tool.review === ReviewStatus.APPROVED ||
|
187 |
+
tool.review === ReviewStatus.DENIED) &&
|
188 |
+
!locals.user?.isAdmin
|
189 |
+
) {
|
190 |
+
return fail(403, { error: true, message: "Permission denied" });
|
191 |
+
}
|
192 |
|
193 |
+
const result = await collections.tools.updateOne({ _id: tool._id }, { $set: { review: status } });
|
|
|
|
|
|
|
194 |
|
195 |
+
if (result.modifiedCount === 0) {
|
196 |
+
return fail(500, { error: true, message: "Failed to update review status" });
|
197 |
+
}
|
198 |
|
199 |
+
if (status === ReviewStatus.PENDING) {
|
200 |
+
const prefixUrl =
|
201 |
+
envPublic.PUBLIC_SHARE_PREFIX || `${envPublic.PUBLIC_ORIGIN || url.origin}${base}`;
|
202 |
+
const toolUrl = `${prefixUrl}/tools/${toolId}`;
|
203 |
+
|
204 |
+
const username = locals.user?.username;
|
205 |
+
|
206 |
+
await sendSlack(
|
207 |
+
`🟢 Tool <${toolUrl}|${tool?.displayName}> requested to be featured by ${
|
208 |
+
username ? `<http://hf.co/${username}|${username}>` : "non-logged in user"
|
209 |
+
}.`
|
210 |
+
);
|
211 |
+
}
|
212 |
+
|
213 |
+
return { from: "setReviewStatus", ok: true, message: "Review status updated" };
|
214 |
+
}
|
src/routes/tools/[toolId]/+page.svelte
CHANGED
@@ -6,6 +6,7 @@
|
|
6 |
import ToolLogo from "$lib/components/ToolLogo.svelte";
|
7 |
import { env as envPublic } from "$env/dynamic/public";
|
8 |
import { useSettingsStore } from "$lib/stores/settings";
|
|
|
9 |
|
10 |
import ReportModal from "../../settings/(nav)/assistants/[assistantId]/ReportModal.svelte";
|
11 |
import { applyAction, enhance } from "$app/forms";
|
@@ -17,6 +18,7 @@
|
|
17 |
import CarbonFlag from "~icons/carbon/flag";
|
18 |
import CarbonLink from "~icons/carbon/link";
|
19 |
import CarbonStar from "~icons/carbon/star";
|
|
|
20 |
|
21 |
export let data;
|
22 |
|
@@ -99,7 +101,7 @@
|
|
99 |
<button
|
100 |
class="{isActive
|
101 |
? 'bg-gray-100 text-gray-800'
|
102 |
-
: 'bg-black !text-white'} mx-auto my-2 flex w-
|
103 |
name="Activate model"
|
104 |
on:click|stopPropagation={() => {
|
105 |
if (isActive) {
|
@@ -164,25 +166,52 @@
|
|
164 |
{/if}
|
165 |
{/if}
|
166 |
{#if data?.user?.isAdmin}
|
167 |
-
|
168 |
-
<
|
169 |
-
<CarbonTrash class="mr-1.5 inline text-xs" />Delete</button
|
170 |
-
>
|
171 |
-
</form>
|
172 |
-
{#if data.tool?.featured}
|
173 |
-
<form method="POST" action="?/unfeature" use:enhance>
|
174 |
<button type="submit" class="flex items-center text-red-600 underline">
|
175 |
-
<CarbonTrash class="mr-1.5 inline text-xs" />
|
176 |
>
|
177 |
</form>
|
178 |
-
{
|
179 |
-
|
|
|
180 |
<button type="submit" class="flex items-center text-green-600 underline">
|
181 |
-
<CarbonStar class="mr-1.5 inline text-xs" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
>
|
183 |
</form>
|
184 |
{/if}
|
185 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
186 |
</div>
|
187 |
</div>
|
188 |
</div>
|
|
|
6 |
import ToolLogo from "$lib/components/ToolLogo.svelte";
|
7 |
import { env as envPublic } from "$env/dynamic/public";
|
8 |
import { useSettingsStore } from "$lib/stores/settings";
|
9 |
+
import { ReviewStatus } from "$lib/types/Review";
|
10 |
|
11 |
import ReportModal from "../../settings/(nav)/assistants/[assistantId]/ReportModal.svelte";
|
12 |
import { applyAction, enhance } from "$app/forms";
|
|
|
18 |
import CarbonFlag from "~icons/carbon/flag";
|
19 |
import CarbonLink from "~icons/carbon/link";
|
20 |
import CarbonStar from "~icons/carbon/star";
|
21 |
+
import CarbonLock from "~icons/carbon/locked";
|
22 |
|
23 |
export let data;
|
24 |
|
|
|
101 |
<button
|
102 |
class="{isActive
|
103 |
? 'bg-gray-100 text-gray-800'
|
104 |
+
: 'bg-black !text-white'} mx-auto my-2 flex w-min items-center justify-center rounded-full px-3 py-1 text-base"
|
105 |
name="Activate model"
|
106 |
on:click|stopPropagation={() => {
|
107 |
if (isActive) {
|
|
|
166 |
{/if}
|
167 |
{/if}
|
168 |
{#if data?.user?.isAdmin}
|
169 |
+
{#if !data.tool?.createdByMe}
|
170 |
+
<form method="POST" action="?/delete" use:enhance>
|
|
|
|
|
|
|
|
|
|
|
171 |
<button type="submit" class="flex items-center text-red-600 underline">
|
172 |
+
<CarbonTrash class="mr-1.5 inline text-xs" />Delete</button
|
173 |
>
|
174 |
</form>
|
175 |
+
{/if}
|
176 |
+
{#if data.tool?.review === ReviewStatus.PENDING}
|
177 |
+
<form method="POST" action="?/approve" use:enhance>
|
178 |
<button type="submit" class="flex items-center text-green-600 underline">
|
179 |
+
<CarbonStar class="mr-1.5 inline text-xs" />Approve</button
|
180 |
+
>
|
181 |
+
</form>
|
182 |
+
<form method="POST" action="?/deny" use:enhance>
|
183 |
+
<button type="submit" class="flex items-center text-red-600">
|
184 |
+
<span class="mr-1.5 font-light no-underline">X</span>
|
185 |
+
<span class="underline">Deny</span>
|
186 |
+
</button>
|
187 |
+
</form>
|
188 |
+
{/if}
|
189 |
+
{#if data.tool?.review === ReviewStatus.APPROVED || data.tool?.review === ReviewStatus.DENIED}
|
190 |
+
<form method="POST" action="?/unrequest" use:enhance>
|
191 |
+
<button type="submit" class="flex items-center text-red-600 underline">
|
192 |
+
<CarbonLock class="mr-1.5 inline text-xs " />Make private</button
|
193 |
>
|
194 |
</form>
|
195 |
{/if}
|
196 |
{/if}
|
197 |
+
{#if data.tool?.createdByMe && data.tool?.review === ReviewStatus.PRIVATE}
|
198 |
+
<form
|
199 |
+
method="POST"
|
200 |
+
action="?/request"
|
201 |
+
use:enhance={async ({ cancel }) => {
|
202 |
+
const confirmed = confirm(
|
203 |
+
"Are you sure you want to request this tool to be featured? Make sure you have tried the tool and that it works as expected. We will review your request once submitted."
|
204 |
+
);
|
205 |
+
if (!confirmed) {
|
206 |
+
cancel();
|
207 |
+
}
|
208 |
+
}}
|
209 |
+
>
|
210 |
+
<button type="submit" class="flex items-center underline">
|
211 |
+
<CarbonStar class="mr-1.5 inline text-xs" />Request to be featured</button
|
212 |
+
>
|
213 |
+
</form>
|
214 |
+
{/if}
|
215 |
</div>
|
216 |
</div>
|
217 |
</div>
|
src/routes/tools/new/+page.server.ts
CHANGED
@@ -3,6 +3,7 @@ import { authCondition, requiresUser } from "$lib/server/auth.js";
|
|
3 |
import { collections } from "$lib/server/database.js";
|
4 |
import { editableToolSchema } from "$lib/server/tools/index.js";
|
5 |
import { usageLimits } from "$lib/server/usageLimits.js";
|
|
|
6 |
import { generateSearchTokens } from "$lib/utils/searchTokens.js";
|
7 |
import { error, fail } from "@sveltejs/kit";
|
8 |
import { ObjectId } from "mongodb";
|
@@ -66,7 +67,7 @@ export const actions = {
|
|
66 |
updatedAt: new Date(),
|
67 |
last24HoursUseCount: 0,
|
68 |
useCount: 0,
|
69 |
-
|
70 |
searchTokens: generateSearchTokens(parse.data.displayName),
|
71 |
});
|
72 |
|
|
|
3 |
import { collections } from "$lib/server/database.js";
|
4 |
import { editableToolSchema } from "$lib/server/tools/index.js";
|
5 |
import { usageLimits } from "$lib/server/usageLimits.js";
|
6 |
+
import { ReviewStatus } from "$lib/types/Review";
|
7 |
import { generateSearchTokens } from "$lib/utils/searchTokens.js";
|
8 |
import { error, fail } from "@sveltejs/kit";
|
9 |
import { ObjectId } from "mongodb";
|
|
|
67 |
updatedAt: new Date(),
|
68 |
last24HoursUseCount: 0,
|
69 |
useCount: 0,
|
70 |
+
review: ReviewStatus.PRIVATE,
|
71 |
searchTokens: generateSearchTokens(parse.data.displayName),
|
72 |
});
|
73 |
|