diff --git a/.gitignore b/.gitignore index b7fe63c..4ece70c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ yarn-error.log* next-env.d.ts .idea + +.env.docker \ No newline at end of file diff --git a/README.md b/README.md index 635c34e..8243668 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,12 @@ The gemini-1.5-pro-latest model is a large-scale language model developed by Goo All models that support [Anthropic API](https://docs.anthropic.com/en/docs/models-overview) are supported by [Assistants Hub](https://assistantshub.ai). -| Model Name | Provider | Streaming
Responses | Documents | Functions | -| --------------- | --------- | ------------------------ | ------------------------ | ------------------------ | -| Claude 3 Opus | Anthropic | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | -| Claude 3 Sonnet | Anthropic | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | -| Claude 3 Haiku | Anthropic | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Model Name | Provider | Streaming
Responses | Documents | Functions | +|-------------------| --------- | ------------------------ | ------------------------ | ------------------------ | +| Claude 3 Opus | Anthropic | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Claude 3.5 Sonnet | Anthropic | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Claude 3 Sonnet | Anthropic | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Claude 3 Haiku | Anthropic | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | ### Gorq Cloud diff --git a/package.json b/package.json index b8510e0..7fe5fe4 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "groq-sdk": "^0.3.3", "highlight.js": "^11.9.0", "marked-react": "^2.0.0", - "next": "14.1.1", + "next": "14.2.4", "nodemailer": "^6.9.13", "openai": "^4.42.0", "react": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fca4eba..8fc8db9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ dependencies: version: 0.21.1 '@auth0/nextjs-auth0': specifier: ^3.5.0 - version: 3.5.0(next@14.1.1) + version: 3.5.0(next@14.2.4) '@aws-sdk/client-s3': specifier: ^3.569.0 version: 3.569.0(aws-crt@1.21.2) @@ -28,7 +28,7 @@ dependencies: version: 0.8.0 '@next/third-parties': specifier: ^14.2.3 - version: 14.2.3(next@14.1.1)(react@18.2.0) + version: 14.2.3(next@14.2.4)(react@18.2.0) '@prisma/client': specifier: 5.8.1 version: 5.8.1(prisma@5.8.1) @@ -64,7 +64,7 @@ dependencies: version: 2.2.1 flowbite-react: specifier: ^0.7.2 - version: 0.7.2(@types/react@18.2.47)(esbuild@0.19.11)(next@14.1.1)(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.1) + version: 0.7.2(@types/react@18.2.47)(esbuild@0.19.11)(next@14.2.4)(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.1) google: specifier: link:@next/third-parties/google version: link:@next/third-parties/google @@ -78,8 +78,8 @@ dependencies: specifier: ^2.0.0 version: 2.0.0(react@18.2.0) next: - specifier: 14.1.1 - version: 14.1.1(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 14.2.4 + version: 14.2.4(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) nodemailer: specifier: ^6.9.13 version: 6.9.13 @@ -210,7 +210,7 @@ packages: - encoding dev: false - /@auth0/nextjs-auth0@3.5.0(next@14.1.1): + /@auth0/nextjs-auth0@3.5.0(next@14.2.4): resolution: { integrity: sha512-uFZEE2QQf1zU+jRK2fwqxRQt+WSqDPYF2tnr7d6BEa7b6L6tpPJ3evzoImbWSY1a7gFdvD7RD/Rvrsx7B5CKVg==, @@ -224,7 +224,7 @@ packages: debug: 4.3.4 joi: 17.13.1 jose: 4.15.5 - next: 14.1.1(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.4(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) oauth4webapi: 2.10.4 openid-client: 5.6.4 tslib: 2.6.2 @@ -1954,10 +1954,10 @@ packages: - supports-color dev: false - /@next/env@14.1.1: + /@next/env@14.2.4: resolution: { - integrity: sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==, + integrity: sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==, } dev: false @@ -1970,10 +1970,10 @@ packages: glob: 7.1.7 dev: true - /@next/swc-darwin-arm64@14.1.1: + /@next/swc-darwin-arm64@14.2.4: resolution: { - integrity: sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==, + integrity: sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==, } engines: { node: '>= 10' } cpu: [arm64] @@ -1982,10 +1982,10 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@14.1.1: + /@next/swc-darwin-x64@14.2.4: resolution: { - integrity: sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==, + integrity: sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==, } engines: { node: '>= 10' } cpu: [x64] @@ -1994,10 +1994,10 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@14.1.1: + /@next/swc-linux-arm64-gnu@14.2.4: resolution: { - integrity: sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==, + integrity: sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==, } engines: { node: '>= 10' } cpu: [arm64] @@ -2006,10 +2006,10 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@14.1.1: + /@next/swc-linux-arm64-musl@14.2.4: resolution: { - integrity: sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==, + integrity: sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==, } engines: { node: '>= 10' } cpu: [arm64] @@ -2018,10 +2018,10 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@14.1.1: + /@next/swc-linux-x64-gnu@14.2.4: resolution: { - integrity: sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==, + integrity: sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==, } engines: { node: '>= 10' } cpu: [x64] @@ -2030,10 +2030,10 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@14.1.1: + /@next/swc-linux-x64-musl@14.2.4: resolution: { - integrity: sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==, + integrity: sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==, } engines: { node: '>= 10' } cpu: [x64] @@ -2042,10 +2042,10 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@14.1.1: + /@next/swc-win32-arm64-msvc@14.2.4: resolution: { - integrity: sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==, + integrity: sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==, } engines: { node: '>= 10' } cpu: [arm64] @@ -2054,10 +2054,10 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@14.1.1: + /@next/swc-win32-ia32-msvc@14.2.4: resolution: { - integrity: sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==, + integrity: sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==, } engines: { node: '>= 10' } cpu: [ia32] @@ -2066,10 +2066,10 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@14.1.1: + /@next/swc-win32-x64-msvc@14.2.4: resolution: { - integrity: sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==, + integrity: sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==, } engines: { node: '>= 10' } cpu: [x64] @@ -2078,7 +2078,7 @@ packages: dev: false optional: true - /@next/third-parties@14.2.3(next@14.1.1)(react@18.2.0): + /@next/third-parties@14.2.3(next@14.2.4)(react@18.2.0): resolution: { integrity: sha512-j4E2xBSsEZq4VX2pVm3LpGltSwCxETic6glJWfHyYQvpoMdplCAYrQKpF+E9Gg3jfsrfmRAIdTE11m+biBCx1Q==, @@ -2087,7 +2087,7 @@ packages: next: ^13.0.0 || ^14.0.0 react: ^18.2.0 dependencies: - next: 14.1.1(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.4(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 third-party-capital: 1.0.20 dev: false @@ -3178,12 +3178,20 @@ packages: tslib: 2.6.2 dev: false - /@swc/helpers@0.5.2: + /@swc/counter@0.1.3: resolution: { - integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==, + integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==, + } + dev: false + + /@swc/helpers@0.5.5: + resolution: + { + integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==, } dependencies: + '@swc/counter': 0.1.3 tslib: 2.6.2 dev: false @@ -5590,7 +5598,7 @@ packages: } dev: true - /flowbite-react@0.7.2(@types/react@18.2.47)(esbuild@0.19.11)(next@14.1.1)(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.1): + /flowbite-react@0.7.2(@types/react@18.2.47)(esbuild@0.19.11)(next@14.2.4)(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.1): resolution: { integrity: sha512-Qq+yKW3955ZWjABzNq02NAS91rE86AHjCVRTXHXZ3dkcoGghqViIpsDCxOBo0NO7xnKkshYU3DocmczcP4pYTA==, @@ -5604,7 +5612,7 @@ packages: contentlayer: 0.3.4(esbuild@0.19.11) flowbite: 2.2.1 markdown-toc: 1.2.0 - next-contentlayer: 0.3.4(contentlayer@0.3.4)(esbuild@0.19.11)(next@14.1.1)(react-dom@18.2.0)(react@18.2.0) + next-contentlayer: 0.3.4(contentlayer@0.3.4)(esbuild@0.19.11)(next@14.2.4)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-icons: 4.12.0(react@18.2.0) @@ -8370,7 +8378,7 @@ packages: } dev: true - /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.19.11)(next@14.1.1)(react-dom@18.2.0)(react@18.2.0): + /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.19.11)(next@14.2.4)(react-dom@18.2.0)(react@18.2.0): resolution: { integrity: sha512-UtUCwgAl159KwfhNaOwyiI7Lg6sdioyKMeh+E7jxx0CJ29JuXGxBEYmCI6+72NxFGIFZKx8lvttbbQhbnYWYSw==, @@ -8384,7 +8392,7 @@ packages: '@contentlayer/core': 0.3.4(esbuild@0.19.11) '@contentlayer/utils': 0.3.4 contentlayer: 0.3.4(esbuild@0.19.11) - next: 14.1.1(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.4(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -8394,27 +8402,30 @@ packages: - supports-color dev: false - /next@14.1.1(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0): + /next@14.2.4(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0): resolution: { - integrity: sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==, + integrity: sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==, } engines: { node: '>=18.17.0' } hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 react: ^18.2.0 react-dom: ^18.2.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': optional: true + '@playwright/test': + optional: true sass: optional: true dependencies: - '@next/env': 14.1.1 + '@next/env': 14.2.4 '@opentelemetry/api': 1.7.0 - '@swc/helpers': 0.5.2 + '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001617 graceful-fs: 4.2.11 @@ -8423,15 +8434,15 @@ packages: react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(react@18.2.0) optionalDependencies: - '@next/swc-darwin-arm64': 14.1.1 - '@next/swc-darwin-x64': 14.1.1 - '@next/swc-linux-arm64-gnu': 14.1.1 - '@next/swc-linux-arm64-musl': 14.1.1 - '@next/swc-linux-x64-gnu': 14.1.1 - '@next/swc-linux-x64-musl': 14.1.1 - '@next/swc-win32-arm64-msvc': 14.1.1 - '@next/swc-win32-ia32-msvc': 14.1.1 - '@next/swc-win32-x64-msvc': 14.1.1 + '@next/swc-darwin-arm64': 14.2.4 + '@next/swc-darwin-x64': 14.2.4 + '@next/swc-linux-arm64-gnu': 14.2.4 + '@next/swc-linux-arm64-musl': 14.2.4 + '@next/swc-linux-x64-gnu': 14.2.4 + '@next/swc-linux-x64-musl': 14.2.4 + '@next/swc-win32-arm64-msvc': 14.2.4 + '@next/swc-win32-ia32-msvc': 14.2.4 + '@next/swc-win32-x64-msvc': 14.2.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros diff --git a/prisma/migrations/20240625053303_adding_flag_for_authenticated_users_only_flag/migration.sql b/prisma/migrations/20240625053303_adding_flag_for_authenticated_users_only_flag/migration.sql new file mode 100644 index 0000000..1e31259 --- /dev/null +++ b/prisma/migrations/20240625053303_adding_flag_for_authenticated_users_only_flag/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Assistant" ADD COLUMN "authenticatedUsersOnly" BOOLEAN DEFAULT true; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index da7a907..523a6f5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -56,26 +56,27 @@ model ModelProviderKey { } model Assistant { - id String @id - modelId String? - model Model? @relation(fields: [modelId], references: [id]) - modelProviderId String? - modelProvider ModelProvider? @relation(fields: [modelProviderId], references: [id]) - object Json? - organizationOwner String? - organizationOwnerType String? - organization Organization? @relation(fields: [organizationOwner, organizationOwnerType], references: [owner, ownerType]) - avatar String? - profile String? - theme Json? - created_at DateTime @default(now()) - updated_at DateTime @updatedAt - Thread Thread[] - Folder Folder[] - File File[] - published Boolean? @default(false) - modelProviderKeyId String? - modelProviderKey ModelProviderKey? @relation(fields: [modelProviderKeyId], references: [id]) + id String @id + modelId String? + model Model? @relation(fields: [modelId], references: [id]) + modelProviderId String? + modelProvider ModelProvider? @relation(fields: [modelProviderId], references: [id]) + object Json? + organizationOwner String? + organizationOwnerType String? + organization Organization? @relation(fields: [organizationOwner, organizationOwnerType], references: [owner, ownerType]) + avatar String? + profile String? + theme Json? + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + Thread Thread[] + Folder Folder[] + File File[] + published Boolean? @default(false) + authenticatedUsersOnly Boolean? @default(true) + modelProviderKeyId String? + modelProviderKey ModelProviderKey? @relation(fields: [modelProviderKeyId], references: [id]) } model Thread { diff --git a/prisma/seed.ts b/prisma/seed.ts index 8e4cd83..a48f25d 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -328,6 +328,26 @@ async function main() { } as Prisma.JsonObject, }, }); + + const claude_3_5_sonnet_20240620 = await prisma.model.upsert({ + where: { id: 'claude-3-5-sonnet-20240620' }, + update: { + features: { + retrieval: false, + } as Prisma.JsonObject, + }, + create: { + id: 'claude-3-5-sonnet-20240620', + name: 'Claude 3.5 Sonnet', + description: + "Claude 3.5 Sonnet is anthropic's most intelligent model with highest level of intelligence and capability", + url: 'https://docs.anthropic.com/en/docs/about-claude/models', + providerId: 'anthropic', + features: { + retrieval: false, + } as Prisma.JsonObject, + }, + }); } main() diff --git a/src/app/api/assistants/[id]/authentication/route.ts b/src/app/api/assistants/[id]/authentication/route.ts new file mode 100644 index 0000000..5a585b2 --- /dev/null +++ b/src/app/api/assistants/[id]/authentication/route.ts @@ -0,0 +1,62 @@ +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/app/api/utils/prisma'; +import { getSession } from '@auth0/nextjs-auth0'; + +const getId = (req: Request) => { + const url = new URL(req.url); + return url.pathname.split('/').splice(-2, 1)[0]; +}; + +export async function PUT(req: NextRequest, res: NextResponse) { + const session = await getSession(); + const id = getId(req); + + try { + if (session?.user) { + let organization = await prisma.organization.findFirst({ + where: { + owner: session?.user.sub, + ownerType: 'personal', + }, + }); + + if (organization) { + let assistant = await prisma.assistant.findFirst({ + where: { + id: id, + }, + select: { + id: true, + organization: true, + authenticatedUsersOnly: true, + }, + }); + + // @ts-ignore + if (!assistant || assistant.organization.id !== organization.id) { + return NextResponse.json({ message: 'Unauthorized' }, { + status: 401, + } as any); + } else { + await prisma.assistant.update({ + where: { + id: assistant.id, + }, + data: { + // @ts-ignore + authenticatedUsersOnly: !assistant.authenticatedUsersOnly, + }, + }); + return NextResponse.json( + { message: 'Update successful' }, + { status: 200 } + ); + } + } + } + } catch (error) { + return NextResponse.json({ message: (error as Error).message }, { + status: 400, + } as any); + } +} diff --git a/src/app/api/assistants/[id]/route.ts b/src/app/api/assistants/[id]/route.ts index 144a4bb..eb1afd4 100644 --- a/src/app/api/assistants/[id]/route.ts +++ b/src/app/api/assistants/[id]/route.ts @@ -26,6 +26,7 @@ export async function GET(req: NextRequest, res: NextResponse) { profile: true, published: true, theme: true, + authenticatedUsersOnly: true, }, }); @@ -36,6 +37,14 @@ export async function GET(req: NextRequest, res: NextResponse) { ); } + if (assistant.authenticatedUsersOnly) { + // Validate that the user is logged in when this condition is applied + const session = await getSession(); + if (!session?.user) { + return Response.json({ message: 'Unauthenticated' }, { status: 401 }); + } + } + // Inject customization properties into the assistant object if (assistant.object) { // @ts-ignore @@ -64,6 +73,8 @@ export async function GET(req: NextRequest, res: NextResponse) { : null; // @ts-ignore assistant.object.published = assistant.published; + // @ts-ignore + assistant.object.authenticatedUsersOnly = assistant.authenticatedUsersOnly; } return Response.json(assistant.object, { status: 200 }); diff --git a/src/app/api/assistants/route.ts b/src/app/api/assistants/route.ts index 4f1c2bc..224dd91 100644 --- a/src/app/api/assistants/route.ts +++ b/src/app/api/assistants/route.ts @@ -19,6 +19,7 @@ export async function GET(req: NextRequest, res: NextResponse) { profile: true, modelId: true, published: true, + authenticatedUsersOnly: true, }, }); let assistantsCollection = assistants.map((assistant) => { @@ -29,6 +30,9 @@ export async function GET(req: NextRequest, res: NextResponse) { assistant.object.modelId = assistant.modelId; // @ts-ignore assistant.object.published = assistant.published; + // @ts-ignore + assistant.object.authenticatedUsersOnly = + assistant.authenticatedUsersOnly; } return assistant.object; }); diff --git a/src/app/api/auth/[auth0]/route.ts b/src/app/api/auth/[auth0]/route.ts index 31e8488..9e91a52 100644 --- a/src/app/api/auth/[auth0]/route.ts +++ b/src/app/api/auth/[auth0]/route.ts @@ -1,7 +1,21 @@ import { handleAuth, handleLogin } from '@auth0/nextjs-auth0'; +import { redirect } from 'next/navigation'; export const GET = handleAuth({ - login: handleLogin({ - returnTo: '/assistants', + login: handleLogin((req) => { + let returnUrl = '/assistants'; + + // @ts-ignore + if (req.url && req.url.searchParams) { + // @ts-ignore + let redirectUrl = req.url.searchParams.get('returnTo'); + if (redirectUrl) { + returnUrl = redirectUrl; + } + } + + return { + returnTo: returnUrl, + }; }), }); diff --git a/src/app/assistants/CreateAssistant.tsx b/src/app/assistants/CreateAssistant.tsx index 94b19cc..865ac7d 100644 --- a/src/app/assistants/CreateAssistant.tsx +++ b/src/app/assistants/CreateAssistant.tsx @@ -28,7 +28,7 @@ export default function CreateAssistant() {

-

+
- +
+
+ {assistant.authenticatedUsersOnly ? ( + + ) : ( + + )}
{assistant.name}
-
+
{assistant.modelId} - {assistant.published ? 'Public' : 'Private'} + {assistant.published ? 'Listed' : 'Private'}
{assistant.description} -
+
- diff --git a/src/app/assistants/[id]/SideNavigation.tsx b/src/app/assistants/[id]/SideNavigation.tsx index 4ad1f5e..33b4c14 100644 --- a/src/app/assistants/[id]/SideNavigation.tsx +++ b/src/app/assistants/[id]/SideNavigation.tsx @@ -15,6 +15,7 @@ import Image from 'next/image'; import { getImageHash } from '@/app/utils/hash'; import React, { useContext } from 'react'; import AssistantContext from '@/app/assistants/[id]/AssistantContext'; +import Link from 'next/link'; export default function SideNavigation() { const { assistant } = useContext(AssistantContext); @@ -67,6 +68,16 @@ export default function SideNavigation() { {assistant.description} + + +
diff --git a/src/app/assistants/[id]/chat/ChatAgent.tsx b/src/app/assistants/[id]/chat/ChatAgent.tsx index 21b7272..e862d3e 100644 --- a/src/app/assistants/[id]/chat/ChatAgent.tsx +++ b/src/app/assistants/[id]/chat/ChatAgent.tsx @@ -1,6 +1,5 @@ 'use client'; -import ChatPopup from '@/app/assistants/[id]/chat/ChatPopup'; import { Avatar, Dropdown, Spinner, Button } from 'flowbite-react'; import { getImageHash } from '@/app/utils/hash'; import { updateAssistant, useGetAssistant } from '@/app/assistants/[id]/client'; @@ -8,6 +7,7 @@ import React, { useEffect, useState } from 'react'; import { Assistant } from '@/app/types/assistant'; import AssistantContext from '@/app/assistants/[id]/AssistantContext'; import ChatPopupFrame from '@/app/assistants/[id]/chat/ChatPopupFrame'; +import { useRouter } from 'next/navigation'; export interface ChatAgentProps { assistant_id: string; @@ -25,9 +25,15 @@ export default function ChatAgent(props: ChatAgentProps) { const [loading, setLoading] = useState(true); const [assistant, setAssistant] = useState(assistantResponse); + const { push } = useRouter(); + useEffect(() => { if (assistantResponse) { setAssistant(assistantResponse); + // @ts-ignore + if (assistantResponse && assistantResponse.message) { + push('/api/auth/login?returnTo=/embed/' + props.assistant_id); + } setLoading(false); } }, [assistantLoading]); @@ -95,12 +101,14 @@ export default function ChatAgent(props: ChatAgentProps) { getAssistantAvatar() )} - ) : ( + ) : assistant.id ? ( + ) : ( + <>Redirecting... )}
diff --git a/src/app/assistants/[id]/chat/ChatConversationStarters.tsx b/src/app/assistants/[id]/chat/ChatConversationStarters.tsx new file mode 100644 index 0000000..5a75d67 --- /dev/null +++ b/src/app/assistants/[id]/chat/ChatConversationStarters.tsx @@ -0,0 +1,35 @@ +import { useContext } from 'react'; +import AssistantContext from '@/app/assistants/[id]/AssistantContext'; +import { Button } from 'flowbite-react'; + +export interface ChatConversationStartersProps { + onClick?: (text:string)=> void +} + +export default function ChatConversationStarters(props: ChatConversationStartersProps) { + const { assistant } = useContext(AssistantContext); + + return ( +
+ { + assistant && assistant.theme && assistant.theme.conversationStarters ? + assistant.theme.conversationStarters.map( + (conversationStarter) => { + return ( + + ); + } + ) :<> + } +
+ ); +} \ No newline at end of file diff --git a/src/app/assistants/[id]/chat/ChatHeader.tsx b/src/app/assistants/[id]/chat/ChatHeader.tsx index 83e7364..82447c2 100644 --- a/src/app/assistants/[id]/chat/ChatHeader.tsx +++ b/src/app/assistants/[id]/chat/ChatHeader.tsx @@ -28,7 +28,7 @@ export default function ChatHeader(props: ChatHeaderProps) { >
-

{assistant.name}

+

{assistant.name}

{!props.minimize && props.setMinimize ? ( @@ -54,7 +54,7 @@ export default function ChatHeader(props: ChatHeaderProps) {
{assistant.description} diff --git a/src/app/assistants/[id]/chat/ChatPage.tsx b/src/app/assistants/[id]/chat/ChatPage.tsx index 844aaf5..3c43b42 100644 --- a/src/app/assistants/[id]/chat/ChatPage.tsx +++ b/src/app/assistants/[id]/chat/ChatPage.tsx @@ -13,8 +13,13 @@ import ChatMessage from '@/app/assistants/[id]/chat/ChatMessage'; import ChatMessageStreaming from '@/app/assistants/[id]/chat/ChatMessageStreaming'; import ChatTyping from '@/app/assistants/[id]/chat/ChatTyping'; import { HiOutlinePencilAlt } from 'react-icons/hi'; +import ChatConversationStarters from '@/app/assistants/[id]/chat/ChatConversationStarters'; -export default function ChatPage() { +export interface ChatPageProps { + short?: boolean; +} + +export default function ChatPage(props: ChatPageProps) { const { assistant } = useContext(AssistantContext); const bottomRef = useRef(null); @@ -28,6 +33,7 @@ export default function ChatPage() { messages, sendMessage, createNewThread, + sendConversationStarter } = useChatContext(); useEffect(() => { @@ -42,16 +48,16 @@ export default function ChatPage() { }, [streamText]); return ( -
-
- -
-
+
+ +
{ return ; })} + { + messages.length === 1 ? : null + } {streamText ? ( <> (assistantResponse); + const { push } = useRouter(); + useEffect(() => { if (assistantResponse) { setAssistant(assistantResponse); + // @ts-ignore + if (assistantResponse && assistantResponse.message) { + push('/api/auth/login?returnTo=/link/' + props.assistantId); + } setLoading(false); } }, [assistantLoading]); @@ -28,11 +35,13 @@ export default function ChatPageContextWrapper(props: { assistantId: string }) {
- ) : ( + ) : assistant.id ? ( + ) : ( + <>Redirecting... ); } diff --git a/src/app/assistants/[id]/chat/ChatPageHeader.tsx b/src/app/assistants/[id]/chat/ChatPageHeader.tsx index 3e548a3..cfd38d2 100644 --- a/src/app/assistants/[id]/chat/ChatPageHeader.tsx +++ b/src/app/assistants/[id]/chat/ChatPageHeader.tsx @@ -1,39 +1,45 @@ 'use client'; -import { Card, Dropdown } from 'flowbite-react'; +import { Card, Dropdown, Navbar, NavbarBrand } from 'flowbite-react'; import Image from 'next/image'; import { useContext } from 'react'; import AssistantContext from '@/app/assistants/[id]/AssistantContext'; import { getImageHash } from '@/app/utils/hash'; +import { UserProfile } from '@/components/user-profile'; +import { useUser } from '@auth0/nextjs-auth0/client'; +import UserDropdown from '@/components/user-dropdown'; +import { getPrimaryBackgroundColor } from '@/app/utils/assistant'; export function ChatPageHeader() { const { assistant } = useContext(AssistantContext); + const { user, error, isLoading } = useUser(); + return ( -
-
-
- Assistant -
-
-

- {assistant.name} -

-

- {assistant.description} -

-
-
-
+ + + Assistant + + {assistant.name} + + + {user ? : <>} + ); } diff --git a/src/app/assistants/[id]/chat/ChatPopup.tsx b/src/app/assistants/[id]/chat/ChatPopup.tsx index 739c8cd..c8ca00d 100644 --- a/src/app/assistants/[id]/chat/ChatPopup.tsx +++ b/src/app/assistants/[id]/chat/ChatPopup.tsx @@ -15,6 +15,7 @@ import { } from '@/app/utils/assistant'; import { useChatContext } from '@/app/assistants/[id]/chat/useChatContext'; import { HiOutlinePencilAlt } from 'react-icons/hi'; +import ChatConversationStarters from '@/app/assistants/[id]/chat/ChatConversationStarters'; export interface ChatPopupProps extends ChatProps { hide: boolean; @@ -38,6 +39,7 @@ export default function ChatPopup(props: ChatPopupProps) { setMessages, sendMessage, createNewThread, + sendConversationStarter } = useChatContext(); useEffect(() => { @@ -78,6 +80,9 @@ export default function ChatPopup(props: ChatPopupProps) { {messages.map((message: Message, index) => { return ; })} + { + messages.length === 1 ? : null + } {streamText ? ( <> (assistantResponse); + const { push } = useRouter(); + useEffect(() => { if (assistantResponse) { setAssistant(assistantResponse); + // @ts-ignore + if (assistantResponse && assistantResponse.message) { + push('/api/auth/login?returnTo=/embed/' + props.assistant_id); + } setLoading(false); } }, [assistantLoading]); @@ -47,7 +54,7 @@ export default function ChatWindow(props: ChatWindowProps) { aria-label='Loading assistant..' className={'self-center p-10'} /> - ) : ( + ) : assistant.id ? ( @@ -57,6 +64,8 @@ export default function ChatWindow(props: ChatWindowProps) { )} + ) : ( + <>Redirecting... )}
diff --git a/src/app/assistants/[id]/chat/useChatContext.ts b/src/app/assistants/[id]/chat/useChatContext.ts index 12c2fcc..97f9349 100644 --- a/src/app/assistants/[id]/chat/useChatContext.ts +++ b/src/app/assistants/[id]/chat/useChatContext.ts @@ -31,21 +31,29 @@ export const useChatContext = () => { useEffect(() => { if (reset) { - setMessages([ - { - created_at: Date.now() / 1000, - role: 'assistant', - content: [ - { - type: 'text', - text: { - value: getInitialPrompt(assistant), - annotations: [], + let initialPrompt = getInitialPrompt(assistant); + let initialMessages: any = []; + + // Hide the initial prompt if there is none set + if (initialPrompt && initialPrompt.trim()) { + initialMessages = [ + { + created_at: Date.now() / 1000, + role: 'assistant', + content: [ + { + type: 'text', + text: { + value: getInitialPrompt(assistant), + annotations: [], + }, }, - }, - ], - }, - ]); + ], + }, + ]; + } + + setMessages(initialMessages); setReset(false); } }, [assistant, reset]); @@ -176,6 +184,26 @@ export const useChatContext = () => { setMessageStatus('in_progress' as string); }; + const sendConversationStarter = async (prompt:string) => { + let message: Message = { + created_at: Date.now() / 1000, + role: 'user', + content: [ + { + type: 'text', + text: { + value: prompt, + annotations: [], + }, + }, + ], + }; + setCurrentMessage(message); + setMessages([...messages, message]); + setTypedMessage(''); + setMessageStatus('in_progress' as string); + }; + const getAssistantThreadStorageKey = () => { return `ai.assistantshub.assistant.${assistant.id}.thread`; }; @@ -206,5 +234,6 @@ export const useChatContext = () => { setFingerprint, sendMessage, createNewThread, + sendConversationStarter }; }; diff --git a/src/app/assistants/[id]/client.ts b/src/app/assistants/[id]/client.ts index 6ba60ca..5dc90d0 100644 --- a/src/app/assistants/[id]/client.ts +++ b/src/app/assistants/[id]/client.ts @@ -294,6 +294,21 @@ export async function updateVisibilityStatus(id: string | undefined) { return [response.status, await response.json()]; } +export async function updateAuthenticationRequirement(id: string | undefined) { + if (!id) { + return [400, { error: 'Assistant ID is required' }]; + } + + let response = await fetch('/api/assistants/' + id + '/authentication', { + method: 'PUT', + headers: { + accept: 'application.json', + }, + }); + + return [response.status, await response.json()]; +} + export async function uploadFile(assistantId: string, file: File) { const formData = new FormData(); formData.append('file', file); diff --git a/src/app/assistants/[id]/customize/AvatarCropUpload.tsx b/src/app/assistants/[id]/customize/AvatarCropUpload.tsx index 5c3697a..c2671a8 100644 --- a/src/app/assistants/[id]/customize/AvatarCropUpload.tsx +++ b/src/app/assistants/[id]/customize/AvatarCropUpload.tsx @@ -139,7 +139,7 @@ const AvatarCropUpload = () => { }; return ( -
+
diff --git a/src/app/assistants/[id]/customize/ColorPicker.tsx b/src/app/assistants/[id]/customize/ColorPicker.tsx index 4faa3b0..54e6829 100644 --- a/src/app/assistants/[id]/customize/ColorPicker.tsx +++ b/src/app/assistants/[id]/customize/ColorPicker.tsx @@ -34,13 +34,13 @@ const ColorPicker = (props: ColorPickerProps) => { }; return ( -
+
-
+
= (
) => setText(e.target.value) diff --git a/src/app/assistants/[id]/customize/DebounceInputWithActions.tsx b/src/app/assistants/[id]/customize/DebounceInputWithActions.tsx new file mode 100644 index 0000000..d163cb4 --- /dev/null +++ b/src/app/assistants/[id]/customize/DebounceInputWithActions.tsx @@ -0,0 +1,62 @@ +import React, { useState, useEffect } from 'react'; +import { useDebounce } from '@/app/utils/useDebounce'; +import { Button, Spinner, TextInput } from 'flowbite-react'; +import { HiCheck, HiTrash } from 'react-icons/hi'; + +export interface DebouncedInputWithActionsProps { + value?: string; + placeholder?: string; + onDebounceTextChange?: (text: string) => void; + onDebounceTextDelete?: () => void; +} + +export const DebouncedInputWithActions: React.FC< + DebouncedInputWithActionsProps +> = (props: DebouncedInputWithActionsProps) => { + const [text, setText] = useState(props.value ? props.value : ''); + const [loading, setLoading] = useState(false); + const debouncedText = useDebounce(text, 300); // 300ms delay + + useEffect(() => { + // This function will run after debouncedText changes and the 300ms delay passes + if (debouncedText && debouncedText !== props.value) { + setLoading(true); + props.onDebounceTextChange + ? props.onDebounceTextChange(debouncedText) + : null; + setLoading(false); + } + }, [debouncedText]); // Only re-run if debouncedText changes + + return ( +
+ ) => + setText(e.target.value) + } + placeholder={props.placeholder ? props.placeholder : ''} + /> +
+ {loading ? ( + + ) : ( + + )} +
+ +
+ ); +}; + +export default DebouncedInputWithActions; diff --git a/src/app/assistants/[id]/customize/EditConversationStarters.tsx b/src/app/assistants/[id]/customize/EditConversationStarters.tsx new file mode 100644 index 0000000..83ff5e7 --- /dev/null +++ b/src/app/assistants/[id]/customize/EditConversationStarters.tsx @@ -0,0 +1,116 @@ +import { Button, Label, Modal, TextInput } from 'flowbite-react'; +import React, { useContext, useEffect, useState } from 'react'; +import { getInitialConversationStarter } from '@/app/utils/assistant'; +import AssistantContext from '@/app/assistants/[id]/AssistantContext'; +import { ulid } from 'ulidx'; +import { HiCheck, HiPlus, HiTrash } from 'react-icons/hi'; +import DebouncedInputWithActions from '@/app/assistants/[id]/customize/DebounceInputWithActions'; +import { ConversationStarter } from '@/app/types/assistant'; + +export const EditConversationStarters: React.FC = () => { + const { assistant, setAssistant } = useContext(AssistantContext); + + const [conversationStarters, setConversationStarters] = useState< + ConversationStarter[] + >(getInitialConversationStarter(assistant)); + const [saveConversationStarter, setSaveConversationStarter] = + useState(null); + const [dirtyConversationStarter, setdirtyConversationStarter] = + useState(null); + const [selectedConversationStarter, setSelectedConversationStarter] = + useState(null); + const [deleteConversationStarter, setDeleteConversationStarter] = + useState(null); + + useEffect(() => { + // Save it to the assistant + setAssistant({ + ...assistant, + theme: { + ...assistant.theme, + conversationStarters: conversationStarters, + }, + }); + }, [conversationStarters]); + + useEffect(() => {}, [dirtyConversationStarter]); + + useEffect(() => {}, [saveConversationStarter]); + + useEffect(() => { + if (deleteConversationStarter) { + const indexToRemove = conversationStarters.findIndex( + (key) => key.id === deleteConversationStarter.id + ); + if (indexToRemove !== -1) { + conversationStarters.splice(indexToRemove, 1); + } + setConversationStarters([...conversationStarters]); + setDeleteConversationStarter(null); + } + }, [deleteConversationStarter]); + + const handleAddConversationStarter = function () { + setConversationStarters([ + ...conversationStarters, + { id: ulid(), prompt: '' }, + ]); + }; + + const handleSaveConversationStarter = function (id: string, text: string) { + conversationStarters.forEach((starter) => { + if (starter.id === id) { + starter.prompt = text; + } + }); + + setConversationStarters([...conversationStarters]); + }; + + const handleDeleteConversationStarter = function (id: string) { + conversationStarters.forEach((iterationConversationStarter) => { + if (iterationConversationStarter.id === id) { + setDeleteConversationStarter(iterationConversationStarter); + } + }); + }; + + return ( +
+
+
+
+ {conversationStarters && conversationStarters.length ? ( + conversationStarters.map((starter: any, index: number) => ( +
+
+ + handleSaveConversationStarter(starter.id, text) + } + onDebounceTextDelete={() => { + handleDeleteConversationStarter(starter.id); + }} + value={starter.prompt} + /> +
+
+ )) + ) : ( + <> + )} +
+ +
+
+
+ ); +}; diff --git a/src/app/assistants/[id]/customize/EditInitialPrompt.tsx b/src/app/assistants/[id]/customize/EditInitialPrompt.tsx index 3250df2..da43260 100644 --- a/src/app/assistants/[id]/customize/EditInitialPrompt.tsx +++ b/src/app/assistants/[id]/customize/EditInitialPrompt.tsx @@ -19,7 +19,7 @@ export const EditInitialPrompt: React.FC = () => { } return ( -
+
diff --git a/src/app/assistants/[id]/customize/EditMessageLabel.tsx b/src/app/assistants/[id]/customize/EditMessageLabel.tsx index c520930..63f9d87 100644 --- a/src/app/assistants/[id]/customize/EditMessageLabel.tsx +++ b/src/app/assistants/[id]/customize/EditMessageLabel.tsx @@ -19,7 +19,7 @@ export const EditMessageLabel: React.FC = () => { } return ( -
+
diff --git a/src/app/assistants/[id]/customize/ProfileCropUpload.tsx b/src/app/assistants/[id]/customize/ProfileCropUpload.tsx index 186173e..4c4d1f5 100644 --- a/src/app/assistants/[id]/customize/ProfileCropUpload.tsx +++ b/src/app/assistants/[id]/customize/ProfileCropUpload.tsx @@ -141,7 +141,7 @@ const ProfileCropUpload = () => { }; return ( -
+
diff --git a/src/app/assistants/[id]/customize/ThemeSelections.tsx b/src/app/assistants/[id]/customize/ThemeSelections.tsx index 5ec9d6e..85bb211 100644 --- a/src/app/assistants/[id]/customize/ThemeSelections.tsx +++ b/src/app/assistants/[id]/customize/ThemeSelections.tsx @@ -12,7 +12,7 @@ const ThemeSelections = () => { const { assistant, setAssistant } = useContext(AssistantContext); return ( -
+
Adjust the look and feel of your assistant to match your preferences

-
+
@@ -42,6 +44,11 @@ export default function Customize() { + + + + + @@ -54,11 +61,10 @@ export default function Customize() { -
+
diff --git a/src/app/assistants/[id]/documents/DocumentsManager.tsx b/src/app/assistants/[id]/documents/DocumentsManager.tsx index ee044c3..9b74e87 100644 --- a/src/app/assistants/[id]/documents/DocumentsManager.tsx +++ b/src/app/assistants/[id]/documents/DocumentsManager.tsx @@ -85,7 +85,7 @@ export default function DocumentsManager() { />
@@ -109,7 +110,7 @@ export default function Customize() {
-
+
diff --git a/src/app/assistants/[id]/settings/EditAssistant.tsx b/src/app/assistants/[id]/settings/EditAssistant.tsx index 175fdb6..0179edf 100644 --- a/src/app/assistants/[id]/settings/EditAssistant.tsx +++ b/src/app/assistants/[id]/settings/EditAssistant.tsx @@ -235,7 +235,7 @@ export default function EditAssistant(props: EditAssistantProps) {
*/}