diff --git a/.github/workflows/aws-preview.yml b/.github/workflows/aws-preview.yml index 8d2a3d76..447cb475 100644 --- a/.github/workflows/aws-preview.yml +++ b/.github/workflows/aws-preview.yml @@ -54,11 +54,11 @@ jobs: WebUrl="https://petercat.ai" \ StaticUrl="https://static.petercat.ai" \ PetercatEnv="preview" \ - AWSGithubSecretName=${{ secrets.AWS_GITHUB_SECRET_NAME }} \ - AWSStaticSecretName=${{ secrets.AWS_STATIC_SECRET_NAME }} \ - AWSLLMTokenSecretName=${{ vars.AWS_LLM_TOKEN_SECRET_NAME }} \ - AWSLLMTokenPublicName=${{ vars.AWS_LLM_TOKEN_PUBLIC_NAME }} \ - AWSStaticKeyPairId=${{ secrets.AWS_STATIC_KEYPAIR_ID }} \ + GithubSecretName=${{ secrets.X_GITHUB_SECRET_NAME }} \ + StaticSecretName=${{ secrets.STATIC_SECRET_NAME }} \ + LLMTokenSecretName=${{ vars.LLM_TOKEN_SECRET_NAME }} \ + LLMTokenPublicName=${{ vars.LLM_TOKEN_PUBLIC_NAME }} \ + StaticKeyPairId=${{ secrets.STATIC_KEYPAIR_ID }} \ S3TempBucketName=${{ vars.S3_TEMP_BUCKET_NAME }} \ GitHubAppID=${{ secrets.X_GITHUB_APP_ID }} \ GithubAppsClientId=${{ secrets.X_GITHUB_APPS_CLIENT_ID }} \ diff --git a/.github/workflows/aws-prod.yml b/.github/workflows/aws-prod.yml index e3292556..c2dc3826 100644 --- a/.github/workflows/aws-prod.yml +++ b/.github/workflows/aws-prod.yml @@ -48,11 +48,11 @@ jobs: WebUrl="https://petercat.ai" \ StaticUrl="https://static.petercat.ai" \ PetercatEnv="production" \ - AWSGithubSecretName=${{ secrets.AWS_GITHUB_SECRET_NAME }} \ - AWSStaticSecretName=${{ secrets.AWS_STATIC_SECRET_NAME }} \ - AWSLLMTokenSecretName=${{ vars.AWS_LLM_TOKEN_SECRET_NAME }} \ - AWSLLMTokenPublicName=${{ vars.AWS_LLM_TOKEN_PUBLIC_NAME }} \ - AWSStaticKeyPairId=${{ secrets.AWS_STATIC_KEYPAIR_ID }} \ + GithubSecretName=${{ secrets.X_GITHUB_SECRET_NAME }} \ + StaticSecretName=${{ secrets.STATIC_SECRET_NAME }} \ + LLMTokenSecretName=${{ vars.LLM_TOKEN_SECRET_NAME }} \ + LLMTokenPublicName=${{ vars.LLM_TOKEN_PUBLIC_NAME }} \ + StaticKeyPairId=${{ secrets.STATIC_KEYPAIR_ID }} \ S3TempBucketName=${{ vars.S3_TEMP_BUCKET_NAME }} \ GitHubAppID=${{ secrets.X_GITHUB_APP_ID }} \ GithubAppsClientId=${{ secrets.X_GITHUB_APPS_CLIENT_ID }} \ diff --git a/.gitignore b/.gitignore index 5beaa9be..37ec3e0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Docker volumnes +docker/volumes/db/data +docker/volumes/storage + # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. __pycache__/ *.pyc @@ -59,4 +63,3 @@ next-env.d.ts dist/ lui/src/style.css - diff --git a/README.en-US.md b/README.en-US.md index 7ccdd241..80f25e2e 100644 --- a/README.en-US.md +++ b/README.en-US.md @@ -104,11 +104,11 @@ The project requires environment variables to be set: | `WEB_URL` | Required | Domain of the frontend web service | `https://petercat.ai` | | `STATIC_URL` | Required | Static resource domain | `https://static.petercat.ai` | | **AWS Related Environment Variables** | -| `AWS_GITHUB_SECRET_NAME` | Required | AWS secret file name | `prod/githubapp/petercat/pem` | -| `AWS_STATIC_SECRET_NAME` | Optional | The name of the AWS-managed CloudFront private key. If configured, CloudFront signed URLs will be used to protect your resources. For more information, see the [AWS documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html). | `prod/petercat/static` | -| `AWS_LLM_TOKEN_SECRET_NAME` | Optional | The name of the LLM signing private key managed by AWS. If configured, Petercat will use the RSA algorithm to manage the user's LLM Token. | `prod/petercat/llm` | -| `AWS_LLM_TOKEN_PUBLIC_NAME` | Optional | The name of the LLM signing public key managed by AWS. If configured, Petercat will use the RSA algorithm to manage the user's LLM Token. | `prod/petercat/llm/pub` | -| `AWS_STATIC_KEYPAIR_ID` | Optional | The Key Pair ID for AWS CloudFront. If configured, CloudFront signed URLs will be used to protect your resources. For more information, see the [AWS documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html). | `APKxxxxxxxx` | +| `X_GITHUB_SECRET_NAME` | Required | AWS secret file name | `prod/githubapp/petercat/pem` | +| `STATIC_SECRET_NAME` | Optional | The name of the AWS-managed CloudFront private key. If configured, CloudFront signed URLs will be used to protect your resources. For more information, see the [AWS documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html). | `prod/petercat/static` | +| `LLM_TOKEN_SECRET_NAME` | Optional | The name of the LLM signing private key managed by AWS. If configured, Petercat will use the RSA algorithm to manage the user's LLM Token. | `prod/petercat/llm` | +| `LLM_TOKEN_PUBLIC_NAME` | Optional | The name of the LLM signing public key managed by AWS. If configured, Petercat will use the RSA algorithm to manage the user's LLM Token. | `prod/petercat/llm/pub` | +| `STATIC_KEYPAIR_ID` | Optional | The Key Pair ID for AWS CloudFront. If configured, CloudFront signed URLs will be used to protect your resources. For more information, see the [AWS documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html). | `APKxxxxxxxx` | | `S3_TEMP_BUCKET_NAME` | Required | AWS S3 bucket for temporary image files | `xxx-temp` | | `SQS_QUEUE_URL` | Required | AWS SQS queue URL | `https://sqs.ap-northeast-1.amazonaws.com/xxx/petercat-task-queue` | | **Supabase Related Environment Variables** | diff --git a/README.ja-JP.md b/README.ja-JP.md index ec474f35..c7b23a43 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -87,11 +87,11 @@ | `WEB_URL` | 必須 | フロントエンドウェブサービスのドメイン | `https://petercat.ai` | | `STATIC_URL` | 必須 | 静的リソースドメイン | `https://static.petercat.ai` | | **AWS関連環境変数** | -| `AWS_GITHUB_SECRET_NAME` | 必須 | AWSシークレットファイル名 | `prod/githubapp/petercat/pem` | -| `AWS_STATIC_SECRET_NAME` | オプション | AWSが管理するCloudFrontのプライベートキーの名前。設定されている場合、CloudFrontの署名付きURLが使用され、リソースが保護されます。詳細については、[AWSドキュメント](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)を参照してください。 | `prod/petercat/static` | -| `AWS_LLM_TOKEN_SECRET_NAME` | オプション | AWSが管理するLLM署名プライベートキーの名前。設定されている場合、PetercatはRSAアルゴリズムを使用してユーザーのLLMトークンを管理します。 | `prod/petercat/llm` | -| `AWS_LLM_TOKEN_PUBLIC_NAME` | オプション | AWSが管理するLLM署名公開キーの名前。設定されている場合、PetercatはRSAアルゴリズムを使用してユーザーのLLMトークンを管理します。 | `prod/petercat/llm/pub` | -| `AWS_STATIC_KEYPAIR_ID` | オプション | AWS CloudFrontのキーID。設定されている場合、CloudFrontの署名付きURLが使用され、リソースが保護されます。詳細については、[AWSドキュメント](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)を参照してください。 | `APKxxxxxxxx` | +| `X_GITHUB_SECRET_NAME` | 必須 | AWSシークレットファイル名 | `prod/githubapp/petercat/pem` | +| `STATIC_SECRET_NAME` | オプション | AWSが管理するCloudFrontのプライベートキーの名前。設定されている場合、CloudFrontの署名付きURLが使用され、リソースが保護されます。詳細については、[AWSドキュメント](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)を参照してください。 | `prod/petercat/static` | +| `LLM_TOKEN_SECRET_NAME` | オプション | AWSが管理するLLM署名プライベートキーの名前。設定されている場合、PetercatはRSAアルゴリズムを使用してユーザーのLLMトークンを管理します。 | `prod/petercat/llm` | +| `LLM_TOKEN_PUBLIC_NAME` | オプション | AWSが管理するLLM署名公開キーの名前。設定されている場合、PetercatはRSAアルゴリズムを使用してユーザーのLLMトークンを管理します。 | `prod/petercat/llm/pub` | +| `STATIC_KEYPAIR_ID` | オプション | AWS CloudFrontのキーID。設定されている場合、CloudFrontの署名付きURLが使用され、リソースが保護されます。詳細については、[AWSドキュメント](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)を参照してください。 | `APKxxxxxxxx` | | `S3_TEMP_BUCKET_NAME` | 必須 | 一時的な画像ファイル用のAWS S3バケット | `xxx-temp` | | `SQS_QUEUE_URL` | 必須 | AWS SQSキューURL | `https://sqs.ap-northeast-1.amazonaws.com/xxx/petercat-task-queue` | | **Supabase関連環境変数** | diff --git a/README.md b/README.md index 146d5f9a..bf0bb5e8 100644 --- a/README.md +++ b/README.md @@ -113,11 +113,11 @@ | `WEB_URL` | 必选 | 前端 Web 服务的域名 | `https://petercat.ai` | `STATIC_URL` | 必选 | 静态资源域名 | `https://static.petercat.ai` | **AWS 相关环境变量** | -| `AWS_GITHUB_SECRET_NAME` | 必选 | AWS 托管的 Github 私钥文件名 | `prod/githubapp/petercat/pem` -| `AWS_STATIC_SECRET_NAME` | 可选 | AWS 托管的 CloudFront 签名私钥名称。如果配置了该项,将使用 CloudFront 签名 URL 来保护你的资源。更多信息请参阅 [AWS 文档](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)。 | `prod/petercat/static` | -| `AWS_LLM_TOKEN_SECRET_NAME` | 可选 | AWS 托管的 llm 签名私钥名称。如果配置了该项,petercat 将使用 RSA 算法托管用户的 LLM Token | `prod/petercat/llm` | -| `AWS_LLM_TOKEN_PUBLIC_NAME` | 可选 | AWS 托管的 llm 签名公钥名称。如果配置了该项,petercat 将使用 RSA 算法托管用户的 LLM Token | `prod/petercat/llm/pub` | -| `AWS_STATIC_KEYPAIR_ID` | 可选 | AWS CloudFront 的 Key Pair ID。如果配置了该项,将使用 CloudFront 签名 URL 来保护你的资源。更多信息请参阅 [AWS 文档](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)。 | `APKxxxxxxxx` | +| `X_GITHUB_SECRET_NAME` | 必选 | AWS 托管的 Github 私钥文件名 | `prod/githubapp/petercat/pem` +| `STATIC_SECRET_NAME` | 可选 | AWS 托管的 CloudFront 签名私钥名称。如果配置了该项,将使用 CloudFront 签名 URL 来保护你的资源。更多信息请参阅 [AWS 文档](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)。 | `prod/petercat/static` | +| `LLM_TOKEN_SECRET_NAME` | 可选 | AWS 托管的 llm 签名私钥名称。如果配置了该项,petercat 将使用 RSA 算法托管用户的 LLM Token | `prod/petercat/llm` | +| `LLM_TOKEN_PUBLIC_NAME` | 可选 | AWS 托管的 llm 签名公钥名称。如果配置了该项,petercat 将使用 RSA 算法托管用户的 LLM Token | `prod/petercat/llm/pub` | +| `STATIC_KEYPAIR_ID` | 可选 | AWS CloudFront 的 Key Pair ID。如果配置了该项,将使用 CloudFront 签名 URL 来保护你的资源。更多信息请参阅 [AWS 文档](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)。 | `APKxxxxxxxx` | | `S3_TEMP_BUCKET_NAME` | 可选 | 用于托管 AWS 临时图片文件 S3 的 bucket | `xxx-temp` | `SQS_QUEUE_URL`| 必选 | AWS SQS 消息队列 URL | `https://sqs.ap-northeast-1.amazonaws.com/xxx/petercat-task-queue` | **SUPABASE 相关 env** | diff --git a/assistant/package.json b/assistant/package.json index a0817c7b..952b86c3 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -1,6 +1,6 @@ { "name": "@petercatai/assistant", - "version": "1.0.16", + "version": "1.0.22", "description": "PeterCat Assistant Application", "module": "dist/esm/index.js", "types": "dist/esm/index.d.ts", diff --git a/assistant/src/Chat/template/LoginCard.tsx b/assistant/src/Chat/template/LoginCard.tsx index 03ac5a6a..573997e7 100644 --- a/assistant/src/Chat/template/LoginCard.tsx +++ b/assistant/src/Chat/template/LoginCard.tsx @@ -4,8 +4,8 @@ import { Button } from 'antd'; import GitHubIcon from '../../icons/GitHubIcon'; import useUser from '../../hooks/useUser'; -const LoginCard = ({ apiDomain, token }: { apiDomain: string; token: string; }) => { - const { user, isLoading, actions } = useUser({ apiDomain, fingerprint: token }); +const LoginCard = ({ apiDomain, webDomain, token }: { apiDomain: string; webDomain?: string; token: string; }) => { + const { user, isLoading, actions } = useUser({ apiDomain, webDomain, fingerprint: token }); if (isLoading) { return diff --git a/assistant/src/Chat/template/index.tsx b/assistant/src/Chat/template/index.tsx index 0a81ec9f..49155493 100644 --- a/assistant/src/Chat/template/index.tsx +++ b/assistant/src/Chat/template/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import GitInsightCard from './GitInsightCard'; import LoginCard from './LoginCard'; -export const UITemplateRender = ({ templateId, apiDomain, token, cardData }: { templateId: string, apiDomain: string; token: string; cardData: any }) => { +export const UITemplateRender = ({ templateId, apiDomain, webDomain, token, cardData }: { templateId: string, apiDomain: string; webDomain?: string; token: string; cardData: any }) => { if (templateId === 'GIT_INSIGHT') { return ( + ); } return null; diff --git a/assistant/src/GitInsight/components/GitInsightIcon.tsx b/assistant/src/GitInsight/components/GitInsightIcon.tsx index 2cb9cee8..24367b6c 100644 --- a/assistant/src/GitInsight/components/GitInsightIcon.tsx +++ b/assistant/src/GitInsight/components/GitInsightIcon.tsx @@ -1,9 +1,9 @@ import Lottie from 'lottie-react'; import React, { useMemo } from 'react'; -const Fork = require('../../assets/fork.json'); -const Commit = require('../../assets/commit.json'); -const Star = require('../../assets/star.json'); +import * as Commit from '../../assets/commit.json'; +import * as Fork from '../../assets/fork.json'; +import * as Star from '../../assets/star.json'; interface GitInsightIconProps { type?: 'fork' | 'commit' | 'star'; diff --git a/assistant/src/hooks/useUser.ts b/assistant/src/hooks/useUser.ts index c0bc06b5..c16113bd 100644 --- a/assistant/src/hooks/useUser.ts +++ b/assistant/src/hooks/useUser.ts @@ -5,7 +5,7 @@ import useSWR from 'swr'; import { popupCenter } from '../utils/popcenter'; import { useEffect } from 'react'; -function useUser({ apiDomain, fingerprint }: { apiDomain: string; fingerprint: string }) { +function useUser({ apiDomain, webDomain = 'https://petercat.ai', fingerprint }: { apiDomain: string; fingerprint: string; webDomain?: string }) { const { data: user, isLoading, mutate } = useSWR( ['user.info'], async () => getUserInfo(apiDomain, { clientId: fingerprint }), @@ -14,8 +14,9 @@ function useUser({ apiDomain, fingerprint }: { apiDomain: string; fingerprint: s const doLogin = () => { + console.log('call do Login', webDomain); popupCenter({ - url: 'https://petercat.ai/user/login', + url: `${webDomain}/user/login`, title: 'Login', w: 600, h: 400, diff --git a/assistant/src/icons/SignatureIcon.tsx b/assistant/src/icons/SignatureIcon.tsx index b651f441..1c4779eb 100644 --- a/assistant/src/icons/SignatureIcon.tsx +++ b/assistant/src/icons/SignatureIcon.tsx @@ -1,10 +1,73 @@ import React from 'react'; const SignatureIcon = (props: any) => ( - - + + + + + + + + + + + + + diff --git a/assistant/src/style.css b/assistant/src/style.css index ed6ba836..db155add 100644 --- a/assistant/src/style.css +++ b/assistant/src/style.css @@ -944,42 +944,42 @@ video:where(.petercat-lui,.petercat-lui *) { .border-\[\#e4e4e7\] { --tw-border-opacity: 1; - border-color: rgb(228 228 231 / var(--tw-border-opacity)); + border-color: rgb(228 228 231 / var(--tw-border-opacity, 1)); } .bg-\[\#3F3F46\] { --tw-bg-opacity: 1; - background-color: rgb(63 63 70 / var(--tw-bg-opacity)); + background-color: rgb(63 63 70 / var(--tw-bg-opacity, 1)); } .bg-\[\#F1F1F1\] { --tw-bg-opacity: 1; - background-color: rgb(241 241 241 / var(--tw-bg-opacity)); + background-color: rgb(241 241 241 / var(--tw-bg-opacity, 1)); } .bg-\[\#FCFCFC\] { --tw-bg-opacity: 1; - background-color: rgb(252 252 252 / var(--tw-bg-opacity)); + background-color: rgb(252 252 252 / var(--tw-bg-opacity, 1)); } .bg-\[\#f1f1f1\] { --tw-bg-opacity: 1; - background-color: rgb(241 241 241 / var(--tw-bg-opacity)); + background-color: rgb(241 241 241 / var(--tw-bg-opacity, 1)); } .bg-gray-700 { --tw-bg-opacity: 1; - background-color: rgb(55 65 81 / var(--tw-bg-opacity)); + background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1)); } .bg-gray-800 { --tw-bg-opacity: 1; - background-color: rgb(31 41 55 / var(--tw-bg-opacity)); + background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1)); } .bg-white { --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); } .object-cover { @@ -1072,47 +1072,47 @@ video:where(.petercat-lui,.petercat-lui *) { .text-black { --tw-text-opacity: 1; - color: rgb(0 0 0 / var(--tw-text-opacity)); + color: rgb(0 0 0 / var(--tw-text-opacity, 1)); } .text-blue-600 { --tw-text-opacity: 1; - color: rgb(37 99 235 / var(--tw-text-opacity)); + color: rgb(37 99 235 / var(--tw-text-opacity, 1)); } .text-gray-400 { --tw-text-opacity: 1; - color: rgb(156 163 175 / var(--tw-text-opacity)); + color: rgb(156 163 175 / var(--tw-text-opacity, 1)); } .text-gray-500 { --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity)); + color: rgb(107 114 128 / var(--tw-text-opacity, 1)); } .text-gray-900 { --tw-text-opacity: 1; - color: rgb(17 24 39 / var(--tw-text-opacity)); + color: rgb(17 24 39 / var(--tw-text-opacity, 1)); } .text-green-600 { --tw-text-opacity: 1; - color: rgb(22 163 74 / var(--tw-text-opacity)); + color: rgb(22 163 74 / var(--tw-text-opacity, 1)); } .text-red-600 { --tw-text-opacity: 1; - color: rgb(220 38 38 / var(--tw-text-opacity)); + color: rgb(220 38 38 / var(--tw-text-opacity, 1)); } .text-red-700 { --tw-text-opacity: 1; - color: rgb(185 28 28 / var(--tw-text-opacity)); + color: rgb(185 28 28 / var(--tw-text-opacity, 1)); } .text-white { --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } .opacity-0 { @@ -1252,12 +1252,12 @@ video:where(.petercat-lui,.petercat-lui *) { .hover\:\!bg-gray-700:hover { --tw-bg-opacity: 1 !important; - background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important; + background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1)) !important; } .hover\:\!bg-white:hover { --tw-bg-opacity: 1 !important; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)) !important; + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)) !important; } .hover\:shadow-lg:hover { diff --git a/assistant/tsconfig.json b/assistant/tsconfig.json index 9e4a7815..cd559556 100644 --- a/assistant/tsconfig.json +++ b/assistant/tsconfig.json @@ -10,7 +10,8 @@ "@@/*": [".dumi/tmp/*"], "lui": ["src"], "lui/*": ["src/*", "*"] - } + }, + "resolveJsonModule": true }, "include": [".dumirc.ts", "src/**/*"] } diff --git a/client/.env.local.example b/client/.env.local.example new file mode 100644 index 00000000..e61d3ff9 --- /dev/null +++ b/client/.env.local.example @@ -0,0 +1,2 @@ +SELF_PATH=/client/ +NEXT_PUBLIC_API_DOMAIN=http://localhost:8001 \ No newline at end of file diff --git a/client/.kiwi/en/DeployBotModal.ts b/client/.kiwi/en/DeployBotModal.ts index 49293ad0..d2c17eca 100644 --- a/client/.kiwi/en/DeployBotModal.ts +++ b/client/.kiwi/en/DeployBotModal.ts @@ -19,6 +19,9 @@ export default { ninDeJiQiRen: 'Your robot has been made public in the marketplace, please check it out in the market.', gongKaiDaoPE: 'Publish to the PeterCat Marketplace', + meiZhaoDaoXiangYao:"Can't find the repository with PeterCat Assistant installed? ", + dianJiCiChu:'Click here ', + shouQuanAnZhuangG:"Authorize organization for PeterCat" }, DeployItem: { shouQi: 'Collapse', @@ -27,5 +30,10 @@ export default { tiaoGuo: 'Skip', baoCunChengGong: 'Saved successfully!', buShuChengGong: 'Deployment successful', + wanCheng: 'Done', + tiJiaoChengGong: 'Submission Successful', + shouQiBuShu: 'Collapse', + buShu: 'Deploy', + daKaiBuShu: 'Open Deployment', }, }; diff --git a/client/.kiwi/en/app.ts b/client/.kiwi/en/app.ts index e1d7d49f..198351ee 100644 --- a/client/.kiwi/en/app.ts +++ b/client/.kiwi/en/app.ts @@ -28,7 +28,8 @@ export default { ' is an intelligent Q&A bot solution which designed for community maintainers and developers.', xiaoMaoMiZhuNi: 'Conquer GitHub with kitten power', wenDang: 'Documentation', - gongZuoTai: 'Dashboard', + release: 'Release Notes', + gongZuoTai: 'Workspace', yanShiAnLi: 'Showcase', }, }; diff --git a/client/.kiwi/en/components.ts b/client/.kiwi/en/components.ts index dc62b11b..e309da8a 100644 --- a/client/.kiwi/en/components.ts +++ b/client/.kiwi/en/components.ts @@ -54,6 +54,9 @@ export default { gengXinZhiShi: 'Update Knowledge', gengXinZhiShiKu: 'Update Knowledge Base (Coming Soon)', tiaoShi: 'Debug', + yiZaiTEX: 'Already deployed on {val1}', + guanWang: 'Official website', + gITHU: ' GitHub platform', }, CreateButton: { chuangJianTOK: 'Create Token', diff --git a/client/.kiwi/en/edit.ts b/client/.kiwi/en/edit.ts index 7ff3761b..f4ccb78e 100644 --- a/client/.kiwi/en/edit.ts +++ b/client/.kiwi/en/edit.ts @@ -9,10 +9,10 @@ export default { chongXinShengChengPei: 'Regenerate Configuration', ziDongShengChengPei: 'Automatically Generate Configuration', diZhiYouWu: 'Invalid Address', - qingShuRuGI: 'Please enter the GitHub project URL', + qingShuRuGI: 'Please enter or select the GitHub project name', fuZhiTOK: 'Copy Token', tOKEN: 'Token has been copied to clipboard', - gITHU: 'GitHub Project URL', + gITHU: 'GitHub project name', bangWoPeiZhiYi: 'Help me create a Q&A bot', chuCiJianMianXian: '👋🏻 Hello! Nice to meet you. Let me introduce myself: I am PeterCat, a robot for an open-source project. You can create a Q&A robot by talking to me.', diff --git a/client/.kiwi/en/index.ts b/client/.kiwi/en/index.ts index 762331d1..1bd359a1 100644 --- a/client/.kiwi/en/index.ts +++ b/client/.kiwi/en/index.ts @@ -2,6 +2,7 @@ import components from './components'; import edit from './edit'; import utils from './utils'; import app from './app'; +import release from './release'; import DeployBotModal from './DeployBotModal'; export default Object.assign( @@ -11,6 +12,7 @@ export default Object.assign( app, utils, edit, + release, DeployBotModal, }, ); diff --git a/client/.kiwi/en/release.ts b/client/.kiwi/en/release.ts new file mode 100644 index 00000000..1275a93d --- /dev/null +++ b/client/.kiwi/en/release.ts @@ -0,0 +1,13 @@ +export default { + page: { + zuiXinChanPinXin: + 'Get the latest product news to stay updated when new features are released', + guanZhu: 'Follow', + xiaoMaoMiJiQi: + 'The Little Kitty robot made its debut at the Shanghai Bund Conference, supporting the open-source community with Q&A scenarios', + waiTanDaHuiShou: 'First release at the Bund Conference', + quanLianLuGuoJi: + 'Full-link internationalization support, added mobile adaptation for the official website, and productization of multi-platform integration links', + guoJiHuaZhiChi: 'Internationalization Support', + }, +}; diff --git a/client/.kiwi/ja/DeployBotModal.ts b/client/.kiwi/ja/DeployBotModal.ts index 6d449b62..460dce7a 100644 --- a/client/.kiwi/ja/DeployBotModal.ts +++ b/client/.kiwi/ja/DeployBotModal.ts @@ -18,6 +18,9 @@ export default { ninDeJiQiRen: 'あなたのロボットはすでに市場に公開されています。市場をご覧ください。', gongKaiDaoPE: 'Peter Cat市場に公開', + meiZhaoDaoXiangYao:'PeterCat Assistant がインストールされたリポジトリが見つかりませんか?', + dianJiCiChu:'こちらをクリック', + shouQuanAnZhuangG:'PeterCat に組織を承認' }, DeployItem: { shouQi: '折りたたむ', @@ -26,5 +29,10 @@ export default { tiaoGuo: 'スキップ', baoCunChengGong: '保存が成功しました!', buShuChengGong: 'デプロイが成功しました', + wanCheng: 'かんりょう', + tiJiaoChengGong: '提出が成功しました', + shouQiBuShu: '折りたたむ', + buShu: 'デプロイ', + daKaiBuShu: 'デプロイを開', }, }; diff --git a/client/.kiwi/ja/app.ts b/client/.kiwi/ja/app.ts index a348046d..263be67d 100644 --- a/client/.kiwi/ja/app.ts +++ b/client/.kiwi/ja/app.ts @@ -28,6 +28,7 @@ export default { 'これはコミュニティのメンテナーと開発者のために作られたQ&Aボットソリューションです', xiaoMaoMiZhuNi: 'Peter CatがあなたのGithub攻略を手助けします', wenDang: 'ドキュメント', + release: '更新ログ', gongZuoTai: '作業台', yanShiAnLi: 'デモケース', }, diff --git a/client/.kiwi/ja/components.ts b/client/.kiwi/ja/components.ts index 805db913..240f4380 100644 --- a/client/.kiwi/ja/components.ts +++ b/client/.kiwi/ja/components.ts @@ -53,6 +53,9 @@ export default { gengXinZhiShi: '知識を更新', gengXinZhiShiKu: 'ナレッジベースを更新(近日公開)', tiaoShi: 'デバッグ', + yiZaiTEX: '{val1} に展開済み', + guanWang: '公式ウェブサイト', + gITHU: ' GitHubプラットフォーム', }, CreateButton: { chuangJianTOK: 'トークンを作成', diff --git a/client/.kiwi/ja/edit.ts b/client/.kiwi/ja/edit.ts index 47a082a3..36410691 100644 --- a/client/.kiwi/ja/edit.ts +++ b/client/.kiwi/ja/edit.ts @@ -9,10 +9,10 @@ export default { chongXinShengChengPei: '設定を再生成', ziDongShengChengPei: '設定を自動生成', diZhiYouWu: 'アドレスに誤りがあります', - qingShuRuGI: 'GitHubプロジェクトのアドレスを入力してください', + qingShuRuGI: 'GitHubプロジェクト名を入力または選択してください', fuZhiTOK: 'トークンをコピー', tOKEN: 'トークンがクリップボードにコピーされました', - gITHU: 'GitHubプロジェクトのアドレス', + gITHU: 'GitHubプロジェクト名', bangWoPeiZhiYi: 'Q&Aボットの設定を手伝ってください', chuCiJianMianXian: '👋🏻 こんにちは!初めまして、自己紹介させていただきます。私はPeterCatと申します。オープンソースプロジェクトのロボットです。私と対話することで、Q&Aロボットを設定できます。', diff --git a/client/.kiwi/ja/index.ts b/client/.kiwi/ja/index.ts index 762331d1..1bd359a1 100644 --- a/client/.kiwi/ja/index.ts +++ b/client/.kiwi/ja/index.ts @@ -2,6 +2,7 @@ import components from './components'; import edit from './edit'; import utils from './utils'; import app from './app'; +import release from './release'; import DeployBotModal from './DeployBotModal'; export default Object.assign( @@ -11,6 +12,7 @@ export default Object.assign( app, utils, edit, + release, DeployBotModal, }, ); diff --git a/client/.kiwi/ja/release.ts b/client/.kiwi/ja/release.ts new file mode 100644 index 00000000..522ef3a9 --- /dev/null +++ b/client/.kiwi/ja/release.ts @@ -0,0 +1,13 @@ +export default { + page: { + zuiXinChanPinXin: + '新しい機能がリリースされたときに最新情報を取得するための最新の製品ニュース', + guanZhu: 'フォロー', + xiaoMaoMiJiQi: + '子猫ロボットが上海外灘カンファレンスで初登場し、オープンソースコミュニティのQ&Aシナリオを支援', + waiTanDaHuiShou: '外灘カンファレンスで初公開', + quanLianLuGuoJi: + 'フルリンク国際化サポート、公式サイトのモバイル端末対応を追加し、マルチプラットフォーム統合リンクの製品化', + guoJiHuaZhiChi: '国際化サポート', + }, +}; diff --git a/client/.kiwi/ko/DeployBotModal.ts b/client/.kiwi/ko/DeployBotModal.ts index 445c5f5e..dd06d4b0 100644 --- a/client/.kiwi/ko/DeployBotModal.ts +++ b/client/.kiwi/ko/DeployBotModal.ts @@ -16,6 +16,9 @@ export default { '이것은 PeterCat 저장소에 issue를 제출하게 되며, 우리 인공 검토를 통과하면 공개될 수 있습니다.', ninDeJiQiRen: '당신의 봇이 시장에 공개되었습니다. 시장을 확인해 보세요.', gongKaiDaoPE: 'PeterCat 시장에 공개', + meiZhaoDaoXiangYao:'PeterCat Assistant가 설치된 저장소를 찾을 수 없습니까?', + dianJiCiChu:'여기를 클릭하세요', + shouQuanAnZhuangG:'PeterCat에 조직 권한 부여' }, DeployItem: { shouQi: '접기', @@ -24,5 +27,10 @@ export default { tiaoGuo: '건너뛰기', baoCunChengGong: '성공적으로 저장!', buShuChengGong: '배포 성공', + wanCheng: '완료', + tiJiaoChengGong: '제출이 성공했습니다', + shouQiBuShu: '접기', + buShu: '배포', + daKaiBuShu: '배포 열기', }, }; diff --git a/client/.kiwi/ko/app.ts b/client/.kiwi/ko/app.ts index bfbd68af..40dd5ba6 100644 --- a/client/.kiwi/ko/app.ts +++ b/client/.kiwi/ko/app.ts @@ -28,6 +28,7 @@ export default { '커뮤니티 유지보수자와 개발자를 위한 스마트 Q&A 봇 솔루션입니다.', xiaoMaoMiZhuNi: 'Peter Cat이 당신의 Github 정복을 도와줍니다', wenDang: '문서', + release: '업데이트 로그', gongZuoTai: '작업대', yanShiAnLi: '데모 사례', }, diff --git a/client/.kiwi/ko/components.ts b/client/.kiwi/ko/components.ts index 27d3ac32..46c0b89e 100644 --- a/client/.kiwi/ko/components.ts +++ b/client/.kiwi/ko/components.ts @@ -53,6 +53,9 @@ export default { gengXinZhiShi: '지식 업데이트', gengXinZhiShiKu: '지식 베이스 업데이트(Coming Soon)', tiaoShi: '디버그', + yiZaiTEX: '{val1}에서 이미 배포됨', + guanWang: '공식 웹사이트', + gITHU: ' GitHub 플랫폼', }, CreateButton: { chuangJianTOK: '토큰 생성', diff --git a/client/.kiwi/ko/edit.ts b/client/.kiwi/ko/edit.ts index 77405ca0..53d28675 100644 --- a/client/.kiwi/ko/edit.ts +++ b/client/.kiwi/ko/edit.ts @@ -9,10 +9,10 @@ export default { chongXinShengChengPei: '구성 다시 생성', ziDongShengChengPei: '구성 자동 생성', diZhiYouWu: '주소 오류', - qingShuRuGI: 'GitHub 프로젝트 주소를 입력하십시오.', + qingShuRuGI: 'GitHub 프로젝트 이름을 입력하거나 선택하세요', fuZhiTOK: '토큰 복사', tOKEN: '토큰이 클립보드에 복사되었습니다.', - gITHU: 'GitHub 프로젝트 주소', + gITHU: 'GitHub 프로젝트 이름', bangWoPeiZhiYi: 'Q&A 봇 설정을 도와주세요.', chuCiJianMianXian: '👋🏻 안녕하세요! 처음 뵙겠습니다. 제 소개를 하겠습니다: 저는 PeterCat입니다, 오픈소스 프로젝트의 로봇입니다. 저와 대화를 통해 질의응답 로봇을 구성할 수 있습니다.', diff --git a/client/.kiwi/ko/index.ts b/client/.kiwi/ko/index.ts index 762331d1..1bd359a1 100644 --- a/client/.kiwi/ko/index.ts +++ b/client/.kiwi/ko/index.ts @@ -2,6 +2,7 @@ import components from './components'; import edit from './edit'; import utils from './utils'; import app from './app'; +import release from './release'; import DeployBotModal from './DeployBotModal'; export default Object.assign( @@ -11,6 +12,7 @@ export default Object.assign( app, utils, edit, + release, DeployBotModal, }, ); diff --git a/client/.kiwi/ko/release.ts b/client/.kiwi/ko/release.ts new file mode 100644 index 00000000..12195914 --- /dev/null +++ b/client/.kiwi/ko/release.ts @@ -0,0 +1,13 @@ +export default { + page: { + zuiXinChanPinXin: + '새로운 기능이 출시될 때 최신 정보를 얻을 수 있는 최신 제품 뉴스', + guanZhu: '팔로우', + xiaoMaoMiJiQi: + '작은 고양이 로봇이 상하이 와이탄 컨퍼런스에서 처음으로 모습을 드러내며 오픈 소스 커뮤니티의 Q&A 시나리오를 지원', + waiTanDaHuiShou: '와이탄 컨퍼런스에서 최초 공개', + quanLianLuGuoJi: + '전체 링크 국제화 지원, 공식 웹사이트의 모바일 버전 지원 추가, 다중 플랫폼 통합 링크 제품화', + guoJiHuaZhiChi: '국제화 지원', + }, +}; diff --git a/client/.kiwi/zh-CN/DeployBotModal.ts b/client/.kiwi/zh-CN/DeployBotModal.ts index 2010e2ea..1a443400 100644 --- a/client/.kiwi/zh-CN/DeployBotModal.ts +++ b/client/.kiwi/zh-CN/DeployBotModal.ts @@ -16,6 +16,9 @@ export default { '这将提交一个 issue 到 PeterCat\n 仓库,待我们人工审核通过后即可完成公开。', ninDeJiQiRen: '您的机器人已经公开到了市场,请前往市场查看。', gongKaiDaoPE: '公开到 PeterCat 市场', + meiZhaoDaoXiangYao:"没找到已安装 PeterCat Assistant 机器人的仓库?", + dianJiCiChu:'点击此处', + shouQuanAnZhuangG:"授权组织给 PeterCat" }, DeployItem: { shouQi: '收起', @@ -24,5 +27,10 @@ export default { tiaoGuo: '跳过', baoCunChengGong: '保存成功!', buShuChengGong: '部署成功', + wanCheng: '完成', + tiJiaoChengGong: '提交成功', + shouQiBuShu: '收起部署', + buShu: '部署', + daKaiBuShu: '打开部署', }, }; diff --git a/client/.kiwi/zh-CN/app.ts b/client/.kiwi/zh-CN/app.ts index 327aaa60..5841762a 100644 --- a/client/.kiwi/zh-CN/app.ts +++ b/client/.kiwi/zh-CN/app.ts @@ -27,6 +27,7 @@ export default { shiZhuanWeiSheQu: '是专为社区维护者和开发者打造的智能答疑机器人解决方案', xiaoMaoMiZhuNi: '小猫咪助你征服 Github', wenDang: '文档', + release: '更新日志', gongZuoTai: '工作台', yanShiAnLi: '演示案例', }, diff --git a/client/.kiwi/zh-CN/components.ts b/client/.kiwi/zh-CN/components.ts index 5c95115f..9966dd1c 100644 --- a/client/.kiwi/zh-CN/components.ts +++ b/client/.kiwi/zh-CN/components.ts @@ -53,6 +53,9 @@ export default { gengXinZhiShi: '更新知识', gengXinZhiShiKu: '更新知识库(Coming Soon)', tiaoShi: '调试', + yiZaiTEX: '已在{val1}部署', + guanWang: '官网', + gITHU: ' GitHub 平台', }, CreateButton: { chuangJianTOK: '创建 Token', diff --git a/client/.kiwi/zh-CN/edit.ts b/client/.kiwi/zh-CN/edit.ts index 9742212f..3d376d11 100644 --- a/client/.kiwi/zh-CN/edit.ts +++ b/client/.kiwi/zh-CN/edit.ts @@ -8,10 +8,10 @@ export default { chongXinShengChengPei: '重新生成配置', ziDongShengChengPei: '自动生成配置', diZhiYouWu: '地址有误', - qingShuRuGI: '请输入 GitHub 项目地址', + qingShuRuGI: '请输入或选择 GitHub 项目名称', fuZhiTOK: '复制 Token', tOKEN: 'Token 已复制到剪贴板', - gITHU: 'Github 项目地址', + gITHU: 'Github 项目名称', bangWoPeiZhiYi: '帮我配置一个答疑机器人', chuCiJianMianXian: '👋🏻 初次见面,先自我介绍一下:我是 PeterCat,一个开源项目的机器人。你可以通过和我对话配置一个答疑机器人。', diff --git a/client/.kiwi/zh-CN/index.ts b/client/.kiwi/zh-CN/index.ts index dcd3a2e9..32243bc2 100644 --- a/client/.kiwi/zh-CN/index.ts +++ b/client/.kiwi/zh-CN/index.ts @@ -1,13 +1,18 @@ import components from './components'; +import release from './release'; import DeployBotModal from './DeployBotModal'; import edit from './edit'; import utils from './utils'; import app from './app'; -export default Object.assign({}, { - components, - app, - utils, - edit, - DeployBotModal, -}); \ No newline at end of file +export default Object.assign( + {}, + { + components, + app, + utils, + edit, + DeployBotModal, + release, + }, +); diff --git a/client/.kiwi/zh-CN/release.ts b/client/.kiwi/zh-CN/release.ts new file mode 100644 index 00000000..5770eec0 --- /dev/null +++ b/client/.kiwi/zh-CN/release.ts @@ -0,0 +1,11 @@ +export default { + page: { + zuiXinChanPinXin: '最新产品新闻, 以便在新版能力发布时获取最新信息', + guanZhu: '关注', + xiaoMaoMiJiQi: '小猫咪机器人首次亮相上海外滩大会,助力开源社区答疑场景', + waiTanDaHuiShou: '外滩大会首次发布', + quanLianLuGuoJi: + '全链路国际化支持,增加官网移动端适配,多平台集成链路产品化', + guoJiHuaZhiChi: '国际化支持', + }, +}; diff --git a/client/.kiwi/zh-TW/DeployBotModal.ts b/client/.kiwi/zh-TW/DeployBotModal.ts index 831749e2..6957db74 100644 --- a/client/.kiwi/zh-TW/DeployBotModal.ts +++ b/client/.kiwi/zh-TW/DeployBotModal.ts @@ -16,6 +16,9 @@ export default { '這將提交一個 issue 到 PeterCat 倉庫,待我們人工審核通過後即可完成公開。', ninDeJiQiRen: '您的機器人已經公開到了市場,請前往市場查看。', gongKaiDaoPE: '公開到 PeterCat 市場', + meiZhaoDaoXiangYao:"找不到已安裝 PeterCat Assistant 的倉庫", + dianJiCiChu:'點擊此處', + shouQuanAnZhuangG:"授權組織給 PeterCat" }, DeployItem: { shouQi: '收起', @@ -24,5 +27,10 @@ export default { tiaoGuo: '跳過', baoCunChengGong: '保存成功!', buShuChengGong: '部署成功', + wanCheng: '完成', + tiJiaoChengGong: '提交成功', + shouQiBuShu: '收起', + buShu: '部署', + daKaiBuShu: '打开部署', }, }; diff --git a/client/.kiwi/zh-TW/app.ts b/client/.kiwi/zh-TW/app.ts index 0a8562c6..9379d9b8 100644 --- a/client/.kiwi/zh-TW/app.ts +++ b/client/.kiwi/zh-TW/app.ts @@ -27,6 +27,7 @@ export default { shiZhuanWeiSheQu: '是專為社區維護者和開發者打造的智能答疑機器人解決方案', xiaoMaoMiZhuNi: '小貓咪助你征服 Github', wenDang: '文檔', + release: '更新日誌', gongZuoTai: '工作台', yanShiAnLi: '演示案例', }, diff --git a/client/.kiwi/zh-TW/components.ts b/client/.kiwi/zh-TW/components.ts index c2244748..530ee1d7 100644 --- a/client/.kiwi/zh-TW/components.ts +++ b/client/.kiwi/zh-TW/components.ts @@ -53,6 +53,9 @@ export default { gengXinZhiShi: '更新知識', gengXinZhiShiKu: '更新知識庫(Coming Soon)', tiaoShi: '調試', + yiZaiTEX: '已在{val1}部署', + guanWang: '官網', + gITHU: ' GitHub 平台', }, CreateButton: { chuangJianTOK: '創建 Token', diff --git a/client/.kiwi/zh-TW/edit.ts b/client/.kiwi/zh-TW/edit.ts index 9a76b1eb..e2c0212d 100644 --- a/client/.kiwi/zh-TW/edit.ts +++ b/client/.kiwi/zh-TW/edit.ts @@ -8,10 +8,10 @@ export default { chongXinShengChengPei: '重新生成配置', ziDongShengChengPei: '自動生成配置', diZhiYouWu: '地址有誤', - qingShuRuGI: '請輸入 GitHub 項目地址', + qingShuRuGI: '請輸入或選擇 GitHub 項目名稱', fuZhiTOK: '複製 Token', tOKEN: 'Token 已複製到剪貼板', - gITHU: 'GitHub 項目地址', + gITHU: 'GitHub 項目名稱', bangWoPeiZhiYi: '幫我配置一個答疑機器人', chuCiJianMianXian: '👋🏻 初次見面,先自我介紹一下:我是 PeterCat,一個開源項目的機器人。你可以通過和我對話配置一個答疑機器人。', diff --git a/client/.kiwi/zh-TW/index.ts b/client/.kiwi/zh-TW/index.ts index 762331d1..1bd359a1 100644 --- a/client/.kiwi/zh-TW/index.ts +++ b/client/.kiwi/zh-TW/index.ts @@ -2,6 +2,7 @@ import components from './components'; import edit from './edit'; import utils from './utils'; import app from './app'; +import release from './release'; import DeployBotModal from './DeployBotModal'; export default Object.assign( @@ -11,6 +12,7 @@ export default Object.assign( app, utils, edit, + release, DeployBotModal, }, ); diff --git a/client/.kiwi/zh-TW/release.ts b/client/.kiwi/zh-TW/release.ts new file mode 100644 index 00000000..ad6b1ab6 --- /dev/null +++ b/client/.kiwi/zh-TW/release.ts @@ -0,0 +1,11 @@ +export default { + page: { + zuiXinChanPinXin: '最新產品新聞,以便在新版能力發布時獲取最新信息', + guanZhu: '關注', + xiaoMaoMiJiQi: '小貓咪機器人首次亮相上海外灘大會,助力開源社區答疑場景', + waiTanDaHuiShou: '外灘大會首次發布', + quanLianLuGuoJi: + '全鏈路國際化支持,增加官網移動端適配,多平台集成鏈路產品化', + guoJiHuaZhiChi: '國際化支持', + }, +}; diff --git a/client/app/factory/edit/components/BotCreateForm.tsx b/client/app/factory/edit/components/BotCreateForm.tsx index 09940538..e076f332 100644 --- a/client/app/factory/edit/components/BotCreateForm.tsx +++ b/client/app/factory/edit/components/BotCreateForm.tsx @@ -24,22 +24,22 @@ import InputList from './InputList'; import BulbIcon from '@/public/icons/BulbIcon'; import GitHubIcon from '@/public/icons/GitHubIcon'; import { random } from 'lodash'; -import { useBotDelete } from '@/app/hooks/useBot'; +import { useBotDelete, useGetGitAvatar } from '@/app/hooks/useBot'; import { ToastContainer, toast } from 'react-toastify'; import { useBot } from '@/app/contexts/BotContext'; import 'react-toastify/dist/ReactToastify.css'; import { AVATARS } from '@/app/constant/avatar'; -import { useRouter } from 'next/navigation'; -import { useAvaliableLLMs } from '@/app/hooks/useAvaliableLLMs'; +import { useAvailableLLMs } from '@/app/hooks/useAvailableLLMs'; import { useTokenList } from '@/app/hooks/useToken'; import CreateButton from '@/app/user/tokens/components/CreateButton'; +import DeleteButtonIcon from '@/public/icons/DeleteButtonIcon'; const BotCreateFrom = () => { const { botProfile, setBotProfile } = useBot(); - const router = useRouter(); + const { data: gitAvatar } = useGetGitAvatar(botProfile?.repoName); const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure(); - const { data: avaliableLLMs = [] } = useAvaliableLLMs(); + const { data: availableLLMs = [] } = useAvailableLLMs(); const { data: userTokens = [] } = useTokenList(); const filteredTokens = useMemo(() => { @@ -66,9 +66,7 @@ const BotCreateFrom = () => { if (isSuccess) { toast.success(I18N.components.BotCreateFrom.shanChuChengGong); onClose(); - setTimeout(() => { - router.push('/factory/list'); - }, 1000); + window.location.href = '/factory/list'; } }, [isSuccess]); @@ -82,6 +80,10 @@ const BotCreateFrom = () => { deleteBot(botProfile?.id!); }; + useEffect(() => { + console.log('botProfile', botProfile); + }, [botProfile]); + const customTitle = (
@@ -127,7 +129,7 @@ const BotCreateFrom = () => { aria-label={I18N.components.BotCreateFrom.gITHU} onClick={() => { setBotProfile((draft: BotProfile) => { - draft.avatar = botProfile?.gitAvatar; + draft.avatar = gitAvatar; }); }} > @@ -184,9 +186,12 @@ const BotCreateFrom = () => { label={I18N.components.BotCreateFrom.xuanZeDaMoXing} isRequired variant="bordered" + defaultSelectedKeys={ + botProfile?.llm ? [botProfile.llm] : [availableLLMs[0]] + } onChange={handleChange} > - {avaliableLLMs.map((llm) => ( + {availableLLMs.map((llm) => ( {llm} ))} @@ -196,11 +201,12 @@ const BotCreateFrom = () => { name="token_id" label={I18N.components.BotCreateFrom.xuanZeTOK} variant="bordered" + defaultSelectedKeys={[botProfile?.token_id || '']} onChange={handleChange} popoverProps={{ style: { zIndex: 10 } }} > - + {I18N.components.BotCreateFrom.shiYongPET} @@ -233,13 +239,9 @@ const BotCreateFrom = () => { - delete bot +
+ +
diff --git a/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx b/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx index d6a0e4fb..1b21bb8e 100644 --- a/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx +++ b/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx @@ -218,15 +218,19 @@ export const DeployContent: React.FC = ({ {I18N.DeployBotModal.DeployContent.shuaXin}
)} +
+ {I18N.DeployBotModal.DeployContent.meiZhaoDaoXiangYao} + {I18N.DeployBotModal.DeployContent.dianJiCiChu} + {I18N.DeployBotModal.DeployContent.shouQuanAnZhuangG}
); }; return ( <> - - {I18N.DeployBotModal.DeployContent.buShuDaoQiTa} - {renderPublicMarket()} {renderDeployWebsite()} {renderBindRepo()} diff --git a/client/app/factory/edit/components/DeployBotModal/index.tsx b/client/app/factory/edit/components/DeployBotModal/index.tsx index e878841d..45693a15 100644 --- a/client/app/factory/edit/components/DeployBotModal/index.tsx +++ b/client/app/factory/edit/components/DeployBotModal/index.tsx @@ -9,6 +9,7 @@ import { ModalBody, ModalFooter, Button, + Image, } from '@nextui-org/react'; import { useBindBotToRepo, @@ -31,8 +32,17 @@ interface IModalProps { onClose: () => void; } +enum DeployStatusEnum { + HIDEDEPLOYINFO = 'HIDEDEPLOYINFO', + SHOWDEPLOYINFO = 'SHOWDEPLOYINFO', + PRESUBMIT = 'PRESUBMIT', +} + const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { const [deployInfo, setDeployInfo] = useState({}); + const [deployStatus, setDeployStatus] = useState( + DeployStatusEnum.HIDEDEPLOYINFO, + ); const { botProfile } = useBot(); const { bindBotToRepo, @@ -96,7 +106,11 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { useEffect(() => { if (lodash.isEqual(originDeployModel, deployInfo)) { setDeployBtnDisabled(true); + deployStatus === DeployStatusEnum.PRESUBMIT && + setDeployStatus(DeployStatusEnum.SHOWDEPLOYINFO); } else { + deployStatus !== DeployStatusEnum.HIDEDEPLOYINFO && + setDeployStatus(DeployStatusEnum.PRESUBMIT); setDeployBtnDisabled(false); } }, [deployInfo, originDeployModel]); @@ -151,17 +165,106 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { isDeployWebsiteSuccess || isBindBotSuccess; + const renderFooterBtn = () => { + if (deployStatus === DeployStatusEnum.HIDEDEPLOYINFO) { + return ( + <> + + + + ); + } + if (deployStatus === DeployStatusEnum.SHOWDEPLOYINFO) { + return ( + <> + + + + ); + } + if (deployStatus === DeployStatusEnum.PRESUBMIT) { + return ( + <> + + + + ); + } + return null; + }; + const renderResultPath = (result: { approval_path: any }, text: string) => { return ( <> {result?.approval_path ? (
- {text} - {" "} - {I18N.DeployBotModal.DeployContent.shenHeZhong} + {text} {I18N.DeployBotModal.DeployContent.shenHeZhong} - + {result.approval_path}
@@ -173,10 +276,11 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { return ( <> {(onClose) => ( @@ -188,7 +292,7 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { - {I18N.DeployBotModal.index.buShuChengGong} + {I18N.DeployBotModal.index.tiJiaoChengGong} @@ -206,7 +310,7 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { className="border-[1.5px] border-[#3F3F46] rounded-[46px] bg-[#3F3F46] text-white" onPress={() => onClose()} > - {I18N.components.BotCreateFrom.queRen} + {I18N.DeployBotModal.index.wanCheng} @@ -220,40 +324,25 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { {I18N.DeployBotModal.index.baoCunChengGong} - - - - - - - - + {deployStatus === DeployStatusEnum.HIDEDEPLOYINFO ? null : ( + + + + + + )} + + {renderFooterBtn()} )} diff --git a/client/app/factory/edit/components/Knowledge.tsx b/client/app/factory/edit/components/Knowledge.tsx index 12719eb2..97f252b5 100644 --- a/client/app/factory/edit/components/Knowledge.tsx +++ b/client/app/factory/edit/components/Knowledge.tsx @@ -59,7 +59,9 @@ const ChunkCard = ({ update_timestamp, content, file_path }: RAGDoc) => { {file_path} - {content} + +
{content}
+
)}
@@ -83,11 +85,7 @@ export default function Knowledge({ repoName, goBack }: IProps) { const [pageSize, setPageSize] = React.useState(12); const [pageNumber, setPageNumber] = React.useState(1); const { taskProfile } = useBotTask(); - const { - data: RagDocData, - isPending, - isLoading: isListLoading, - } = useBotRAGChunkList( + const { data: RagDocData, isFetching } = useBotRAGChunkList( repoName, pageSize, pageNumber, @@ -123,8 +121,8 @@ export default function Knowledge({ repoName, goBack }: IProps) {
- - {list.length > 0 || isPending ? ( + + {list.length > 0 || isFetching ? ( ) : (
@@ -144,6 +142,9 @@ export default function Knowledge({ repoName, goBack }: IProps) { page={pageNumber} size="lg" onChange={(page) => setPageNumber(page)} + classNames={{ + cursor: 'bg-gray-700', + }} /> ) : null}
diff --git a/client/app/factory/edit/components/KnowledgeBtn.tsx b/client/app/factory/edit/components/KnowledgeBtn.tsx index 96b65a63..c1e13750 100644 --- a/client/app/factory/edit/components/KnowledgeBtn.tsx +++ b/client/app/factory/edit/components/KnowledgeBtn.tsx @@ -26,8 +26,6 @@ const KnowledgeBtn = (props: IProps) => { const { data: taskList } = useGetBotRagTask( repoName, - // if repoName is not empty, query taskList - !!repoName, // if task is running, query every 5s // if task is completed, query once taskLoading, diff --git a/client/app/factory/edit/page.tsx b/client/app/factory/edit/page.tsx index beef4fcb..9a6c6f4f 100644 --- a/client/app/factory/edit/page.tsx +++ b/client/app/factory/edit/page.tsx @@ -1,6 +1,6 @@ 'use client'; import I18N from '@/app/utils/I18N'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState, Key } from 'react'; import { Tabs, Tab, @@ -10,9 +10,11 @@ import { ModalBody, ModalFooter, Button, - Input, Avatar, Checkbox, + Autocomplete, + AutocompleteItem, + Input, } from '@nextui-org/react'; import Image from 'next/image'; import BotCreateFrom from '@/app/factory/edit/components/BotCreateForm'; @@ -26,22 +28,21 @@ import { useBotCreate, useBotEdit, } from '@/app/hooks/useBot'; -import { useAgreement } from '@/app/hooks/useAgreement'; +import { useAgreement, useAgreementStatus } from '@/app/hooks/useAgreement'; import FullPageSkeleton from '@/components/FullPageSkeleton'; -import { isEmpty } from 'lodash'; +import { isEmpty, map, size } from 'lodash'; import { Chat } from '@petercatai/assistant'; import AIBtnIcon from '@/public/icons/AIBtnIcon'; import ChatIcon from '@/public/icons/ChatIcon'; import ConfigIcon from '@/public/icons/ConfigIcon'; import SaveIcon from '@/public/icons/SaveIcon'; import { useBot } from '@/app/contexts/BotContext'; -import useUser from '@/app/hooks/useUser'; +import { useUser, useUserRepos } from '@/app/hooks/useUser'; import Knowledge from './components/Knowledge'; import { useGlobal } from '@/app/contexts/GlobalContext'; import KnowledgeBtn from './components/KnowledgeBtn'; import { BotTaskProvider } from './components/TaskContext'; import { useSearchParams } from 'next/navigation'; -import 'react-toastify/dist/ReactToastify.css'; import { extractFullRepoNameFromGitHubUrl } from '@/app/utils/tools'; import DeployBotModal from './components/DeployBotModal'; import Markdown from '@/components/Markdown'; @@ -51,6 +52,8 @@ import AgreementJA from '../../../.kiwi/ja/agreement.md'; import AgreementKO from '../../../.kiwi/ko/agreement.md'; import AgreementZhTW from '../../../.kiwi/zh-TW/agreement.md'; +import 'react-toastify/dist/ReactToastify.css'; + const API_HOST = process.env.NEXT_PUBLIC_API_DOMAIN; enum VisibleTypeEnum { BOT_CONFIG = 'BOT_CONFIG', @@ -64,6 +67,11 @@ export default function Edit() { const { language } = useGlobal(); const { botProfile, setBotProfile } = useBot(); const { user, status } = useUser(); + const { + data: agreementStatus, + isLoading: getAgreementStatusLoading, + error: getAgreementStatusError, + } = useAgreementStatus(); const router = useRouter(); const searchParams = useSearchParams(); const id = searchParams.get('id'); @@ -73,7 +81,7 @@ export default function Edit() { const [visibleType, setVisibleType] = React.useState( VisibleTypeEnum.BOT_CONFIG, ); - const [gitUrl, setGitUrl] = React.useState(''); + const [gitRepoName, setGitRepoName] = React.useState(''); const [deployModalIsOpen, setDeployModalIsOpen] = useState(false); const [agreementModalIsOpen, setAgreementModalIsOpen] = useState(false); const [agreementAccepted, setAgreementAccepted] = @@ -103,15 +111,21 @@ export default function Edit() { } if (!user || user.id.startsWith('client|')) { router.push(`${apiDomain}/api/auth/login`); - } else { - if (!user?.agreement_accepted) { - setAgreementModalIsOpen(true); - } else { - setAgreementModalIsOpen(false); - } } }, [user, status]); + useEffect(() => { + if (getAgreementStatusLoading) { + return; + } + if (getAgreementStatusError) { + setAgreementModalIsOpen(true); + } else { + const agreementAccepted = agreementStatus?.data?.agreement_accepted; + setAgreementModalIsOpen(!agreementAccepted); + } + }, [agreementStatus, getAgreementStatusError, getAgreementStatusLoading]); + const { updateBot: onUpdateBot, isLoading: updateBotLoading, @@ -169,6 +183,8 @@ export default function Edit() { [id, botProfile?.id], ); + const { data: repos } = useUserRepos(!isEdit); + const botId = useMemo(() => { if (!!id && id !== 'new') { return id; @@ -185,6 +201,7 @@ export default function Edit() { ); useEffect(() => { + console.log('config', config); if (!isEmpty(config)) { setBotProfile((draft) => { draft.id = config.id; @@ -312,46 +329,64 @@ export default function Edit() { )}
); - const manualConfigLabel = ( -
- {I18N.edit.page.gITHU} - {botProfile.id && ( - { - toast.success(I18N.edit.page.tOKEN); - }} - > - {/* @ts-ignore */} - - {I18N.edit.page.fuZhiTOK} - - - )} -
- ); const manualConfigContent = (
-
- { - const url = e.target.value; - setGitUrl(url); - }} - value={gitUrl} - isDisabled={isEdit} - required - classNames={{ label: 'w-full' }} - className="mt-1 mb-6 block w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" - /> +
+ {I18N.edit.page.gITHU} + {botProfile.id && ( + { + toast.success(I18N.edit.page.tOKEN); + }} + > + {/* @ts-ignore */} + + {I18N.edit.page.fuZhiTOK} + + + )} +
+
+ {!isEdit ? ( + { + const repoName = extractFullRepoNameFromGitHubUrl(value); + setGitRepoName(repoName || ''); + }} + onSelectionChange={(key) => { + setGitRepoName(`${key}`); + }} + allowsCustomValue + defaultInputValue={gitRepoName || botProfile.repoName} + variant="bordered" + className="mt-1 mb-6 block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" + label={I18N.edit.page.qingShuRuGI} + > + {map(repos, (item) => ( + {item.label} + ))} + + ) : ( + + )} +
{!isEdit ? (
@@ -361,7 +396,7 @@ export default function Edit() { startContent={} isLoading={createBotLoading} onClick={() => { - const repoName = extractFullRepoNameFromGitHubUrl(gitUrl); + const repoName = gitRepoName || botProfile.repoName; if (repoName) { onCreateBot({ repo_name: repoName!!, diff --git a/client/app/factory/list/components/BotCard.tsx b/client/app/factory/list/components/BotCard.tsx index 6a1e67a4..47078f9a 100644 --- a/client/app/factory/list/components/BotCard.tsx +++ b/client/app/factory/list/components/BotCard.tsx @@ -18,25 +18,72 @@ import { } from '@nextui-org/react'; import { useRouter } from 'next/navigation'; import { useBotDelete, useGetBotRagTask } from '@/app/hooks/useBot'; -import CloudIcon from '@/public/icons/CloudIcon'; -import MinusCircleIcon from '@/public/icons/MinusCircleIcon'; import { TaskStatus } from '@/types/task'; import ErrorBadgeIcon from '@/public/icons/ErrorBadgeIcon'; -import CheckBadgeIcon from '@/public/icons/CheckBadgeIcon'; -import LoadingIcon from '@/public/icons/LoadingIcon'; +import KnowledgeTaskCompleteIcon from '@/public/icons/CheckBadgeIcon'; +import KnowledgeTaskRunningIcon from '@/public/icons/LoadingIcon'; import { RagTask } from '@/app/services/BotsController'; +import CardGithubIcon from '@/public/icons/CardGithubIcon'; +import CardHomeIcon from '@/public/icons/CardHomeIcon'; +import CardCartIcon from '@/public/icons/CardCartIcon'; import { useKnowledgeUpdate } from '@/app/hooks/useKnowledgeUpdate'; + declare type Bot = Tables<'bots'>; +interface BotInfo extends Bot { + github_installed?: boolean; + picture?: string; + nickname?: string; +} + +const BotInfoIconList = (props: { bot: BotInfo }) => { + const { bot } = props; + const showHomeIcon = bot.domain_whitelist && bot.domain_whitelist.length > 0; + const showCartIcon = bot.public; + const showGithubIcon = bot.github_installed; + const texts = [ + showGithubIcon ? I18N.components.BotCard.gITHU : '', + showHomeIcon ? I18N.components.BotCard.guanWang : undefined, + showCartIcon ? I18N.components.Navbar.shiChang : undefined, + ].filter(Boolean); + const toolTipText = I18N.template?.(I18N.components.BotCard.yiZaiTEX, { + val1: texts.join('、'), + }); + const isSingle = texts.length === 1; + return ( + +
+
{showGithubIcon && }
+
+ {showHomeIcon && } +
+
+ {showCartIcon && } +
+
+
+ ); +}; + const BotCard = (props: { bot: Bot }) => { + const [isHovered, setIsHovered] = useState(false); const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure(); const { bot } = props; const router = useRouter(); const { deleteBot, isLoading, isSuccess } = useBotDelete(); - const { data: taskInfo } = useGetBotRagTask(bot.id, true, false); const { mutate: updateKnowledge, isPending: isUpdating } = useKnowledgeUpdate(); + const { data: taskInfo } = useGetBotRagTask(bot.repo_name!, false); useEffect(() => { if (isSuccess) { @@ -60,14 +107,14 @@ const BotCard = (props: { bot: Bot }) => { ? TaskStatus.COMPLETED : 'others'; if (status === TaskStatus.COMPLETED) { - return ; + return ; } if (status === TaskStatus.ERROR) { return ; } return ( - + ); }; @@ -98,7 +145,13 @@ const BotCard = (props: { bot: Bot }) => { className="relative overflow-hidden w-full h-full bg-cover bg-center rounded-[8px]" style={{ backgroundImage: `url(${bot.avatar})` }} > -
+
{ src={bot.avatar!} />
+
+ +
-
-
+
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > +
{ {bot.name}
-
- {bot.public ? : } -
{renderTaskStatusIcon(taskInfo ?? [])}
diff --git a/client/app/github/installed/page.tsx b/client/app/github/installed/page.tsx index 8f889533..13705f1c 100644 --- a/client/app/github/installed/page.tsx +++ b/client/app/github/installed/page.tsx @@ -1,19 +1,47 @@ 'use client'; -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { Button } from '@nextui-org/react'; +import { useRouter } from 'next/navigation'; export default function GithubAppInstalled() { + const router = useRouter(); + const [countdown, setCountdown] = useState(5); + + useEffect(() => { + if (countdown > 0) { + const timer = setInterval(() => { + setCountdown((prevCountdown) => prevCountdown - 1); + }, 1000); + return () => clearInterval(timer); + } else { + redirectToLink(); + } + }, [countdown]); + + const redirectToLink = () => { + router.push('/factory/list'); + }; + + const handleClick = () => { + redirectToLink(); + }; + return (
-

+

Installation Approved

-

- Thank you for installing PeterCat's GitHub App! -

+

Thank you for installing PeterCat's GitHub App!

Your Team will now be able to use robots for your GitHub organization!

+
- ) + ); } diff --git a/client/app/globals.css b/client/app/globals.css index a93d9b1c..808700cf 100644 --- a/client/app/globals.css +++ b/client/app/globals.css @@ -2,6 +2,13 @@ @tailwind components; @tailwind utilities; +@font-face { + font-family: 'Solitreo'; + src: url('https://mdn.alipayobjects.com/huamei_j8gzmo/afts/file/A*o3yfRIN_sHgAAAAAAAAAAAAADrPSAQ/Solitreo-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + html { background-color: #000 !important; } @@ -239,3 +246,12 @@ div.fp-watermark { } } + +.hover-reverse-colors:hover * { + stroke: #fff; +} + +.hover-reverse-colors:hover { + background-color: #DC2626; + color: #fff !important; +} diff --git a/client/app/hooks/useAgreement.ts b/client/app/hooks/useAgreement.ts index 9a56cf2f..d5e91f70 100644 --- a/client/app/hooks/useAgreement.ts +++ b/client/app/hooks/useAgreement.ts @@ -1,5 +1,8 @@ -import { useMutation } from '@tanstack/react-query'; -import { acceptAgreement } from '@/app/services/UserController'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { + acceptAgreement, + getAgreementStatus, +} from '@/app/services/UserController'; export function useAgreement() { const mutation = useMutation({ mutationFn: acceptAgreement, @@ -13,3 +16,11 @@ export function useAgreement() { isSuccess: mutation.isSuccess, }; } + +export const useAgreementStatus = () => { + return useQuery({ + queryKey: [`agreement`], + queryFn: async () => getAgreementStatus(), + retry: false, + }); +}; diff --git a/client/app/hooks/useAvailableLLMs.ts b/client/app/hooks/useAvailableLLMs.ts new file mode 100644 index 00000000..749600ad --- /dev/null +++ b/client/app/hooks/useAvailableLLMs.ts @@ -0,0 +1,10 @@ +import { useQuery } from '@tanstack/react-query'; +import { getAvailableLLMs } from '../services/UserController'; + +export function useAvailableLLMs() { + return useQuery({ + queryKey: [`availableLLMS.llms`], + queryFn: async () => getAvailableLLMs(), + retry: true, + }); +} diff --git a/client/app/hooks/useAvaliableLLMs.ts b/client/app/hooks/useAvaliableLLMs.ts deleted file mode 100644 index b7e60713..00000000 --- a/client/app/hooks/useAvaliableLLMs.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { getAvaliableLLMs } from '../services/UserController'; - -export function useAvaliableLLMs() { - return useQuery({ - queryKey: [`avaliable.llms`], - queryFn: async () => getAvaliableLLMs(), - retry: true, - }); -} \ No newline at end of file diff --git a/client/app/hooks/useBot.ts b/client/app/hooks/useBot.ts index 83684e9e..bb986c80 100644 --- a/client/app/hooks/useBot.ts +++ b/client/app/hooks/useBot.ts @@ -4,11 +4,13 @@ import { deleteBot, deployWebsite, getBotApprovalList, + getBotBoundRepos, getBotConfig, getBotDetail, getBotInfoByRepoName, getBotList, getChunkList, + getGitAvatarByRepoName, getRagTask, getUserPeterCatAppRepos, publicBot, @@ -32,6 +34,16 @@ export const useBotDetail = (id: string) => { }); }; +export const useGetBotBoundRepos = (id: string) => { + return useQuery({ + queryKey: [`bot.boundRepos.${id}`, id], + queryFn: async () => getBotBoundRepos(id), + select: (data) => data, + enabled: !!id, + retry: false, + }); +}; + export const useBotConfig = (id: string, enabled: boolean) => { return useQuery({ queryKey: [`bot.config.${id}`, id], @@ -135,14 +147,13 @@ export const useBotRAGChunkList = ( export const useGetBotRagTask = ( repoName: string, - enabled: boolean = true, refetchInterval: boolean = true, ) => { return useQuery({ queryKey: [`rag.task`, repoName], queryFn: async () => getRagTask(repoName), select: (data) => data, - enabled, + enabled: !!repoName, retry: true, refetchInterval: refetchInterval ? 3 * 1000 : undefined, }); @@ -191,7 +202,16 @@ export const useGetUserPeterCatAppRepos = (enabled: boolean = true) => { queryKey: ['github.user.app.repos'], queryFn: async () => getUserPeterCatAppRepos(), select: (data) => data.data, - enabled + enabled, + }); +}; + +export const useGetGitAvatar = (repoName?: string) => { + return useQuery({ + queryKey: ['github.repo.name', repoName], + queryFn: async () => getGitAvatarByRepoName(repoName!), + select: (data) => data.data.data, + enabled: !!repoName, }); }; diff --git a/client/app/hooks/useUser.ts b/client/app/hooks/useUser.ts index 23357dc1..2099af71 100644 --- a/client/app/hooks/useUser.ts +++ b/client/app/hooks/useUser.ts @@ -1,19 +1,34 @@ import { useUser as useAssistUser } from '@petercatai/assistant'; import { useFingerprint } from './useFingerprint'; +import { useQuery } from '@tanstack/react-query'; +import { getUserRepos } from '../services/UserController'; +import { map } from 'lodash'; const API_DOMAIN = process.env.NEXT_PUBLIC_API_DOMAIN!; -export default function useUser() { +export const useUser = () => { const { data: fingerprint } = useFingerprint(); const { user, isLoading, actions } = useAssistUser({ apiDomain: API_DOMAIN, - fingerprint: fingerprint?.visitorId! + webDomain: '', + fingerprint: fingerprint?.visitorId!, }); return { user, isLoading, actions, - status: isLoading ? "pending" : 'success', + status: isLoading ? 'pending' : 'success', }; -} +}; + +export const useUserRepos = (enabled: boolean) => { + return useQuery({ + queryKey: [`user.repos`], + queryFn: async () => getUserRepos(), + enabled, + select: (data) => + map(data.data, (item) => ({ label: item.name, key: item.name })), + retry: true, + }); +}; diff --git a/client/app/layout.tsx b/client/app/layout.tsx index 16e91345..8feebc6f 100644 --- a/client/app/layout.tsx +++ b/client/app/layout.tsx @@ -34,7 +34,8 @@ export default function RootLayout({ {pathname === '/' || pathname === '/policy' || - pathname === '/agreement' ? ( + pathname === '/agreement' || + pathname === '/release' ? ( children ) : (
diff --git a/client/app/market/page.tsx b/client/app/market/page.tsx index cc16aab4..b6474d5a 100644 --- a/client/app/market/page.tsx +++ b/client/app/market/page.tsx @@ -6,38 +6,29 @@ import BotCard from '@/components/BotCard'; import { useBotList } from '@/app/hooks/useBot'; import FullPageSkeleton from '@/components/FullPageSkeleton'; import { useGlobal } from '@/app/contexts/GlobalContext'; -import { Assistant } from '@petercatai/assistant'; +import { Assistant, useUser } from '@petercatai/assistant'; import { useFingerprint } from '../hooks/useFingerprint'; import Crash from '@/components/Crash'; declare type Bot = Tables<'bots'>; const ASSISTANT_API_HOST = process.env.NEXT_PUBLIC_API_DOMAIN; +const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN!; export default function Market() { const { search } = useGlobal(); const [visible, setVisible] = useState(false); const [isComplete, setComplete] = useState(false); const [currentBot, setCurrentBot] = useState(''); - const { data: bots, isLoading, error } = useBotList(false, search); - const [userInfo, setUserInfo] = useState(null); const { data } = useFingerprint(); - - useEffect(() => { - setUserInfo(sessionStorage.getItem('userInfo')); - }, []); + const { user, isLoading: userLoading } = useUser({ apiDomain, fingerprint: data?.visitorId || '' }); + const { data: bots, isLoading, error } = useBotList(false, search, !!user); const isOpening = useMemo( - () => !userInfo && (isLoading || !isComplete), - [isComplete, isLoading, userInfo], + () => !user && (userLoading || isLoading || !isComplete), + [isComplete, userLoading, isLoading, user], ); - useEffect(() => { - if (data) { - sessionStorage.setItem('userInfo', JSON.stringify(data?.visitorId)); - } - }, [data]); - const handleCardClick = (id: string) => { setVisible(true); setCurrentBot(id); diff --git a/client/app/page.tsx b/client/app/page.tsx index cfdaa88a..e035894e 100644 --- a/client/app/page.tsx +++ b/client/app/page.tsx @@ -10,6 +10,7 @@ import LottieLightningCat from '@/app/assets/lightning_cat.json'; import LottieHelixCat from '@/app/assets/helix_cat.json'; import LottieOctopusCat from '@/app/assets/octopus_cat.json'; import HomeHeader from '@/components/HomeHeader'; +import HomeFooter from '@/components/HomeFooter'; import { useGlobal } from '@/app/contexts/GlobalContext'; const Lottie = dynamic(() => import('lottie-react'), { ssr: false }); @@ -42,76 +43,6 @@ const MOBILE_EXAMPLE_VIDEO_EN = [ 'https://gw.alipayobjects.com/v/huamei_j8gzmo/afts/video/A*2WtmRaBv6eAAAAAAAAAAAAAADrPSAQ', ]; -function Contributors() { - return ( - <> - CONTRIBUTORS - - - ); -} - export default function Homepage() { const videoRefs = { banner: useRef(null), @@ -314,7 +245,7 @@ export default function Homepage() {

{I18N.app.page.liJiChangShi} @@ -586,131 +517,7 @@ export default function Homepage() {
- +
)} diff --git a/client/app/release/page.tsx b/client/app/release/page.tsx new file mode 100644 index 00000000..eb101b39 --- /dev/null +++ b/client/app/release/page.tsx @@ -0,0 +1,81 @@ +'use client'; + +import React from 'react'; +import I18N from '@/app/utils/I18N'; +import Image from 'next/image'; +import HomeHeader from '@/components/HomeHeader'; +import HomeFooter from '@/components/HomeFooter'; + +export default function Release() { + const releaseNotes = [ + { + date: 'NOV 22 / 2024', + version: '1.0.1', + title: I18N.release.page.guoJiHuaZhiChi, + summary: I18N.release.page.quanLianLuGuoJi, + url: 'https://link.medium.com/QOA4Ed8NUOb', + }, + { + date: 'SEP 6 / 2024', + version: '1.0.0', + title: I18N.release.page.waiTanDaHuiShou, + summary: I18N.release.page.xiaoMaoMiJiQi, + url: 'https://mp.weixin.qq.com/s/PnHVc1_yBPu2HiA2En9cAg', + }, + ]; + return ( +
+ +
+

+ {I18N.app.page.release} +

+

+ {I18N.release.page.guanZhu}PeterCat + {I18N.release.page.zuiXinChanPinXin}

+ + GitHub Release Notes + +
+
+ {releaseNotes.map((note, index) => ( +
+
+

+ {note.date} +

+ + V{note.version} + +
+ +

+ {note.title} +

+

{note.summary}

+
+
+ ))} +
+ +
+ ); +} diff --git a/client/app/services/BotsController.ts b/client/app/services/BotsController.ts index d0db8a37..bcd3e467 100644 --- a/client/app/services/BotsController.ts +++ b/client/app/services/BotsController.ts @@ -16,6 +16,13 @@ export async function getBotDetail(id: string): Promise { return response.data.data; } +export async function getBotBoundRepos(id: string): Promise { + const response = await axios.get( + `${apiDomain}/api/bot/bound_to_repository?bot_id=${id}`, + ); + return response.data.data; +} + // Get current user's bot profile by id export async function getBotConfig(id: string): Promise { const response = await axios.get(`${apiDomain}/api/bot/config?id=${id}`); @@ -67,6 +74,10 @@ export async function getBotInfoByRepoName(params: { return axios.post(`${apiDomain}/api/bot/config/generator`, params); } +export async function getGitAvatarByRepoName(repo_name: string) { + return axios.get(`${apiDomain}/api/bot/git/avatar?repo_name=${repo_name}`); +} + export async function getChunkList( repo_name: string, page_size: number, diff --git a/client/app/services/UserController.ts b/client/app/services/UserController.ts index 08f1742d..7406e267 100644 --- a/client/app/services/UserController.ts +++ b/client/app/services/UserController.ts @@ -20,7 +20,21 @@ export async function acceptAgreement() { return response.data; } -export async function getAvaliableLLMs() { +export async function getAgreementStatus() { + const response = await axios.get(`${apiDomain}/api/auth/agreement/status`, { + withCredentials: true, + }); + return response.data; +} + +export async function getUserRepos() { + const response = await axios.get(`${apiDomain}/api/auth/repos`, { + withCredentials: true, + }); + return response.data; +} + +export async function getAvailableLLMs() { const response = await axios.get(`${apiDomain}/api/user/llms`, { withCredentials: true, }); diff --git a/client/app/utils/tools.ts b/client/app/utils/tools.ts index a7df2aad..8f525ef6 100644 --- a/client/app/utils/tools.ts +++ b/client/app/utils/tools.ts @@ -13,10 +13,11 @@ export const extractParametersByTools = (content: string) => { return null; }; -export const extractFullRepoNameFromGitHubUrl = (githubUrl: string) => { +export const extractFullRepoNameFromGitHubUrl = (input: string) => { try { - const regex = /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)(\/.*)?$/; - const match = githubUrl.match(regex); + // Use a regex that matches both full URLs and `username/reponame` format. + const regex = /^(?:https:\/\/github\.com\/)?([^\/]+)\/([^\/]+)(?:\/.*)?$/; + const match = input.match(regex); if (match && match[1] && match[2]) { return `${match[1]}/${match[2]}`; @@ -24,7 +25,7 @@ export const extractFullRepoNameFromGitHubUrl = (githubUrl: string) => { return null; } } catch (error) { - console.error('Error parsing GitHub URL:', error); + console.error('Error parsing input:', error); return null; } }; diff --git a/client/components/BotCard.tsx b/client/components/BotCard.tsx index 2e72dc60..74f4d53a 100644 --- a/client/components/BotCard.tsx +++ b/client/components/BotCard.tsx @@ -27,7 +27,13 @@ const BotCard = (props: { className="relative overflow-hidden w-full h-full bg-cover bg-center rounded-[8px]" style={{ backgroundImage: `url(${bot.avatar})` }} > -
+
-

{bot.description} diff --git a/client/components/HomeFooter.tsx b/client/components/HomeFooter.tsx new file mode 100644 index 00000000..67b04d0e --- /dev/null +++ b/client/components/HomeFooter.tsx @@ -0,0 +1,203 @@ +import React from 'react'; +import Image from 'next/image'; +import I18N from '@/app/utils/I18N'; + +const HomeFooter = () => { + function Contributors() { + return ( + <> + CONTRIBUTORS +

+ + ); + } + + return ( + + ); +}; + +export default HomeFooter; diff --git a/client/components/HomeHeader.tsx b/client/components/HomeHeader.tsx index f2fead32..c63ed55d 100644 --- a/client/components/HomeHeader.tsx +++ b/client/components/HomeHeader.tsx @@ -52,7 +52,7 @@ const HomeHeader = () => { {I18N.app.page.yanShiAnLi} {I18N.app.page.gongZuoTai} @@ -64,6 +64,12 @@ const HomeHeader = () => { > {I18N.app.page.wenDang} + + {I18N.app.page.release} + setShowMobileNav(false)} diff --git a/client/components/Navbar.tsx b/client/components/Navbar.tsx index dab2ca71..8e73b348 100644 --- a/client/components/Navbar.tsx +++ b/client/components/Navbar.tsx @@ -19,18 +19,18 @@ export function Navbar() { const router = useRouter(); const pathname = usePathname(); const navs = [ - { - id: 'market', - label: I18N.components.Navbar.shiChang, - href: '/market', - icon: , - }, { id: 'factory', label: I18N.components.Navbar.kongJian, href: '/factory/list', icon: , }, + { + id: 'market', + label: I18N.components.Navbar.shiChang, + href: '/market', + icon: , + }, ]; if (pathname.includes('/factory/edit')) { @@ -54,7 +54,7 @@ export function Navbar() { setSearch(''); }; return ( -
+
petercat diff --git a/client/components/User.tsx b/client/components/User.tsx index 485501ae..887a9068 100644 --- a/client/components/User.tsx +++ b/client/components/User.tsx @@ -1,6 +1,5 @@ 'use client'; import I18N from '@/app/utils/I18N'; -import { useRouter } from 'next/navigation'; import { Avatar, Button, @@ -9,7 +8,7 @@ import { DropdownMenu, DropdownTrigger, } from '@nextui-org/react'; -import useUser from '../app/hooks/useUser'; +import { useUser } from '../app/hooks/useUser'; import GitHubIcon from '@/public/icons/GitHubIcon'; import Link from 'next/link'; @@ -23,13 +22,14 @@ export default function Profile() { className="min-w-[88px] px-4 h-10 inline-block transition-colors bg-[#3F3F46] text-[#FFFFFF] rounded-full leading-10 text-center" > - {I18N.components.User.dengLu} + {I18N.components.User.dengLu} + ); } const avatar = ( - + {I18N.components.User.tOKEN} - + {I18N.components.User.dengChu} diff --git a/client/kiwi-config.json b/client/kiwi-config.json index a3dc2ddc..099226aa 100644 --- a/client/kiwi-config.json +++ b/client/kiwi-config.json @@ -1,9 +1,7 @@ { "fileType": "ts", "srcLang": "zh-CN", - "distLangs": [ - "en-US" - ], + "distLangs": ["en-US"], "googleApiKey": "", "baiduApiKey": { "appId": "", @@ -19,5 +17,8 @@ "defaultTranslateKeyApi": "Pinyin", "importI18N": "import I18N from '@/app/utils/I18N';", "ignoreDir": [], - "ignoreFile": ["client/components/LangSwitcher.tsx"] + "ignoreFile": [ + "client/components/LangSwitcher.tsx", + "client/app/contexts/GlobalContext.tsx" + ] } diff --git a/client/package.json b/client/package.json index 0abd1323..6b54fa7b 100644 --- a/client/package.json +++ b/client/package.json @@ -22,7 +22,7 @@ "@fullpage/react-fullpage": "^0.1.42", "@next/bundle-analyzer": "^13.4.19", "@nextui-org/react": "^2.2.9", - "@petercatai/assistant": "^1.0.16", + "@petercatai/assistant": "1.0.22", "@sentry/nextjs": "^8.28.0", "@supabase/supabase-js": "^2.32.0", "@tanstack/react-query": "^5.17.19", diff --git a/client/public/icons/CardCartIcon.tsx b/client/public/icons/CardCartIcon.tsx new file mode 100644 index 00000000..e6d121ad --- /dev/null +++ b/client/public/icons/CardCartIcon.tsx @@ -0,0 +1,43 @@ +const CardCartIcon = () => ( + + + + + + + + + + + + + + +); +export default CardCartIcon; diff --git a/client/public/icons/CardGithubIcon.tsx b/client/public/icons/CardGithubIcon.tsx new file mode 100644 index 00000000..e2e4a81c --- /dev/null +++ b/client/public/icons/CardGithubIcon.tsx @@ -0,0 +1,23 @@ +const CardGithubIcon = () => ( + + + + + +); +export default CardGithubIcon; diff --git a/client/public/icons/CardHomeIcon.tsx b/client/public/icons/CardHomeIcon.tsx new file mode 100644 index 00000000..57ccda83 --- /dev/null +++ b/client/public/icons/CardHomeIcon.tsx @@ -0,0 +1,25 @@ +const CardHomeIcon = () => ( + + + + + +); +export default CardHomeIcon; diff --git a/client/public/icons/CheckBadgeIcon.tsx b/client/public/icons/CheckBadgeIcon.tsx index 04815ff1..7bc7b017 100644 --- a/client/public/icons/CheckBadgeIcon.tsx +++ b/client/public/icons/CheckBadgeIcon.tsx @@ -1,17 +1,19 @@ -const CheckBadgeIcon = () => ( +const KnowledgeTaskCompleteIcon = () => ( + ); -export default CheckBadgeIcon; +export default KnowledgeTaskCompleteIcon; diff --git a/client/public/icons/DeleteButtonIcon.tsx b/client/public/icons/DeleteButtonIcon.tsx new file mode 100644 index 00000000..88162fc5 --- /dev/null +++ b/client/public/icons/DeleteButtonIcon.tsx @@ -0,0 +1,115 @@ +const DeleteButtonIcon = () => ( +
+
+ + + + + + + + + + + + + + + Delete Robot +
+
+); +export default DeleteButtonIcon; diff --git a/client/public/icons/LoadingIcon.tsx b/client/public/icons/LoadingIcon.tsx index e5ef0116..599dd2a5 100644 --- a/client/public/icons/LoadingIcon.tsx +++ b/client/public/icons/LoadingIcon.tsx @@ -1,18 +1,18 @@ -const LoadingIcon = () => ( +const KnowledgeTaskRunningIcon = () => ( ); -export default LoadingIcon; +export default KnowledgeTaskRunningIcon; diff --git a/client/public/images/chevron-down.svg b/client/public/images/chevron-down.svg new file mode 100644 index 00000000..69f531bd --- /dev/null +++ b/client/public/images/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/public/images/chevron-up.svg b/client/public/images/chevron-up.svg new file mode 100644 index 00000000..15e1df39 --- /dev/null +++ b/client/public/images/chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/public/images/delete-button.svg b/client/public/images/delete-button.svg deleted file mode 100644 index 4838497e..00000000 --- a/client/public/images/delete-button.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/client/tailwind.config.js b/client/tailwind.config.js index de9a4115..946fa2ae 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -18,7 +18,10 @@ module.exports = { }, boxShadow: { 'large': '0px 20px 25px -5px rgba(0, 0, 0, 0.10), 0px 10px 10px -5px rgba(0, 0, 0, 0.04);' - } + }, + fontFamily: { + solitreo: ['Solitreo', 'sans-serif'], + }, }, }, darkMode: "class", diff --git a/client/types/database.types.ts b/client/types/database.types.ts index c134ef1c..20f05584 100644 --- a/client/types/database.types.ts +++ b/client/types/database.types.ts @@ -34,6 +34,33 @@ export type Database = { }; public: { Tables: { + bot_approval: { + Row: { + approval_path: string | null; + approval_status: string | null; + bot_id: string | null; + created_at: string; + id: string; + task_type: string | null; + }; + Insert: { + approval_path?: string | null; + approval_status?: string | null; + bot_id?: string | null; + created_at?: string; + id?: string; + task_type?: string | null; + }; + Update: { + approval_path?: string | null; + approval_status?: string | null; + bot_id?: string | null; + created_at?: string; + id?: string; + task_type?: string | null; + }; + Relationships: []; + }; bots: { Row: { avatar: string | null; @@ -44,11 +71,15 @@ export type Database = { id: string; label: string | null; llm: string | null; + n: number | null; name: string; prompt: string | null; public: boolean | null; repo_name: string | null; starters: string[] | null; + temperature: number | null; + token_id: string | null; + top_p: number | null; uid: string | null; updated_at: string | null; }; @@ -61,11 +92,15 @@ export type Database = { id?: string; label?: string | null; llm?: string | null; + n?: number | null; name?: string; prompt?: string | null; public?: boolean | null; repo_name?: string | null; starters?: string[] | null; + temperature?: number | null; + token_id?: string | null; + top_p?: number | null; uid?: string | null; updated_at?: string | null; }; @@ -78,11 +113,15 @@ export type Database = { id?: string; label?: string | null; llm?: string | null; + n?: number | null; name?: string; prompt?: string | null; public?: boolean | null; repo_name?: string | null; starters?: string[] | null; + temperature?: number | null; + token_id?: string | null; + top_p?: number | null; uid?: string | null; updated_at?: string | null; }; @@ -124,60 +163,30 @@ export type Database = { }; Relationships: []; }; - github_app_authorization: { - Row: { - code: string | null; - created_at: string; - expires_at: string | null; - id: number; - installation_id: string | null; - permissions: Json | null; - token: string | null; - }; - Insert: { - code?: string | null; - created_at?: string; - expires_at?: string | null; - id?: number; - installation_id?: string | null; - permissions?: Json | null; - token?: string | null; - }; - Update: { - code?: string | null; - created_at?: string; - expires_at?: string | null; - id?: number; - installation_id?: string | null; - permissions?: Json | null; - token?: string | null; - }; - Relationships: []; - }; github_repo_config: { Row: { created_at: string; id: string; - repo_name: string | null; - robot_id: string | null; owner_id: string | null; repo_id: string | null; + repo_name: string | null; + robot_id: string | null; }; Insert: { created_at?: string; id?: string; - repo_name?: string | null; - robot_id?: string | null; owner_id?: string | null; repo_id?: string | null; + repo_name?: string | null; + robot_id?: string | null; }; Update: { created_at?: string; id?: string; - repo_name?: string | null; - robot_id?: string | null; owner_id?: string | null; repo_id?: string | null; + repo_name?: string | null; + robot_id?: string | null; }; Relationships: []; }; @@ -222,6 +231,7 @@ export type Database = { }; profiles: { Row: { + agreement_accepted: boolean | null; created_at: string; id: string; name: string | null; @@ -229,9 +239,9 @@ export type Database = { picture: string | null; sid: string | null; sub: string | null; - agreement_accepted: boolean | null; }; Insert: { + agreement_accepted?: boolean | null; created_at?: string; id: string; name?: string | null; @@ -239,9 +249,9 @@ export type Database = { picture?: string | null; sid?: string | null; sub?: string | null; - agreement_accepted?: boolean | null; }; Update: { + agreement_accepted?: boolean | null; created_at?: string; id?: string; name?: string | null; @@ -249,7 +259,6 @@ export type Database = { picture?: string | null; sid?: string | null; sub?: string | null; - agreement_accepted?: boolean | null; }; Relationships: []; }; @@ -263,7 +272,7 @@ export type Database = { file_sha: string | null; id: string; metadata: Json | null; - repo_name: string | null; + repo_name: string; update_timestamp: string | null; }; Insert: { @@ -275,7 +284,7 @@ export type Database = { file_sha?: string | null; id?: string; metadata?: Json | null; - repo_name?: string | null; + repo_name: string; update_timestamp?: string | null; }; Update: { @@ -287,7 +296,7 @@ export type Database = { file_sha?: string | null; id?: string; metadata?: Json | null; - repo_name?: string | null; + repo_name?: string; update_timestamp?: string | null; }; Relationships: []; @@ -403,35 +412,95 @@ export type Database = { }; Relationships: []; }; - user_token_usage: { + user_rate_limit: { Row: { created_at: string; - id: number; + id: string; last_request: string | null; request_count: number | null; user_id: string | null; }; Insert: { created_at?: string; - id?: number; + id?: string; last_request?: string | null; request_count?: number | null; user_id?: string | null; }; Update: { created_at?: string; - id?: number; + id?: string; last_request?: string | null; request_count?: number | null; user_id?: string | null; }; Relationships: []; }; + user_token_usage: { + Row: { + bot_id: string | null; + created_at: string; + date: string | null; + id: string; + input_token: number | null; + output_token: number | null; + token_id: string | null; + total_token: number | null; + user_id: string | null; + }; + Insert: { + bot_id?: string | null; + created_at?: string; + date?: string | null; + id?: string; + input_token?: number | null; + output_token?: number | null; + token_id?: string | null; + total_token?: number | null; + user_id?: string | null; + }; + Update: { + bot_id?: string | null; + created_at?: string; + date?: string | null; + id?: string; + input_token?: number | null; + output_token?: number | null; + token_id?: string | null; + total_token?: number | null; + user_id?: string | null; + }; + Relationships: []; + }; }; Views: { [_ in never]: never; }; Functions: { + binary_quantize: + | { + Args: { + '': string; + }; + Returns: unknown; + } + | { + Args: { + '': unknown; + }; + Returns: unknown; + }; + count_rag_docs_by_sha: { + Args: { + file_sha_input: string; + }; + Returns: { + count: number; + file_sha: string; + repo_name: string; + file_path: string; + }[]; + }; execute_sql: { Args: { query: string; @@ -453,18 +522,125 @@ export type Database = { hello_message: string; }[]; }; + get_bot_stats: { + Args: { + filter_bot_id: string; + }; + Returns: { + call_cnt: number; + }[]; + }; + get_user_stats: { + Args: { + filter_user_id: string; + start_date: string; + end_date: string; + }; + Returns: { + usage_date: string; + input_tokens: number; + output_tokens: number; + total_tokens: number; + }[]; + }; + halfvec_avg: { + Args: { + '': number[]; + }; + Returns: unknown; + }; + halfvec_out: { + Args: { + '': unknown; + }; + Returns: unknown; + }; + halfvec_send: { + Args: { + '': unknown; + }; + Returns: string; + }; + halfvec_typmod_in: { + Args: { + '': unknown[]; + }; + Returns: number; + }; + hnsw_bit_support: { + Args: { + '': unknown; + }; + Returns: unknown; + }; + hnsw_halfvec_support: { + Args: { + '': unknown; + }; + Returns: unknown; + }; + hnsw_sparsevec_support: { + Args: { + '': unknown; + }; + Returns: unknown; + }; hnswhandler: { Args: { '': unknown; }; Returns: unknown; }; + ivfflat_bit_support: { + Args: { + '': unknown; + }; + Returns: unknown; + }; + ivfflat_halfvec_support: { + Args: { + '': unknown; + }; + Returns: unknown; + }; ivfflathandler: { Args: { '': unknown; }; Returns: unknown; }; + l2_norm: + | { + Args: { + '': unknown; + }; + Returns: number; + } + | { + Args: { + '': unknown; + }; + Returns: number; + }; + l2_normalize: + | { + Args: { + '': string; + }; + Returns: string; + } + | { + Args: { + '': unknown; + }; + Returns: unknown; + } + | { + Args: { + '': unknown; + }; + Returns: unknown; + }; match_embedding_docs: { Args: { query_embedding: string; @@ -491,18 +667,43 @@ export type Database = { similarity: number; }[]; }; - vector_avg: { + sparsevec_out: { Args: { - '': number[]; + '': unknown; + }; + Returns: unknown; + }; + sparsevec_send: { + Args: { + '': unknown; }; Returns: string; }; - vector_dims: { + sparsevec_typmod_in: { Args: { - '': string; + '': unknown[]; }; Returns: number; }; + vector_avg: { + Args: { + '': number[]; + }; + Returns: string; + }; + vector_dims: + | { + Args: { + '': string; + }; + Returns: number; + } + | { + Args: { + '': unknown; + }; + Returns: number; + }; vector_norm: { Args: { '': string; @@ -535,321 +736,6 @@ export type Database = { [_ in never]: never; }; }; - storage: { - Tables: { - buckets: { - Row: { - allowed_mime_types: string[] | null; - avif_autodetection: boolean | null; - created_at: string | null; - file_size_limit: number | null; - id: string; - name: string; - owner: string | null; - owner_id: string | null; - public: boolean | null; - updated_at: string | null; - }; - Insert: { - allowed_mime_types?: string[] | null; - avif_autodetection?: boolean | null; - created_at?: string | null; - file_size_limit?: number | null; - id: string; - name: string; - owner?: string | null; - owner_id?: string | null; - public?: boolean | null; - updated_at?: string | null; - }; - Update: { - allowed_mime_types?: string[] | null; - avif_autodetection?: boolean | null; - created_at?: string | null; - file_size_limit?: number | null; - id?: string; - name?: string; - owner?: string | null; - owner_id?: string | null; - public?: boolean | null; - updated_at?: string | null; - }; - Relationships: []; - }; - migrations: { - Row: { - executed_at: string | null; - hash: string; - id: number; - name: string; - }; - Insert: { - executed_at?: string | null; - hash: string; - id: number; - name: string; - }; - Update: { - executed_at?: string | null; - hash?: string; - id?: number; - name?: string; - }; - Relationships: []; - }; - objects: { - Row: { - bucket_id: string | null; - created_at: string | null; - id: string; - last_accessed_at: string | null; - metadata: Json | null; - name: string | null; - owner: string | null; - owner_id: string | null; - path_tokens: string[] | null; - updated_at: string | null; - user_metadata: Json | null; - version: string | null; - }; - Insert: { - bucket_id?: string | null; - created_at?: string | null; - id?: string; - last_accessed_at?: string | null; - metadata?: Json | null; - name?: string | null; - owner?: string | null; - owner_id?: string | null; - path_tokens?: string[] | null; - updated_at?: string | null; - user_metadata?: Json | null; - version?: string | null; - }; - Update: { - bucket_id?: string | null; - created_at?: string | null; - id?: string; - last_accessed_at?: string | null; - metadata?: Json | null; - name?: string | null; - owner?: string | null; - owner_id?: string | null; - path_tokens?: string[] | null; - updated_at?: string | null; - user_metadata?: Json | null; - version?: string | null; - }; - Relationships: [ - { - foreignKeyName: 'objects_bucketId_fkey'; - columns: ['bucket_id']; - isOneToOne: false; - referencedRelation: 'buckets'; - referencedColumns: ['id']; - }, - ]; - }; - s3_multipart_uploads: { - Row: { - bucket_id: string; - created_at: string; - id: string; - in_progress_size: number; - key: string; - owner_id: string | null; - upload_signature: string; - user_metadata: Json | null; - version: string; - }; - Insert: { - bucket_id: string; - created_at?: string; - id: string; - in_progress_size?: number; - key: string; - owner_id?: string | null; - upload_signature: string; - user_metadata?: Json | null; - version: string; - }; - Update: { - bucket_id?: string; - created_at?: string; - id?: string; - in_progress_size?: number; - key?: string; - owner_id?: string | null; - upload_signature?: string; - user_metadata?: Json | null; - version?: string; - }; - Relationships: [ - { - foreignKeyName: 's3_multipart_uploads_bucket_id_fkey'; - columns: ['bucket_id']; - isOneToOne: false; - referencedRelation: 'buckets'; - referencedColumns: ['id']; - }, - ]; - }; - s3_multipart_uploads_parts: { - Row: { - bucket_id: string; - created_at: string; - etag: string; - id: string; - key: string; - owner_id: string | null; - part_number: number; - size: number; - upload_id: string; - version: string; - }; - Insert: { - bucket_id: string; - created_at?: string; - etag: string; - id?: string; - key: string; - owner_id?: string | null; - part_number: number; - size?: number; - upload_id: string; - version: string; - }; - Update: { - bucket_id?: string; - created_at?: string; - etag?: string; - id?: string; - key?: string; - owner_id?: string | null; - part_number?: number; - size?: number; - upload_id?: string; - version?: string; - }; - Relationships: [ - { - foreignKeyName: 's3_multipart_uploads_parts_bucket_id_fkey'; - columns: ['bucket_id']; - isOneToOne: false; - referencedRelation: 'buckets'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 's3_multipart_uploads_parts_upload_id_fkey'; - columns: ['upload_id']; - isOneToOne: false; - referencedRelation: 's3_multipart_uploads'; - referencedColumns: ['id']; - }, - ]; - }; - }; - Views: { - [_ in never]: never; - }; - Functions: { - can_insert_object: { - Args: { - bucketid: string; - name: string; - owner: string; - metadata: Json; - }; - Returns: undefined; - }; - extension: { - Args: { - name: string; - }; - Returns: string; - }; - filename: { - Args: { - name: string; - }; - Returns: string; - }; - foldername: { - Args: { - name: string; - }; - Returns: string[]; - }; - get_size_by_bucket: { - Args: Record; - Returns: { - size: number; - bucket_id: string; - }[]; - }; - list_multipart_uploads_with_delimiter: { - Args: { - bucket_id: string; - prefix_param: string; - delimiter_param: string; - max_keys?: number; - next_key_token?: string; - next_upload_token?: string; - }; - Returns: { - key: string; - id: string; - created_at: string; - }[]; - }; - list_objects_with_delimiter: { - Args: { - bucket_id: string; - prefix_param: string; - delimiter_param: string; - max_keys?: number; - start_after?: string; - next_token?: string; - }; - Returns: { - name: string; - id: string; - metadata: Json; - updated_at: string; - }[]; - }; - operation: { - Args: Record; - Returns: string; - }; - search: { - Args: { - prefix: string; - bucketname: string; - limits?: number; - levels?: number; - offsets?: number; - search?: string; - sortcolumn?: string; - sortorder?: string; - }; - Returns: { - name: string; - id: string; - updated_at: string; - created_at: string; - last_accessed_at: string; - metadata: Json; - }[]; - }; - }; - Enums: { - [_ in never]: never; - }; - CompositeTypes: { - [_ in never]: never; - }; - }; }; type PublicSchema = Database[Extract]; @@ -933,3 +819,18 @@ export type Enums< : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] ? PublicSchema['Enums'][PublicEnumNameOrOptions] : never; + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof PublicSchema['CompositeTypes'] + | { schema: keyof Database }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof Database; + } + ? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } + ? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof PublicSchema['CompositeTypes'] + ? PublicSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] + : never; diff --git a/client/yarn.lock b/client/yarn.lock index 21401693..8efbf301 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2842,10 +2842,10 @@ resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.2.1.tgz#cb0d111ef700136f4580349ff0226bf25c853f23" integrity sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw== -"@petercatai/assistant@^1.0.16": - version "1.0.16" - resolved "https://registry.yarnpkg.com/@petercatai/assistant/-/assistant-1.0.16.tgz#315d74220a5765155a51dc0b1f6a8cb475040e2e" - integrity sha512-sCsBAjDAVwfXOqjy8Q0gnFvh6iXGM84v6zotSGAinJINqb52hEkrL3J3n0/Wp/6UXWQiiE0IdSA4qi9W7k9QhA== +"@petercatai/assistant@1.0.22": + version "1.0.22" + resolved "https://registry.yarnpkg.com/@petercatai/assistant/-/assistant-1.0.22.tgz#a4113bf4eae9dc66ad0f0e2b33b1f579ca1252a2" + integrity sha512-E8uMZRK3bdD9Oh2mQhK6Zd2A+KV6dt/H2F/fnv/cBT6KOdywwDQIx94K/2fTcpZJXPsUCTMcOhl2877FNaJkxQ== dependencies: "@ant-design/icons" "^5.3.5" "@ant-design/pro-chat" "^1.9.0" diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index f3377f10..00000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,53 +0,0 @@ -# Usage -# Start: docker compose up -# With helpers: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up -# Stop: docker compose down -# Destroy: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down -v --remove-orphans - -name: petercat -version: "1.0" - -services: - backend-core: - image: petercat-backend-base - build: - context: ../server - dockerfile: ../docker/Dockerfile.aws.lambda - restart: always - ports: - - 8080:8080 - environment: - AWS_GITHUB_SECRET_NAME: ${AWS_GITHUB_SECRET_NAME} - S3_TEMP_BUCKET_NAME: ${S3_TEMP_BUCKET_NAME} - API_URL: ${API_URL} - WEB_URL: ${WEB_URL} - X_GITHUB_APP_ID: ${X_GITHUB_APP_ID} - X_GITHUB_APPS_CLIENT_ID: ${X_GITHUB_APPS_CLIENT_ID} - X_GITHUB_APPS_CLIENT_SECRET: ${X_GITHUB_APPS_CLIENT_SECRET} - API_IDENTIFIER: ${API_IDENTIFIER} - FASTAPI_SECRET_KEY: ${FASTAPI_SECRET_KEY} - OPENAI_API_KEY: ${OPENAI_API_KEY} - GEMINI_API_KEY: ${GEMINI_API_KEY} - SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_KEY} - SUPABASE_URL: ${SUPABASE_URL} - GITHUB_TOKEN: ${GITHUB_TOKEN} - TAVILY_API_KEY: ${TAVILY_API_KEY} - SQS_QUEUE_URL: ${SQS_QUEUE_URL} - AUTH0_DOMAIN: ${AUTH0_DOMAIN} - AUTH0_CLIENT_ID: ${AUTH0_CLIENT_ID} - AUTH0_CLIENT_SECRET: ${AUTH0_CLIENT_SECRET} - front-end: - image: petercat-frontend - depends_on: - - backend-core - build: - context: .. - dockerfile: ./docker/Dockerfile.vercel - restart: always - ports: - - 3000:3000 - environment: - SUPABASE_URL: ${SUPABASE_URL} - SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_KEY} - NEXT_PUBLIC_API_DOMAIN: http://0.0.0.0:8080 - NEXT_STANDALONE: true \ No newline at end of file diff --git a/docs/guides/quick_ assistant_start.md b/docs/guides/quick_ assistant_start.md index 8beaf68d..846d8440 100644 --- a/docs/guides/quick_ assistant_start.md +++ b/docs/guides/quick_ assistant_start.md @@ -37,7 +37,7 @@ import { Assistant } from '@petercatai/assistant'; import '@petercatai/assistant/style'; const YourPetercataiAssistant = () => { - return ; + return ; }; function App() { diff --git a/docs/guides/quick_ assistant_start_cn.md b/docs/guides/quick_ assistant_start_cn.md index 56f14997..268d1382 100644 --- a/docs/guides/quick_ assistant_start_cn.md +++ b/docs/guides/quick_ assistant_start_cn.md @@ -36,7 +36,7 @@ import { Assistant } from '@petercatai/assistant'; import '@petercatai/assistant/style'; const YourPetercataiAssistant = () => { - return ; + return ; }; function App() { diff --git a/docs/guides/self_hosted_aws.md b/docs/guides/self_hosted_aws.md index 45ba8710..39bf7a6b 100644 --- a/docs/guides/self_hosted_aws.md +++ b/docs/guides/self_hosted_aws.md @@ -98,7 +98,7 @@ sam deploy \ --config-file .aws/petercat-ap-southeast.toml \ --parameter-overrides APIUrl=$API_URL \ WebUrl=$WEB_URL \ - AWSSecretName=$AWS_GITHUB_SECRET_NAME \ + AWSSecretName=$X_GITHUB_SECRET_NAME \ S3TempBucketName=$S3_TEMP_BUCKET_NAME \ GitHubAppID=$X_GITHUB_APP_ID \ GithubAppsClientId=$X_GITHUB_APPS_CLIENT_ID \ diff --git a/docs/guides/self_hosted_aws_cn.md b/docs/guides/self_hosted_aws_cn.md index 86c37de1..9caf215a 100644 --- a/docs/guides/self_hosted_aws_cn.md +++ b/docs/guides/self_hosted_aws_cn.md @@ -98,7 +98,7 @@ sam deploy \ --config-file .aws/petercat-ap-southeast.toml \ --parameter-overrides APIUrl=$API_URL \ WebUrl=$WEB_URL \ - AWSSecretName=$AWS_GITHUB_SECRET_NAME \ + AWSSecretName=$X_GITHUB_SECRET_NAME \ S3TempBucketName=$S3_TEMP_BUCKET_NAME \ GitHubAppID=$X_GITHUB_APP_ID \ GithubAppsClientId=$X_GITHUB_APPS_CLIENT_ID \ diff --git a/docs/guides/self_hosted_local.md b/docs/guides/self_hosted_local.md index 2ee2a075..345b7495 100644 --- a/docs/guides/self_hosted_local.md +++ b/docs/guides/self_hosted_local.md @@ -1,6 +1,7 @@ +``` # Self-Hosting -## Install Locally +## Local Installation ### Step 1: Clone the Repository Clone the project repository to your local machine: @@ -10,89 +11,71 @@ git clone https://github.com/petercat-ai/petercat.git ``` ### Step 2: Install Dependencies -Install all required dependencies using Yarn: +Install all necessary dependencies using Yarn: ```bash yarn run bootstrap ``` -### Step 3: Copy the `.env.example` Files -Copy the server environment configuration example files: +### Step 3: Start Supabase Locally + +Refer to [Supabase Self-Hosting Guide](https://supabase.com/docs/guides/self-hosting/docker#installing-and-running-supabase): ```bash -cp server/.env.example server/.env -``` -Copy the Client environment configuration example files: -```bash -cp client/.env.example client/.env -``` +# Get the code +git clone --depth 1 https://github.com/supabase/supabase -### Step 4: Update the `.env` Files -Open the `.env` file and update the necessary keys. You can use any text editor, like `vim`, `emacs`, `vscode`, or `nano`: +# Go to the docker folder +cd supabase/docker -```bash -vim server/.env -``` +# Copy the fake env vars +cp .env.example .env -For local development, configure only the Supabase and OpenAI settings: +# Pull the latest images +docker compose pull -```bash -# Supabase Project URL from https://supabase.com/dashboard/project/_/settings/database -SUPABASE_URL=https://{{YOUR_PROJECT_ID}}.supabase.co +# Start the services (in detached mode) +docker compose up -d +``` -# Supabase Project API key for `anon public` -SUPABASE_SERVICE_KEY=xxxx.yyyyy.zzzzz +### Step 4: Copy the `.env.example` Files +Copy the client environment configuration example file: +```bash +cp client/.env.local.example client/.env +``` -# OpenAI API key -OPENAI_API_KEY=sk-xxxx +Copy the server environment configuration example file: +```bash +cp server/.env.local.example server/.env ``` -### Step 5: Initialize the Database Structure +Open the `server/.env` file and update the `SERVICE_ROLE_KEY` field to match the value of `SERVICE_ROLE_KEY` from the `docker/.env` file in Supabase. + +### Step 5: Initialize Database Schema #### Step 5.1: Navigate to the Migrations Folder -Navigate to the `migrations` folder to prepare for the database setup: +Navigate to the `migrations` folder to prepare for database setup: ```bash cd migrations ``` #### Step 5.2: Install Supabase CLI -Install the Supabase CLI following the instructions on [Supabase's Getting Started Guide](https://supabase.com/docs/guides/cli/getting-started): +Install the Supabase CLI following the [Supabase Getting Started Guide](https://supabase.com/docs/guides/cli/getting-started): ```bash brew install supabase/tap/supabase ``` -#### Step 5.3: Link to the Remote Project -To connect to the Supabase project, you'll need to enter the database password. You can find this password in the [Supabase Dashboard](https://supabase.com/dashboard/project/_/settings/database): - -```bash -supabase link --project-ref {YOUR_PROJECT_ID} -``` - -If the connection is successful, you'll see output like this: - -``` -Enter your database password (or leave blank to skip): -Connecting to remote database... -Finished supabase link. -Local config differs from linked project. Try updating supabase/config.toml -[api] -enabled = true -port = 54321 -schemas = ["public", "graphql_public"] -extra_search_path = ["public", "extensions"] -max_rows = 1000 -``` - -#### Step 5.4: Perform Migration +#### Step 5.3: Run Migrations Apply the database migrations to your remote database: ```bash -supabase db push +# The postgres db URL can be found in the .env file from Step 4 +supabase db push --db-url "postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@127.0.0.1:5432/postgres" ``` -If successful, you'll see output similar to: +If successful, you should see output similar to the following: ``` Connecting to remote database... @@ -104,20 +87,21 @@ Applying migration 20240902023033_remote_schema.sql... Finished supabase db push. ``` -### Step 6: Bootstrap Server +### Step 6: Start the Server Start the server with the following command: ```bash -yarn run server +yarn run server:local ``` -Check if the server is running by opening `http://127.0.0.1:8000/api/health_checker` in your browser. +Verify the server is running by opening `http://127.0.0.1:8001/api/health_checker` in your browser. -### Step 7: Bootstrap Client +### Step 7: Start the Client Start the client with the following command: ```bash yarn run client ``` -You can check the client service by opening `http://127.0.0.1:3000` in your browser. +Verify the client service by opening `http://127.0.0.1:3000` in your browser. +``` \ No newline at end of file diff --git a/docs/guides/self_hosted_local_cn.md b/docs/guides/self_hosted_local_cn.md index 31c06ae9..322ef118 100644 --- a/docs/guides/self_hosted_local_cn.md +++ b/docs/guides/self_hosted_local_cn.md @@ -16,37 +16,41 @@ git clone https://github.com/petercat-ai/petercat.git yarn run bootstrap ``` -### 第三步:复制 `.env.example` 文件 -复制服务器环境配置示例文件: +### 第三步:在本地启动 supabase + +参考 https://supabase.com/docs/guides/self-hosting/docker#installing-and-running-supabase -```bash -cp server/.env.example server/.env -``` -复制客户端环境配置示例文件: -```bash -cp client/.env.example client/.env ``` +# Get the code +git clone --depth 1 https://github.com/supabase/supabase -### 第四步:更新 `.env` 文件 -打开 `.env` 文件并更新必要的键值。您可以使用任何文本编辑器,例如 `vim`、`emacs`、`vscode` 或 `nano`: +# Go to the docker folder +cd supabase/docker -```bash -vim server/.env -``` +# Copy the fake env vars +cp .env.example .env + +# Pull the latest images +docker compose pull -对于本地开发,只需配置 Supabase 和 OpenAI 设置: +# Start the services (in detached mode) +docker compose up -d +``` +### 第四步:复制 `.env.example` 文件 +复制客户端环境配置示例文件: ```bash -# Supabase 项目 URL,获取路径:https://supabase.com/dashboard/project/_/settings/database -SUPABASE_URL=https://{{YOUR_PROJECT_ID}}.supabase.co +cp client/.env.local.example client/.env +``` -# Supabase 项目 API 密钥,`anon public` -SUPABASE_SERVICE_KEY=xxxx.yyyyy.zzzzz +复制服务器环境配置示例文件: -# OpenAI API 密钥 -OPENAI_API_KEY=sk-xxxx +```bash +cp server/.env.local.example server/.env ``` +打开 `server/.env` 文件,把 `SERVICE_ROLE_KEY` 字段改成从 supabase 的 `docker/.env` 文件的 `SERVICE_ROLE_KEY` 的值 + ### 第五步:初始化数据库结构 #### 第五步 5.1:导航到 Migrations 文件夹 @@ -63,33 +67,12 @@ cd migrations brew install supabase/tap/supabase ``` -#### 第五步 5.3:连接到远程项目 -要连接到 Supabase 项目,您需要输入数据库密码。您可以在 [Supabase 控制面板](https://supabase.com/dashboard/project/_/settings/database) 中找到该密码: - -```bash -supabase link --project-ref {YOUR_PROJECT_ID} -``` - -如果连接成功,您将看到类似以下的输出: - -``` -Enter your database password (or leave blank to skip): -Connecting to remote database... -Finished supabase link. -Local config differs from linked project. Try updating supabase/config.toml -[api] -enabled = true -port = 54321 -schemas = ["public", "graphql_public"] -extra_search_path = ["public", "extensions"] -max_rows = 1000 -``` - -#### 第五步 5.4:执行迁移 +#### 第五步 5.3:执行迁移 将数据库迁移应用到您的远程数据库: ```bash -supabase db push +# postgres db url 在第四步的 .env 文件中可以找到 +supabase db push --db-url "postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@127.0.0.1:5432/postgres" ``` 如果成功,您将看到类似以下的输出: @@ -108,10 +91,10 @@ Finished supabase db push. 使用以下命令启动服务器: ```bash -yarn run server +yarn run server:local ``` -通过在浏览器中打开 `http://127.0.0.1:8000/api/health_checker` 检查服务器是否正在运行。 +通过在浏览器中打开 `http://127.0.0.1:8001/api/health_checker` 检查服务器是否正在运行。 ### 第七步:启动客户端 使用以下命令启动客户端: diff --git a/docs/guides/self_hosting_docker.md b/docs/guides/self_hosting_docker.md deleted file mode 100644 index 7eaa7bea..00000000 --- a/docs/guides/self_hosting_docker.md +++ /dev/null @@ -1,49 +0,0 @@ -# Self-Hosting with Docker - -Docker is the easiest way to get started with self-hosted Petercat. This guide assumes you are running the command from the machine you intend to host from. - -## Before you begin - -You need the following installed in your system: -- [Git](https://git-scm.com/downloads) -- Docker ([Windows](https://docs.docker.com/desktop/install/windows-install/), [MacOS](https://docs.docker.com/desktop/install/mac-install/), or [Linux](https://docs.docker.com/desktop/install/linux-install/)). - - -## Running Petercat - -Follow these steps to start Supabase locally: - - -- **Step 0**: Clone the repository: - - ```bash - git clone https://github.com/petercat-ai/petercat.git && cd server - ``` - -- **Step 1**: Copy the `.env.example` files - - ```bash - cp .env.example .env - ``` - -- **Step 2**: Update the `.env` files - - ```bash - vim .env # or emacs or vscode or nano - ``` - Update services keys in the `.env` file. - - ***OPENAI***: - - You need to update the `OPENAI_API_KEY` variable in the `.env` file. You can get your API key [here](https://platform.openai.com/api-keys). You need to create an account first. And put your credit card information. Don't worry, you won't be charged unless you use the API. You can find more information about the pricing [here](https://openai.com/pricing/). - - ***SUPABASE***: - - You need to update the `SUPABASE_URL` and `SUPABASE_SERVICE_KEY` variable in the `.env` file. You can get your help from [here](https://supabase.com/docs/guides/database/connecting-to-postgres#finding-your-database-hostname). You need to create a supabase account first. - - -- **Step 4**: Launch the project - - ```bash - docker compose --env-file .env -f docker/docker-compose.yml up - ``` diff --git a/migrations/supabase/migrations/20240911082857_remote_schema.sql b/migrations/supabase/migrations/20240911082857_remote_schema.sql index 2dc5ed68..6427a7f8 100644 --- a/migrations/supabase/migrations/20240911082857_remote_schema.sql +++ b/migrations/supabase/migrations/20240911082857_remote_schema.sql @@ -1,4 +1,3 @@ - SET statement_timeout = 0; SET lock_timeout = 0; SET idle_in_transaction_session_timeout = 0; @@ -9,27 +8,16 @@ SET check_function_bodies = false; SET xmloption = content; SET client_min_messages = warning; SET row_security = off; - CREATE EXTENSION IF NOT EXISTS "pg_net" WITH SCHEMA "extensions"; - CREATE EXTENSION IF NOT EXISTS "pgsodium" WITH SCHEMA "pgsodium"; - COMMENT ON SCHEMA "public" IS 'standard public schema'; - CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql"; - CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions"; - CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions"; - CREATE EXTENSION IF NOT EXISTS "pgjwt" WITH SCHEMA "extensions"; - CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault"; - CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions"; - CREATE EXTENSION IF NOT EXISTS "vector" WITH SCHEMA "public"; - CREATE OR REPLACE FUNCTION "public"."execute_sql"("query" "text") RETURNS TABLE("id" "uuid", "created_at" timestamp with time zone, "uid" character varying, "avatar" character varying, "description" character varying, "prompt" "text", "files" "text"[], "enable_img_generation" boolean, "label" character varying, "name" character varying, "starters" "text"[], "public" boolean, "updated_at" timestamp with time zone, "hello_message" "text") LANGUAGE "plpgsql" AS $$ @@ -37,9 +25,7 @@ BEGIN RETURN QUERY EXECUTE query; END; $$; - ALTER FUNCTION "public"."execute_sql"("query" "text") OWNER TO "postgres"; - CREATE OR REPLACE FUNCTION "public"."handle_new_user"() RETURNS "trigger" LANGUAGE "plpgsql" SECURITY DEFINER SET "search_path" TO 'public' @@ -50,9 +36,7 @@ begin return new; end; $$; - ALTER FUNCTION "public"."handle_new_user"() OWNER TO "postgres"; - CREATE OR REPLACE FUNCTION "public"."match_antd_doc"("query_embedding" "public"."vector", "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" "uuid", "content" "text", "metadata" "jsonb", "similarity" double precision) LANGUAGE "plpgsql" AS $$ @@ -69,9 +53,7 @@ begin order by antd_doc.embedding <=> query_embedding; end; $$; - ALTER FUNCTION "public"."match_antd_doc"("query_embedding" "public"."vector", "filter" "jsonb") OWNER TO "postgres"; - CREATE OR REPLACE FUNCTION "public"."match_antd_documents"("query_embedding" "public"."vector", "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" "uuid", "content" "text", "metadata" "jsonb", "similarity" double precision) LANGUAGE "plpgsql" AS $$ @@ -88,9 +70,7 @@ begin order by antd_documents.embedding <=> query_embedding; end; $$; - ALTER FUNCTION "public"."match_antd_documents"("query_embedding" "public"."vector", "filter" "jsonb") OWNER TO "postgres"; - CREATE OR REPLACE FUNCTION "public"."match_antd_knowledge"("query_embedding" "public"."vector", "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" "uuid", "content" "text", "metadata" "jsonb", "similarity" double precision) LANGUAGE "plpgsql" AS $$ @@ -107,9 +87,7 @@ begin order by antd_knowledge.embedding <=> query_embedding; end; $$; - ALTER FUNCTION "public"."match_antd_knowledge"("query_embedding" "public"."vector", "filter" "jsonb") OWNER TO "postgres"; - CREATE OR REPLACE FUNCTION "public"."match_docs"("query_embedding" "public"."vector", "match_count" integer DEFAULT NULL::integer, "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" bigint, "content" "text", "metadata" "jsonb", "similarity" double precision) LANGUAGE "plpgsql" AS $$ @@ -127,9 +105,7 @@ begin limit match_count; end; $$; - ALTER FUNCTION "public"."match_docs"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") OWNER TO "postgres"; - CREATE OR REPLACE FUNCTION "public"."match_documents"("query_embedding" "public"."vector", "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" "uuid", "content" "text", "metadata" "jsonb", "similarity" double precision) LANGUAGE "plpgsql" AS $$ @@ -146,10 +122,8 @@ begin order by documents.embedding <=> query_embedding; end; $$; - ALTER FUNCTION "public"."match_documents"("query_embedding" "public"."vector", "filter" "jsonb") OWNER TO "postgres"; - -CREATE OR REPLACE FUNCTION "public"."match_embedding_docs"("query_embedding" "public"."vector", "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" "uuid", "content" "text", "metadata" "jsonb", "embedding" "public"."vector", "similarity" double precision) +CREATE OR REPLACE FUNCTION "public"."match_rag_docs"("query_embedding" "public"."vector", "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" "uuid", "content" "text", "metadata" "jsonb", "embedding" "public"."vector", "similarity" double precision) LANGUAGE "plpgsql" AS $$ #variable_conflict use_column @@ -164,11 +138,11 @@ begin ) as similarity from rag_docs where metadata @> jsonb_extract_path(filter, 'metadata') - and repo_name = jsonb_extract_path_text(filter, 'repo_name') + and bot_id = jsonb_extract_path_text(filter, 'bot_id') order by rag_docs.embedding <=> query_embedding; end; $$; - +ALTER FUNCTION "public"."match_rag_docs"("query_embedding" "public"."vector", "filter" "jsonb") OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."match_text"("query_embedding" "public"."vector", "match_count" integer DEFAULT NULL::integer, "filter" "jsonb" DEFAULT '{}'::"jsonb") RETURNS TABLE("id" bigint, "content" "text", "metadata" "jsonb", "similarity" double precision) LANGUAGE "plpgsql" AS $$ @@ -186,9 +160,7 @@ begin limit match_count; end; $$; - ALTER FUNCTION "public"."match_text"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") OWNER TO "postgres"; - CREATE OR REPLACE FUNCTION "public"."rag_docs"("query_embedding" "public"."vector", "filter" "jsonb" DEFAULT '{}'::"jsonb", "query_repo_name" "text" DEFAULT ''::"text") RETURNS TABLE("id" "uuid", "metadata" "jsonb", "content" "text", "similarity" double precision) LANGUAGE "sql" STABLE AS $$ @@ -202,9 +174,7 @@ CREATE OR REPLACE FUNCTION "public"."rag_docs"("query_embedding" "public"."vecto and repo_name = query_repo_name order by (rag_docs.embedding <=> query_embedding) asc $$; - ALTER FUNCTION "public"."rag_docs"("query_embedding" "public"."vector", "filter" "jsonb", "query_repo_name" "text") OWNER TO "postgres"; - CREATE OR REPLACE FUNCTION "public"."update_timestamp_function"() RETURNS "trigger" LANGUAGE "plpgsql" AS $$ @@ -213,13 +183,9 @@ BEGIN RETURN NEW; END; $$; - ALTER FUNCTION "public"."update_timestamp_function"() OWNER TO "postgres"; - SET default_tablespace = ''; - SET default_table_access_method = "heap"; - CREATE TABLE IF NOT EXISTS "public"."bots" ( "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -238,11 +204,8 @@ CREATE TABLE IF NOT EXISTS "public"."bots" ( "repo_name" "text", "token_id" "text" DEFAULT '''''::text'::"text" ); - ALTER TABLE "public"."bots" OWNER TO "postgres"; - COMMENT ON TABLE "public"."bots" IS 'bots list'; - CREATE TABLE IF NOT EXISTS "public"."git_issue_tasks" ( "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -254,9 +217,7 @@ CREATE TABLE IF NOT EXISTS "public"."git_issue_tasks" ( "bot_id" character varying, "page_index" numeric ); - ALTER TABLE "public"."git_issue_tasks" OWNER TO "postgres"; - CREATE TABLE IF NOT EXISTS "public"."github_app_authorization" ( "id" bigint NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -266,11 +227,8 @@ CREATE TABLE IF NOT EXISTS "public"."github_app_authorization" ( "permissions" "json", "code" character varying ); - ALTER TABLE "public"."github_app_authorization" OWNER TO "postgres"; - COMMENT ON TABLE "public"."github_app_authorization" IS 'Authorizations of Github App'; - ALTER TABLE "public"."github_app_authorization" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( SEQUENCE NAME "public"."github_app_authorization_id_seq" START WITH 1 @@ -279,16 +237,13 @@ ALTER TABLE "public"."github_app_authorization" ALTER COLUMN "id" ADD GENERATED NO MAXVALUE CACHE 1 ); - CREATE TABLE IF NOT EXISTS "public"."github_repo_config" ( "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, "repo_name" character varying, "robot_id" character varying ); - ALTER TABLE "public"."github_repo_config" OWNER TO "postgres"; - CREATE TABLE IF NOT EXISTS "public"."llm_tokens" ( "id" bigint NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -301,9 +256,7 @@ CREATE TABLE IF NOT EXISTS "public"."llm_tokens" ( "encrypted_token" "text", "user_id" "text" ); - ALTER TABLE "public"."llm_tokens" OWNER TO "postgres"; - ALTER TABLE "public"."llm_tokens" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( SEQUENCE NAME "public"."llm_tokens_id_seq" START WITH 1 @@ -312,7 +265,6 @@ ALTER TABLE "public"."llm_tokens" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS NO MAXVALUE CACHE 1 ); - CREATE TABLE IF NOT EXISTS "public"."profiles" ( "id" character varying NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -320,12 +272,9 @@ CREATE TABLE IF NOT EXISTS "public"."profiles" ( "name" character varying, "picture" character varying, "sid" character varying, - "sub" character varying, - "agreement_accepted" boolean DEFAULT false; + "sub" character varying ); - ALTER TABLE "public"."profiles" OWNER TO "postgres"; - CREATE TABLE IF NOT EXISTS "public"."rag_docs" ( "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, "content" "text", @@ -338,9 +287,7 @@ CREATE TABLE IF NOT EXISTS "public"."rag_docs" ( "bot_id" character varying, "update_timestamp" timestamp with time zone ); - ALTER TABLE "public"."rag_docs" OWNER TO "postgres"; - CREATE TABLE IF NOT EXISTS "public"."rag_issues" ( "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, "update_timestamp" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -352,9 +299,7 @@ CREATE TABLE IF NOT EXISTS "public"."rag_issues" ( "bot_id" character varying, "comment_id" character varying ); - ALTER TABLE "public"."rag_issues" OWNER TO "postgres"; - CREATE TABLE IF NOT EXISTS "public"."rag_tasks" ( "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -369,9 +314,7 @@ CREATE TABLE IF NOT EXISTS "public"."rag_tasks" ( "bot_id" character varying, "page_index" numeric ); - ALTER TABLE "public"."rag_tasks" OWNER TO "postgres"; - CREATE TABLE IF NOT EXISTS "public"."user_llm_tokens" ( "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -381,9 +324,7 @@ CREATE TABLE IF NOT EXISTS "public"."user_llm_tokens" ( "encrypted_token" "text", "sanitized_token" "text" ); - ALTER TABLE "public"."user_llm_tokens" OWNER TO "postgres"; - CREATE TABLE IF NOT EXISTS "public"."user_token_usage" ( "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, @@ -395,353 +336,272 @@ CREATE TABLE IF NOT EXISTS "public"."user_token_usage" ( "token_id" "text", "total_token" bigint ); - ALTER TABLE "public"."user_token_usage" OWNER TO "postgres"; - COMMENT ON TABLE "public"."user_token_usage" IS 'token usage of people'; - ALTER TABLE ONLY "public"."bots" ADD CONSTRAINT "bots_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."git_issue_tasks" ADD CONSTRAINT "git_issue_tasks_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."github_app_authorization" ADD CONSTRAINT "github_app_authorization_installation_id_key" UNIQUE ("installation_id"); - ALTER TABLE ONLY "public"."github_app_authorization" ADD CONSTRAINT "github_app_authorization_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."github_repo_config" ADD CONSTRAINT "github_repo_config_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."rag_issues" ADD CONSTRAINT "issue_docs_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."llm_tokens" ADD CONSTRAINT "llm_tokens_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."profiles" ADD CONSTRAINT "profile_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."rag_docs" ADD CONSTRAINT "rag_docs_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."rag_tasks" ADD CONSTRAINT "rag_tasks_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."user_llm_tokens" ADD CONSTRAINT "user_llm_tokens_pkey" PRIMARY KEY ("id"); - ALTER TABLE ONLY "public"."user_token_usage" ADD CONSTRAINT "user_token_usage_pkey" PRIMARY KEY ("id"); - CREATE OR REPLACE TRIGGER "update_timestamp_trigger" BEFORE INSERT OR UPDATE ON "public"."rag_docs" FOR EACH ROW EXECUTE FUNCTION "public"."update_timestamp_function"(); - CREATE POLICY "Enable read access for all users" ON "public"."llm_tokens" FOR SELECT USING (true); - ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres"; - GRANT USAGE ON SCHEMA "public" TO "postgres"; GRANT USAGE ON SCHEMA "public" TO "anon"; GRANT USAGE ON SCHEMA "public" TO "authenticated"; GRANT USAGE ON SCHEMA "public" TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_in"("cstring", "oid", integer) TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_in"("cstring", "oid", integer) TO "anon"; GRANT ALL ON FUNCTION "public"."vector_in"("cstring", "oid", integer) TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_in"("cstring", "oid", integer) TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_out"("public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_out"("public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_out"("public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_out"("public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_recv"("internal", "oid", integer) TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_recv"("internal", "oid", integer) TO "anon"; GRANT ALL ON FUNCTION "public"."vector_recv"("internal", "oid", integer) TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_recv"("internal", "oid", integer) TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_send"("public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_send"("public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_send"("public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_send"("public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_typmod_in"("cstring"[]) TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_typmod_in"("cstring"[]) TO "anon"; GRANT ALL ON FUNCTION "public"."vector_typmod_in"("cstring"[]) TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_typmod_in"("cstring"[]) TO "service_role"; - GRANT ALL ON FUNCTION "public"."array_to_vector"(real[], integer, boolean) TO "postgres"; GRANT ALL ON FUNCTION "public"."array_to_vector"(real[], integer, boolean) TO "anon"; GRANT ALL ON FUNCTION "public"."array_to_vector"(real[], integer, boolean) TO "authenticated"; GRANT ALL ON FUNCTION "public"."array_to_vector"(real[], integer, boolean) TO "service_role"; - GRANT ALL ON FUNCTION "public"."array_to_vector"(double precision[], integer, boolean) TO "postgres"; GRANT ALL ON FUNCTION "public"."array_to_vector"(double precision[], integer, boolean) TO "anon"; GRANT ALL ON FUNCTION "public"."array_to_vector"(double precision[], integer, boolean) TO "authenticated"; GRANT ALL ON FUNCTION "public"."array_to_vector"(double precision[], integer, boolean) TO "service_role"; - GRANT ALL ON FUNCTION "public"."array_to_vector"(integer[], integer, boolean) TO "postgres"; GRANT ALL ON FUNCTION "public"."array_to_vector"(integer[], integer, boolean) TO "anon"; GRANT ALL ON FUNCTION "public"."array_to_vector"(integer[], integer, boolean) TO "authenticated"; GRANT ALL ON FUNCTION "public"."array_to_vector"(integer[], integer, boolean) TO "service_role"; - GRANT ALL ON FUNCTION "public"."array_to_vector"(numeric[], integer, boolean) TO "postgres"; GRANT ALL ON FUNCTION "public"."array_to_vector"(numeric[], integer, boolean) TO "anon"; GRANT ALL ON FUNCTION "public"."array_to_vector"(numeric[], integer, boolean) TO "authenticated"; GRANT ALL ON FUNCTION "public"."array_to_vector"(numeric[], integer, boolean) TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_to_float4"("public"."vector", integer, boolean) TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_to_float4"("public"."vector", integer, boolean) TO "anon"; GRANT ALL ON FUNCTION "public"."vector_to_float4"("public"."vector", integer, boolean) TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_to_float4"("public"."vector", integer, boolean) TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector"("public"."vector", integer, boolean) TO "postgres"; GRANT ALL ON FUNCTION "public"."vector"("public"."vector", integer, boolean) TO "anon"; GRANT ALL ON FUNCTION "public"."vector"("public"."vector", integer, boolean) TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector"("public"."vector", integer, boolean) TO "service_role"; - GRANT ALL ON FUNCTION "public"."cosine_distance"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."cosine_distance"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."cosine_distance"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."cosine_distance"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."execute_sql"("query" "text") TO "anon"; GRANT ALL ON FUNCTION "public"."execute_sql"("query" "text") TO "authenticated"; GRANT ALL ON FUNCTION "public"."execute_sql"("query" "text") TO "service_role"; - GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "anon"; GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "authenticated"; GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "service_role"; - GRANT ALL ON FUNCTION "public"."hnswhandler"("internal") TO "postgres"; GRANT ALL ON FUNCTION "public"."hnswhandler"("internal") TO "anon"; GRANT ALL ON FUNCTION "public"."hnswhandler"("internal") TO "authenticated"; GRANT ALL ON FUNCTION "public"."hnswhandler"("internal") TO "service_role"; - GRANT ALL ON FUNCTION "public"."inner_product"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."inner_product"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."inner_product"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."inner_product"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."ivfflathandler"("internal") TO "postgres"; GRANT ALL ON FUNCTION "public"."ivfflathandler"("internal") TO "anon"; GRANT ALL ON FUNCTION "public"."ivfflathandler"("internal") TO "authenticated"; GRANT ALL ON FUNCTION "public"."ivfflathandler"("internal") TO "service_role"; - GRANT ALL ON FUNCTION "public"."l1_distance"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."l1_distance"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."l1_distance"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."l1_distance"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."l2_distance"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."l2_distance"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."l2_distance"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."l2_distance"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."match_antd_doc"("query_embedding" "public"."vector", "filter" "jsonb") TO "anon"; GRANT ALL ON FUNCTION "public"."match_antd_doc"("query_embedding" "public"."vector", "filter" "jsonb") TO "authenticated"; GRANT ALL ON FUNCTION "public"."match_antd_doc"("query_embedding" "public"."vector", "filter" "jsonb") TO "service_role"; - GRANT ALL ON FUNCTION "public"."match_antd_documents"("query_embedding" "public"."vector", "filter" "jsonb") TO "anon"; GRANT ALL ON FUNCTION "public"."match_antd_documents"("query_embedding" "public"."vector", "filter" "jsonb") TO "authenticated"; GRANT ALL ON FUNCTION "public"."match_antd_documents"("query_embedding" "public"."vector", "filter" "jsonb") TO "service_role"; - GRANT ALL ON FUNCTION "public"."match_antd_knowledge"("query_embedding" "public"."vector", "filter" "jsonb") TO "anon"; GRANT ALL ON FUNCTION "public"."match_antd_knowledge"("query_embedding" "public"."vector", "filter" "jsonb") TO "authenticated"; GRANT ALL ON FUNCTION "public"."match_antd_knowledge"("query_embedding" "public"."vector", "filter" "jsonb") TO "service_role"; - GRANT ALL ON FUNCTION "public"."match_docs"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") TO "anon"; GRANT ALL ON FUNCTION "public"."match_docs"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") TO "authenticated"; GRANT ALL ON FUNCTION "public"."match_docs"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") TO "service_role"; - GRANT ALL ON FUNCTION "public"."match_documents"("query_embedding" "public"."vector", "filter" "jsonb") TO "anon"; GRANT ALL ON FUNCTION "public"."match_documents"("query_embedding" "public"."vector", "filter" "jsonb") TO "authenticated"; GRANT ALL ON FUNCTION "public"."match_documents"("query_embedding" "public"."vector", "filter" "jsonb") TO "service_role"; - +GRANT ALL ON FUNCTION "public"."match_rag_docs"("query_embedding" "public"."vector", "filter" "jsonb") TO "anon"; +GRANT ALL ON FUNCTION "public"."match_rag_docs"("query_embedding" "public"."vector", "filter" "jsonb") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."match_rag_docs"("query_embedding" "public"."vector", "filter" "jsonb") TO "service_role"; GRANT ALL ON FUNCTION "public"."match_text"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") TO "anon"; GRANT ALL ON FUNCTION "public"."match_text"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") TO "authenticated"; GRANT ALL ON FUNCTION "public"."match_text"("query_embedding" "public"."vector", "match_count" integer, "filter" "jsonb") TO "service_role"; - GRANT ALL ON FUNCTION "public"."rag_docs"("query_embedding" "public"."vector", "filter" "jsonb", "query_repo_name" "text") TO "anon"; GRANT ALL ON FUNCTION "public"."rag_docs"("query_embedding" "public"."vector", "filter" "jsonb", "query_repo_name" "text") TO "authenticated"; GRANT ALL ON FUNCTION "public"."rag_docs"("query_embedding" "public"."vector", "filter" "jsonb", "query_repo_name" "text") TO "service_role"; - GRANT ALL ON FUNCTION "public"."update_timestamp_function"() TO "anon"; GRANT ALL ON FUNCTION "public"."update_timestamp_function"() TO "authenticated"; GRANT ALL ON FUNCTION "public"."update_timestamp_function"() TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_accum"(double precision[], "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_accum"(double precision[], "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_accum"(double precision[], "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_accum"(double precision[], "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_add"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_add"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_add"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_add"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_avg"(double precision[]) TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_avg"(double precision[]) TO "anon"; GRANT ALL ON FUNCTION "public"."vector_avg"(double precision[]) TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_avg"(double precision[]) TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_cmp"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_cmp"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_cmp"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_cmp"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_combine"(double precision[], double precision[]) TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_combine"(double precision[], double precision[]) TO "anon"; GRANT ALL ON FUNCTION "public"."vector_combine"(double precision[], double precision[]) TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_combine"(double precision[], double precision[]) TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_dims"("public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_dims"("public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_dims"("public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_dims"("public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_eq"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_eq"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_eq"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_eq"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_ge"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_ge"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_ge"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_ge"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_gt"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_gt"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_gt"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_gt"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_l2_squared_distance"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_l2_squared_distance"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_l2_squared_distance"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_l2_squared_distance"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_le"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_le"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_le"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_le"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_lt"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_lt"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_lt"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_lt"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_mul"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_mul"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_mul"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_mul"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_ne"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_ne"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_ne"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_ne"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_negative_inner_product"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_negative_inner_product"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_negative_inner_product"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_negative_inner_product"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_norm"("public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_norm"("public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_norm"("public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_norm"("public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_spherical_distance"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_spherical_distance"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_spherical_distance"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_spherical_distance"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."vector_sub"("public"."vector", "public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."vector_sub"("public"."vector", "public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."vector_sub"("public"."vector", "public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."vector_sub"("public"."vector", "public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."avg"("public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."avg"("public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."avg"("public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."avg"("public"."vector") TO "service_role"; - GRANT ALL ON FUNCTION "public"."sum"("public"."vector") TO "postgres"; GRANT ALL ON FUNCTION "public"."sum"("public"."vector") TO "anon"; GRANT ALL ON FUNCTION "public"."sum"("public"."vector") TO "authenticated"; GRANT ALL ON FUNCTION "public"."sum"("public"."vector") TO "service_role"; - GRANT ALL ON TABLE "public"."bots" TO "anon"; GRANT ALL ON TABLE "public"."bots" TO "authenticated"; GRANT ALL ON TABLE "public"."bots" TO "service_role"; - GRANT ALL ON TABLE "public"."git_issue_tasks" TO "anon"; GRANT ALL ON TABLE "public"."git_issue_tasks" TO "authenticated"; GRANT ALL ON TABLE "public"."git_issue_tasks" TO "service_role"; - GRANT ALL ON TABLE "public"."github_app_authorization" TO "anon"; GRANT ALL ON TABLE "public"."github_app_authorization" TO "authenticated"; GRANT ALL ON TABLE "public"."github_app_authorization" TO "service_role"; - GRANT ALL ON SEQUENCE "public"."github_app_authorization_id_seq" TO "anon"; GRANT ALL ON SEQUENCE "public"."github_app_authorization_id_seq" TO "authenticated"; GRANT ALL ON SEQUENCE "public"."github_app_authorization_id_seq" TO "service_role"; - GRANT ALL ON TABLE "public"."github_repo_config" TO "anon"; GRANT ALL ON TABLE "public"."github_repo_config" TO "authenticated"; GRANT ALL ON TABLE "public"."github_repo_config" TO "service_role"; - GRANT ALL ON TABLE "public"."llm_tokens" TO "anon"; GRANT ALL ON TABLE "public"."llm_tokens" TO "authenticated"; GRANT ALL ON TABLE "public"."llm_tokens" TO "service_role"; - GRANT ALL ON SEQUENCE "public"."llm_tokens_id_seq" TO "anon"; GRANT ALL ON SEQUENCE "public"."llm_tokens_id_seq" TO "authenticated"; GRANT ALL ON SEQUENCE "public"."llm_tokens_id_seq" TO "service_role"; - GRANT ALL ON TABLE "public"."profiles" TO "anon"; GRANT ALL ON TABLE "public"."profiles" TO "authenticated"; GRANT ALL ON TABLE "public"."profiles" TO "service_role"; - GRANT ALL ON TABLE "public"."rag_docs" TO "anon"; GRANT ALL ON TABLE "public"."rag_docs" TO "authenticated"; GRANT ALL ON TABLE "public"."rag_docs" TO "service_role"; - GRANT ALL ON TABLE "public"."rag_issues" TO "anon"; GRANT ALL ON TABLE "public"."rag_issues" TO "authenticated"; GRANT ALL ON TABLE "public"."rag_issues" TO "service_role"; - GRANT ALL ON TABLE "public"."rag_tasks" TO "anon"; GRANT ALL ON TABLE "public"."rag_tasks" TO "authenticated"; GRANT ALL ON TABLE "public"."rag_tasks" TO "service_role"; - GRANT ALL ON TABLE "public"."user_llm_tokens" TO "anon"; GRANT ALL ON TABLE "public"."user_llm_tokens" TO "authenticated"; GRANT ALL ON TABLE "public"."user_llm_tokens" TO "service_role"; - GRANT ALL ON TABLE "public"."user_token_usage" TO "anon"; GRANT ALL ON TABLE "public"."user_token_usage" TO "authenticated"; GRANT ALL ON TABLE "public"."user_token_usage" TO "service_role"; - ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role"; - ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role"; - ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role"; - RESET ALL; - -- -- Dumped schema changes for auth and storage --- - +--; diff --git a/migrations/supabase/migrations/20240912023152_remote_schema.sql b/migrations/supabase/migrations/20240912023152_remote_schema.sql index d51bfe06..2abc90f9 100644 --- a/migrations/supabase/migrations/20240912023152_remote_schema.sql +++ b/migrations/supabase/migrations/20240912023152_remote_schema.sql @@ -1,5 +1,4 @@ set check_function_bodies = off; - CREATE OR REPLACE FUNCTION public.get_user_stats(filter_user_id text, start_date date, end_date date) RETURNS TABLE(usage_date date, input_tokens bigint, output_tokens bigint, total_tokens bigint) LANGUAGE plpgsql @@ -17,7 +16,4 @@ BEGIN AND u.date <= end_date GROUP BY u.date; END; -$function$ -; - - +$function$; diff --git a/migrations/supabase/migrations/20240913114522_remote_schema.sql b/migrations/supabase/migrations/20240913114522_remote_schema.sql index 3d17702f..24e868b3 100644 --- a/migrations/supabase/migrations/20240913114522_remote_schema.sql +++ b/migrations/supabase/migrations/20240913114522_remote_schema.sql @@ -1,7 +1,5 @@ alter table "public"."bots" alter column "token_id" set default ''::text; - set check_function_bodies = off; - CREATE OR REPLACE FUNCTION public.get_bot_stats(filter_bot_id text) RETURNS TABLE(call_cnt bigint) LANGUAGE plpgsql @@ -14,7 +12,4 @@ BEGIN WHERE u.bot_id = filter_bot_id -- 使用别名来引用参数 GROUP BY u.bot_id; END; -$function$ -; - - +$function$; diff --git a/migrations/supabase/migrations/20241015085655_remote_schema.sql b/migrations/supabase/migrations/20241015085655_remote_schema.sql index 2d4fab98..20cd0568 100644 --- a/migrations/supabase/migrations/20241015085655_remote_schema.sql +++ b/migrations/supabase/migrations/20241015085655_remote_schema.sql @@ -1,9 +1,6 @@ drop function if exists "public"."match_docs"(query_embedding vector, match_count integer, filter jsonb); - drop function if exists "public"."match_documents"(query_embedding vector, filter jsonb); - drop function if exists "public"."match_text"(query_embedding vector, match_count integer, filter jsonb); - create table "public"."bot_approval" ( "id" uuid not null default gen_random_uuid(), "created_at" timestamp with time zone not null default now(), @@ -12,16 +9,12 @@ create table "public"."bot_approval" ( "approval_path" character varying, "approval_status" character varying default ''::character varying ); - - create table "public"."github_app_installations" ( "id" bigint generated by default as identity not null, "created_at" timestamp with time zone not null default now(), "owner_name" text, "repo_name" text ); - - create table "public"."user_rate_limit" ( "id" uuid not null default gen_random_uuid(), "created_at" timestamp with time zone not null default now(), @@ -29,154 +22,77 @@ create table "public"."user_rate_limit" ( "last_request" timestamp without time zone, "request_count" bigint ); - - alter table "public"."user_rate_limit" enable row level security; - alter table "public"."bots" alter column "llm" set default ''::character varying; - alter table "public"."bots" alter column "token_id" drop default; - alter table "public"."github_repo_config" add column "owner_id" character varying; - alter table "public"."github_repo_config" add column "repo_id" character varying; - CREATE UNIQUE INDEX "bot_ approval_pkey" ON public.bot_approval USING btree (id); - CREATE UNIQUE INDEX github_app_installations_pkey ON public.github_app_installations USING btree (id); - CREATE UNIQUE INDEX user_rate_limit_pkey ON public.user_rate_limit USING btree (id); - alter table "public"."bot_approval" add constraint "bot_ approval_pkey" PRIMARY KEY using index "bot_ approval_pkey"; - alter table "public"."github_app_installations" add constraint "github_app_installations_pkey" PRIMARY KEY using index "github_app_installations_pkey"; - alter table "public"."user_rate_limit" add constraint "user_rate_limit_pkey" PRIMARY KEY using index "user_rate_limit_pkey"; - grant delete on table "public"."bot_approval" to "anon"; - grant insert on table "public"."bot_approval" to "anon"; - grant references on table "public"."bot_approval" to "anon"; - grant select on table "public"."bot_approval" to "anon"; - grant trigger on table "public"."bot_approval" to "anon"; - grant truncate on table "public"."bot_approval" to "anon"; - grant update on table "public"."bot_approval" to "anon"; - grant delete on table "public"."bot_approval" to "authenticated"; - grant insert on table "public"."bot_approval" to "authenticated"; - grant references on table "public"."bot_approval" to "authenticated"; - grant select on table "public"."bot_approval" to "authenticated"; - grant trigger on table "public"."bot_approval" to "authenticated"; - grant truncate on table "public"."bot_approval" to "authenticated"; - grant update on table "public"."bot_approval" to "authenticated"; - grant delete on table "public"."bot_approval" to "service_role"; - grant insert on table "public"."bot_approval" to "service_role"; - grant references on table "public"."bot_approval" to "service_role"; - grant select on table "public"."bot_approval" to "service_role"; - grant trigger on table "public"."bot_approval" to "service_role"; - grant truncate on table "public"."bot_approval" to "service_role"; - grant update on table "public"."bot_approval" to "service_role"; - grant delete on table "public"."github_app_installations" to "anon"; - grant insert on table "public"."github_app_installations" to "anon"; - grant references on table "public"."github_app_installations" to "anon"; - grant select on table "public"."github_app_installations" to "anon"; - grant trigger on table "public"."github_app_installations" to "anon"; - grant truncate on table "public"."github_app_installations" to "anon"; - grant update on table "public"."github_app_installations" to "anon"; - grant delete on table "public"."github_app_installations" to "authenticated"; - grant insert on table "public"."github_app_installations" to "authenticated"; - grant references on table "public"."github_app_installations" to "authenticated"; - grant select on table "public"."github_app_installations" to "authenticated"; - grant trigger on table "public"."github_app_installations" to "authenticated"; - grant truncate on table "public"."github_app_installations" to "authenticated"; - grant update on table "public"."github_app_installations" to "authenticated"; - grant delete on table "public"."github_app_installations" to "service_role"; - grant insert on table "public"."github_app_installations" to "service_role"; - grant references on table "public"."github_app_installations" to "service_role"; - grant select on table "public"."github_app_installations" to "service_role"; - grant trigger on table "public"."github_app_installations" to "service_role"; - grant truncate on table "public"."github_app_installations" to "service_role"; - grant update on table "public"."github_app_installations" to "service_role"; - grant delete on table "public"."user_rate_limit" to "anon"; - grant insert on table "public"."user_rate_limit" to "anon"; - grant references on table "public"."user_rate_limit" to "anon"; - grant select on table "public"."user_rate_limit" to "anon"; - grant trigger on table "public"."user_rate_limit" to "anon"; - grant truncate on table "public"."user_rate_limit" to "anon"; - grant update on table "public"."user_rate_limit" to "anon"; - grant delete on table "public"."user_rate_limit" to "authenticated"; - grant insert on table "public"."user_rate_limit" to "authenticated"; - grant references on table "public"."user_rate_limit" to "authenticated"; - grant select on table "public"."user_rate_limit" to "authenticated"; - grant trigger on table "public"."user_rate_limit" to "authenticated"; - grant truncate on table "public"."user_rate_limit" to "authenticated"; - grant update on table "public"."user_rate_limit" to "authenticated"; - grant delete on table "public"."user_rate_limit" to "service_role"; - grant insert on table "public"."user_rate_limit" to "service_role"; - grant references on table "public"."user_rate_limit" to "service_role"; - grant select on table "public"."user_rate_limit" to "service_role"; - grant trigger on table "public"."user_rate_limit" to "service_role"; - grant truncate on table "public"."user_rate_limit" to "service_role"; - grant update on table "public"."user_rate_limit" to "service_role"; - - diff --git a/migrations/supabase/migrations/20241015090438_remote_schema.sql b/migrations/supabase/migrations/20241015090438_remote_schema.sql index 91a860a9..3c9e5bbc 100644 --- a/migrations/supabase/migrations/20241015090438_remote_schema.sql +++ b/migrations/supabase/migrations/20241015090438_remote_schema.sql @@ -1,7 +1,3 @@ drop function if exists "public"."match_antd_doc"(query_embedding vector, filter jsonb); - drop function if exists "public"."match_antd_documents"(query_embedding vector, filter jsonb); - drop function if exists "public"."match_antd_knowledge"(query_embedding vector, filter jsonb); - - diff --git a/migrations/supabase/migrations/20241210072126_remote_schema.sql b/migrations/supabase/migrations/20241210072126_remote_schema.sql new file mode 100644 index 00000000..691a9bd7 --- /dev/null +++ b/migrations/supabase/migrations/20241210072126_remote_schema.sql @@ -0,0 +1,170 @@ +revoke delete on table "public"."github_app_authorization" from "anon"; + +revoke insert on table "public"."github_app_authorization" from "anon"; + +revoke references on table "public"."github_app_authorization" from "anon"; + +revoke select on table "public"."github_app_authorization" from "anon"; + +revoke trigger on table "public"."github_app_authorization" from "anon"; + +revoke truncate on table "public"."github_app_authorization" from "anon"; + +revoke update on table "public"."github_app_authorization" from "anon"; + +revoke delete on table "public"."github_app_authorization" from "authenticated"; + +revoke insert on table "public"."github_app_authorization" from "authenticated"; + +revoke references on table "public"."github_app_authorization" from "authenticated"; + +revoke select on table "public"."github_app_authorization" from "authenticated"; + +revoke trigger on table "public"."github_app_authorization" from "authenticated"; + +revoke truncate on table "public"."github_app_authorization" from "authenticated"; + +revoke update on table "public"."github_app_authorization" from "authenticated"; + +revoke delete on table "public"."github_app_authorization" from "service_role"; + +revoke insert on table "public"."github_app_authorization" from "service_role"; + +revoke references on table "public"."github_app_authorization" from "service_role"; + +revoke select on table "public"."github_app_authorization" from "service_role"; + +revoke trigger on table "public"."github_app_authorization" from "service_role"; + +revoke truncate on table "public"."github_app_authorization" from "service_role"; + +revoke update on table "public"."github_app_authorization" from "service_role"; + +revoke delete on table "public"."github_app_installations" from "anon"; + +revoke insert on table "public"."github_app_installations" from "anon"; + +revoke references on table "public"."github_app_installations" from "anon"; + +revoke select on table "public"."github_app_installations" from "anon"; + +revoke trigger on table "public"."github_app_installations" from "anon"; + +revoke truncate on table "public"."github_app_installations" from "anon"; + +revoke update on table "public"."github_app_installations" from "anon"; + +revoke delete on table "public"."github_app_installations" from "authenticated"; + +revoke insert on table "public"."github_app_installations" from "authenticated"; + +revoke references on table "public"."github_app_installations" from "authenticated"; + +revoke select on table "public"."github_app_installations" from "authenticated"; + +revoke trigger on table "public"."github_app_installations" from "authenticated"; + +revoke truncate on table "public"."github_app_installations" from "authenticated"; + +revoke update on table "public"."github_app_installations" from "authenticated"; + +revoke delete on table "public"."github_app_installations" from "service_role"; + +revoke insert on table "public"."github_app_installations" from "service_role"; + +revoke references on table "public"."github_app_installations" from "service_role"; + +revoke select on table "public"."github_app_installations" from "service_role"; + +revoke trigger on table "public"."github_app_installations" from "service_role"; + +revoke truncate on table "public"."github_app_installations" from "service_role"; + +revoke update on table "public"."github_app_installations" from "service_role"; + +alter table "public"."github_app_authorization" drop constraint "github_app_authorization_installation_id_key"; + +drop function if exists "public"."match_rag_docs"(query_embedding vector, filter jsonb); + +alter table "public"."github_app_authorization" drop constraint "github_app_authorization_pkey"; + +alter table "public"."github_app_installations" drop constraint "github_app_installations_pkey"; + +alter table "public"."rag_docs" drop constraint "rag_docs_pkey"; + +drop index if exists "public"."github_app_authorization_installation_id_key"; + +drop index if exists "public"."github_app_authorization_pkey"; + +drop index if exists "public"."github_app_installations_pkey"; + +drop index if exists "public"."rag_docs_pkey"; + +drop table "public"."github_app_authorization"; + +drop table "public"."github_app_installations"; + +alter table "public"."bots" add column "n" smallint default '1'::smallint; + +alter table "public"."bots" add column "temperature" real default '0.2'::real; + +alter table "public"."bots" add column "top_p" real; + +alter table "public"."profiles" add column "agreement_accepted" boolean default false; + +alter table "public"."rag_docs" alter column "repo_name" set not null; + +CREATE UNIQUE INDEX rag_docs_pkey ON public.rag_docs USING btree (id, repo_name); + +alter table "public"."rag_docs" add constraint "rag_docs_pkey" PRIMARY KEY using index "rag_docs_pkey"; + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.count_rag_docs_by_sha(file_sha_input text) + RETURNS TABLE(count bigint, file_sha character varying, repo_name text, file_path character varying) + LANGUAGE plpgsql +AS $function$ +BEGIN + RETURN QUERY + SELECT + COUNT(id) AS count, + r.file_sha AS file_sha, + r.repo_name AS repo_name, + r.file_path AS file_path + FROM + rag_docs r + WHERE + r.file_sha = file_sha_input + GROUP BY + r.file_sha, + r.repo_name, + r.file_path + ORDER BY + count ASC; +END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.match_embedding_docs(query_embedding vector, filter jsonb DEFAULT '{}'::jsonb) + RETURNS TABLE(id uuid, content text, metadata jsonb, embedding vector, similarity double precision) + LANGUAGE plpgsql +AS $function$ +#variable_conflict use_column +begin + return query + select + id, + content, + metadata, + embedding, + 1 - (rag_docs.embedding <=> query_embedding + ) as similarity + from rag_docs + where metadata @> jsonb_extract_path(filter, 'metadata') + and repo_name = jsonb_extract_path_text(filter, 'repo_name') + order by rag_docs.embedding <=> query_embedding; +end; +$function$ +; + + diff --git a/migrations/supabase/migrations/20241218093541_remote_schema.sql b/migrations/supabase/migrations/20241218093541_remote_schema.sql new file mode 100644 index 00000000..1de61dc0 --- /dev/null +++ b/migrations/supabase/migrations/20241218093541_remote_schema.sql @@ -0,0 +1,70 @@ +alter table "public"."profiles" drop constraint "profile_pkey"; + +drop index if exists "public"."profile_pkey"; + +alter table "public"."profiles" alter column "sub" set not null; + +CREATE INDEX idx_file_sha ON public.rag_docs USING btree (file_sha); + +CREATE UNIQUE INDEX profiles_pkey ON public.profiles USING btree (id); + +alter table "public"."profiles" add constraint "profiles_pkey" PRIMARY KEY using index "profiles_pkey"; + +set check_function_bodies = off; + +create or replace view "public"."bots_with_profiles_and_github" as SELECT bots.id, + bots.created_at, + bots.updated_at, + bots.avatar, + bots.description, + bots.name, + bots.public, + bots.starters, + bots.uid, + bots.repo_name, + profiles.picture, + profiles.nickname, + CASE + WHEN (github_repo_config.robot_id IS NOT NULL) THEN true + ELSE false + END AS github_installed + FROM ((bots + LEFT JOIN profiles ON (((bots.uid)::text = (profiles.sub)::text))) + LEFT JOIN github_repo_config ON ((bots.id = (github_repo_config.robot_id)::uuid))); + + +CREATE OR REPLACE FUNCTION public.get_bots_with_profiles_and_github() + RETURNS TABLE(id uuid, created_at timestamp without time zone, updated_at timestamp without time zone, avatar text, description text, name text, public boolean, starters text, uid text, domain_whitelist text[], repo_name text, picture text, nickname text, github_installed boolean) + LANGUAGE plpgsql +AS $function$ +BEGIN + RETURN QUERY + SELECT + b.id::uuid, + b.created_at::timestamp, + b.updated_at::timestamp, + b.avatar::text, + b.description::text, + b.name::text, + b.public::boolean, + b.starters::text, + b.uid::text, + b.domain_whitelist::text[], + b.repo_name::text, + p.picture::text, + p.nickname::text, + CASE + WHEN grc.robot_id IS NOT NULL THEN TRUE + ELSE FALSE + END AS github_installed + FROM + bots b + LEFT JOIN + profiles p ON b.uid = p.sub + LEFT JOIN + github_repo_config grc ON b.id::varchar = grc.robot_id; +END; +$function$ +; + + diff --git a/package.json b/package.json index 2cc59f55..669c086e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "client": "cd client && yarn run dev", "assistant": "cd assistant && yarn run dev", "server": "cd server && ./venv/bin/python3 -m uvicorn main:app --reload", + "server:local": "cd server && ./venv/bin/python3 -m uvicorn main:app --port 8001 --reload", "env:pull": "cd server && ./venv/bin/python3 scripts/envs.py pull", "client:server": "concurrently \"yarn run server\" \"yarn run client\"", "assistant:server": "concurrently \"yarn run server\" \"yarn run assistant\"", diff --git a/petercat_utils/README.md b/petercat_utils/README.md index b4f3998d..55227e62 100644 --- a/petercat_utils/README.md +++ b/petercat_utils/README.md @@ -112,11 +112,11 @@ | `WEB_URL` | 必选 | 前端 Web 服务的域名 | `https://petercat.ai` | `STATIC_URL` | 必选 | 静态资源域名 | `https://static.petercat.ai` | **AWS 相关环境变量** | -| `AWS_GITHUB_SECRET_NAME` | 必选 | AWS 托管的 Github 私钥文件名 | `prod/githubapp/petercat/pem` -| `AWS_STATIC_SECRET_NAME` | 可选 | AWS 托管的 CloudFront 签名私钥名称。如果配置了该项,将使用 CloudFront 签名 URL 来保护你的资源。更多信息请参阅 [AWS 文档](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)。 | `prod/petercat/static` | -| `AWS_LLM_TOKEN_SECRET_NAME` | 可选 | AWS 托管的 llm 签名私钥名称。如果配置了该项,petercat 将使用 RSA 算法托管用户的 LLM Token | `prod/petercat/llm` | -| `AWS_LLM_TOKEN_PUBLIC_NAME` | 可选 | AWS 托管的 llm 签名公钥名称。如果配置了该项,petercat 将使用 RSA 算法托管用户的 LLM Token | `prod/petercat/llm/pub` | -| `AWS_STATIC_KEYPAIR_ID` | 可选 | AWS CloudFront 的 Key Pair ID。如果配置了该项,将使用 CloudFront 签名 URL 来保护你的资源。更多信息请参阅 [AWS 文档](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)。 | `APKxxxxxxxx` | +| `X_GITHUB_SECRET_NAME` | 必选 | AWS 托管的 Github 私钥文件名 | `prod/githubapp/petercat/pem` +| `STATIC_SECRET_NAME` | 可选 | AWS 托管的 CloudFront 签名私钥名称。如果配置了该项,将使用 CloudFront 签名 URL 来保护你的资源。更多信息请参阅 [AWS 文档](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)。 | `prod/petercat/static` | +| `LLM_TOKEN_SECRET_NAME` | 可选 | AWS 托管的 llm 签名私钥名称。如果配置了该项,petercat 将使用 RSA 算法托管用户的 LLM Token | `prod/petercat/llm` | +| `LLM_TOKEN_PUBLIC_NAME` | 可选 | AWS 托管的 llm 签名公钥名称。如果配置了该项,petercat 将使用 RSA 算法托管用户的 LLM Token | `prod/petercat/llm/pub` | +| `STATIC_KEYPAIR_ID` | 可选 | AWS CloudFront 的 Key Pair ID。如果配置了该项,将使用 CloudFront 签名 URL 来保护你的资源。更多信息请参阅 [AWS 文档](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)。 | `APKxxxxxxxx` | | `S3_TEMP_BUCKET_NAME` | 可选 | 用于托管 AWS 临时图片文件 S3 的 bucket | `xxx-temp` | `SQS_QUEUE_URL`| 必选 | AWS SQS 消息队列 URL | `https://sqs.ap-northeast-1.amazonaws.com/xxx/petercat-task-queue` | **SUPABASE 相关 env** | diff --git a/petercat_utils/rag_helper/retrieval.py b/petercat_utils/rag_helper/retrieval.py index fd988ac5..faafa4e3 100644 --- a/petercat_utils/rag_helper/retrieval.py +++ b/petercat_utils/rag_helper/retrieval.py @@ -1,7 +1,6 @@ import json from typing import Any, Dict - from langchain_community.vectorstores import SupabaseVectorStore from langchain_openai import OpenAIEmbeddings @@ -9,7 +8,6 @@ from ..data_class import GitDocConfig, RAGGitDocConfig, S3Config from ..db.client.supabase import get_client - TABLE_NAME = "rag_docs" QUERY_NAME = "match_embedding_docs" CHUNK_SIZE = 2000 @@ -118,15 +116,16 @@ def add_knowledge_by_doc(config: RAGGitDocConfig): supabase = get_client() is_doc_added_query = ( supabase.table(TABLE_NAME) - .select("id, repo_name, commit_id, file_path") + .select("id") .eq("repo_name", config.repo_name) .eq("commit_id", loader.commit_id) .eq("file_path", config.file_path) + .limit(1) .execute() ) if not is_doc_added_query.data: is_doc_equal_query = ( - supabase.table(TABLE_NAME).select("*").eq("file_sha", loader.file_sha) + supabase.table(TABLE_NAME).select("id").eq("file_sha", loader.file_sha).limit(1) ).execute() if not is_doc_equal_query.data: # If there is no file with the same file_sha, perform embedding. @@ -139,6 +138,18 @@ def add_knowledge_by_doc(config: RAGGitDocConfig): ) return store else: + # Prioritize obtaining the minimal set of records to avoid overlapping with the original records. + minimum_repeat_result = supabase.rpc('count_rag_docs_by_sha', {'file_sha_input': loader.file_sha}).execute() + target_filter = minimum_repeat_result.data[0] + # Copy the minimal set + insert_docs = ( + supabase.table(TABLE_NAME) + .select("*") + .eq("repo_name", target_filter['repo_name']) + .eq("file_path", target_filter['file_path']) + .eq("file_sha", target_filter['file_sha']) + .execute() + ) new_commit_list = [ { **{k: v for k, v in item.items() if k != "id"}, @@ -146,7 +157,7 @@ def add_knowledge_by_doc(config: RAGGitDocConfig): "commit_id": loader.commit_id, "file_path": config.file_path, } - for item in is_doc_equal_query.data + for item in insert_docs.data ] insert_result = supabase.table(TABLE_NAME).insert(new_commit_list).execute() return insert_result @@ -169,9 +180,9 @@ def reload_knowledge(config: RAGGitDocConfig): def search_knowledge( - query: str, - repo_name: str, - meta_filter: Dict[str, Any] = {}, + query: str, + repo_name: str, + meta_filter: Dict[str, Any] = {}, ): retriever = init_retriever( {"filter": {"metadata": meta_filter, "repo_name": repo_name}} diff --git a/pyproject.toml b/pyproject.toml index ef116755..6790e442 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "petercat_utils" -version = "0.1.39" +version = "0.1.40" description = "" authors = ["raoha.rh "] readme = "README.md" diff --git a/server/.env.example b/server/.env.example index ae5b7302..b5e86f1f 100644 --- a/server/.env.example +++ b/server/.env.example @@ -6,9 +6,9 @@ STATIC_URL=STATIC_URL FASTAPI_SECRET_KEY=fastapi_secret_key # `Project URL` field of https://supabase.com/dashboard/project/_/settings/database -SUPABASE_URL=https://xxxx.supabase.co +SUPABASE_URL=${SUPABASE_PUBLIC_URL} # `Project API keys`: `anon public` field of https://supabase.com/dashboard/project/_/settings/database -SUPABASE_SERVICE_KEY=xxxx.yyyy.zzzz-aaa +SUPABASE_SERVICE_KEY=${SERVICE_ROLE_KEY} SUPABASE_PASSWORD=aABCDEFG # OpenAI API KEY @@ -23,8 +23,14 @@ X_GITHUB_APP_ID=github_app_id X_GITHUB_APPS_CLIENT_ID=github_apps_client_id X_GITHUB_APPS_CLIENT_SECRET=github_apps_client_secret +PETERCAT_AUTH0_ENABLED=False +# OPTIONAL - Local Authorization Configures + +PETERCAT_LOCAL_UID="petercat|001" +PETERCAT_LOCAL_UNAME="petercat" # OPTIONAL - AUTH0 Configures + API_IDENTIFIER=api_identifier AUTH0_DOMAIN=auth0_domain AUTH0_CLIENT_ID=auth0_client_id @@ -32,9 +38,9 @@ AUTH0_CLIENT_SECRET=auth0_client_secret # OPTIONAL - AWS Configures SQS_QUEUE_URL=https://sqs.ap-northeast-1.amazonaws.com/{your_aws_user}/{your_aws_sqs_message} -AWS_GITHUB_SECRET_NAME="prod/githubapp/petercat/pem" -AWS_STATIC_SECRET_NAME="prod/petercat/static" -AWS_LLM_TOKEN_SECRET_NAME="prod/petercat/llm" -AWS_LLM_TOKEN_PUBLIC_NAME="petercat/prod/llm/pub" -AWS_STATIC_KEYPAIR_ID="xxxxxx" +X_GITHUB_SECRET_NAME="prod/githubapp/petercat/pem" +STATIC_SECRET_NAME="prod/petercat/static" +LLM_TOKEN_SECRET_NAME="prod/petercat/llm" +LLM_TOKEN_PUBLIC_NAME="petercat/prod/llm/pub" +STATIC_KEYPAIR_ID="xxxxxx" S3_TEMP_BUCKET_NAME=S3_TEMP_BUCKET_NAME diff --git a/server/.env.local.example b/server/.env.local.example new file mode 100644 index 00000000..a0354e2d --- /dev/null +++ b/server/.env.local.example @@ -0,0 +1,47 @@ +# App Base Configures +API_URL=http://localhost:8001 +WEB_URL=http://localhost:3000 +# OPTIONAL - standalong static url if required +STATIC_URL=STATIC_URL + +FASTAPI_SECRET_KEY=fastapi_secret_key +# `Project URL`: +SUPABASE_URL=http://localhost:8001 +# `Project API keys`: SERVICE_ROLE_KEY from supabase .env file +SUPABASE_SERVICE_KEY=${SERVICE_ROLE_KEY} + +SUPABASE_PASSWORD=aABCDEFG +# OpenAI API KEY +OPENAI_API_KEY=sk-xxxx +# OPTIONAL - Gemini +GEMINI_API_KEY=gemini_api_key +# Tavily Api Key +TAVILY_API_KEY=tavily_api_key + +# OPTIONAL - Github Apps Configures +X_GITHUB_APP_ID=github_app_id +X_GITHUB_APPS_CLIENT_ID=github_apps_client_id +X_GITHUB_APPS_CLIENT_SECRET=github_apps_client_secret + +# OPTIONAL - Local Authorization Configures +PETERCAT_LOCAL_UID="petercat|001" +PETERCAT_LOCAL_UNAME="petercat" +PETERCAT_LOCAL_GITHUB_TOKEN="github_pat_xxxx" + +# OPTIONAL - SKIP AUTH0 Authorization +PETERCAT_AUTH0_ENABLED=True + +# OPTIONAL - AUTH0 Configures +API_IDENTIFIER=api_identifier +AUTH0_DOMAIN=auth0_domain +AUTH0_CLIENT_ID=auth0_client_id +AUTH0_CLIENT_SECRET=auth0_client_secret + +# OPTIONAL - AWS Configures +SQS_QUEUE_URL=https://sqs.ap-northeast-1.amazonaws.com/{your_aws_user}/{your_aws_sqs_message} +X_GITHUB_SECRET_NAME="prod/githubapp/petercat/pem" +STATIC_SECRET_NAME="prod/petercat/static" +LLM_TOKEN_SECRET_NAME="prod/petercat/llm" +LLM_TOKEN_PUBLIC_NAME="petercat/prod/llm/pub" +STATIC_KEYPAIR_ID="xxxxxx" +S3_TEMP_BUCKET_NAME=S3_TEMP_BUCKET_NAME diff --git a/server/agent/prompts/issue_helper.py b/server/agent/prompts/issue_helper.py index 5ae64217..2843b89a 100644 --- a/server/agent/prompts/issue_helper.py +++ b/server/agent/prompts/issue_helper.py @@ -10,7 +10,10 @@ - If you don’t have any useful conclusions, use your own knowledge to assist the user as much as possible, but do not fabricate facts. - Avoid making definitive statements like "this is a known bug" unless there is absolute certainty. Such irresponsible assumptions can be misleading. - At the end of the conversation, be sure to include the following wording and adhere to the language used in previous conversations: +
+🪧 Tips For further assistance, please describe your question in the comments and @petercat-assistant to start a conversation with me. +
## Issue Information: ``` @@ -29,7 +32,10 @@ - If you don’t have any useful conclusions, use your own knowledge to assist the user as much as possible, but do not fabricate facts. - Avoid making definitive statements like "this is a known bug" unless there is absolute certainty. Such irresponsible assumptions can be misleading. - At the end of the conversation, be sure to include the following wording and adhere to the language used in previous conversations: +
+🪧 Tips For further assistance, please describe your question in the comments and @petercat-assistant to start a conversation with me. +
issue_content: {issue_content} issue_number: {issue_number} diff --git a/server/agent/prompts/pull_request.py b/server/agent/prompts/pull_request.py index 1bf48805..2b6666c4 100644 --- a/server/agent/prompts/pull_request.py +++ b/server/agent/prompts/pull_request.py @@ -30,7 +30,10 @@ - **Changes**: A markdown table of files and their summaries. Group files with similar changes together into a single row to save space. - At the end of the conversation, be sure to include the following wording and adhere to the language used in previous conversations: +
+🪧 Tips For further assistance, please describe your question in the comments and @petercat-assistant to start a conversation with me. +
## Task 2: Code Review @@ -105,7 +108,10 @@ - Never attempt to create a new issue or PR under any circumstances; instead, express an apology. - If you don’t have any useful conclusions, use your own knowledge to assist the user as much as possible, but do not fabricate facts. - At the end of the conversation, be sure to include the following wording and adhere to the language used in previous conversations: +
+🪧 Tips For further assistance, please describe your question in the comments and @petercat-assistant to start a conversation with me. +
""" diff --git a/server/auth/clients/__init__.py b/server/auth/clients/__init__.py new file mode 100644 index 00000000..a5ad37d3 --- /dev/null +++ b/server/auth/clients/__init__.py @@ -0,0 +1,12 @@ +from auth.clients.auth0 import Auth0Client +from auth.clients.base import BaseAuthClient +from auth.clients.local import LocalClient + +from petercat_utils import get_env_variable + +PETERCAT_AUTH0_ENABLED = get_env_variable("PETERCAT_AUTH0_ENABLED", "True") == "True" + +def get_auth_client() -> BaseAuthClient: + if PETERCAT_AUTH0_ENABLED: + return Auth0Client() + return LocalClient() diff --git a/server/auth/clients/auth0.py b/server/auth/clients/auth0.py new file mode 100644 index 00000000..ab329728 --- /dev/null +++ b/server/auth/clients/auth0.py @@ -0,0 +1,92 @@ +import httpx +import secrets + +from fastapi import Request +from auth.clients.base import BaseAuthClient +from petercat_utils import get_env_variable +from starlette.config import Config +from authlib.integrations.starlette_client import OAuth + +CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID") +CLIENT_SECRET = get_env_variable("AUTH0_CLIENT_SECRET") +AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") +API_AUDIENCE = get_env_variable("API_IDENTIFIER") +API_URL = get_env_variable("API_URL") + +CALLBACK_URL = f"{API_URL}/api/auth/callback" + +config = Config( + environ={ + "AUTH0_CLIENT_ID": CLIENT_ID, + "AUTH0_CLIENT_SECRET": CLIENT_SECRET, + } +) + +class Auth0Client(BaseAuthClient): + _client: OAuth + + def __init__(self): + self._client = OAuth(config) + self._client.register( + name="auth0", + server_metadata_url=f"https://{AUTH0_DOMAIN}/.well-known/openid-configuration", + client_kwargs={"scope": "openid email profile"}, + ) + + async def login(self, request): + return await self._client.auth0.authorize_redirect( + request, redirect_uri=CALLBACK_URL + ) + + async def get_oauth_token(self): + url = f'https://{AUTH0_DOMAIN}/oauth/token' + headers = {"content-type": "application/x-www-form-urlencoded"} + data = { + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET, + 'audience': API_AUDIENCE, + 'grant_type': 'client_credentials' + } + async with httpx.AsyncClient() as client: + response = await client.post(url, data=data, headers=headers) + return response.json()['access_token'] + + async def get_user_info(self, request: Request) -> dict: + auth0_token = await self._client.auth0.authorize_access_token(request) + access_token = auth0_token["access_token"] + userinfo_url = f"https://{AUTH0_DOMAIN}/userinfo" + headers = {"authorization": f"Bearer {access_token}"} + async with httpx.AsyncClient() as client: + user_info_response = await client.get(userinfo_url, headers=headers) + if user_info_response.status_code == 200: + user_info = user_info_response.json() + data = { + "id": user_info["sub"], + "nickname": user_info.get("nickname"), + "name": user_info.get("name"), + "picture": user_info.get("picture"), + "sub": user_info["sub"], + "sid": secrets.token_urlsafe(32), + "agreement_accepted": user_info.get("agreement_accepted"), + } + return data + else: + return None + + async def get_access_token(self, user_id: str, provider="github"): + token = await self.get_oauth_token() + user_accesstoken_url = f"https://{AUTH0_DOMAIN}/api/v2/users/{user_id}" + + async with httpx.AsyncClient() as client: + headers = {"authorization": f"Bearer {token}"} + user_info_response = await client.get(user_accesstoken_url, headers=headers) + user = user_info_response.json() + identity = next( + ( + identity + for identity in user["identities"] + if identity["provider"] == provider + ), + None, + ) + return identity["access_token"] \ No newline at end of file diff --git a/server/auth/clients/base.py b/server/auth/clients/base.py new file mode 100644 index 00000000..9a34d0c5 --- /dev/null +++ b/server/auth/clients/base.py @@ -0,0 +1,51 @@ +import secrets + +from abc import abstractmethod +from fastapi import Request +from utils.random_str import random_str +from petercat_utils import get_client + + +class BaseAuthClient: + def __init__(self): + pass + + def generateAnonymousUser(self, clientId: str) -> tuple[str, dict]: + token = f"client|{clientId}" + seed = clientId[:4] + random_name = f"{seed}_{random_str(4)}" + data = { + "id": token, + "sub": token, + "nickname": random_name, + "name": random_name, + "picture": f"https://picsum.photos/seed/{seed}/100/100", + "sid": secrets.token_urlsafe(32), + "agreement_accepted": False, + } + + return token, data + + async def anonymouseLogin(self, request: Request) -> dict: + clientId = request.query_params.get("clientId") or random_str() + token, data = self.generateAnonymousUser(clientId = clientId) + supabase = get_client() + supabase.table("profiles").upsert(data).execute() + request.session["user"] = data + return data + + @abstractmethod + async def login(self, request: Request): + pass + + @abstractmethod + async def get_oauth_token(self) -> str: + pass + + @abstractmethod + async def get_user_info(self, request: Request) -> dict: + pass + + @abstractmethod + async def get_access_token(self, user_id: str, provider="github") -> str: + pass \ No newline at end of file diff --git a/server/auth/clients/local.py b/server/auth/clients/local.py new file mode 100644 index 00000000..da52f1fc --- /dev/null +++ b/server/auth/clients/local.py @@ -0,0 +1,42 @@ +import secrets +from fastapi import Request +from fastapi.responses import RedirectResponse +from petercat_utils import get_client, get_env_variable +from auth.clients.base import BaseAuthClient + +PETERCAT_LOCAL_UID = get_env_variable("PETERCAT_LOCAL_UID") +PETERCAT_LOCAL_UNAME = get_env_variable("PETERCAT_LOCAL_UNAME") +PETERCAT_LOCAL_GITHUB_TOKEN = get_env_variable("PETERCAT_LOCAL_GITHUB_TOKEN") +WEB_URL = get_env_variable("WEB_URL") +WEB_LOGIN_SUCCESS_URL = f"{WEB_URL}/user/login" + +class LocalClient(BaseAuthClient): + def __init__(self): + pass + + async def login(self, request: Request): + data = await self.get_user_info() + supabase = get_client() + supabase.table("profiles").upsert(data).execute() + request.session["user"] = data + + return RedirectResponse(url=f"{WEB_LOGIN_SUCCESS_URL}", status_code=302) + + async def get_user_info(self, user_id): + token = PETERCAT_LOCAL_UID + username = PETERCAT_LOCAL_UNAME + seed = token[:4] + + return { + "id": token, + "sub": token, + "nickname": username, + "name": username, + "picture": f"https://picsum.photos/seed/{seed}/100/100", + "sid": secrets.token_urlsafe(32), + "agreement_accepted": False, + } + + + async def get_access_token(self, user_id): + return PETERCAT_LOCAL_GITHUB_TOKEN \ No newline at end of file diff --git a/server/auth/get_oauth_token.py b/server/auth/get_oauth_token.py deleted file mode 100644 index 6e23c17b..00000000 --- a/server/auth/get_oauth_token.py +++ /dev/null @@ -1,22 +0,0 @@ -import httpx -from petercat_utils import get_env_variable - -AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") - -API_AUDIENCE = get_env_variable("API_IDENTIFIER") -CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID") -CLIENT_SECRET = get_env_variable("AUTH0_CLIENT_SECRET") - -async def get_oauth_token(): - url = f'https://{AUTH0_DOMAIN}/oauth/token' - headers = {"content-type": "application/x-www-form-urlencoded"} - data = { - 'client_id': CLIENT_ID, - 'client_secret': CLIENT_SECRET, - 'audience': API_AUDIENCE, - 'grant_type': 'client_credentials' - } - async with httpx.AsyncClient() as client: - response = await client.post(url, data=data, headers=headers) - print(f"url={url}, response={response}") - return response.json()['access_token'] \ No newline at end of file diff --git a/server/auth/get_user_info.py b/server/auth/get_user_info.py index 950f916f..168f6c07 100644 --- a/server/auth/get_user_info.py +++ b/server/auth/get_user_info.py @@ -1,80 +1,12 @@ -from fastapi import Request -import httpx -import secrets +from fastapi import Depends, Request +from auth.clients import get_auth_client +from auth.clients.base import BaseAuthClient from core.models.user import User -from utils.random_str import random_str - -from .get_oauth_token import get_oauth_token -from petercat_utils import get_client, get_env_variable - +from petercat_utils import get_env_variable AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") -async def getUserInfoByToken(token): - userinfo_url = f"https://{AUTH0_DOMAIN}/userinfo" - headers = {"authorization": f"Bearer {token}"} - async with httpx.AsyncClient() as client: - user_info_response = await client.get(userinfo_url, headers=headers) - if user_info_response.status_code == 200: - user_info = user_info_response.json() - data = { - "id": user_info["sub"], - "nickname": user_info.get("nickname"), - "name": user_info.get("name"), - "picture": user_info.get("picture"), - "avatar": user_info.get("picture"), - "sub": user_info["sub"], - "sid": secrets.token_urlsafe(32), - "agreement_accepted": user_info.get("agreement_accepted"), - } - return data - else: - return None - - -async def getUserAccessToken(user_id: str, provider="github"): - token = await get_oauth_token() - user_accesstoken_url = f"https://{AUTH0_DOMAIN}/api/v2/users/{user_id}" - - async with httpx.AsyncClient() as client: - headers = {"authorization": f"Bearer {token}"} - user_info_response = await client.get(user_accesstoken_url, headers=headers) - user = user_info_response.json() - identity = next( - ( - identity - for identity in user["identities"] - if identity["provider"] == provider - ), - None, - ) - return identity["access_token"] - - -async def generateAnonymousUser(clientId: str): - token = f"client|{clientId}" - seed = clientId[:4] - random_name = f"{seed}_{random_str(4)}" - data = { - "id": token, - "sub": token, - "nickname": random_name, - "name": random_name, - "picture": f"https://picsum.photos/seed/{seed}/100/100", - "sid": secrets.token_urlsafe(32), - "agreement_accepted": False, - } - - return token, data - - -async def getAnonymousUserInfoByToken(token: str): - supabase = get_client() - rows = supabase.table("profiles").select("*").eq("id", token).execute() - return rows.data[0] if (len(rows.data) > 0) else None - - async def get_user_id(request: Request): user_info = request.session.get("user") try: @@ -86,7 +18,7 @@ async def get_user_id(request: Request): return None -async def get_user(request: Request) -> User | None: +async def get_user(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)) -> User | None: user_info = request.session.get("user") if user_info is None: return None @@ -94,5 +26,5 @@ async def get_user(request: Request) -> User | None: if user_info["sub"].startswith("client|"): return User(**user_info, anonymous=True) - access_token = await getUserAccessToken(user_id=user_info["sub"]) + access_token = await auth_client.get_access_token(user_id=user_info["sub"]) return User(**user_info, access_token=access_token, anonymous=False) diff --git a/server/auth/middleware.py b/server/auth/middleware.py index 44a8912e..73244095 100644 --- a/server/auth/middleware.py +++ b/server/auth/middleware.py @@ -1,88 +1,87 @@ import traceback from typing import Awaitable, Callable + from fastapi import HTTPException, Request, status from fastapi.responses import JSONResponse -from petercat_utils import get_env_variable +from fastapi.security import OAuth2PasswordBearer from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import Response -from fastapi.security import OAuth2PasswordBearer from core.dao.botDAO import BotDAO - -WEB_URL = get_env_variable("WEB_URL") -ENVRIMENT = get_env_variable("PETERCAT_ENV", "development") +from env import ENVIRONMENT, WEB_URL ALLOW_LIST = [ - "/", - "/favicon.ico", - "/api/health_checker", - "/api/bot/list", - "/api/bot/detail", - "/api/github/app/webhook", - "/app/installation/callback", + "/", + "/favicon.ico", + "/api/health_checker", + "/api/bot/list", + "/api/bot/detail", + "/api/github/app/webhook", + "/app/installation/callback", ] ANONYMOUS_USER_ALLOW_LIST = [ - "/api/auth/userinfo", - "/api/chat/qa", - "/api/chat/stream_qa", + "/api/auth/userinfo", + "/api/chat/qa", + "/api/chat/stream_qa", ] oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token") + class AuthMiddleWare(BaseHTTPMiddleware): - async def oauth(self, request: Request): - try: - referer = request.headers.get('referer') - origin = request.headers.get('origin') - if referer and referer.startswith(WEB_URL): - return True - - token = await oauth2_scheme(request=request) - if token: - bot_dao = BotDAO() - bot = bot_dao.get_bot(bot_id=token) - return bot and ( - "*" in bot.domain_whitelist - or - origin in bot.domain_whitelist - ) - except HTTPException: - return False - - async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response: - try: - # if ENVRIMENT == "development": - # return await call_next(request) - - # Auth 相关的直接放过 - if request.url.path.startswith("/api/auth"): - return await call_next(request) - - if request.url.path in ALLOW_LIST: - return await call_next(request) - - if await self.oauth(request=request): - return await call_next(request) - - # 获取 session 中的用户信息 - user = request.session.get("user") - if not user: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") - - if user['sub'].startswith("client|"): - if request.url.path in ANONYMOUS_USER_ALLOW_LIST: - return await call_next(request) - else: - # 如果没有用户信息,返回 401 Unauthorized 错误 - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Anonymous User Not Allow") - - return await call_next(request) - except HTTPException as e: - print(traceback.format_exception(e)) - # 处理 HTTP 异常 - return JSONResponse(status_code=e.status_code, content={"detail": e.detail}) - except Exception as e: - # 处理其他异常 - return JSONResponse(status_code=500, content={"detail": f"Internal Server Error: {e}"}) + async def oauth(self, request: Request): + try: + referer = request.headers.get('referer') + origin = request.headers.get('origin') + if referer and referer.startswith(WEB_URL): + return True + + token = await oauth2_scheme(request=request) + if token: + bot_dao = BotDAO() + bot = bot_dao.get_bot(bot_id=token) + return bot and ( + "*" in bot.domain_whitelist + or + origin in bot.domain_whitelist + ) + except HTTPException: + return False + + async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response: + try: + if ENVIRONMENT == "development": + return await call_next(request) + + # Auth 相关的直接放过 + if request.url.path.startswith("/api/auth"): + return await call_next(request) + + if request.url.path in ALLOW_LIST: + return await call_next(request) + + if await self.oauth(request=request): + return await call_next(request) + + # 获取 session 中的用户信息 + user = request.session.get("user") + if not user: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") + + if user['sub'].startswith("client|"): + if request.url.path in ANONYMOUS_USER_ALLOW_LIST: + return await call_next(request) + else: + # 如果没有用户信息,返回 401 Unauthorized 错误 + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Anonymous User Not Allow") + + return await call_next(request) + except HTTPException as e: + print(traceback.format_exception(e)) + # 处理 HTTP 异常 + return JSONResponse(status_code=e.status_code, content={"detail": e.detail}) + except Exception as e: + # 处理其他异常 + return JSONResponse(status_code=500, content={"detail": f"Internal Server Error: {e}"}) diff --git a/server/auth/rate_limit.py b/server/auth/rate_limit.py index cb820378..da57de95 100644 --- a/server/auth/rate_limit.py +++ b/server/auth/rate_limit.py @@ -1,22 +1,24 @@ -from typing import Annotated -from fastapi import Cookie, HTTPException +from typing import Optional +from fastapi import Depends, HTTPException from datetime import datetime, timedelta +from auth.clients import get_auth_client +from auth.clients.base import BaseAuthClient from petercat_utils import get_client, get_env_variable -from auth.get_user_info import getUserInfoByToken +from auth.get_user_info import get_user_id RATE_LIMIT_ENABLED = get_env_variable("RATE_LIMIT_ENABLED", "False") == 'True' RATE_LIMIT_REQUESTS = get_env_variable("RATE_LIMIT_REQUESTS") or 100 RATE_LIMIT_DURATION = timedelta(minutes=int(get_env_variable("RATE_LIMIT_DURATION") or 1)) -async def verify_rate_limit(petercat_user_token: Annotated[str | None, Cookie()] = None): +async def verify_rate_limit(user_id: Optional[str] = Depends(get_user_id), auth_client: BaseAuthClient = Depends(get_auth_client)): if not RATE_LIMIT_ENABLED: return - if not petercat_user_token: + if not user_id: raise HTTPException(status_code=403, detail="Must Login") - user = await getUserInfoByToken(petercat_user_token) + user = await auth_client.get_user_info(user_id) if user is None: raise HTTPException( diff --git a/server/auth/router.py b/server/auth/router.py index bb6d4f8d..d9fc65db 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -1,40 +1,25 @@ -from fastapi import APIRouter, Request, HTTPException, status, Depends +from typing import Annotated, Optional + +from fastapi import APIRouter, Request, HTTPException, Depends from fastapi.responses import RedirectResponse, JSONResponse -import secrets -from petercat_utils import get_client, get_env_variable -from starlette.config import Config -from authlib.integrations.starlette_client import OAuth -from typing import Annotated +from github import Github -from auth.get_user_info import generateAnonymousUser, getUserInfoByToken, get_user_id -AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") +from auth.clients import get_auth_client +from auth.clients.base import BaseAuthClient +from auth.get_user_info import get_user_id +from core.dao.profilesDAO import ProfilesDAO +from petercat_utils import get_client, get_env_variable -API_AUDIENCE = get_env_variable("API_IDENTIFIER") -CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID") -CLIENT_SECRET = get_env_variable("AUTH0_CLIENT_SECRET") +API_URL = get_env_variable("API_URL") +WEB_URL = get_env_variable("WEB_URL") -API_URL = get_env_variable("API_URL") CALLBACK_URL = f"{API_URL}/api/auth/callback" LOGIN_URL = f"{API_URL}/api/auth/login" -WEB_URL = get_env_variable("WEB_URL") WEB_LOGIN_SUCCESS_URL = f"{WEB_URL}/user/login" MARKET_URL = f"{WEB_URL}/market" -config = Config(environ={ - "AUTH0_CLIENT_ID": CLIENT_ID, - "AUTH0_CLIENT_SECRET": CLIENT_SECRET, -}) - -oauth = OAuth(config) -oauth.register( - name="auth0", - server_metadata_url=f'https://{AUTH0_DOMAIN}/.well-known/openid-configuration', - client_kwargs={ - 'scope': 'openid email profile' - } -) router = APIRouter( prefix="/api/auth", @@ -42,89 +27,86 @@ responses={404: {"description": "Not found"}}, ) -async def getAnonymousUser(request: Request): - clientId = request.query_params.get("clientId") - if not clientId: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing clientId") - token, data = await generateAnonymousUser(clientId) - - supabase = get_client() - supabase.table("profiles").upsert(data).execute() - request.session['user'] = data - return data - @router.get("/login") -async def login(request: Request): - if CLIENT_ID is None: - return { - "message": "enviroments CLIENT_ID and CLIENT_SECRET required.", - } - redirect_response = await oauth.auth0.authorize_redirect(request, redirect_uri=CALLBACK_URL) - return redirect_response - -@router.get('/logout') +async def login(request: Request, auth_client = Depends(get_auth_client)): + return await auth_client.login(request) + +@router.get("/logout") async def logout(request: Request): - request.session.pop('user', None) - redirect = request.query_params.get('redirect') + request.session.pop("user", None) + redirect = request.query_params.get("redirect") if redirect: - return RedirectResponse(url=f'{redirect}', status_code=302) - return { "success": True } + return RedirectResponse(url=f"{redirect}", status_code=302) + return {"success": True} -@router.get("/callback") -async def callback(request: Request): - auth0_token = await oauth.auth0.authorize_access_token(request) - user_info = await getUserInfoByToken(token=auth0_token['access_token']) +@router.get("/callback") +async def callback(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)): + user_info = await auth_client.get_user_info(request) if user_info: - data = { - "id": user_info["sub"], - "nickname": user_info.get("nickname"), - "name": user_info.get("name"), - "picture": user_info.get("picture"), - "sub": user_info["sub"], - "sid": secrets.token_urlsafe(32), - "agreement_accepted": user_info.get("agreement_accepted"), - } - request.session['user'] = dict(data) + request.session["user"] = dict(user_info) supabase = get_client() - supabase.table("profiles").upsert(data).execute() - return RedirectResponse(url=f'{WEB_LOGIN_SUCCESS_URL}', status_code=302) + supabase.table("profiles").upsert(user_info).execute() + return RedirectResponse(url=f"{WEB_LOGIN_SUCCESS_URL}", status_code=302) + @router.get("/userinfo") -async def userinfo(request: Request): - user = request.session.get('user') +async def userinfo(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)): + user = request.session.get("user") if not user: - data = await getAnonymousUser(request) - return { "data": data, "status": 200} - return { "data": user, "status": 200} + data = await auth_client.anonymouseLogin(request) + return {"data": data, "status": 200} + return {"data": user, "status": 200} + + +@router.get("/agreement/status") +async def get_agreement_status(user_id: Optional[str] = Depends(get_user_id)): + if not user_id: + raise HTTPException(status_code=401, detail="User not found") + try: + profiles_dao = ProfilesDAO() + response = profiles_dao.get_agreement_status(user_id=user_id) + if not response: + raise HTTPException( + status_code=404, detail="User does not exist, accept failed." + ) + return {"success": True, "data": response} + except Exception as e: + raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}") + @router.post("/accept/agreement", status_code=200) async def bot_generator( - request: Request, - user_id: Annotated[str | None, Depends(get_user_id)] = None, + request: Request, + user_id: Annotated[str | None, Depends(get_user_id)] = None, ): if not user_id: - return JSONResponse( - content={ - "success": False, - "errorMessage": "User not found", - }, - status_code=401, - ) + raise HTTPException(status_code=401, detail="User not found") try: - supabase = get_client() - response = supabase.table("profiles").update({"agreement_accepted": True}).match({"id": user_id}).execute() - - if not response.data: - return JSONResponse( - content={ - "success": False, - "errorMessage": "User does not exist, accept failed.", - } - ) - request.session['user'] = response.data[0] - return JSONResponse(content={"success": True}) + profiles_dao = ProfilesDAO() + response = profiles_dao.accept_agreement(user_id=user_id) + if response: + request.session["user"] = response + return JSONResponse(content={"success": True}) + else: + raise HTTPException(status_code=400, detail="User update failed") + except Exception as e: + raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}") + + +@router.get("/repos") +async def get_user_repos(user_id: Optional[str] = Depends(get_user_id), auth_client: BaseAuthClient = Depends(get_auth_client)): + if not user_id: + raise HTTPException(status_code=401, detail="User not found") + try: + access_token = await auth_client.get_access_token(user_id=user_id) + g = Github(access_token) + user = g.get_user() + repos = user.get_repos() + + repo_names = [ + {"name": repo.full_name} for repo in repos if repo.permissions.maintain + ] + return {"data": repo_names, "status": 200} except Exception as e: - return JSONResponse( - content={"success": False, "errorMessage": str(e)}, status_code=500 - ) + raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}") diff --git a/server/aws/service.py b/server/aws/service.py index c125b061..d451b317 100644 --- a/server/aws/service.py +++ b/server/aws/service.py @@ -5,22 +5,22 @@ import rsa from datetime import datetime, timedelta -from utils.get_private_key import get_private_key +from utils.private_key import get_private_key from .schemas import ImageMetaData from .constants import S3_TEMP_BUCKET_NAME, STATIC_URL from .exceptions import UploadError -REGIN_NAME = get_env_variable("AWS_REGION") -AWS_STATIC_SECRET_NAME = get_env_variable("AWS_STATIC_SECRET_NAME") -AWS_STATIC_KEYPAIR_ID = get_env_variable("AWS_STATIC_KEYPAIR_ID") +REGION_NAME = get_env_variable("AWS_REGION") +STATIC_SECRET_NAME = get_env_variable("STATIC_SECRET_NAME") +STATIC_KEYPAIR_ID = get_env_variable("STATIC_KEYPAIR_ID") def rsa_signer(message): - private_key_str = get_private_key(REGIN_NAME, AWS_STATIC_SECRET_NAME) + private_key_str = get_private_key(STATIC_SECRET_NAME) private_key = rsa.PrivateKey.load_pkcs1(private_key_str.encode('utf-8')) return rsa.sign(message, private_key, 'SHA-1') def create_signed_url(url, expire_minutes=60) -> str: - cloudfront_signer = CloudFrontSigner(AWS_STATIC_KEYPAIR_ID, rsa_signer) + cloudfront_signer = CloudFrontSigner(STATIC_KEYPAIR_ID, rsa_signer) # 设置过期时间 expire_date = datetime.now() + timedelta(minutes=expire_minutes) @@ -65,7 +65,7 @@ def upload_image_to_s3(file, metadata: ImageMetaData, s3_client): # you need to redirect your static domain to your s3 bucket domain s3_url = f"{STATIC_URL}/{s3_key}" signed_url = create_signed_url(url=s3_url, expire_minutes=60) \ - if (AWS_STATIC_SECRET_NAME and AWS_STATIC_KEYPAIR_ID) \ + if (STATIC_SECRET_NAME and STATIC_KEYPAIR_ID) \ else s3_url return {"message": "File uploaded successfully", "url": signed_url } except Exception as e: diff --git a/server/bot/list.py b/server/bot/list.py new file mode 100644 index 00000000..040582a8 --- /dev/null +++ b/server/bot/list.py @@ -0,0 +1,60 @@ +from typing import Optional + +from github import Github, Auth +from core.dao.repositoryConfigDAO import RepositoryConfigDAO +from petercat_utils import get_client + + +def query_list( + name: Optional[str] = None, + user_id: Optional[str] = None, + access_token: Optional[str] = None, + personal: Optional[str] = None, +): + try: + supabase = get_client() + query = ( + supabase.rpc("get_bots_with_profiles_and_github") + if personal == "true" + else supabase.table("bots").select( + "id, created_at, updated_at, avatar, description, name, public, starters, uid, repo_name" + ) + ) + if name: + query = query.filter("name", "like", f"%{name}%") + + if personal == "true": + if not access_token or not user_id: + return {"data": [], "personal": personal} + + auth = Auth.Token(token=access_token) + github_user = Github(auth=auth).get_user() + orgs_ids = [org.id for org in github_user.get_orgs()] + bot_ids = [] + + repository_config_dao = RepositoryConfigDAO() + bots = repository_config_dao.query_bot_id_by_owners( + orgs_ids + [github_user.id] + ) + + if bots: + bot_ids = [bot["robot_id"] for bot in bots if bot["robot_id"]] + + or_clause = f"uid.eq.{user_id}" + ( + f",id.in.({','.join(map(str, bot_ids))})" if bot_ids else "" + ) + query = query.or_(or_clause) + else: + query = query.eq("public", True) + + query = query.order("updated_at", desc=True) + data = query.execute() + if data.data: + unique_data = {} + for item in data.data: + unique_data[item["id"]] = item + deduplicated_list = list(unique_data.values()) + return deduplicated_list + return data.data + except Exception as e: + print(f"query list error: {e}") diff --git a/server/bot/router.py b/server/bot/router.py index b422571c..8eb0e6ee 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -3,8 +3,10 @@ from fastapi.responses import JSONResponse from github import Github, Auth from auth.get_user_info import get_user, get_user_id +from bot.list import query_list from core.dao.botApprovalDAO import BotApprovalDAO from core.dao.botDAO import BotDAO +from core.dao.repositoryConfigDAO import RepositoryConfigDAO from core.models.bot_approval import ApprovalStatus, BotApproval, TaskType from core.models.user import User from petercat_utils import get_client @@ -28,40 +30,28 @@ def get_bot_list( None, description="Filter bots by personal category" ), name: Optional[str] = Query(None, description="Filter bots by name"), - user_id: Annotated[str | None, Depends(get_user_id)] = None, + user: Annotated[User | None, Depends(get_user)] = None, ): try: - supabase = get_client() - query = supabase.table("bots").select( - "id, created_at, updated_at, avatar, description, name, public, starters, uid" - ) - if personal == "true": - if not user_id: - return {"data": [], "personal": personal} - query = query.eq("uid", user_id).order("updated_at", desc=True) - if name: - query = ( - supabase.table("bots") - .select( - "id, created_at, updated_at, avatar, description, name, public, starters, uid" - ) - .filter("name", "like", f"%{name}%") - ) + data = query_list(name, user.id, user.access_token, personal) + + return {"data": data if data else [], "personal": personal} - query = ( - query.eq("public", True).order("updated_at", desc=True) - if not personal or personal != "true" - else query + except Exception as e: + return JSONResponse( + content={"success": False, "errorMessage": str(e)}, status_code=500 ) - data = query.execute() - if not data or not data.data: - return {"data": [], "personal": personal} - return {"data": data.data, "personal": personal} +@router.get("/bound_to_repository") +def get_bot_bind_repository(bot_id: str): + try: + repository_config = RepositoryConfigDAO() + repo_config_list = repository_config.get_by_bot_id(bot_id) + return {"data": repo_config_list, "status": 200} except Exception as e: return JSONResponse( - content={"success": False, "errorMessage": str(e)}, status_code=500 + content={"success": False, "errorMessage": str(e)}, status_code=404 ) @@ -77,7 +67,7 @@ def get_bot_detail( data = ( supabase.table("bots") .select( - "id, created_at, updated_at, avatar, description, name, starters, public, hello_message, repo_name" + "id, created_at, updated_at, avatar, description, name, starters, public, hello_message, repo_name, llm, token_id" ) .eq("id", id) .execute() @@ -92,12 +82,25 @@ def get_bot_detail( @router.get("/config") def get_bot_config( id: Optional[str] = Query(None, description="Filter bots by personal category"), - user_id: Annotated[str | None, Depends(get_user_id)] = None, + user: Annotated[User | None, Depends(get_user)] = None, ): + if not user or not user.access_token or not id: + return {"data": []} try: + auth = Auth.Token(token=user.access_token) + github_user = Github(auth=auth).get_user() + orgs_ids = [org.id for org in github_user.get_orgs()] + bot_ids = [] + + repository_config_dao = RepositoryConfigDAO() + bots = repository_config_dao.query_bot_id_by_owners(orgs_ids + [github_user.id]) + + bot_ids = [bot["robot_id"] for bot in bots if bot["robot_id"]] + bot_ids.append(id) + supabase = get_client() data = ( - supabase.table("bots").select("*").eq("id", id).eq("uid", user_id).execute() + supabase.table("bots").select("*").eq("id", id).in_("id", bot_ids).execute() ) return {"data": data.data, "status": 200} except Exception as e: @@ -174,6 +177,21 @@ async def bot_generator( ) +@router.get("/git/avatar", status_code=200) +async def get_git_avatar( + repo_name: str, +): + try: + g = Github() + repo = g.get_repo(repo_name) + avatar = repo.organization.avatar_url if repo.organization else None + return JSONResponse(content={"success": True, "data": avatar}) + except Exception as e: + return JSONResponse( + content={"success": False, "errorMessage": str(e)}, status_code=500 + ) + + @router.put("/update/{id}", status_code=200) def update_bot( id: str, diff --git a/server/bot/util.ts b/server/bot/util.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/server/core/dao/authorizationDAO.py b/server/core/dao/authorizationDAO.py deleted file mode 100644 index 871a40f2..00000000 --- a/server/core/dao/authorizationDAO.py +++ /dev/null @@ -1,39 +0,0 @@ -from supabase.client import Client - -from petercat_utils.db.client.supabase import get_client - -from core.dao.BaseDAO import BaseDAO -from core.models.authorization import Authorization - -class AuthorizationDAO(BaseDAO): - client: Client - - def __init__(self): - super().__init__() - self.client = get_client() - - def exists(self, installation_id: str) -> bool: - try: - authorization = self.client.table("github_app_authorization")\ - .select('*', count="exact")\ - .eq('installation_id', installation_id) \ - .execute() - - return bool(authorization.count) - - except Exception as e: - print("Error: ", e) - return {"message": "User creation failed"} - - def create(self, data: Authorization): - try: - authorization = self.client.from_("github_app_authorization")\ - .insert(data.model_dump())\ - .execute() - if authorization: - return True, {"message": "User created successfully"} - else: - return False, {"message": "User creation failed"} - except Exception as e: - print("Error: ", e) - return False, {"message": "User creation failed"} \ No newline at end of file diff --git a/server/core/dao/profilesDAO.py b/server/core/dao/profilesDAO.py new file mode 100644 index 00000000..a4053d67 --- /dev/null +++ b/server/core/dao/profilesDAO.py @@ -0,0 +1,39 @@ +from core.dao.BaseDAO import BaseDAO +from supabase.client import Client + +from petercat_utils.db.client.supabase import get_client + + +class ProfilesDAO(BaseDAO): + client: Client + + def __init__(self): + super().__init__() + self.client = get_client() + + def get_agreement_status(self, user_id: str): + + resp = ( + self.client.table("profiles") + .select("agreement_accepted") + .eq("id", user_id) + .execute() + ) + agreement_accepted = resp.data[0] + return agreement_accepted + + def accept_agreement(self, user_id: str): + try: + response = ( + self.client.table("profiles") + .update({"agreement_accepted": True}) + .match({"id": user_id}) + .execute() + ) + + if not response.data: + return False, {"message": "User does not exist, accept failed."} + return response.data[0] + except Exception as e: + print("Error: ", e) + return False, {"message": "Profile Update failed"} diff --git a/server/core/dao/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py index 8cc174ce..e1f8f6d1 100644 --- a/server/core/dao/repositoryConfigDAO.py +++ b/server/core/dao/repositoryConfigDAO.py @@ -3,7 +3,6 @@ from core.models.bot import RepoBindBotConfigVO from core.models.repository import RepositoryConfig from supabase.client import Client - from petercat_utils.db.client.supabase import get_client @@ -26,43 +25,133 @@ def create(self, data: RepositoryConfig): else: return False, {"message": "GithubRepoConfig creation failed"} except Exception as e: - print("Error: ", e) - return False, {"message": "GithubRepoConfig creation failed"} - - def query_by_owners(self, orgs: list[str]): - response = ( - self.client.table("github_repo_config") - .select("*") - .filter("owner_id", "in", f"({','.join(map(str, orgs))})") - .execute() - ) - - return response.data - - def update_bot_to_repos( - self, - repos: List[RepoBindBotConfigVO], - ) -> bool: - for repo in repos: - res = ( + print(f"Error: {e}") + return False, {"message": f"GithubRepoConfig creation failed: {e}"} + + def create_batch(self, data_list: List[RepositoryConfig]): + try: + records_data = [data.model_dump(exclude=["id"]) for data in data_list] + repo_ids = [data.repo_id for data in data_list] + + # 查询现有的 repo_id + response = ( + self.client.table("github_repo_config") + .select("repo_id") + .in_("repo_id", repo_ids) + .execute() + ) + existing_repo_ids = ( + {record["repo_id"] for record in response.data} + if response.data + else set() + ) + + # 筛选出未存在的记录 + new_records_data = [ + data + for data in records_data + if data["repo_id"] not in existing_repo_ids + ] + + if not new_records_data: + return False, { + "message": "No new GithubRepoConfig records to insert, all repo_ids already exist" + } + + # 执行插入操作 + repo_config_result = ( + self.client.table("github_repo_config") + .insert(new_records_data) + .execute() + ) + + return repo_config_result + except Exception as e: + print(f"Error: {e}") + return False, {"message": f"GithubRepoConfig batch creation failed: {e}"} + + def query_by_owners(self, orgs: List[str]): + try: + response = ( + self.client.table("github_repo_config") + .select("*") + .filter("owner_id", "in", f"({','.join(map(str, orgs))})") + .execute() + ) + return response.data + except Exception as e: + print(f"Error: {e}") + return None + + def query_bot_id_by_owners(self, orgs: List[str]): + try: + response = ( self.client.table("github_repo_config") - .update({"robot_id": repo.robot_id}) - .match({"repo_id": repo.repo_id}) + .select("robot_id") + .filter("owner_id", "in", f"({','.join(map(str, orgs))})") .execute() ) - if not res: - raise ValueError("Failed to bind the bot.") + return response.data + except Exception as e: + print(f"Error: {e}") + return None + + def update_bot_to_repos(self, repos: List[RepoBindBotConfigVO]) -> bool: + try: + for repo in repos: + res = ( + self.client.table("github_repo_config") + .update({"robot_id": repo.robot_id}) + .match({"repo_id": repo.repo_id}) + .execute() + ) + if not res: + raise ValueError("Failed to bind the bot.") + return True + except Exception as e: + print(f"Error: {e}") + return False def get_by_repo_name(self, repo_name: str): - response = ( - self.client.table("github_repo_config") - .select("*") - .eq("repo_name", repo_name) - .execute() - ) - - if not response.data or not response.data[0]: + try: + response = ( + self.client.table("github_repo_config") + .select("*") + .eq("repo_name", repo_name) + .execute() + ) + if not response.data or not response.data[0]: + return None + repo_config = response.data[0] + return RepositoryConfig(**repo_config) + except Exception as e: + print(f"Error: {e}") + return None + + def get_by_bot_id(self, bot_id: str): + try: + response = ( + self.client.table("github_repo_config") + .select("*") + .eq("robot_id", bot_id) + .execute() + ) + if not response.data or not response.data[0]: + return None + return [RepositoryConfig(**repo) for repo in response.data] + except Exception as e: + print(f"Error: {e}") return None - repo_config = response.data[0] - return RepositoryConfig(**repo_config) + def delete_by_repo_ids(self, repo_ids: List[str]): + try: + response = ( + self.client.table("github_repo_config") + .delete() + .in_("repo_id", repo_ids) + .execute() + ) + return response + except Exception as e: + print(f"Error: {e}") + return None diff --git a/server/core/models/profiles.py b/server/core/models/profiles.py new file mode 100644 index 00000000..2b6d686b --- /dev/null +++ b/server/core/models/profiles.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel +from datetime import datetime +from typing import Optional + +class Profiles(BaseModel): + id: str + created_at: datetime = datetime.now() + nickname: Optional[str] = "" + name: Optional[str] = "" + picture: Optional[str] = "" + sid: Optional[str] = "" + sub: Optional[str] = "" + agreement_accepted: Optional[bool] = False diff --git a/server/core/service/user_llm_token.py b/server/core/service/user_llm_token.py index c8351ce5..669696f6 100644 --- a/server/core/service/user_llm_token.py +++ b/server/core/service/user_llm_token.py @@ -6,13 +6,13 @@ from core.dao.userLLmTokenDAO import UserLLMTokenDAO from core.models.user_llm_token import UserLLMToken -from utils.get_private_key import get_private_key +from utils.private_key import get_private_key from utils.rsa import decrypt_token, encrypt_token from utils.sanitize_token import sanitize_token -REGIN_NAME = get_env_variable("AWS_REGION") -AWS_LLM_TOKEN_SECRET_NAME = get_env_variable("AWS_LLM_TOKEN_SECRET_NAME") -AWS_LLM_TOKEN_PUBLIC_NAME = get_env_variable("AWS_LLM_TOKEN_PUBLIC_NAME") +REGION_NAME = get_env_variable("AWS_REGION") +LLM_TOKEN_SECRET_NAME = get_env_variable("LLM_TOKEN_SECRET_NAME") +LLM_TOKEN_PUBLIC_NAME = get_env_variable("LLM_TOKEN_PUBLIC_NAME") class CreateUserLLMTokenVO(BaseModel): user_id: Optional[str] = None @@ -30,7 +30,7 @@ def __init__(self) -> None: def create_llm_token(self, create_llm_token_data: CreateUserLLMTokenVO): - public_key = get_private_key(REGIN_NAME, AWS_LLM_TOKEN_PUBLIC_NAME) + public_key = get_private_key(LLM_TOKEN_PUBLIC_NAME) encrypted_token = encrypt_token(public_key.encode('utf-8'), create_llm_token_data.token) encrypted_token = base64.b64encode(encrypted_token).decode('utf-8') @@ -46,7 +46,7 @@ def create_llm_token(self, create_llm_token_data: CreateUserLLMTokenVO): self.llm_token_dao.create(llm_token_model) def get_llm_token(self, id: str, user_id: Optional[str] = None) -> UserLLMTokenVO: - private_key_str = get_private_key(REGIN_NAME, AWS_LLM_TOKEN_SECRET_NAME) + private_key_str = get_private_key(LLM_TOKEN_SECRET_NAME) token_model = self.llm_token_dao.get_by_id(id=id, user_id=user_id) encrypted_token = base64.b64decode(token_model.encrypted_token.encode('utf-8')) diff --git a/server/env.py b/server/env.py new file mode 100644 index 00000000..72f79905 --- /dev/null +++ b/server/env.py @@ -0,0 +1,6 @@ +# list all env variables +from petercat_utils import get_env_variable + +WEB_URL = get_env_variable("WEB_URL") +ENVIRONMENT = get_env_variable("PETERCAT_ENV", "development") +API_URL = get_env_variable("API_URL") diff --git a/server/event_handler/intsall.py b/server/event_handler/intsall.py new file mode 100644 index 00000000..757fd739 --- /dev/null +++ b/server/event_handler/intsall.py @@ -0,0 +1,98 @@ +from typing import Any, List +from github import Github, Auth +from github import GithubException +from core.dao.repositoryConfigDAO import RepositoryConfigDAO +from core.models.repository import RepositoryConfig +import time + + +class InstallationEventHandler: + event: Any + auth: Auth.AppAuth + g: Github + + def __init__(self, payload: Any, auth: Auth.AppAuth, installation_id: int) -> None: + self.event: Any = payload + self.auth: Auth.AppAuth = auth + self.g: Github = Github(auth=auth) + + def delete_config(self): + repositories = self.event["repositories"] + repo_ids = [str(repo["id"]) for repo in repositories] + repository_config = RepositoryConfigDAO() + repository_config.delete_by_repo_ids(repo_ids) + + def add_config(self): + repositories = self.event["repositories"] + owner_id = self.event["installation"]["account"]["id"] + repository_config_dao = RepositoryConfigDAO() + repository_configs: List[RepositoryConfig] = [] + for repo in repositories: + repository_config = RepositoryConfig( + owner_id=str(owner_id), + repo_name=repo["full_name"], + repo_id=str(repo["id"]), + robot_id="", + created_at=int(time.time()), + ) + repository_configs.append(repository_config) + repository_config_dao.create_batch(repository_configs) + + async def execute(self): + try: + action = self.event["action"] + if action == "deleted": + self.delete_config() + return {"success": True} + if action == "created": + self.add_config() + return {"success": True} + except GithubException as e: + print(f"处理 GitHub 请求时出错:{e}") + return {"success": False, "error": str(e)} + + +class InstallationEditEventHandler: + event: Any + auth: Auth.AppAuth + g: Github + + def __init__(self, payload: Any, auth: Auth.AppAuth, installation_id: int) -> None: + self.event: Any = payload + self.auth: Auth.AppAuth = auth + self.g: Github = Github(auth=auth) + + def delete_config(self): + repositories = self.event["repositories_removed"] + repo_ids = [str(repo["id"]) for repo in repositories] + repository_config = RepositoryConfigDAO() + repository_config.delete_by_repo_ids(repo_ids) + + def add_config(self): + repositories = self.event["repositories_added"] + owner_id = self.event["installation"]["account"]["id"] + repository_config_dao = RepositoryConfigDAO() + repository_configs: List[RepositoryConfig] = [] + for repo in repositories: + repository_config = RepositoryConfig( + owner_id=str(owner_id), + repo_name=repo["full_name"], + repo_id=str(repo["id"]), + robot_id="", + created_at=int(time.time()), + ) + repository_configs.append(repository_config) + repository_config_dao.create_batch(repository_configs) + + async def execute(self): + try: + action = self.event["action"] + if action == "removed": + self.delete_config() + return {"success": True} + if action == "added": + self.add_config() + return {"success": True} + except GithubException as e: + print(f"处理 GitHub 请求时出错:{e}") + return {"success": False, "error": str(e)} diff --git a/server/github_app/handlers.py b/server/github_app/handlers.py index 25c03140..0581221f 100644 --- a/server/github_app/handlers.py +++ b/server/github_app/handlers.py @@ -1,5 +1,6 @@ from typing import Union +from event_handler.intsall import InstallationEventHandler, InstallationEditEventHandler from petercat_utils import get_env_variable from github import Auth @@ -25,6 +26,8 @@ def get_handler( DiscussionEventHandler, DiscussionCommentEventHandler, PullRequestReviewCommentEventHandler, + InstallationEventHandler, + InstallationEditEventHandler, None, ]: handlers = { @@ -33,8 +36,10 @@ def get_handler( "issue_comment": IssueCommentEventHandler, "discussion": DiscussionEventHandler, "discussion_comment": DiscussionCommentEventHandler, - "pull_request_review_comment":PullRequestReviewCommentEventHandler, - "pull_request_review":PullRequestReviewCommentEventHandler, + "pull_request_review_comment": PullRequestReviewCommentEventHandler, + "pull_request_review": PullRequestReviewCommentEventHandler, + "installation": InstallationEventHandler, + "installation_repositories": InstallationEditEventHandler, } return ( handlers.get(event)(payload=payload, auth=auth, installation_id=installation_id) diff --git a/server/github_app/router.py b/server/github_app/router.py index d91a2860..a05fb1d7 100644 --- a/server/github_app/router.py +++ b/server/github_app/router.py @@ -1,4 +1,6 @@ +import logging from typing import Annotated + from fastapi import ( APIRouter, BackgroundTasks, @@ -8,35 +10,24 @@ Request, status, ) -import logging from fastapi.responses import RedirectResponse - -import time from github import Auth, Github + from auth.get_user_info import get_user -from core.dao.authorizationDAO import AuthorizationDAO from core.dao.repositoryConfigDAO import RepositoryConfigDAO from core.models.bot import RepoBindBotRequest -from core.models.repository import RepositoryConfig -from core.models.authorization import Authorization from core.models.user import User - +from env import WEB_URL from github_app.handlers import get_handler from github_app.purchased import PurchaseServer from github_app.utils import ( - get_app_installations_access_token, - get_installation_repositories, - get_jwt, get_private_key, - get_user_orgs, ) - from petercat_utils import get_env_variable -REGIN_NAME = get_env_variable("AWS_REGION") -AWS_GITHUB_SECRET_NAME = get_env_variable("AWS_GITHUB_SECRET_NAME") +REGION_NAME = get_env_variable("AWS_REGION") +X_GITHUB_SECRET_NAME = get_env_variable("X_GITHUB_SECRET_NAME") APP_ID = get_env_variable("X_GITHUB_APP_ID") -WEB_URL = get_env_variable("WEB_URL") logger = logging.getLogger() logger.setLevel("INFO") @@ -51,55 +42,17 @@ # https://github.com/login/oauth/authorize?client_id=Iv1.c2e88b429e541264 @router.get("/app/installation/callback") def github_app_callback(code: str, installation_id: str, setup_action: str): - authorization_dao = AuthorizationDAO() - repository_config_dao = RepositoryConfigDAO() - if setup_action == "install": - if authorization_dao.exists(installation_id=installation_id): - message = (f"Installation_id {installation_id} Exists",) - return RedirectResponse( - url=f"{WEB_URL}/github/installed/{message}", status_code=302 - ) - else: - jwt = get_jwt() - access_token = get_app_installations_access_token( - installation_id=installation_id, jwt=jwt - ) - print(f"get_app_installations_access_token: {access_token}") - authorization = Authorization( - **access_token, - code=code, - installation_id=installation_id, - created_at=int(time.time()), - ) - success, message = authorization_dao.create(authorization) - installed_repositories = get_installation_repositories( - access_token=access_token["token"] - ) - for repo in installed_repositories["repositories"]: - repository_config = RepositoryConfig( - owner_id=str(repo["owner"]["id"]), - repo_name=repo["full_name"], - repo_id=str(repo["id"]), - robot_id="", - created_at=int(time.time()), - ) - repository_config_dao.create(repository_config) - - return RedirectResponse( - url=f"{WEB_URL}/github/installed?message={message}", status_code=302 - ) - # ignore others setup_action,such as deleted our app - return { - "success": False, - "message": f"Invalid setup_action value {setup_action},please delete the app first then re-install the app.", - } + return RedirectResponse( + url=f"{WEB_URL}/github/installed?installation_id={installation_id}&setup_action={setup_action}&code={code}", + status_code=302, + ) @router.post("/app/webhook") async def github_app_webhook( - request: Request, - background_tasks: BackgroundTasks, - x_github_event: str = Header(...), + request: Request, + background_tasks: BackgroundTasks, + x_github_event: str = Header(...), ): payload = await request.json() if x_github_event == "marketplace_purchase": @@ -111,9 +64,7 @@ async def github_app_webhook( try: auth = Auth.AppAuth( app_id=APP_ID, - private_key=get_private_key( - region_name=REGIN_NAME, secret_id=AWS_GITHUB_SECRET_NAME - ), + private_key=get_private_key(secret_id=X_GITHUB_SECRET_NAME), jwt_algorithm="RS256", ).get_installation_auth(installation_id=int(installation_id)) except Exception as e: @@ -131,8 +82,8 @@ async def github_app_webhook( @router.get("/user/repos_installed_app") -def get_user_repos_installed_app_( - user: Annotated[User | None, Depends(get_user)] = None +def get_user_repos_installed_app( + user: Annotated[User | None, Depends(get_user)] = None ): """ Get github user installed app repositories which saved in platform database. @@ -146,7 +97,7 @@ def get_user_repos_installed_app_( auth = Auth.Token(token=user.access_token) g = Github(auth=auth) github_user = g.get_user() - orgs = get_user_orgs(github_user.login, auth.token) + orgs = github_user.get_orgs() repository_config_dao = RepositoryConfigDAO() installations = repository_config_dao.query_by_owners( [org.id for org in orgs] + [github_user.id] @@ -162,8 +113,8 @@ def get_user_repos_installed_app_( @router.post("/repo/bind_bot", status_code=200) def bind_bot_to_repo( - request: RepoBindBotRequest, - user: Annotated[User | None, Depends(get_user)] = None, + request: RepoBindBotRequest, + user: Annotated[User | None, Depends(get_user)] = None, ): if user is None: raise HTTPException( diff --git a/server/github_app/utils.py b/server/github_app/utils.py index 98e825eb..abc51867 100644 --- a/server/github_app/utils.py +++ b/server/github_app/utils.py @@ -7,11 +7,11 @@ from cryptography.hazmat.backends import default_backend from petercat_utils.utils.env import get_env_variable -from utils.get_private_key import get_private_key +from utils.private_key import get_private_key APP_ID = get_env_variable("X_GITHUB_APP_ID") -AWS_GITHUB_SECRET_NAME = get_env_variable("AWS_GITHUB_SECRET_NAME") -REGIN_NAME = get_env_variable("AWS_REGION") +X_GITHUB_SECRET_NAME = get_env_variable("X_GITHUB_SECRET_NAME") +REGION_NAME = get_env_variable("AWS_REGION") def get_jwt(): @@ -24,7 +24,7 @@ def get_jwt(): "iss": APP_ID, } - pem = get_private_key(region_name=REGIN_NAME, secret_id=AWS_GITHUB_SECRET_NAME) + pem = get_private_key(secret_id=X_GITHUB_SECRET_NAME) private_key = serialization.load_pem_private_key( pem.encode("utf-8"), password=None, backend=default_backend() ) @@ -58,16 +58,3 @@ def get_installation_repositories(access_token: str): }, ) return resp.json() - - -def get_user_orgs(username, access_token: str): - url = f"https://api.github.com/users/{username}/orgs" - resp = requests.get( - url, - headers={ - "X-GitHub-Api-Version": "2022-11-28", - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {access_token}", - }, - ) - return resp.json() diff --git a/server/main.py b/server/main.py index a5d747d6..6089da19 100644 --- a/server/main.py +++ b/server/main.py @@ -1,34 +1,29 @@ import os -from fastapi.responses import RedirectResponse import uvicorn - from fastapi import FastAPI -from starlette.middleware.sessions import SessionMiddleware from fastapi.middleware.cors import CORSMiddleware -from auth.cors_middleware import AuthCORSMiddleWare -from i18n.translations import I18nConfig, I18nMiddleware - -from auth.middleware import AuthMiddleWare -from petercat_utils import get_env_variable - +from fastapi.responses import RedirectResponse +from starlette.middleware.sessions import SessionMiddleware # Import fastapi routers from auth import router as auth_router +from auth.cors_middleware import AuthCORSMiddleWare +from auth.middleware import AuthMiddleWare +from aws import router as aws_router from bot import router as bot_router from chat import router as chat_router +from env import ENVIRONMENT, API_URL, WEB_URL +from github_app import router as github_app_router +from i18n.translations import I18nConfig, I18nMiddleware +from petercat_utils import get_env_variable from rag import router as rag_router from task import router as task_router -from github_app import router as github_app_router -from aws import router as aws_router from user import router as user_router AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") API_AUDIENCE = get_env_variable("API_IDENTIFIER") CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID") -API_URL = get_env_variable("API_URL") -WEB_URL = get_env_variable("WEB_URL") -ENVRIMENT = get_env_variable("PETERCAT_ENV", "development") CALLBACK_URL = f"{API_URL}/api/auth/callback" is_dev = bool(get_env_variable("IS_DEV")) @@ -77,10 +72,10 @@ def home_page(): @app.get("/api/health_checker") def health_checker(): return { - "ENVRIMENT": ENVRIMENT, + "ENVIRONMENT": ENVIRONMENT, "API_URL": API_URL, "WEB_URL": WEB_URL, - "CALLBACK_URL": CALLBACK_URL, + "CALLBACK_URL": CALLBACK_URL } @@ -89,8 +84,8 @@ def health_checker(): uvicorn.run( "main:app", host="0.0.0.0", - port=int(os.environ.get("PORT", "8080")), + port=int(os.environ.get("PETERCAT_PORT", "8080")), reload=True, ) else: - uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080"))) + uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PETERCAT_PORT", "8080"))) diff --git a/server/tests/test_main.py b/server/tests/test_main.py index 3eb22630..f4ce6371 100644 --- a/server/tests/test_main.py +++ b/server/tests/test_main.py @@ -1,19 +1,17 @@ from fastapi.testclient import TestClient -from petercat_utils import get_env_variable -from main import app -API_URL = get_env_variable("API_URL") -WEB_URL = get_env_variable("WEB_URL") -ENVRIMENT = get_env_variable("PETERCAT_ENV", "development") +from env import ENVIRONMENT, WEB_URL, API_URL +from main import app client = TestClient(app) + def test_health_checker(): response = client.get("/api/health_checker") assert response.status_code == 200 assert response.json() == { - 'ENVRIMENT': ENVRIMENT, - 'API_URL': API_URL, - 'CALLBACK_URL': f'{API_URL}/api/auth/callback', - 'WEB_URL': WEB_URL, - } \ No newline at end of file + "ENVIRONMENT": ENVIRONMENT, + "API_URL": API_URL, + "CALLBACK_URL": f"{API_URL}/api/auth/callback", + "WEB_URL": WEB_URL, + } diff --git a/server/utils/get_private_key.py b/server/utils/get_private_key.py deleted file mode 100644 index 8391fd16..00000000 --- a/server/utils/get_private_key.py +++ /dev/null @@ -1,14 +0,0 @@ -import boto3 -from botocore.exceptions import ClientError - -def get_private_key(region_name: str, secret_id: str) -> str: - session = boto3.session.Session() - client = session.client(service_name="secretsmanager", region_name=region_name) - try: - get_secret_value_response = client.get_secret_value(SecretId=secret_id) - except ClientError as e: - # For a list of exceptions thrown, see - # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html - raise e - - return get_secret_value_response["SecretString"] diff --git a/server/utils/private_key/__init__.py b/server/utils/private_key/__init__.py new file mode 100644 index 00000000..e2adf9c2 --- /dev/null +++ b/server/utils/private_key/__init__.py @@ -0,0 +1,9 @@ +from petercat_utils import get_env_variable +from utils.private_key.local import LocalPrivateKeyProvider +from utils.private_key.s3 import S3PrivateKeyProvider + +PETERCAT_SECRETS_PROVIDER = get_env_variable("PETERCAT_SECRETS_PROVIDER", "aws") + +def get_private_key(secret_id: str): + provider = S3PrivateKeyProvider() if PETERCAT_SECRETS_PROVIDER == 'aws' else LocalPrivateKeyProvider() + return provider.get_private_key(secret_id) diff --git a/server/utils/private_key/base.py b/server/utils/private_key/base.py new file mode 100644 index 00000000..ddcf2deb --- /dev/null +++ b/server/utils/private_key/base.py @@ -0,0 +1,4 @@ + +class BasePrivateKeyProvider(): + def get_private_key(self, name: str) -> str: + pass diff --git a/server/utils/private_key/local.py b/server/utils/private_key/local.py new file mode 100644 index 00000000..478e0757 --- /dev/null +++ b/server/utils/private_key/local.py @@ -0,0 +1,6 @@ +from utils.private_key.base import BasePrivateKeyProvider + +class LocalPrivateKeyProvider(BasePrivateKeyProvider): + def get_private_key(self, secret_id: str) -> str: + # if local, use secret_id itself as private key + return secret_id \ No newline at end of file diff --git a/server/utils/private_key/s3.py b/server/utils/private_key/s3.py new file mode 100644 index 00000000..1180f7e2 --- /dev/null +++ b/server/utils/private_key/s3.py @@ -0,0 +1,20 @@ +import boto3 +from botocore.exceptions import ClientError +from petercat_utils import get_env_variable +from utils.private_key.base import BasePrivateKeyProvider + +REGION_NAME = get_env_variable("AWS_REGION") + + +class S3PrivateKeyProvider(BasePrivateKeyProvider): + def get_private_key(self, secret_id: str) -> str: + session = boto3.session.Session() + client = session.client(service_name="secretsmanager", region_name=REGION_NAME) + try: + get_secret_value_response = client.get_secret_value(SecretId=secret_id) + except ClientError as e: + # For a list of exceptions thrown, see + # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html + raise e + + return get_secret_value_response["SecretString"] \ No newline at end of file diff --git a/subscriber/requirements.txt b/subscriber/requirements.txt index 23c995c0..3ff51c19 100644 --- a/subscriber/requirements.txt +++ b/subscriber/requirements.txt @@ -1 +1 @@ -petercat_utils>=0.1.39 +petercat_utils>=0.1.40 diff --git a/template.yml b/template.yml index 70133959..2f848f5a 100644 --- a/template.yml +++ b/template.yml @@ -92,23 +92,23 @@ Parameters: Type: String Description: Auth0 Client Secret Default: 1 - AWSGithubSecretName: + GithubSecretName: Type: String Description: Name of the GitHub secret stored in AWS Secrets Manager Default: 1 - AWSStaticSecretName: + StaticSecretName: Type: String Description: Name of the static secret stored in AWS Secrets Manager Default: 1 - AWSLLMTokenSecretName: + LLMTokenSecretName: Type: String Description: Name of the LLM token secret stored in AWS Secrets Manager Default: 1 - AWSLLMTokenPublicName: + LLMTokenPublicName: Type: String Description: Name of the LLM public token stored in AWS Secrets Manager Default: 1 - AWSStaticKeyPairId: + StaticKeyPairId: Type: String Description: Key Pair ID for static resources Default: 1 @@ -127,11 +127,11 @@ Resources: Variables: AWS_LWA_INVOKE_MODE: RESPONSE_STREAM PETERCAT_ENV: !Ref PetercatEnv - AWS_GITHUB_SECRET_NAME: !Ref AWSGithubSecretName - AWS_STATIC_SECRET_NAME: !Ref AWSStaticSecretName - AWS_LLM_TOKEN_SECRET_NAME: !Ref AWSLLMTokenSecretName - AWS_LLM_TOKEN_PUBLIC_NAME: !Ref AWSLLMTokenPublicName - AWS_STATIC_KEYPAIR_ID: !Ref AWSStaticKeyPairId + X_GITHUB_SECRET_NAME: !Ref GithubSecretName + STATIC_SECRET_NAME: !Ref StaticSecretName + LLM_TOKEN_SECRET_NAME: !Ref LLMTokenSecretName + LLM_TOKEN_PUBLIC_NAME: !Ref LLMTokenPublicName + STATIC_KEYPAIR_ID: !Ref StaticKeyPairId S3_TEMP_BUCKET_NAME: !Ref S3TempBucketName API_URL: !Ref APIUrl WEB_URL: !Ref WebUrl