From c20a74cdb3d2dbde8d74280232927af2332320e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Mon, 2 Dec 2024 14:29:23 +0800 Subject: [PATCH 01/46] fix: resolve the display issue on the bot configuration page. (#549) * fix: fix git reponame * fix: show llm selection * chore: remove debugger --- .../app/factory/edit/components/BotCreateForm.tsx | 13 +++++++++---- client/app/factory/edit/page.tsx | 2 +- client/app/hooks/useAvailableLLMs.ts | 10 ++++++++++ client/app/hooks/useAvaliableLLMs.ts | 10 ---------- client/app/services/UserController.ts | 2 +- server/bot/router.py | 2 +- 6 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 client/app/hooks/useAvailableLLMs.ts delete mode 100644 client/app/hooks/useAvaliableLLMs.ts diff --git a/client/app/factory/edit/components/BotCreateForm.tsx b/client/app/factory/edit/components/BotCreateForm.tsx index 09940538..5389dc45 100644 --- a/client/app/factory/edit/components/BotCreateForm.tsx +++ b/client/app/factory/edit/components/BotCreateForm.tsx @@ -31,7 +31,7 @@ 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'; @@ -39,7 +39,7 @@ const BotCreateFrom = () => { const { botProfile, setBotProfile } = useBot(); const router = useRouter(); const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure(); - const { data: avaliableLLMs = [] } = useAvaliableLLMs(); + const { data: availableLLMs = [] } = useAvailableLLMs(); const { data: userTokens = [] } = useTokenList(); const filteredTokens = useMemo(() => { @@ -54,6 +54,7 @@ const BotCreateFrom = () => { ) => { const name = e.target.name as keyof Omit; const value = e.target.value; + console.log(name, value); setBotProfile((draft: BotProfile) => { // @ts-ignore draft[name] = value; @@ -184,9 +185,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 +200,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} diff --git a/client/app/factory/edit/page.tsx b/client/app/factory/edit/page.tsx index beef4fcb..5b7c2422 100644 --- a/client/app/factory/edit/page.tsx +++ b/client/app/factory/edit/page.tsx @@ -346,7 +346,7 @@ export default function Edit() { const url = e.target.value; setGitUrl(url); }} - value={gitUrl} + value={gitUrl || botProfile.repoName} isDisabled={isEdit} required classNames={{ label: 'w-full' }} 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/services/UserController.ts b/client/app/services/UserController.ts index 08f1742d..345808d0 100644 --- a/client/app/services/UserController.ts +++ b/client/app/services/UserController.ts @@ -20,7 +20,7 @@ export async function acceptAgreement() { return response.data; } -export async function getAvaliableLLMs() { +export async function getAvailableLLMs() { const response = await axios.get(`${apiDomain}/api/user/llms`, { withCredentials: true, }); diff --git a/server/bot/router.py b/server/bot/router.py index b422571c..c01380ec 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -77,7 +77,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() From a843d0d77920e18ccb8d21f534c82b612898248b Mon Sep 17 00:00:00 2001 From: liuzhide <44251801+ch-liuzhide@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:39:01 +0800 Subject: [PATCH 02/46] revert(get_user_repos_installed_app): Rollback the method for obtaining a user's GitHub organization. (#550) revert(get_user_repos_installed_app): revert get user repo function --- server/github_app/router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/github_app/router.py b/server/github_app/router.py index d91a2860..c2e0211d 100644 --- a/server/github_app/router.py +++ b/server/github_app/router.py @@ -131,7 +131,7 @@ async def github_app_webhook( @router.get("/user/repos_installed_app") -def get_user_repos_installed_app_( +def get_user_repos_installed_app( user: Annotated[User | None, Depends(get_user)] = None ): """ @@ -146,7 +146,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] From 5f33065eed25b4fd1150cd955bf04d7fffce4b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Mon, 2 Dec 2024 16:17:44 +0800 Subject: [PATCH 03/46] chore: update the signatureIcon and fix typo (#551) * style: fix the loading * chore: update the signatureIcon * chore: fix type * chore: release @petercatai/assistant@1.0.18 --- assistant/package.json | 2 +- assistant/src/icons/SignatureIcon.tsx | 69 +++++++++++++++++++++++++-- client/.kiwi/en/app.ts | 2 +- client/components/Navbar.tsx | 2 +- client/package.json | 2 +- client/yarn.lock | 8 ++-- 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/assistant/package.json b/assistant/package.json index a0817c7b..9f7871a1 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -1,6 +1,6 @@ { "name": "@petercatai/assistant", - "version": "1.0.16", + "version": "1.0.18", "description": "PeterCat Assistant Application", "module": "dist/esm/index.js", "types": "dist/esm/index.d.ts", 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/client/.kiwi/en/app.ts b/client/.kiwi/en/app.ts index e1d7d49f..6d2cd80f 100644 --- a/client/.kiwi/en/app.ts +++ b/client/.kiwi/en/app.ts @@ -28,7 +28,7 @@ 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', + gongZuoTai: 'Workspace', yanShiAnLi: 'Showcase', }, }; diff --git a/client/components/Navbar.tsx b/client/components/Navbar.tsx index dab2ca71..07ad891a 100644 --- a/client/components/Navbar.tsx +++ b/client/components/Navbar.tsx @@ -54,7 +54,7 @@ export function Navbar() { setSearch(''); }; return ( -
+
petercat diff --git a/client/package.json b/client/package.json index 0abd1323..a8ba844c 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.18", "@sentry/nextjs": "^8.28.0", "@supabase/supabase-js": "^2.32.0", "@tanstack/react-query": "^5.17.19", diff --git a/client/yarn.lock b/client/yarn.lock index 21401693..41cf0017 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.17": + version "1.0.17" + resolved "https://registry.yarnpkg.com/@petercatai/assistant/-/assistant-1.0.17.tgz#c6499ee8908f116664e448b54a076d564414a965" + integrity sha512-9IGCO+BlVRvXM1OZegclofOYb57tXnOlxVvOt+rltLqzre7TDnDNFl1lb9SFjmCjBQOVCm/IXZCG6KifIb4l4Q== dependencies: "@ant-design/icons" "^5.3.5" "@ant-design/pro-chat" "^1.9.0" From 11e80d1cd5b2fefa713780f545aaca86f4b797e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Tue, 3 Dec 2024 11:18:37 +0800 Subject: [PATCH 04/46] refactore: get the agreement statues from db instead of session (#552) * style: fix the loading * chore: update the signatureIcon * chore: fix type * chore: release @petercatai/assistant@1.0.18 * refactore: get the agreement statues from db instead of session * chore: remove log * chore: fix ci * chore: fix test --- client/app/factory/edit/page.tsx | 25 ++++++++++---- client/app/hooks/useAgreement.ts | 15 +++++++-- client/app/services/UserController.ts | 7 ++++ server/auth/router.py | 47 ++++++++++++++------------- server/core/dao/profilesDAO.py | 33 +++++++++++++++++++ server/core/models/profiles.py | 13 ++++++++ server/github_app/router.py | 1 - 7 files changed, 108 insertions(+), 33 deletions(-) create mode 100644 server/core/dao/profilesDAO.py create mode 100644 server/core/models/profiles.py diff --git a/client/app/factory/edit/page.tsx b/client/app/factory/edit/page.tsx index 5b7c2422..acfe9ff4 100644 --- a/client/app/factory/edit/page.tsx +++ b/client/app/factory/edit/page.tsx @@ -26,7 +26,7 @@ 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 { Chat } from '@petercatai/assistant'; @@ -64,6 +64,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'); @@ -103,15 +108,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, 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/services/UserController.ts b/client/app/services/UserController.ts index 345808d0..a5946f63 100644 --- a/client/app/services/UserController.ts +++ b/client/app/services/UserController.ts @@ -20,6 +20,13 @@ export async function acceptAgreement() { return response.data; } +export async function getAgreementStatus() { + const response = await axios.get(`${apiDomain}/api/auth/agreement/status`, { + withCredentials: true, + }); + return response.data; +} + export async function getAvailableLLMs() { const response = await axios.get(`${apiDomain}/api/user/llms`, { withCredentials: true, diff --git a/server/auth/router.py b/server/auth/router.py index bb6d4f8d..bb8a2ca4 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -1,10 +1,11 @@ +from core.dao.profilesDAO import ProfilesDAO from fastapi import APIRouter, Request, HTTPException, status, 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 typing import Annotated, Optional from auth.get_user_info import generateAnonymousUser, getUserInfoByToken, get_user_id @@ -98,33 +99,33 @@ async def userinfo(request: 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, ): 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: - 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/core/dao/profilesDAO.py b/server/core/dao/profilesDAO.py new file mode 100644 index 00000000..0983dd5d --- /dev/null +++ b/server/core/dao/profilesDAO.py @@ -0,0 +1,33 @@ +from core.dao.BaseDAO import BaseDAO +from supabase.client import Client + +from core.models.profiles import Profiles +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"} + \ No newline at end of file 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/github_app/router.py b/server/github_app/router.py index c2e0211d..411bb676 100644 --- a/server/github_app/router.py +++ b/server/github_app/router.py @@ -28,7 +28,6 @@ get_installation_repositories, get_jwt, get_private_key, - get_user_orgs, ) from petercat_utils import get_env_variable From 3d3fbc2b7833f163d25ff78c7ee8732e9ce770a9 Mon Sep 17 00:00:00 2001 From: liuzhide <44251801+ch-liuzhide@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:45:30 +0800 Subject: [PATCH 05/46] refactor(deployment): optimize the deployment robot popup form modal (#558) * revert(get_user_repos_installed_app): revert get user repo function * refactor(deployment): optimize the deployment robot popup form modal * build(profilesdao): remove useless Profiles * fix typo error DeployStatusEnum.SHOWDEPLOYINFO --- client/.kiwi/en/DeployBotModal.ts | 5 + client/.kiwi/ja/DeployBotModal.ts | 5 + client/.kiwi/ko/DeployBotModal.ts | 5 + client/.kiwi/zh-CN/DeployBotModal.ts | 5 + client/.kiwi/zh-TW/DeployBotModal.ts | 5 + .../DeployBotModal/DeployContent.tsx | 3 - .../edit/components/DeployBotModal/index.tsx | 168 +++++++++++++----- client/public/images/chevron-down.svg | 3 + client/public/images/chevron-up.svg | 3 + server/core/dao/profilesDAO.py | 60 ++++--- 10 files changed, 192 insertions(+), 70 deletions(-) create mode 100644 client/public/images/chevron-down.svg create mode 100644 client/public/images/chevron-up.svg diff --git a/client/.kiwi/en/DeployBotModal.ts b/client/.kiwi/en/DeployBotModal.ts index 49293ad0..29a7834a 100644 --- a/client/.kiwi/en/DeployBotModal.ts +++ b/client/.kiwi/en/DeployBotModal.ts @@ -27,5 +27,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/ja/DeployBotModal.ts b/client/.kiwi/ja/DeployBotModal.ts index 6d449b62..fe081b3e 100644 --- a/client/.kiwi/ja/DeployBotModal.ts +++ b/client/.kiwi/ja/DeployBotModal.ts @@ -26,5 +26,10 @@ export default { tiaoGuo: 'スキップ', baoCunChengGong: '保存が成功しました!', buShuChengGong: 'デプロイが成功しました', + wanCheng: 'かんりょう', + tiJiaoChengGong: '提出が成功しました', + shouQiBuShu: '折りたたむ', + buShu: 'デプロイ', + daKaiBuShu: 'デプロイを開', }, }; diff --git a/client/.kiwi/ko/DeployBotModal.ts b/client/.kiwi/ko/DeployBotModal.ts index 445c5f5e..de08b856 100644 --- a/client/.kiwi/ko/DeployBotModal.ts +++ b/client/.kiwi/ko/DeployBotModal.ts @@ -24,5 +24,10 @@ export default { tiaoGuo: '건너뛰기', baoCunChengGong: '성공적으로 저장!', buShuChengGong: '배포 성공', + wanCheng: '완료', + tiJiaoChengGong: '제출이 성공했습니다', + shouQiBuShu: '접기', + buShu: '배포', + daKaiBuShu: '배포 열기', }, }; diff --git a/client/.kiwi/zh-CN/DeployBotModal.ts b/client/.kiwi/zh-CN/DeployBotModal.ts index 2010e2ea..90723209 100644 --- a/client/.kiwi/zh-CN/DeployBotModal.ts +++ b/client/.kiwi/zh-CN/DeployBotModal.ts @@ -24,5 +24,10 @@ export default { tiaoGuo: '跳过', baoCunChengGong: '保存成功!', buShuChengGong: '部署成功', + wanCheng: '完成', + tiJiaoChengGong: '提交成功', + shouQiBuShu: '收起部署', + buShu: '部署', + daKaiBuShu: '打开部署', }, }; diff --git a/client/.kiwi/zh-TW/DeployBotModal.ts b/client/.kiwi/zh-TW/DeployBotModal.ts index 831749e2..fd20d415 100644 --- a/client/.kiwi/zh-TW/DeployBotModal.ts +++ b/client/.kiwi/zh-TW/DeployBotModal.ts @@ -24,5 +24,10 @@ export default { tiaoGuo: '跳過', baoCunChengGong: '保存成功!', buShuChengGong: '部署成功', + wanCheng: '完成', + tiJiaoChengGong: '提交成功', + shouQiBuShu: '收起', + buShu: '部署', + daKaiBuShu: '打开部署', }, }; diff --git a/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx b/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx index d6a0e4fb..cdf5f251 100644 --- a/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx +++ b/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx @@ -224,9 +224,6 @@ export const DeployContent: React.FC = ({ }; 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..53c29e0d 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 ? ( @@ -188,7 +291,7 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { - {I18N.DeployBotModal.index.buShuChengGong} + {I18N.DeployBotModal.index.tiJiaoChengGong}
@@ -206,7 +309,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 +323,25 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { {I18N.DeployBotModal.index.baoCunChengGong}
- - - - - - - - + {deployStatus === DeployStatusEnum.HIDEDEPLOYINFO ? null : ( + + + + + + )} + + {renderFooterBtn()} )} 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/server/core/dao/profilesDAO.py b/server/core/dao/profilesDAO.py index 0983dd5d..a4053d67 100644 --- a/server/core/dao/profilesDAO.py +++ b/server/core/dao/profilesDAO.py @@ -1,33 +1,39 @@ from core.dao.BaseDAO import BaseDAO from supabase.client import Client -from core.models.profiles import Profiles 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"} - \ No newline at end of file + 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"} From 9deebc1765452b0e9fde2929f35002ee122a7448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Wed, 4 Dec 2024 15:46:17 +0800 Subject: [PATCH 06/46] feat: add the page of release notes (#559) * feat: init release page * feat: tewak the release notes page * chore: add mult-language for the release notes --- client/.kiwi/en/app.ts | 1 + client/.kiwi/en/index.ts | 2 + client/.kiwi/en/release.ts | 13 ++ client/.kiwi/ja/app.ts | 1 + client/.kiwi/ja/index.ts | 2 + client/.kiwi/ja/release.ts | 13 ++ client/.kiwi/ko/app.ts | 1 + client/.kiwi/ko/index.ts | 2 + client/.kiwi/ko/release.ts | 13 ++ client/.kiwi/zh-CN/app.ts | 1 + client/.kiwi/zh-CN/index.ts | 19 +-- client/.kiwi/zh-CN/release.ts | 11 ++ client/.kiwi/zh-TW/app.ts | 1 + client/.kiwi/zh-TW/index.ts | 2 + client/.kiwi/zh-TW/release.ts | 11 ++ client/app/globals.css | 8 ++ client/app/layout.tsx | 3 +- client/app/page.tsx | 197 +----------------------------- client/app/release/page.tsx | 81 ++++++++++++ client/components/HomeFooter.tsx | 203 +++++++++++++++++++++++++++++++ client/components/HomeHeader.tsx | 6 + client/tailwind.config.js | 5 +- 22 files changed, 392 insertions(+), 204 deletions(-) create mode 100644 client/.kiwi/en/release.ts create mode 100644 client/.kiwi/ja/release.ts create mode 100644 client/.kiwi/ko/release.ts create mode 100644 client/.kiwi/zh-CN/release.ts create mode 100644 client/.kiwi/zh-TW/release.ts create mode 100644 client/app/release/page.tsx create mode 100644 client/components/HomeFooter.tsx diff --git a/client/.kiwi/en/app.ts b/client/.kiwi/en/app.ts index 6d2cd80f..198351ee 100644 --- a/client/.kiwi/en/app.ts +++ b/client/.kiwi/en/app.ts @@ -28,6 +28,7 @@ export default { ' is an intelligent Q&A bot solution which designed for community maintainers and developers.', xiaoMaoMiZhuNi: 'Conquer GitHub with kitten power', wenDang: 'Documentation', + release: 'Release Notes', gongZuoTai: 'Workspace', yanShiAnLi: 'Showcase', }, 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/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/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/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/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/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/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/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/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/globals.css b/client/app/globals.css index a93d9b1c..7fcd12ab 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,4 @@ div.fp-watermark { } } + 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/page.tsx b/client/app/page.tsx index cfdaa88a..999bbca2 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), @@ -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/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..8d659b2f 100644 --- a/client/components/HomeHeader.tsx +++ b/client/components/HomeHeader.tsx @@ -64,6 +64,12 @@ const HomeHeader = () => { > {I18N.app.page.wenDang} + + {I18N.app.page.release} + setShowMobileNav(false)} 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", From e4ec98fb1c006c884b31ad7c1221219e8aa1dc64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Wed, 4 Dec 2024 17:08:02 +0800 Subject: [PATCH 07/46] fix: get the GitHub avatar from the api instead of the cache (#561) * feat: add the api to get the github avatar * fix: close the issue #554 --- .../factory/edit/components/BotCreateForm.tsx | 5 +++-- client/app/hooks/useBot.ts | 12 +++++++++++- client/app/services/BotsController.ts | 4 ++++ server/bot/router.py | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/client/app/factory/edit/components/BotCreateForm.tsx b/client/app/factory/edit/components/BotCreateForm.tsx index 5389dc45..2033cd04 100644 --- a/client/app/factory/edit/components/BotCreateForm.tsx +++ b/client/app/factory/edit/components/BotCreateForm.tsx @@ -24,7 +24,7 @@ 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'; @@ -37,6 +37,7 @@ import CreateButton from '@/app/user/tokens/components/CreateButton'; const BotCreateFrom = () => { const { botProfile, setBotProfile } = useBot(); + const { data: gitAvatar } = useGetGitAvatar(botProfile?.repoName); const router = useRouter(); const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure(); const { data: availableLLMs = [] } = useAvailableLLMs(); @@ -128,7 +129,7 @@ const BotCreateFrom = () => { aria-label={I18N.components.BotCreateFrom.gITHU} onClick={() => { setBotProfile((draft: BotProfile) => { - draft.avatar = botProfile?.gitAvatar; + draft.avatar = gitAvatar; }); }} > diff --git a/client/app/hooks/useBot.ts b/client/app/hooks/useBot.ts index 83684e9e..beca50da 100644 --- a/client/app/hooks/useBot.ts +++ b/client/app/hooks/useBot.ts @@ -9,6 +9,7 @@ import { getBotInfoByRepoName, getBotList, getChunkList, + getGitAvatarByRepoName, getRagTask, getUserPeterCatAppRepos, publicBot, @@ -191,7 +192,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/services/BotsController.ts b/client/app/services/BotsController.ts index 4908337c..eff1896b 100644 --- a/client/app/services/BotsController.ts +++ b/client/app/services/BotsController.ts @@ -67,6 +67,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/server/bot/router.py b/server/bot/router.py index c01380ec..6608367a 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -173,6 +173,22 @@ async def bot_generator( content={"success": False, "errorMessage": str(e)}, status_code=500 ) +@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( From 3a4d7ee05764754a48f63e9aed0d06722500de64 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Thu, 5 Dec 2024 12:21:48 +0800 Subject: [PATCH 08/46] fix: initial request error --- client/app/market/page.tsx | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) 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); From 89a53df99eea1dd7ef3552c7351fc49128004281 Mon Sep 17 00:00:00 2001 From: liuzhide <44251801+ch-liuzhide@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:18:29 +0800 Subject: [PATCH 09/46] feat(botcard): restore the UI effect (#562) * feat(botcard): restore the UI effect of the bot card to reflect the deployment status * fix ts error * refactor(ui): optimize the display of the knowledge base update status for cards in the space * refactor(ui): optimize the UI of the knowledge fragment list area * remove todo * remove use less icon import --- client/.kiwi/en/components.ts | 3 + client/.kiwi/ja/components.ts | 3 + client/.kiwi/ko/components.ts | 3 + client/.kiwi/zh-CN/components.ts | 3 + client/.kiwi/zh-TW/components.ts | 3 + .../app/factory/edit/components/Knowledge.tsx | 17 ++-- .../factory/edit/components/KnowledgeBtn.tsx | 2 - .../app/factory/list/components/BotCard.tsx | 88 ++++++++++++++++--- client/app/hooks/useBot.ts | 14 ++- client/app/services/BotsController.ts | 7 ++ client/components/BotCard.tsx | 9 +- client/public/icons/CardCartIcon.tsx | 43 +++++++++ client/public/icons/CardGithubIcon.tsx | 23 +++++ client/public/icons/CardHomeIcon.tsx | 25 ++++++ client/public/icons/CheckBadgeIcon.tsx | 18 ++-- client/public/icons/LoadingIcon.tsx | 14 +-- server/bot/router.py | 17 +++- server/core/dao/repositoryConfigDAO.py | 13 +++ 18 files changed, 260 insertions(+), 45 deletions(-) create mode 100644 client/public/icons/CardCartIcon.tsx create mode 100644 client/public/icons/CardGithubIcon.tsx create mode 100644 client/public/icons/CardHomeIcon.tsx 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/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/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/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-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/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/list/components/BotCard.tsx b/client/app/factory/list/components/BotCard.tsx index 36c5fb71..6533480f 100644 --- a/client/app/factory/list/components/BotCard.tsx +++ b/client/app/factory/list/components/BotCard.tsx @@ -17,23 +17,68 @@ import { Tooltip, } 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 { + useBotDelete, + useGetBotBoundRepos, + useGetBotRagTask, +} from '@/app/hooks/useBot'; 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'; declare type Bot = Tables<'bots'>; +const BotInfoIconList = (props: { bot: Bot }) => { + const { bot } = props; + const { data } = useGetBotBoundRepos(bot.id); + const showHomeIcon = bot.domain_whitelist && bot.domain_whitelist.length > 0; + const showCartIcon = bot.public; + const showGithubIcon = data && Array.isArray(data) && data.length > 0; + 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 { data: taskInfo } = useGetBotRagTask(bot.repo_name!, false); useEffect(() => { if (isSuccess) { @@ -57,14 +102,14 @@ const BotCard = (props: { bot: Bot }) => { ? TaskStatus.COMPLETED : 'others'; if (status === TaskStatus.COMPLETED) { - return ; + return ; } if (status === TaskStatus.ERROR) { return ; } return ( - + ); }; @@ -82,7 +127,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/hooks/useBot.ts b/client/app/hooks/useBot.ts index beca50da..9232e939 100644 --- a/client/app/hooks/useBot.ts +++ b/client/app/hooks/useBot.ts @@ -4,6 +4,7 @@ import { deleteBot, deployWebsite, getBotApprovalList, + getBotBoundRepos, getBotConfig, getBotDetail, getBotInfoByRepoName, @@ -33,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], @@ -136,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, }); diff --git a/client/app/services/BotsController.ts b/client/app/services/BotsController.ts index eff1896b..9660d62f 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}`); 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/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/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/server/bot/router.py b/server/bot/router.py index 6608367a..dad0793b 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -5,6 +5,7 @@ from auth.get_user_info import get_user, get_user_id 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 @@ -33,7 +34,7 @@ def get_bot_list( try: supabase = get_client() query = supabase.table("bots").select( - "id, created_at, updated_at, avatar, description, name, public, starters, uid" + "id, created_at, updated_at, avatar, description, name, public, starters, uid, repo_name" ) if personal == "true": if not user_id: @@ -43,7 +44,7 @@ def get_bot_list( query = ( supabase.table("bots") .select( - "id, created_at, updated_at, avatar, description, name, public, starters, uid" + "id, created_at, updated_at, avatar, description, name, public, starters, uid, repo_name" ) .filter("name", "like", f"%{name}%") ) @@ -65,6 +66,18 @@ def get_bot_list( ) +@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=404 + ) + + @router.get("/detail") def get_bot_detail( id: Optional[str] = Query(None, description="Filter bots by personal category") diff --git a/server/core/dao/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py index 8cc174ce..7cf9f747 100644 --- a/server/core/dao/repositoryConfigDAO.py +++ b/server/core/dao/repositoryConfigDAO.py @@ -66,3 +66,16 @@ def get_by_repo_name(self, repo_name: str): repo_config = response.data[0] return RepositoryConfig(**repo_config) + + def get_by_bot_id(self, bot_id: str): + 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 + repo_configs = [RepositoryConfig(**repo) for repo in response.data] + + return repo_configs \ No newline at end of file From 346373b76adcd93523de627104416c9123435f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Fri, 6 Dec 2024 15:03:40 +0800 Subject: [PATCH 10/46] feat: redireact to the factory after user installed the github app (#570) --- client/app/github/installed/page.tsx | 40 +++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) 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!

+
- ) + ); } From a38c4dcea1570a5e2485fc1df2398c9de5bed169 Mon Sep 17 00:00:00 2001 From: liuzhide <44251801+ch-liuzhide@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:14:59 +0800 Subject: [PATCH 11/46] feat(deployment): add a prompt for authorizing the github organiztion (#571) * feat(deployment): add a prompt for authorizing the github organiztion Added a prompt for authorizing the Petercat OAuth for the GitHub organization under the list of repositories for deployable bots. * add space for en word --- client/.kiwi/en/DeployBotModal.ts | 3 +++ client/.kiwi/ja/DeployBotModal.ts | 3 +++ client/.kiwi/ko/DeployBotModal.ts | 3 +++ client/.kiwi/zh-CN/DeployBotModal.ts | 3 +++ client/.kiwi/zh-TW/DeployBotModal.ts | 3 +++ .../edit/components/DeployBotModal/DeployContent.tsx | 7 +++++++ .../app/factory/edit/components/DeployBotModal/index.tsx | 5 +++-- client/kiwi-config.json | 9 +++++---- 8 files changed, 30 insertions(+), 6 deletions(-) diff --git a/client/.kiwi/en/DeployBotModal.ts b/client/.kiwi/en/DeployBotModal.ts index 29a7834a..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', diff --git a/client/.kiwi/ja/DeployBotModal.ts b/client/.kiwi/ja/DeployBotModal.ts index fe081b3e..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: '折りたたむ', diff --git a/client/.kiwi/ko/DeployBotModal.ts b/client/.kiwi/ko/DeployBotModal.ts index de08b856..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: '접기', diff --git a/client/.kiwi/zh-CN/DeployBotModal.ts b/client/.kiwi/zh-CN/DeployBotModal.ts index 90723209..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: '收起', diff --git a/client/.kiwi/zh-TW/DeployBotModal.ts b/client/.kiwi/zh-TW/DeployBotModal.ts index fd20d415..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: '收起', diff --git a/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx b/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx index cdf5f251..1b21bb8e 100644 --- a/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx +++ b/client/app/factory/edit/components/DeployBotModal/DeployContent.tsx @@ -218,6 +218,13 @@ export const DeployContent: React.FC = ({ {I18N.DeployBotModal.DeployContent.shuaXin}
)} +
); diff --git a/client/app/factory/edit/components/DeployBotModal/index.tsx b/client/app/factory/edit/components/DeployBotModal/index.tsx index 53c29e0d..45693a15 100644 --- a/client/app/factory/edit/components/DeployBotModal/index.tsx +++ b/client/app/factory/edit/components/DeployBotModal/index.tsx @@ -276,10 +276,11 @@ const MyBotDeployModal: React.FC = ({ isOpen, onClose }) => { return ( <> {(onClose) => ( 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" + ] } From 5401bef3111e08ae9f1763a020a58b1bdefc2a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Fri, 6 Dec 2024 17:23:28 +0800 Subject: [PATCH 12/46] feat: handle the uninstall action (#572) --- server/core/dao/repositoryConfigDAO.py | 11 ++++++++- server/event_handler/intsall.py | 31 ++++++++++++++++++++++++++ server/github_app/handlers.py | 7 ++++-- 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 server/event_handler/intsall.py diff --git a/server/core/dao/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py index 7cf9f747..26151aa0 100644 --- a/server/core/dao/repositoryConfigDAO.py +++ b/server/core/dao/repositoryConfigDAO.py @@ -78,4 +78,13 @@ def get_by_bot_id(self, bot_id: str): return None repo_configs = [RepositoryConfig(**repo) for repo in response.data] - return repo_configs \ No newline at end of file + return repo_configs + + def delete_by_repo_ids(self, repo_ids: list): + response = ( + self.client.table("github_repo_config") + .delete() + .in_("repo_id", repo_ids) + .execute() + ) + return response diff --git a/server/event_handler/intsall.py b/server/event_handler/intsall.py new file mode 100644 index 00000000..7e843602 --- /dev/null +++ b/server/event_handler/intsall.py @@ -0,0 +1,31 @@ +from typing import Any +from github import Github, Auth +from github import GithubException +from core.dao.repositoryConfigDAO import RepositoryConfigDAO + + +class InstallEventHandler: + 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) + + async def execute(self): + try: + action = self.event["action"] + if action == "deleted": + self.delete_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..3caea4ad 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 InstallEventHandler from petercat_utils import get_env_variable from github import Auth @@ -25,6 +26,7 @@ def get_handler( DiscussionEventHandler, DiscussionCommentEventHandler, PullRequestReviewCommentEventHandler, + InstallEventHandler, None, ]: handlers = { @@ -33,8 +35,9 @@ 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": InstallEventHandler, } return ( handlers.get(event)(payload=payload, auth=auth, installation_id=installation_id) From d1465b9bed38d6cd320a45c1a6ae2c31dc7e4199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Mon, 9 Dec 2024 14:44:39 +0800 Subject: [PATCH 13/46] chore: update the database types (#576) --- client/types/database.types.ts | 601 +++++++++++++++------------------ 1 file changed, 266 insertions(+), 335 deletions(-) diff --git a/client/types/database.types.ts b/client/types/database.types.ts index c134ef1c..3f183d40 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; }; @@ -158,26 +197,26 @@ export type Database = { 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 +261,7 @@ export type Database = { }; profiles: { Row: { + agreement_accepted: boolean | null; created_at: string; id: string; name: string | null; @@ -229,9 +269,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 +279,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 +289,6 @@ export type Database = { picture?: string | null; sid?: string | null; sub?: string | null; - agreement_accepted?: boolean | null; }; Relationships: []; }; @@ -263,7 +302,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 +314,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 +326,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 +442,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 +552,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 +697,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 +766,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 +849,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; From 57291ddae8140162e19f951be1ce62614bd9b858 Mon Sep 17 00:00:00 2001 From: yingying Date: Mon, 9 Dec 2024 17:51:02 +0800 Subject: [PATCH 14/46] refactor(install): Refactor GitHub App Installation Logic --- server/core/dao/repositoryConfigDAO.py | 130 +++++++++++++++---------- server/event_handler/intsall.py | 23 ++++- server/github_app/router.py | 46 +-------- 3 files changed, 105 insertions(+), 94 deletions(-) diff --git a/server/core/dao/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py index 26151aa0..1fc1746e 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,65 +25,94 @@ 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() - ) + print(f"Error: {e}") + return False, {"message": f"GithubRepoConfig creation failed: {e}"} - return response.data + def create_batch(self, data_list: List[RepositoryConfig]): + try: + records_data = [data.model_dump(exclude=["id"]) for data in data_list] + repo_config = ( + self.client.from_("github_repo_config").insert(records_data).execute() + ) + if repo_config: + return True, { + "message": "GithubRepoConfig records created successfully" + } + else: + return False, {"message": "GithubRepoConfig batch creation failed"} + except Exception as e: + print(f"Error: {e}") + return False, {"message": f"GithubRepoConfig batch creation failed: {e}"} - def update_bot_to_repos( - self, - repos: List[RepoBindBotConfigVO], - ) -> bool: - for repo in repos: - res = ( + def query_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("*") + .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 get_by_repo_name(self, repo_name: str): - response = ( - self.client.table("github_repo_config") - .select("*") - .eq("repo_name", repo_name) - .execute() - ) + 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 - if not response.data or not response.data[0]: + def get_by_repo_name(self, repo_name: str): + 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 - repo_config = response.data[0] - - return RepositoryConfig(**repo_config) def get_by_bot_id(self, bot_id: str): - response = ( - self.client.table("github_repo_config") - .select("*") - .eq("robot_id", bot_id) - .execute() - ) - if not response.data or not response.data[0]: + 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_configs = [RepositoryConfig(**repo) for repo in response.data] - - return repo_configs - def delete_by_repo_ids(self, repo_ids: list): - response = ( - self.client.table("github_repo_config") - .delete() - .in_("repo_id", repo_ids) - .execute() - ) - return response + 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/event_handler/intsall.py b/server/event_handler/intsall.py index 7e843602..4baedb9b 100644 --- a/server/event_handler/intsall.py +++ b/server/event_handler/intsall.py @@ -1,7 +1,9 @@ -from typing import Any +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 InstallEventHandler: @@ -20,12 +22,31 @@ def delete_config(self): 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)} diff --git a/server/github_app/router.py b/server/github_app/router.py index 411bb676..c8443d25 100644 --- a/server/github_app/router.py +++ b/server/github_app/router.py @@ -50,48 +50,10 @@ # 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") From 22e06a2d185022fec665ed3429762f1a9605dbc0 Mon Sep 17 00:00:00 2001 From: yingying Date: Tue, 10 Dec 2024 11:37:11 +0800 Subject: [PATCH 15/46] feat: add the create_batch function for the repositoryDAO --- server/core/dao/repositoryConfigDAO.py | 42 +++++++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/server/core/dao/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py index 1fc1746e..58d6fe01 100644 --- a/server/core/dao/repositoryConfigDAO.py +++ b/server/core/dao/repositoryConfigDAO.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Counter, List from core.dao.BaseDAO import BaseDAO from core.models.bot import RepoBindBotConfigVO from core.models.repository import RepositoryConfig @@ -31,15 +31,41 @@ def create(self, data: RepositoryConfig): def create_batch(self, data_list: List[RepositoryConfig]): try: records_data = [data.model_dump(exclude=["id"]) for data in data_list] - repo_config = ( - self.client.from_("github_repo_config").insert(records_data).execute() + 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() ) - if repo_config: - return True, { - "message": "GithubRepoConfig records created successfully" + 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" } - else: - return False, {"message": "GithubRepoConfig batch creation failed"} + + # 执行插入操作 + 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}"} From 52646b4ea6d329935a04ae47605f4955f1c30ad4 Mon Sep 17 00:00:00 2001 From: yingying Date: Tue, 10 Dec 2024 11:54:06 +0800 Subject: [PATCH 16/46] feat: add the InstallationEditEventHandler --- server/event_handler/intsall.py | 48 ++++++++++++++++++++++++++++++++- server/github_app/handlers.py | 8 +++--- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/server/event_handler/intsall.py b/server/event_handler/intsall.py index 4baedb9b..757fd739 100644 --- a/server/event_handler/intsall.py +++ b/server/event_handler/intsall.py @@ -6,7 +6,7 @@ import time -class InstallEventHandler: +class InstallationEventHandler: event: Any auth: Auth.AppAuth g: Github @@ -50,3 +50,49 @@ async def execute(self): 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 3caea4ad..0581221f 100644 --- a/server/github_app/handlers.py +++ b/server/github_app/handlers.py @@ -1,6 +1,6 @@ from typing import Union -from event_handler.intsall import InstallEventHandler +from event_handler.intsall import InstallationEventHandler, InstallationEditEventHandler from petercat_utils import get_env_variable from github import Auth @@ -26,7 +26,8 @@ def get_handler( DiscussionEventHandler, DiscussionCommentEventHandler, PullRequestReviewCommentEventHandler, - InstallEventHandler, + InstallationEventHandler, + InstallationEditEventHandler, None, ]: handlers = { @@ -37,7 +38,8 @@ def get_handler( "discussion_comment": DiscussionCommentEventHandler, "pull_request_review_comment": PullRequestReviewCommentEventHandler, "pull_request_review": PullRequestReviewCommentEventHandler, - "installation": InstallEventHandler, + "installation": InstallationEventHandler, + "installation_repositories": InstallationEditEventHandler, } return ( handlers.get(event)(payload=payload, auth=auth, installation_id=installation_id) From 4dccb7597d31b9d8c8be186a761ee5ef23239e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Tue, 10 Dec 2024 16:13:06 +0800 Subject: [PATCH 17/46] chore: sync the db (#582) * chore: remove useless table * chore: sync the db migration * chore: fix ci --- client/types/database.types.ts | 30 ---- .../20240911082857_remote_schema.sql | 156 +--------------- .../20240912023152_remote_schema.sql | 6 +- .../20240913114522_remote_schema.sql | 7 +- .../20241015085655_remote_schema.sql | 84 --------- .../20241015090438_remote_schema.sql | 4 - .../20241210072126_remote_schema.sql | 170 ++++++++++++++++++ server/core/dao/authorizationDAO.py | 39 ---- server/core/dao/repositoryConfigDAO.py | 2 +- server/github_app/router.py | 7 - 10 files changed, 181 insertions(+), 324 deletions(-) create mode 100644 migrations/supabase/migrations/20241210072126_remote_schema.sql delete mode 100644 server/core/dao/authorizationDAO.py diff --git a/client/types/database.types.ts b/client/types/database.types.ts index 3f183d40..20f05584 100644 --- a/client/types/database.types.ts +++ b/client/types/database.types.ts @@ -163,36 +163,6 @@ 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; 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/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/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py index 58d6fe01..b9c410fe 100644 --- a/server/core/dao/repositoryConfigDAO.py +++ b/server/core/dao/repositoryConfigDAO.py @@ -1,4 +1,4 @@ -from typing import Counter, List +from typing import List from core.dao.BaseDAO import BaseDAO from core.models.bot import RepoBindBotConfigVO from core.models.repository import RepositoryConfig diff --git a/server/github_app/router.py b/server/github_app/router.py index c8443d25..01e4ea94 100644 --- a/server/github_app/router.py +++ b/server/github_app/router.py @@ -11,22 +11,15 @@ 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 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, ) From f96230f5bbbc466340c00e4a1426034eaa52e734 Mon Sep 17 00:00:00 2001 From: yingying Date: Thu, 12 Dec 2024 15:34:54 +0800 Subject: [PATCH 18/46] feat: add auto complete --- client/.kiwi/en/edit.ts | 4 +- client/.kiwi/ja/edit.ts | 4 +- client/.kiwi/ko/edit.ts | 4 +- client/.kiwi/zh-CN/edit.ts | 4 +- client/.kiwi/zh-TW/edit.ts | 4 +- client/app/factory/edit/page.tsx | 111 ++++++++++++++++---------- client/app/hooks/useUser.ts | 22 ++++- client/app/services/UserController.ts | 7 ++ client/app/utils/tools.ts | 9 ++- client/components/User.tsx | 9 ++- server/auth/get_user_info.py | 1 + server/auth/router.py | 92 ++++++++++++++------- 12 files changed, 178 insertions(+), 93 deletions(-) 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/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/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/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-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/app/factory/edit/page.tsx b/client/app/factory/edit/page.tsx index acfe9ff4..79192e31 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'; @@ -28,20 +30,19 @@ import { } from '@/app/hooks/useBot'; 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', @@ -78,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] = @@ -180,6 +183,8 @@ export default function Edit() { [id, botProfile?.id], ); + const { data: repos } = useUserRepos(!isEdit); + const botId = useMemo(() => { if (!!id && id !== 'new') { return id; @@ -323,46 +328,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 || botProfile.repoName} - 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 ? (
@@ -372,7 +395,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/hooks/useUser.ts b/client/app/hooks/useUser.ts index 23357dc1..53099261 100644 --- a/client/app/hooks/useUser.ts +++ b/client/app/hooks/useUser.ts @@ -1,19 +1,33 @@ 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! + 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/services/UserController.ts b/client/app/services/UserController.ts index a5946f63..7406e267 100644 --- a/client/app/services/UserController.ts +++ b/client/app/services/UserController.ts @@ -27,6 +27,13 @@ export async function getAgreementStatus() { 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/User.tsx b/client/components/User.tsx index 485501ae..0677a2b3 100644 --- a/client/components/User.tsx +++ b/client/components/User.tsx @@ -9,7 +9,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 +23,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/server/auth/get_user_info.py b/server/auth/get_user_info.py index 950f916f..7b587108 100644 --- a/server/auth/get_user_info.py +++ b/server/auth/get_user_info.py @@ -11,6 +11,7 @@ AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") + async def getUserInfoByToken(token): userinfo_url = f"https://{AUTH0_DOMAIN}/userinfo" headers = {"authorization": f"Bearer {token}"} diff --git a/server/auth/router.py b/server/auth/router.py index bb8a2ca4..7a7a3e8b 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -1,3 +1,5 @@ +import json +from github import Github from core.dao.profilesDAO import ProfilesDAO from fastapi import APIRouter, Request, HTTPException, status, Depends from fastapi.responses import RedirectResponse, JSONResponse @@ -7,7 +9,12 @@ from authlib.integrations.starlette_client import OAuth from typing import Annotated, Optional -from auth.get_user_info import generateAnonymousUser, getUserInfoByToken, get_user_id +from auth.get_user_info import ( + generateAnonymousUser, + getUserAccessToken, + getUserInfoByToken, + get_user_id, +) AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") @@ -15,26 +22,26 @@ CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID") CLIENT_SECRET = get_env_variable("AUTH0_CLIENT_SECRET") -API_URL = get_env_variable("API_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_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, -}) +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' - } + server_metadata_url=f"https://{AUTH0_DOMAIN}/.well-known/openid-configuration", + client_kwargs={"scope": "openid email profile"}, ) router = APIRouter( @@ -43,38 +50,46 @@ 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") + 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 + 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) + redirect_response = await oauth.auth0.authorize_redirect( + request, redirect_uri=CALLBACK_URL + ) return redirect_response -@router.get('/logout') + +@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']) + user_info = await getUserInfoByToken(token=auth0_token["access_token"]) if user_info: data = { @@ -86,18 +101,20 @@ async def callback(request: Request): "sid": secrets.token_urlsafe(32), "agreement_accepted": user_info.get("agreement_accepted"), } - request.session['user'] = dict(data) + request.session["user"] = dict(data) supabase = get_client() supabase.table("profiles").upsert(data).execute() - return RedirectResponse(url=f'{WEB_LOGIN_SUCCESS_URL}', status_code=302) + return RedirectResponse(url=f"{WEB_LOGIN_SUCCESS_URL}", status_code=302) + @router.get("/userinfo") async def userinfo(request: Request): - user = request.session.get('user') + user = request.session.get("user") if not user: data = await getAnonymousUser(request) - return { "data": data, "status": 200} - return { "data": user, "status": 200} + 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)): @@ -107,11 +124,14 @@ async def get_agreement_status(user_id: Optional[str] = Depends(get_user_id)): 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.") + 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, @@ -123,9 +143,27 @@ async def bot_generator( profiles_dao = ProfilesDAO() response = profiles_dao.accept_agreement(user_id=user_id) if response: - request.session['user'] = 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)): + if not user_id: + raise HTTPException(status_code=401, detail="User not found") + try: + access_token = await getUserAccessToken(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: + raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}") From fa69b38fabcc272b7fc069853881b03867824e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Thu, 12 Dec 2024 15:35:26 +0800 Subject: [PATCH 19/46] style: add the hover style for the bot delete button (#583) --- .../factory/edit/components/BotCreateForm.tsx | 11 +- client/app/globals.css | 6 + client/public/icons/DeleteButtonIcon.tsx | 108 ++++++++++++++++++ client/public/images/delete-button.svg | 38 ------ 4 files changed, 118 insertions(+), 45 deletions(-) create mode 100644 client/public/icons/DeleteButtonIcon.tsx delete mode 100644 client/public/images/delete-button.svg diff --git a/client/app/factory/edit/components/BotCreateForm.tsx b/client/app/factory/edit/components/BotCreateForm.tsx index 2033cd04..96da5fed 100644 --- a/client/app/factory/edit/components/BotCreateForm.tsx +++ b/client/app/factory/edit/components/BotCreateForm.tsx @@ -34,6 +34,7 @@ import { useRouter } from 'next/navigation'; 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(); @@ -239,13 +240,9 @@ const BotCreateFrom = () => { - delete bot +
+ +
diff --git a/client/app/globals.css b/client/app/globals.css index 7fcd12ab..c8d69dc3 100644 --- a/client/app/globals.css +++ b/client/app/globals.css @@ -247,3 +247,9 @@ div.fp-watermark { } + .hover-reverse-colors:hover * { + stroke: #fff; + } + .hover-reverse-colors:hover { + fill: #DD1010; + } diff --git a/client/public/icons/DeleteButtonIcon.tsx b/client/public/icons/DeleteButtonIcon.tsx new file mode 100644 index 00000000..fcb014cf --- /dev/null +++ b/client/public/icons/DeleteButtonIcon.tsx @@ -0,0 +1,108 @@ +const DeleteButtonIcon = () => ( + + + + + + + + + + + + + + + + +); +export default DeleteButtonIcon; 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 From dca4520301f9b792e9c3501a6d205c3b7470093a Mon Sep 17 00:00:00 2001 From: yingying Date: Thu, 12 Dec 2024 17:16:44 +0800 Subject: [PATCH 20/46] feat: enable org --- client/app/hooks/useBot.ts | 6 ++-- server/bot/router.py | 42 +++++++++++++++++--------- server/core/dao/repositoryConfigDAO.py | 16 +++++++++- server/github_app/utils.py | 13 -------- 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/client/app/hooks/useBot.ts b/client/app/hooks/useBot.ts index 9232e939..bb986c80 100644 --- a/client/app/hooks/useBot.ts +++ b/client/app/hooks/useBot.ts @@ -34,7 +34,7 @@ export const useBotDetail = (id: string) => { }); }; -export const useGetBotBoundRepos =(id:string)=>{ +export const useGetBotBoundRepos = (id: string) => { return useQuery({ queryKey: [`bot.boundRepos.${id}`, id], queryFn: async () => getBotBoundRepos(id), @@ -42,7 +42,7 @@ export const useGetBotBoundRepos =(id:string)=>{ enabled: !!id, retry: false, }); -} +}; export const useBotConfig = (id: string, enabled: boolean) => { return useQuery({ @@ -153,7 +153,7 @@ export const useGetBotRagTask = ( queryKey: [`rag.task`, repoName], queryFn: async () => getRagTask(repoName), select: (data) => data, - enabled:!!repoName, + enabled: !!repoName, retry: true, refetchInterval: refetchInterval ? 3 * 1000 : undefined, }); diff --git a/server/bot/router.py b/server/bot/router.py index dad0793b..4518f28e 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -30,6 +30,7 @@ def get_bot_list( ), 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() @@ -39,21 +40,35 @@ def get_bot_list( if personal == "true": if not user_id: return {"data": [], "personal": personal} - query = query.eq("uid", user_id).order("updated_at", desc=True) - if name: + + auth = Auth.Token(token=user.access_token) + g = Github(auth=auth) + github_user = g.get_user() + orgs = github_user.get_orgs() + repository_config_dao = RepositoryConfigDAO() + bots = repository_config_dao.query_by_owners( + [org.id for org in orgs] + [github_user.id] + ) + bot_ids = [bot["robot_id"] for bot in bots] + bot_ids_str = ",".join(map(str, bot_ids)) # 将 bots ID 列表转换为字符串 + or_clause = f"uid.eq.{user_id},id.in.({bot_ids_str})" + + # 添加过滤条件 query = ( - supabase.table("bots") - .select( - "id, created_at, updated_at, avatar, description, name, public, starters, uid, repo_name" - ) + query.or_(or_clause).order("updated_at", desc=True) + if not name + else query.or_(or_clause) .filter("name", "like", f"%{name}%") + .order("updated_at", desc=True) + ) + else: + query = ( + query.eq("public", True).order("updated_at", desc=True) + if not name + else query.eq("public", True) + .filter("name", "like", f"%{name}%") + .order("updated_at", desc=True) ) - - query = ( - query.eq("public", True).order("updated_at", desc=True) - if not personal or personal != "true" - else query - ) data = query.execute() if not data or not data.data: @@ -186,6 +201,7 @@ async def bot_generator( content={"success": False, "errorMessage": str(e)}, status_code=500 ) + @router.get("/git/avatar", status_code=200) async def get_git_avatar( repo_name: str, @@ -201,8 +217,6 @@ async def get_git_avatar( ) - - @router.put("/update/{id}", status_code=200) def update_bot( id: str, diff --git a/server/core/dao/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py index b9c410fe..128a63f4 100644 --- a/server/core/dao/repositoryConfigDAO.py +++ b/server/core/dao/repositoryConfigDAO.py @@ -74,8 +74,22 @@ def query_by_owners(self, orgs: List[str]): try: response = ( self.client.table("github_repo_config") - .select("*") + .select("robot_id") + .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") + .select("robot_id") .filter("owner_id", "in", f"({','.join(map(str, orgs))})") + .filter("robot_id", "isnot", None) .execute() ) return response.data diff --git a/server/github_app/utils.py b/server/github_app/utils.py index 98e825eb..2b727fed 100644 --- a/server/github_app/utils.py +++ b/server/github_app/utils.py @@ -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() From c40bba1da6504a4890e3bc8a4abad3ec71597f2a Mon Sep 17 00:00:00 2001 From: yingying Date: Thu, 12 Dec 2024 17:23:22 +0800 Subject: [PATCH 21/46] chore: fix ci --- server/auth/router.py | 1 - 1 file changed, 1 deletion(-) diff --git a/server/auth/router.py b/server/auth/router.py index 7a7a3e8b..3d4e7305 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -1,4 +1,3 @@ -import json from github import Github from core.dao.profilesDAO import ProfilesDAO from fastapi import APIRouter, Request, HTTPException, status, Depends From 8b2bd31e45670ac249deffab27eadb98bdca4bff Mon Sep 17 00:00:00 2001 From: yingying Date: Thu, 12 Dec 2024 18:17:38 +0800 Subject: [PATCH 22/46] chore: fix cr --- server/bot/router.py | 16 ++++++++++------ server/bot/util.ts | 0 server/core/dao/repositoryConfigDAO.py | 3 +-- 3 files changed, 11 insertions(+), 8 deletions(-) delete mode 100644 server/bot/util.ts diff --git a/server/bot/router.py b/server/bot/router.py index 4518f28e..14fa3162 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -29,7 +29,6 @@ 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: @@ -38,20 +37,25 @@ def get_bot_list( "id, created_at, updated_at, avatar, description, name, public, starters, uid, repo_name" ) if personal == "true": - if not user_id: + if not user or not user.access_token: return {"data": [], "personal": personal} - + user_id = user.id auth = Auth.Token(token=user.access_token) g = Github(auth=auth) github_user = g.get_user() orgs = github_user.get_orgs() repository_config_dao = RepositoryConfigDAO() - bots = repository_config_dao.query_by_owners( + bots = repository_config_dao.query_bot_id_by_owners( [org.id for org in orgs] + [github_user.id] ) - bot_ids = [bot["robot_id"] for bot in bots] + bot_ids = [bot["robot_id"] for bot in bots if bot["robot_id"]] bot_ids_str = ",".join(map(str, bot_ids)) # 将 bots ID 列表转换为字符串 - or_clause = f"uid.eq.{user_id},id.in.({bot_ids_str})" + + or_clause = ( + f"uid.eq.{user_id},id.in.({bot_ids_str})" + if bot_ids_str + else f"uid.eq.{user_id}" + ) # 添加过滤条件 query = ( 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/repositoryConfigDAO.py b/server/core/dao/repositoryConfigDAO.py index 128a63f4..e1f8f6d1 100644 --- a/server/core/dao/repositoryConfigDAO.py +++ b/server/core/dao/repositoryConfigDAO.py @@ -74,7 +74,7 @@ def query_by_owners(self, orgs: List[str]): try: response = ( self.client.table("github_repo_config") - .select("robot_id") + .select("*") .filter("owner_id", "in", f"({','.join(map(str, orgs))})") .execute() ) @@ -89,7 +89,6 @@ def query_bot_id_by_owners(self, orgs: List[str]): self.client.table("github_repo_config") .select("robot_id") .filter("owner_id", "in", f"({','.join(map(str, orgs))})") - .filter("robot_id", "isnot", None) .execute() ) return response.data From a1f80f67e8edb9e7d90bf033f8f4b2619c52bf32 Mon Sep 17 00:00:00 2001 From: yingying Date: Fri, 13 Dec 2024 16:20:09 +0800 Subject: [PATCH 23/46] refactor: fix cr --- server/bot/router.py | 48 ++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/server/bot/router.py b/server/bot/router.py index 14fa3162..00775b9e 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -36,48 +36,38 @@ def get_bot_list( query = supabase.table("bots").select( "id, created_at, updated_at, avatar, description, name, public, starters, uid, repo_name" ) + if personal == "true": if not user or not user.access_token: return {"data": [], "personal": personal} - user_id = user.id + auth = Auth.Token(token=user.access_token) - g = Github(auth=auth) - github_user = g.get_user() - orgs = github_user.get_orgs() + 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( - [org.id for org in orgs] + [github_user.id] + orgs_ids + [github_user.id] ) - bot_ids = [bot["robot_id"] for bot in bots if bot["robot_id"]] - bot_ids_str = ",".join(map(str, bot_ids)) # 将 bots ID 列表转换为字符串 - or_clause = ( - f"uid.eq.{user_id},id.in.({bot_ids_str})" - if bot_ids_str - else f"uid.eq.{user_id}" - ) + if bots: + bot_ids = [bot["robot_id"] for bot in bots if bot["robot_id"]] - # 添加过滤条件 - query = ( - query.or_(or_clause).order("updated_at", desc=True) - if not name - else query.or_(or_clause) - .filter("name", "like", f"%{name}%") - .order("updated_at", desc=True) + 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).order("updated_at", desc=True) - if not name - else query.eq("public", True) - .filter("name", "like", f"%{name}%") - .order("updated_at", desc=True) - ) + query = query.eq("public", True) + if name: + query = query.filter("name", "like", f"%{name}%") + + query = query.order("updated_at", desc=True) data = query.execute() - if not data or not data.data: - return {"data": [], "personal": personal} - return {"data": data.data, "personal": personal} + + return {"data": data.data if data and data.data else [], "personal": personal} except Exception as e: return JSONResponse( From 61bd4ef576d47a4a8e0f730c4a4d3b3b663616c3 Mon Sep 17 00:00:00 2001 From: MadratJerry Date: Mon, 16 Dec 2024 11:00:08 +0800 Subject: [PATCH 24/46] =?UTF-8?q?fix=EF=BC=9Asolve=20the=20dup=20retrieval?= =?UTF-8?q?=20issue=20(#575)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: merge partial env variable and skip validation in the dev * fix: fix dup retrieval * feat: update petercat-utils --- petercat_utils/rag_helper/retrieval.py | 27 +++-- pyproject.toml | 2 +- server/auth/middleware.py | 137 ++++++++++++------------- server/auth/router.py | 22 ++-- server/env.py | 6 ++ server/github_app/router.py | 21 ++-- server/main.py | 25 ++--- server/tests/test_main.py | 8 +- subscriber/requirements.txt | 2 +- 9 files changed, 129 insertions(+), 121 deletions(-) create mode 100644 server/env.py diff --git a/petercat_utils/rag_helper/retrieval.py b/petercat_utils/rag_helper/retrieval.py index 5a3207dd..1cb889e1 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/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/router.py b/server/auth/router.py index 3d4e7305..240d9d40 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -1,19 +1,18 @@ -from github import Github -from core.dao.profilesDAO import ProfilesDAO +import secrets +from typing import Annotated, Optional + +from authlib.integrations.starlette_client import OAuth from fastapi import APIRouter, Request, HTTPException, status, Depends from fastapi.responses import RedirectResponse, JSONResponse -import secrets -from petercat_utils import get_client, get_env_variable +from github import Github from starlette.config import Config -from authlib.integrations.starlette_client import OAuth -from typing import Annotated, Optional +from auth.get_user_info import generateAnonymousUser, getUserInfoByToken, get_user_id from auth.get_user_info import ( - generateAnonymousUser, getUserAccessToken, - getUserInfoByToken, - get_user_id, ) +from core.dao.profilesDAO import ProfilesDAO +from petercat_utils import get_client, get_env_variable AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") @@ -26,6 +25,7 @@ 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" @@ -133,8 +133,8 @@ async def get_agreement_status(user_id: Optional[str] = Depends(get_user_id)): @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: raise HTTPException(status_code=401, detail="User not found") 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/github_app/router.py b/server/github_app/router.py index 01e4ea94..981ddf94 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,27 +10,24 @@ Request, status, ) -import logging from fastapi.responses import RedirectResponse - from github import Auth, Github + from auth.get_user_info import get_user from core.dao.repositoryConfigDAO import RepositoryConfigDAO from core.models.bot import RepoBindBotRequest 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_private_key, ) - 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") APP_ID = get_env_variable("X_GITHUB_APP_ID") -WEB_URL = get_env_variable("WEB_URL") logger = logging.getLogger() logger.setLevel("INFO") @@ -51,9 +50,9 @@ def github_app_callback(code: str, installation_id: str, setup_action: str): @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": @@ -86,7 +85,7 @@ 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 + user: Annotated[User | None, Depends(get_user)] = None ): """ Get github user installed app repositories which saved in platform database. @@ -116,8 +115,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/main.py b/server/main.py index a5d747d6..0d4c82fc 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,7 +72,7 @@ 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, diff --git a/server/tests/test_main.py b/server/tests/test_main.py index 3eb22630..e8d343e0 100644 --- a/server/tests/test_main.py +++ b/server/tests/test_main.py @@ -1,18 +1,16 @@ from fastapi.testclient import TestClient + +from env import ENVIRONMENT, WEB_URL, API_URL 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") - client = TestClient(app) def test_health_checker(): response = client.get("/api/health_checker") assert response.status_code == 200 assert response.json() == { - 'ENVRIMENT': ENVRIMENT, + 'ENVIRONMENT': ENVIRONMENT, 'API_URL': API_URL, 'CALLBACK_URL': f'{API_URL}/api/auth/callback', 'WEB_URL': WEB_URL, 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 From 819dfa0524463600944ccc5b269b9d30a241fdb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Mon, 16 Dec 2024 14:12:23 +0800 Subject: [PATCH 25/46] fix: solve the issue where the configuration information of robots from other organizations cannot be found (#588) * fix: get the bot config belongs to the org * chore: fix ci * chore: fix ci --- server/bot/router.py | 17 +++++++++++++++-- server/tests/test_main.py | 12 ++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/server/bot/router.py b/server/bot/router.py index 00775b9e..4bd489ed 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -114,12 +114,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: diff --git a/server/tests/test_main.py b/server/tests/test_main.py index e8d343e0..f4ce6371 100644 --- a/server/tests/test_main.py +++ b/server/tests/test_main.py @@ -1,17 +1,17 @@ from fastapi.testclient import TestClient from env import ENVIRONMENT, WEB_URL, API_URL -from petercat_utils import get_env_variable 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() == { - 'ENVIRONMENT': ENVIRONMENT, - '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, + } From 9cfca4a6fc46c9ab7d014cbd8aa5d0b2826ac784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Mon, 16 Dec 2024 15:03:45 +0800 Subject: [PATCH 26/46] style: tweak the style of the delete bot button (#589) style: tweak the style of the delete bot btn --- .../factory/edit/components/BotCreateForm.tsx | 11 +- client/app/factory/edit/page.tsx | 1 + client/app/globals.css | 14 +- client/public/icons/DeleteButtonIcon.tsx | 217 +++++++++--------- 4 files changed, 126 insertions(+), 117 deletions(-) diff --git a/client/app/factory/edit/components/BotCreateForm.tsx b/client/app/factory/edit/components/BotCreateForm.tsx index 96da5fed..e076f332 100644 --- a/client/app/factory/edit/components/BotCreateForm.tsx +++ b/client/app/factory/edit/components/BotCreateForm.tsx @@ -30,7 +30,6 @@ import { useBot } from '@/app/contexts/BotContext'; import 'react-toastify/dist/ReactToastify.css'; import { AVATARS } from '@/app/constant/avatar'; -import { useRouter } from 'next/navigation'; import { useAvailableLLMs } from '@/app/hooks/useAvailableLLMs'; import { useTokenList } from '@/app/hooks/useToken'; import CreateButton from '@/app/user/tokens/components/CreateButton'; @@ -39,7 +38,6 @@ import DeleteButtonIcon from '@/public/icons/DeleteButtonIcon'; const BotCreateFrom = () => { const { botProfile, setBotProfile } = useBot(); const { data: gitAvatar } = useGetGitAvatar(botProfile?.repoName); - const router = useRouter(); const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure(); const { data: availableLLMs = [] } = useAvailableLLMs(); const { data: userTokens = [] } = useTokenList(); @@ -56,7 +54,6 @@ const BotCreateFrom = () => { ) => { const name = e.target.name as keyof Omit; const value = e.target.value; - console.log(name, value); setBotProfile((draft: BotProfile) => { // @ts-ignore draft[name] = value; @@ -69,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]); @@ -85,6 +80,10 @@ const BotCreateFrom = () => { deleteBot(botProfile?.id!); }; + useEffect(() => { + console.log('botProfile', botProfile); + }, [botProfile]); + const customTitle = (
diff --git a/client/app/factory/edit/page.tsx b/client/app/factory/edit/page.tsx index 79192e31..9a6c6f4f 100644 --- a/client/app/factory/edit/page.tsx +++ b/client/app/factory/edit/page.tsx @@ -201,6 +201,7 @@ export default function Edit() { ); useEffect(() => { + console.log('config', config); if (!isEmpty(config)) { setBotProfile((draft) => { draft.id = config.id; diff --git a/client/app/globals.css b/client/app/globals.css index c8d69dc3..808700cf 100644 --- a/client/app/globals.css +++ b/client/app/globals.css @@ -247,9 +247,11 @@ div.fp-watermark { } - .hover-reverse-colors:hover * { - stroke: #fff; - } - .hover-reverse-colors:hover { - fill: #DD1010; - } +.hover-reverse-colors:hover * { + stroke: #fff; +} + +.hover-reverse-colors:hover { + background-color: #DC2626; + color: #fff !important; +} diff --git a/client/public/icons/DeleteButtonIcon.tsx b/client/public/icons/DeleteButtonIcon.tsx index fcb014cf..88162fc5 100644 --- a/client/public/icons/DeleteButtonIcon.tsx +++ b/client/public/icons/DeleteButtonIcon.tsx @@ -1,108 +1,115 @@ const DeleteButtonIcon = () => ( - - - - - - - - - - - - - - - - +
+
+ + + + + + + + + + + + + + + Delete Robot +
+
); export default DeleteButtonIcon; From de69310d94f766ac6c6aeae357173fefbf54f83f Mon Sep 17 00:00:00 2001 From: liuzhide <44251801+ch-liuzhide@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:21:51 +0800 Subject: [PATCH 27/46] refactor(esm): replace require json file with import json file (#574) * refactor(esm): replace require json file with import json file * refactor(package.json): upgrade @petercatai/assistant --- assistant/package.json | 2 +- .../GitInsight/components/GitInsightIcon.tsx | 6 +- assistant/src/style.css | 62 +++++++++---------- assistant/tsconfig.json | 3 +- client/package.json | 2 +- client/yarn.lock | 8 +-- 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/assistant/package.json b/assistant/package.json index 9f7871a1..86c23827 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -1,6 +1,6 @@ { "name": "@petercatai/assistant", - "version": "1.0.18", + "version": "1.0.20", "description": "PeterCat Assistant Application", "module": "dist/esm/index.js", "types": "dist/esm/index.d.ts", 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/style.css b/assistant/src/style.css index ed6ba836..fb35752c 100644 --- a/assistant/src/style.css +++ b/assistant/src/style.css @@ -112,8 +112,8 @@ */ *:where(.petercat-lui,.petercat-lui *), -::before:where(.petercat-lui,.petercat-lui *), -::after:where(.petercat-lui,.petercat-lui *) { +:where(.petercat-lui,.petercat-lui *)::before, +:where(.petercat-lui,.petercat-lui *)::after { box-sizing: border-box; /* 1 */ border-width: 0; @@ -124,8 +124,8 @@ /* 2 */ } -::before:where(.petercat-lui,.petercat-lui *), -::after:where(.petercat-lui,.petercat-lui *) { +:where(.petercat-lui,.petercat-lui *)::before, +:where(.petercat-lui,.petercat-lui *)::after { --tw-content: ''; } @@ -378,8 +378,8 @@ progress:where(.petercat-lui,.petercat-lui *) { Correct the cursor style of increment and decrement buttons in Safari. */ -::-webkit-inner-spin-button:where(.petercat-lui,.petercat-lui *), -::-webkit-outer-spin-button:where(.petercat-lui,.petercat-lui *) { +:where(.petercat-lui,.petercat-lui *) ::-webkit-inner-spin-button, +:where(.petercat-lui,.petercat-lui *) ::-webkit-outer-spin-button { height: auto; } @@ -399,7 +399,7 @@ Correct the cursor style of increment and decrement buttons in Safari. Remove the inner padding in Chrome and Safari on macOS. */ -::-webkit-search-decoration:where(.petercat-lui,.petercat-lui *) { +:where(.petercat-lui,.petercat-lui *) ::-webkit-search-decoration { -webkit-appearance: none; } @@ -408,7 +408,7 @@ Remove the inner padding in Chrome and Safari on macOS. 2. Change font properties to `inherit` in Safari. */ -::-webkit-file-upload-button:where(.petercat-lui,.petercat-lui *) { +:where(.petercat-lui,.petercat-lui *) ::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; @@ -481,15 +481,15 @@ textarea:where(.petercat-lui,.petercat-lui *) { 2. Set the default placeholder color to the user's configured gray 400 color. */ -input::-moz-placeholder:where(.petercat-lui,.petercat-lui *), textarea::-moz-placeholder:where(.petercat-lui,.petercat-lui *) { +:where(.petercat-lui,.petercat-lui *) input::-moz-placeholder, :where(.petercat-lui,.petercat-lui *) textarea::-moz-placeholder { opacity: 1; /* 1 */ color: #9ca3af; /* 2 */ } -input::placeholder:where(.petercat-lui,.petercat-lui *), -textarea::placeholder:where(.petercat-lui,.petercat-lui *) { +:where(.petercat-lui,.petercat-lui *) input::placeholder, +:where(.petercat-lui,.petercat-lui *) textarea::placeholder { opacity: 1; /* 1 */ color: #9ca3af; @@ -545,7 +545,7 @@ video:where(.petercat-lui,.petercat-lui *) { /* Make elements with the HTML hidden attribute stay hidden by default */ -[hidden]:where(.petercat-lui,.petercat-lui *) { +[hidden]:where(:not([hidden="until-found"])):where(.petercat-lui,.petercat-lui *) { display: none; } @@ -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/package.json b/client/package.json index a8ba844c..9aeb5350 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.18", + "@petercatai/assistant": "1.0.20", "@sentry/nextjs": "^8.28.0", "@supabase/supabase-js": "^2.32.0", "@tanstack/react-query": "^5.17.19", diff --git a/client/yarn.lock b/client/yarn.lock index 41cf0017..7278dc7b 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.17": - version "1.0.17" - resolved "https://registry.yarnpkg.com/@petercatai/assistant/-/assistant-1.0.17.tgz#c6499ee8908f116664e448b54a076d564414a965" - integrity sha512-9IGCO+BlVRvXM1OZegclofOYb57tXnOlxVvOt+rltLqzre7TDnDNFl1lb9SFjmCjBQOVCm/IXZCG6KifIb4l4Q== +"@petercatai/assistant@1.0.20": + version "1.0.20" + resolved "https://registry.yarnpkg.com/@petercatai/assistant/-/assistant-1.0.20.tgz#2d2dc1beb296c8524219a6de7eee1575cb3b4c92" + integrity sha512-csfRRsKB9FbBM+cMcCTQQowsuuFRVerSrxfMRTWoI1XHhBW3ormbt1XTeYKiubmwz4iKznR+2UCrZrCl75ckmA== dependencies: "@ant-design/icons" "^5.3.5" "@ant-design/pro-chat" "^1.9.0" From bedd37097c25736a0479b0538338632dd89e5f06 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Fri, 13 Dec 2024 18:48:28 +0800 Subject: [PATCH 28/46] feat: run supabase locally --- .gitignore | 5 +- client/.env.local.example | 2 + docker/docker-compose.yml | 53 ------------------ docs/guides/self_hosted_local.md | 68 ++++++----------------- docs/guides/self_hosted_local_cn.md | 44 ++------------- docs/guides/self_hosting_docker.md | 49 ---------------- package.json | 4 +- server/.env.example | 86 ++++++++++++++++++++++++++++- server/main.py | 6 +- 9 files changed, 118 insertions(+), 199 deletions(-) create mode 100644 client/.env.local.example delete mode 100644 docker/docker-compose.yml delete mode 100644 docs/guides/self_hosting_docker.md 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/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/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/self_hosted_local.md b/docs/guides/self_hosted_local.md index 2ee2a075..57a71d68 100644 --- a/docs/guides/self_hosted_local.md +++ b/docs/guides/self_hosted_local.md @@ -1,6 +1,7 @@ -# Self-Hosting +``` +# Self-Hosting Guide -## Install Locally +## Local Installation ### Step 1: Clone the Repository Clone the project repository to your local machine: @@ -16,83 +17,47 @@ Install all required dependencies using Yarn: yarn run bootstrap ``` -### Step 3: Copy the `.env.example` Files -Copy the server environment configuration example files: +### Step 3: Copy `.env.example` Files +Copy the server environment configuration example file: ```bash cp server/.env.example server/.env ``` -Copy the Client environment configuration example files: +Copy the client environment configuration example file: ```bash cp client/.env.example client/.env ``` -### 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`: - -```bash -vim server/.env -``` - -For local development, configure only the Supabase and OpenAI settings: +### Step 4: Start Supabase Locally with Docker Compose ```bash -# Supabase Project URL from https://supabase.com/dashboard/project/_/settings/database -SUPABASE_URL=https://{{YOUR_PROJECT_ID}}.supabase.co - -# Supabase Project API key for `anon public` -SUPABASE_SERVICE_KEY=xxxx.yyyyy.zzzzz - -# OpenAI API key -OPENAI_API_KEY=sk-xxxx +yarn run docker ``` -### Step 5: Initialize the Database Structure +### Step 5: Initialize the 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 instructions in 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: Apply Migrations Apply the database migrations to your remote database: ```bash -supabase db push +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 will see output similar to the following: ``` Connecting to remote database... @@ -104,7 +69,7 @@ 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 @@ -113,7 +78,7 @@ yarn run server Check if the server is running by opening `http://127.0.0.1:8000/api/health_checker` in your browser. -### Step 7: Bootstrap Client +### Step 7: Start the Client Start the client with the following command: ```bash @@ -121,3 +86,4 @@ yarn run client ``` You can check 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..2885f3b8 100644 --- a/docs/guides/self_hosted_local_cn.md +++ b/docs/guides/self_hosted_local_cn.md @@ -27,24 +27,10 @@ cp server/.env.example server/.env cp client/.env.example client/.env ``` -### 第四步:更新 `.env` 文件 -打开 `.env` 文件并更新必要的键值。您可以使用任何文本编辑器,例如 `vim`、`emacs`、`vscode` 或 `nano`: +### 第四步:使用 docker compose 在本地启动 supabase ```bash -vim server/.env -``` - -对于本地开发,只需配置 Supabase 和 OpenAI 设置: - -```bash -# Supabase 项目 URL,获取路径:https://supabase.com/dashboard/project/_/settings/database -SUPABASE_URL=https://{{YOUR_PROJECT_ID}}.supabase.co - -# Supabase 项目 API 密钥,`anon public` -SUPABASE_SERVICE_KEY=xxxx.yyyyy.zzzzz - -# OpenAI API 密钥 -OPENAI_API_KEY=sk-xxxx +yarn run docker ``` ### 第五步:初始化数据库结构 @@ -63,33 +49,11 @@ 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 +supabase db push --db-url "postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@127.0.0.1:5432/postgres" ``` 如果成功,您将看到类似以下的输出: 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/package.json b/package.json index 2cc59f55..e2d038d7 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,15 @@ "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 && 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\"", "build:docker": "docker build -t petercat .", "build:pypi": "rm -rf dist && python3 -m build", "publish:test": "python3 -m twine upload --repository petercat-utils dist/* ", - "publish:pypi": "python3 -m twine upload --repository pypi dist/* " + "publish:pypi": "python3 -m twine upload --repository pypi dist/* ", + "docker": "docker compose --env-file server/.env -f docker/docker-compose.yml up -d" }, "engines": { "node": "^18 || >=20" diff --git a/server/.env.example b/server/.env.example index ae5b7302..06e94291 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,3 +1,85 @@ +############ +# Secrets +# YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION +############ + +POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password +JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long +ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE +SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q +DASHBOARD_USERNAME=supabase +DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated + +############ +# Database - You can change these to any PostgreSQL database that has logical replication enabled. +############ + +POSTGRES_HOST=db +POSTGRES_DB=postgres +POSTGRES_PORT=5432 +# default user is postgres + +############ +# Supavisor -- Database pooler +############ +POOLER_PROXY_PORT_TRANSACTION=6543 +POOLER_DEFAULT_POOL_SIZE=20 +POOLER_MAX_CLIENT_CONN=100 +POOLER_TENANT_ID=your-tenant-id + + +############ +# API Proxy - Configuration for the Kong Reverse proxy. +############ + +KONG_HTTP_PORT=8001 +KONG_HTTPS_PORT=8443 + + +############ +# API - Configuration for PostgREST. +############ + +PGRST_DB_SCHEMAS=public,storage,graphql_public + +############ +# Studio - Configuration for the Dashboard +############ + +STUDIO_DEFAULT_ORGANIZATION=Default Organization +STUDIO_DEFAULT_PROJECT=Default Project + +STUDIO_PORT=3000 +# replace if you intend to use Studio outside of localhost +SUPABASE_PUBLIC_URL=http://localhost:8000 + +# Enable webp support +IMGPROXY_ENABLE_WEBP_DETECTION=true + +############ +# Functions - Configuration for Functions +############ +# NOTE: VERIFY_JWT applies to all functions. Per-function VERIFY_JWT is not supported yet. +FUNCTIONS_VERIFY_JWT=false + +############ +# Logs - Configuration for Logflare +# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction +############ + +LOGFLARE_LOGGER_BACKEND_API_KEY=your-super-secret-and-long-logflare-key + +# Change vector.toml sinks to reflect this change +LOGFLARE_API_KEY=your-super-secret-and-long-logflare-key + +# Docker socket location - this value will differ depending on your OS +DOCKER_SOCKET_LOCATION=/var/run/docker.sock + +# Google Cloud Project details +GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID +GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER + + # App Base Configures API_URL=http://localhost:8000 WEB_URL=http://localhost:3000 @@ -6,9 +88,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 diff --git a/server/main.py b/server/main.py index 0d4c82fc..793489de 100644 --- a/server/main.py +++ b/server/main.py @@ -71,11 +71,13 @@ def home_page(): @app.get("/api/health_checker") def health_checker(): + supabase_url = get_env_variable("SUPABASE_URL") return { "ENVIRONMENT": ENVIRONMENT, "API_URL": API_URL, "WEB_URL": WEB_URL, "CALLBACK_URL": CALLBACK_URL, + "supabase_url": supabase_url, } @@ -84,8 +86,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"))) From 5be93026dbb05a77907bf7f10e37ac334c4625d7 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Mon, 16 Dec 2024 18:24:48 +0800 Subject: [PATCH 29/46] feat: update doc --- docs/guides/self_hosted_local.md | 44 +++++++++++----- docs/guides/self_hosted_local_cn.md | 28 +++++++--- package.json | 2 +- server/.env.example | 82 ----------------------------- 4 files changed, 53 insertions(+), 103 deletions(-) diff --git a/docs/guides/self_hosted_local.md b/docs/guides/self_hosted_local.md index 57a71d68..c61de0df 100644 --- a/docs/guides/self_hosted_local.md +++ b/docs/guides/self_hosted_local.md @@ -1,5 +1,5 @@ ``` -# Self-Hosting Guide +# Self-Hosting ## Local Installation @@ -11,30 +11,45 @@ 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 `.env.example` Files +### Step 3: Copy the `.env.example` Files Copy the server environment configuration example file: ```bash -cp server/.env.example server/.env +cp server/.env.local.example server/.env ``` Copy the client environment configuration example file: ```bash -cp client/.env.example client/.env +cp client/.env.local.example client/.env ``` -### Step 4: Start Supabase Locally with Docker Compose +### Step 4: Start Supabase Locally + +Refer to [Supabase Self-Hosting Guide](https://supabase.com/docs/guides/self-hosting/docker#installing-and-running-supabase): ```bash -yarn run docker +# Get the code +git clone --depth 1 https://github.com/supabase/supabase + +# Go to the docker folder +cd supabase/docker + +# Copy the fake env vars +cp .env.example .env + +# Pull the latest images +docker compose pull + +# Start the services (in detached mode) +docker compose up -d ``` -### Step 5: Initialize the Database Schema +### Step 5: Initialize Database Schema #### Step 5.1: Navigate to the Migrations Folder Navigate to the `migrations` folder to prepare for database setup: @@ -44,20 +59,21 @@ cd migrations ``` #### Step 5.2: Install Supabase CLI -Install the Supabase CLI following the instructions in the [Supabase 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: Apply Migrations +#### Step 5.3: Run Migrations Apply the database migrations to your remote database: ```bash +# 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 will see output similar to the following: +If successful, you should see output similar to the following: ``` Connecting to remote database... @@ -73,10 +89,10 @@ Finished supabase db push. 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: Start the Client Start the client with the following command: @@ -85,5 +101,5 @@ Start the client with the following command: 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 2885f3b8..bee321f0 100644 --- a/docs/guides/self_hosted_local_cn.md +++ b/docs/guides/self_hosted_local_cn.md @@ -20,17 +20,32 @@ yarn run bootstrap 复制服务器环境配置示例文件: ```bash -cp server/.env.example server/.env +cp server/.env.local.example server/.env ``` 复制客户端环境配置示例文件: ```bash -cp client/.env.example client/.env +cp client/.env.local.example client/.env ``` -### 第四步:使用 docker compose 在本地启动 supabase +### 第四步:在本地启动 supabase -```bash -yarn run docker +参考 https://supabase.com/docs/guides/self-hosting/docker#installing-and-running-supabase + +``` +# Get the code +git clone --depth 1 https://github.com/supabase/supabase + +# Go to the docker folder +cd supabase/docker + +# Copy the fake env vars +cp .env.example .env + +# Pull the latest images +docker compose pull + +# Start the services (in detached mode) +docker compose up -d ``` ### 第五步:初始化数据库结构 @@ -53,6 +68,7 @@ brew install supabase/tap/supabase 将数据库迁移应用到您的远程数据库: ```bash +# 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" ``` @@ -72,7 +88,7 @@ Finished supabase db push. 使用以下命令启动服务器: ```bash -yarn run server +yarn run server:local ``` 通过在浏览器中打开 `http://127.0.0.1:8000/api/health_checker` 检查服务器是否正在运行。 diff --git a/package.json b/package.json index e2d038d7..275f381b 100644 --- a/package.json +++ b/package.json @@ -7,7 +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 && python3 -m uvicorn main:app --port 8001 --reload", + "server:local": "cd server && 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/server/.env.example b/server/.env.example index 06e94291..3fd11573 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,85 +1,3 @@ -############ -# Secrets -# YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION -############ - -POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password -JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long -ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE -SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q -DASHBOARD_USERNAME=supabase -DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated - -############ -# Database - You can change these to any PostgreSQL database that has logical replication enabled. -############ - -POSTGRES_HOST=db -POSTGRES_DB=postgres -POSTGRES_PORT=5432 -# default user is postgres - -############ -# Supavisor -- Database pooler -############ -POOLER_PROXY_PORT_TRANSACTION=6543 -POOLER_DEFAULT_POOL_SIZE=20 -POOLER_MAX_CLIENT_CONN=100 -POOLER_TENANT_ID=your-tenant-id - - -############ -# API Proxy - Configuration for the Kong Reverse proxy. -############ - -KONG_HTTP_PORT=8001 -KONG_HTTPS_PORT=8443 - - -############ -# API - Configuration for PostgREST. -############ - -PGRST_DB_SCHEMAS=public,storage,graphql_public - -############ -# Studio - Configuration for the Dashboard -############ - -STUDIO_DEFAULT_ORGANIZATION=Default Organization -STUDIO_DEFAULT_PROJECT=Default Project - -STUDIO_PORT=3000 -# replace if you intend to use Studio outside of localhost -SUPABASE_PUBLIC_URL=http://localhost:8000 - -# Enable webp support -IMGPROXY_ENABLE_WEBP_DETECTION=true - -############ -# Functions - Configuration for Functions -############ -# NOTE: VERIFY_JWT applies to all functions. Per-function VERIFY_JWT is not supported yet. -FUNCTIONS_VERIFY_JWT=false - -############ -# Logs - Configuration for Logflare -# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction -############ - -LOGFLARE_LOGGER_BACKEND_API_KEY=your-super-secret-and-long-logflare-key - -# Change vector.toml sinks to reflect this change -LOGFLARE_API_KEY=your-super-secret-and-long-logflare-key - -# Docker socket location - this value will differ depending on your OS -DOCKER_SOCKET_LOCATION=/var/run/docker.sock - -# Google Cloud Project details -GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID -GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER - - # App Base Configures API_URL=http://localhost:8000 WEB_URL=http://localhost:3000 From cf441a9ec982aa0ea00144f10984e6ac3e4ac637 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Mon, 16 Dec 2024 18:24:56 +0800 Subject: [PATCH 30/46] feat: update doc --- server/.env.local.example | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 server/.env.local.example diff --git a/server/.env.local.example b/server/.env.local.example new file mode 100644 index 00000000..b6dbf64f --- /dev/null +++ b/server/.env.local.example @@ -0,0 +1,40 @@ +# 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` field of https://supabase.com/dashboard/project/_/settings/database +SUPABASE_URL=${SUPABASE_PUBLIC_URL} +# `Project API keys`: `anon public` field of https://supabase.com/dashboard/project/_/settings/database +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 - 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} +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" +S3_TEMP_BUCKET_NAME=S3_TEMP_BUCKET_NAME From e9e9d54417aaff61d839436e283d61a28766ca03 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Mon, 16 Dec 2024 18:29:39 +0800 Subject: [PATCH 31/46] feat: update doc --- docs/guides/self_hosted_local.md | 26 ++++++++++++++------------ docs/guides/self_hosted_local_cn.md | 29 ++++++++++++++++------------- package.json | 3 +-- server/.env.local.example | 6 +++--- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/docs/guides/self_hosted_local.md b/docs/guides/self_hosted_local.md index c61de0df..345b7495 100644 --- a/docs/guides/self_hosted_local.md +++ b/docs/guides/self_hosted_local.md @@ -17,18 +17,7 @@ Install all necessary dependencies using Yarn: yarn run bootstrap ``` -### Step 3: Copy the `.env.example` Files -Copy the server environment configuration example file: - -```bash -cp server/.env.local.example server/.env -``` -Copy the client environment configuration example file: -```bash -cp client/.env.local.example client/.env -``` - -### Step 4: Start Supabase Locally +### Step 3: Start Supabase Locally Refer to [Supabase Self-Hosting Guide](https://supabase.com/docs/guides/self-hosting/docker#installing-and-running-supabase): @@ -49,6 +38,19 @@ docker compose pull docker compose up -d ``` +### Step 4: Copy the `.env.example` Files +Copy the client environment configuration example file: +```bash +cp client/.env.local.example client/.env +``` + +Copy the server environment configuration example file: +```bash +cp server/.env.local.example server/.env +``` + +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 diff --git a/docs/guides/self_hosted_local_cn.md b/docs/guides/self_hosted_local_cn.md index bee321f0..322ef118 100644 --- a/docs/guides/self_hosted_local_cn.md +++ b/docs/guides/self_hosted_local_cn.md @@ -16,18 +16,7 @@ git clone https://github.com/petercat-ai/petercat.git yarn run bootstrap ``` -### 第三步:复制 `.env.example` 文件 -复制服务器环境配置示例文件: - -```bash -cp server/.env.local.example server/.env -``` -复制客户端环境配置示例文件: -```bash -cp client/.env.local.example client/.env -``` - -### 第四步:在本地启动 supabase +### 第三步:在本地启动 supabase 参考 https://supabase.com/docs/guides/self-hosting/docker#installing-and-running-supabase @@ -48,6 +37,20 @@ docker compose pull docker compose up -d ``` +### 第四步:复制 `.env.example` 文件 +复制客户端环境配置示例文件: +```bash +cp client/.env.local.example client/.env +``` + +复制服务器环境配置示例文件: + +```bash +cp server/.env.local.example server/.env +``` + +打开 `server/.env` 文件,把 `SERVICE_ROLE_KEY` 字段改成从 supabase 的 `docker/.env` 文件的 `SERVICE_ROLE_KEY` 的值 + ### 第五步:初始化数据库结构 #### 第五步 5.1:导航到 Migrations 文件夹 @@ -91,7 +94,7 @@ Finished supabase db push. 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/package.json b/package.json index 275f381b..d79236da 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,7 @@ "build:docker": "docker build -t petercat .", "build:pypi": "rm -rf dist && python3 -m build", "publish:test": "python3 -m twine upload --repository petercat-utils dist/* ", - "publish:pypi": "python3 -m twine upload --repository pypi dist/* ", - "docker": "docker compose --env-file server/.env -f docker/docker-compose.yml up -d" + "publish:pypi": "python3 -m twine upload --repository pypi dist/* " }, "engines": { "node": "^18 || >=20" diff --git a/server/.env.local.example b/server/.env.local.example index b6dbf64f..54648bc6 100644 --- a/server/.env.local.example +++ b/server/.env.local.example @@ -5,9 +5,9 @@ WEB_URL=http://localhost:3000 STATIC_URL=STATIC_URL FASTAPI_SECRET_KEY=fastapi_secret_key -# `Project URL` field of https://supabase.com/dashboard/project/_/settings/database -SUPABASE_URL=${SUPABASE_PUBLIC_URL} -# `Project API keys`: `anon public` field of https://supabase.com/dashboard/project/_/settings/database +# `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 From 7e00c8a86a030120812d851d5ed8e952b900bdef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=86=E6=B2=89?= Date: Mon, 16 Dec 2024 22:19:33 +0800 Subject: [PATCH 32/46] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d79236da..669c086e 100644 --- a/package.json +++ b/package.json @@ -7,7 +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 && python3 -m uvicorn main:app --port 8001 --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\"", From 594eaf273764c6a5198b30b3164c159c883459f2 Mon Sep 17 00:00:00 2001 From: afc163 Date: Tue, 17 Dec 2024 14:38:01 +0800 Subject: [PATCH 33/46] style: put further assistance text in
(#592) --- server/agent/prompts/issue_helper.py | 6 ++++++ server/agent/prompts/pull_request.py | 6 ++++++ 2 files changed, 12 insertions(+) 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. +
""" From 6d2e401dceb5fa19b1f56fdb1f86da042be4c65a Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Tue, 17 Dec 2024 17:05:18 +0800 Subject: [PATCH 34/46] feat: support locally auth --- server/.env.example | 6 +++ server/.env.local.example | 6 +++ server/auth/clients/__init__.py | 12 +++++ server/auth/clients/auth0.py | 92 +++++++++++++++++++++++++++++++++ server/auth/clients/base.py | 51 ++++++++++++++++++ server/auth/clients/local.py | 38 ++++++++++++++ server/auth/get_oauth_token.py | 22 -------- server/auth/get_user_info.py | 74 ++------------------------ server/auth/rate_limit.py | 14 ++--- server/auth/router.py | 90 +++++++------------------------- 10 files changed, 235 insertions(+), 170 deletions(-) create mode 100644 server/auth/clients/__init__.py create mode 100644 server/auth/clients/auth0.py create mode 100644 server/auth/clients/base.py create mode 100644 server/auth/clients/local.py delete mode 100644 server/auth/get_oauth_token.py diff --git a/server/.env.example b/server/.env.example index 3fd11573..09b1373b 100644 --- a/server/.env.example +++ b/server/.env.example @@ -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 diff --git a/server/.env.local.example b/server/.env.local.example index 54648bc6..2e2460ab 100644 --- a/server/.env.local.example +++ b/server/.env.local.example @@ -23,6 +23,12 @@ 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" + +# OPTIONAL - SKIP AUTH0 Authorization +PETERCAT_AUTH0_ENABLED=True # OPTIONAL - AUTH0 Configures API_IDENTIFIER=api_identifier 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..f6b2a634 --- /dev/null +++ b/server/auth/clients/local.py @@ -0,0 +1,38 @@ +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") +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(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, + } + 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 7b587108..64fff60f 100644 --- a/server/auth/get_user_info.py +++ b/server/auth/get_user_info.py @@ -1,80 +1,14 @@ from fastapi import Request -import httpx -import secrets +from auth.clients import get_auth_client 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 - + auth_client = get_auth_client() + return await auth_client.get_access_token(user_id=user_id, provider=provider) async def get_user_id(request: Request): user_info = request.session.get("user") 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 240d9d40..af22dd78 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -1,47 +1,25 @@ -import secrets from typing import Annotated, Optional -from authlib.integrations.starlette_client import OAuth -from fastapi import APIRouter, Request, HTTPException, status, Depends +from fastapi import APIRouter, Request, HTTPException, Depends from fastapi.responses import RedirectResponse, JSONResponse from github import Github -from starlette.config import Config -from auth.get_user_info import generateAnonymousUser, getUserInfoByToken, get_user_id -from auth.get_user_info import ( - getUserAccessToken, -) + +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 -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") - API_URL = get_env_variable("API_URL") +WEB_URL = get_env_variable("WEB_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", @@ -49,32 +27,9 @@ 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 - +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): @@ -86,31 +41,21 @@ async def logout(request: Request): @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"]) - +async def callback(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)): + user_info = await auth_client.get_user_info(request) + print(f"user_info={user_info}") 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() + 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): +async def userinfo(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)): user = request.session.get("user") if not user: - data = await getAnonymousUser(request) + data = await auth_client.anonymouseLogin(request) return {"data": data, "status": 200} return {"data": user, "status": 200} @@ -155,7 +100,8 @@ async def get_user_repos(user_id: Optional[str] = Depends(get_user_id)): if not user_id: raise HTTPException(status_code=401, detail="User not found") try: - access_token = await getUserAccessToken(user_id=user_id) + client = get_auth_client() + access_token = await client.get_access_token(user_id=user_id) g = Github(access_token) user = g.get_user() repos = user.get_repos() From c4dbcb514e2530046a5dd88fe3594cb51829e084 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Tue, 17 Dec 2024 17:09:22 +0800 Subject: [PATCH 35/46] feat: support locally auth --- server/auth/get_user_info.py | 11 ++++------- server/auth/router.py | 6 ++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/server/auth/get_user_info.py b/server/auth/get_user_info.py index 64fff60f..168f6c07 100644 --- a/server/auth/get_user_info.py +++ b/server/auth/get_user_info.py @@ -1,15 +1,12 @@ -from fastapi import Request +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 petercat_utils import get_env_variable AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") -async def getUserAccessToken(user_id: str, provider="github"): - auth_client = get_auth_client() - return await auth_client.get_access_token(user_id=user_id, provider=provider) - async def get_user_id(request: Request): user_info = request.session.get("user") try: @@ -21,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 @@ -29,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/router.py b/server/auth/router.py index af22dd78..d9fc65db 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -43,7 +43,6 @@ async def logout(request: Request): @router.get("/callback") async def callback(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)): user_info = await auth_client.get_user_info(request) - print(f"user_info={user_info}") if user_info: request.session["user"] = dict(user_info) supabase = get_client() @@ -96,12 +95,11 @@ async def bot_generator( @router.get("/repos") -async def get_user_repos(user_id: Optional[str] = Depends(get_user_id)): +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: - client = get_auth_client() - access_token = await client.get_access_token(user_id=user_id) + access_token = await auth_client.get_access_token(user_id=user_id) g = Github(access_token) user = g.get_user() repos = user.get_repos() From 178c432164dc96b3de2eb24a3b870f4c0856de04 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Tue, 17 Dec 2024 17:19:12 +0800 Subject: [PATCH 36/46] feat: support locally auth --- server/.env.local.example | 1 + server/auth/clients/local.py | 6 +++++- server/main.py | 4 +--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/server/.env.local.example b/server/.env.local.example index 2e2460ab..390407c6 100644 --- a/server/.env.local.example +++ b/server/.env.local.example @@ -26,6 +26,7 @@ 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 diff --git a/server/auth/clients/local.py b/server/auth/clients/local.py index f6b2a634..da52f1fc 100644 --- a/server/auth/clients/local.py +++ b/server/auth/clients/local.py @@ -6,6 +6,7 @@ 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" @@ -21,7 +22,7 @@ async def login(self, request: Request): return RedirectResponse(url=f"{WEB_LOGIN_SUCCESS_URL}", status_code=302) - async def get_user_info(user_id): + async def get_user_info(self, user_id): token = PETERCAT_LOCAL_UID username = PETERCAT_LOCAL_UNAME seed = token[:4] @@ -36,3 +37,6 @@ async def get_user_info(user_id): "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/main.py b/server/main.py index 793489de..6089da19 100644 --- a/server/main.py +++ b/server/main.py @@ -71,13 +71,11 @@ def home_page(): @app.get("/api/health_checker") def health_checker(): - supabase_url = get_env_variable("SUPABASE_URL") return { "ENVIRONMENT": ENVIRONMENT, "API_URL": API_URL, "WEB_URL": WEB_URL, - "CALLBACK_URL": CALLBACK_URL, - "supabase_url": supabase_url, + "CALLBACK_URL": CALLBACK_URL } From 719dab21a9e40088cac6b1cec17741ade5515e80 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Tue, 17 Dec 2024 18:05:37 +0800 Subject: [PATCH 37/46] feat: support locally auth --- assistant/package.json | 2 +- assistant/src/Chat/template/LoginCard.tsx | 4 ++-- assistant/src/Chat/template/index.tsx | 8 ++++++-- assistant/src/hooks/useUser.ts | 5 +++-- assistant/src/style.css | 24 +++++++++++------------ client/app/hooks/useUser.ts | 1 + client/components/User.tsx | 1 - client/package.json | 2 +- client/yarn.lock | 8 ++++---- 9 files changed, 30 insertions(+), 25 deletions(-) diff --git a/assistant/package.json b/assistant/package.json index 86c23827..952b86c3 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -1,6 +1,6 @@ { "name": "@petercatai/assistant", - "version": "1.0.20", + "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/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/style.css b/assistant/src/style.css index fb35752c..db155add 100644 --- a/assistant/src/style.css +++ b/assistant/src/style.css @@ -112,8 +112,8 @@ */ *:where(.petercat-lui,.petercat-lui *), -:where(.petercat-lui,.petercat-lui *)::before, -:where(.petercat-lui,.petercat-lui *)::after { +::before:where(.petercat-lui,.petercat-lui *), +::after:where(.petercat-lui,.petercat-lui *) { box-sizing: border-box; /* 1 */ border-width: 0; @@ -124,8 +124,8 @@ /* 2 */ } -:where(.petercat-lui,.petercat-lui *)::before, -:where(.petercat-lui,.petercat-lui *)::after { +::before:where(.petercat-lui,.petercat-lui *), +::after:where(.petercat-lui,.petercat-lui *) { --tw-content: ''; } @@ -378,8 +378,8 @@ progress:where(.petercat-lui,.petercat-lui *) { Correct the cursor style of increment and decrement buttons in Safari. */ -:where(.petercat-lui,.petercat-lui *) ::-webkit-inner-spin-button, -:where(.petercat-lui,.petercat-lui *) ::-webkit-outer-spin-button { +::-webkit-inner-spin-button:where(.petercat-lui,.petercat-lui *), +::-webkit-outer-spin-button:where(.petercat-lui,.petercat-lui *) { height: auto; } @@ -399,7 +399,7 @@ Correct the cursor style of increment and decrement buttons in Safari. Remove the inner padding in Chrome and Safari on macOS. */ -:where(.petercat-lui,.petercat-lui *) ::-webkit-search-decoration { +::-webkit-search-decoration:where(.petercat-lui,.petercat-lui *) { -webkit-appearance: none; } @@ -408,7 +408,7 @@ Remove the inner padding in Chrome and Safari on macOS. 2. Change font properties to `inherit` in Safari. */ -:where(.petercat-lui,.petercat-lui *) ::-webkit-file-upload-button { +::-webkit-file-upload-button:where(.petercat-lui,.petercat-lui *) { -webkit-appearance: button; /* 1 */ font: inherit; @@ -481,15 +481,15 @@ textarea:where(.petercat-lui,.petercat-lui *) { 2. Set the default placeholder color to the user's configured gray 400 color. */ -:where(.petercat-lui,.petercat-lui *) input::-moz-placeholder, :where(.petercat-lui,.petercat-lui *) textarea::-moz-placeholder { +input::-moz-placeholder:where(.petercat-lui,.petercat-lui *), textarea::-moz-placeholder:where(.petercat-lui,.petercat-lui *) { opacity: 1; /* 1 */ color: #9ca3af; /* 2 */ } -:where(.petercat-lui,.petercat-lui *) input::placeholder, -:where(.petercat-lui,.petercat-lui *) textarea::placeholder { +input::placeholder:where(.petercat-lui,.petercat-lui *), +textarea::placeholder:where(.petercat-lui,.petercat-lui *) { opacity: 1; /* 1 */ color: #9ca3af; @@ -545,7 +545,7 @@ video:where(.petercat-lui,.petercat-lui *) { /* Make elements with the HTML hidden attribute stay hidden by default */ -[hidden]:where(:not([hidden="until-found"])):where(.petercat-lui,.petercat-lui *) { +[hidden]:where(.petercat-lui,.petercat-lui *) { display: none; } diff --git a/client/app/hooks/useUser.ts b/client/app/hooks/useUser.ts index 53099261..2099af71 100644 --- a/client/app/hooks/useUser.ts +++ b/client/app/hooks/useUser.ts @@ -10,6 +10,7 @@ export const useUser = () => { const { data: fingerprint } = useFingerprint(); const { user, isLoading, actions } = useAssistUser({ apiDomain: API_DOMAIN, + webDomain: '', fingerprint: fingerprint?.visitorId!, }); diff --git a/client/components/User.tsx b/client/components/User.tsx index 0677a2b3..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, diff --git a/client/package.json b/client/package.json index 9aeb5350..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.20", + "@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/yarn.lock b/client/yarn.lock index 7278dc7b..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.20": - version "1.0.20" - resolved "https://registry.yarnpkg.com/@petercatai/assistant/-/assistant-1.0.20.tgz#2d2dc1beb296c8524219a6de7eee1575cb3b4c92" - integrity sha512-csfRRsKB9FbBM+cMcCTQQowsuuFRVerSrxfMRTWoI1XHhBW3ormbt1XTeYKiubmwz4iKznR+2UCrZrCl75ckmA== +"@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" From feadeda23f680898cb1dba24dfb89e0a3107066d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Tue, 17 Dec 2024 18:51:14 +0800 Subject: [PATCH 38/46] chore: reorder the nav tab (#594) --- client/app/page.tsx | 2 +- client/components/HomeHeader.tsx | 2 +- client/components/Navbar.tsx | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/app/page.tsx b/client/app/page.tsx index 999bbca2..e035894e 100644 --- a/client/app/page.tsx +++ b/client/app/page.tsx @@ -245,7 +245,7 @@ export default function Homepage() {

{I18N.app.page.liJiChangShi} diff --git a/client/components/HomeHeader.tsx b/client/components/HomeHeader.tsx index 8d659b2f..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} diff --git a/client/components/Navbar.tsx b/client/components/Navbar.tsx index 07ad891a..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')) { From 0e81fc00ec40b2a80e00abae095910494d60cbf2 Mon Sep 17 00:00:00 2001 From: yingying Date: Wed, 18 Dec 2024 16:51:10 +0800 Subject: [PATCH 39/46] refactor: reorganize the query list api --- server/bot/list.py | 52 ++++++++++++++++++++++++++++++++++++++++++++ server/bot/router.py | 38 +++----------------------------- 2 files changed, 55 insertions(+), 35 deletions(-) create mode 100644 server/bot/list.py diff --git a/server/bot/list.py b/server/bot/list.py new file mode 100644 index 00000000..788e39f4 --- /dev/null +++ b/server/bot/list.py @@ -0,0 +1,52 @@ +from typing import Optional + +from github import Github +from core.dao.repositoryConfigDAO import RepositoryConfigDAO +from petercat_utils import get_client +from github import Github, Auth + + +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.table("bots").select( + "id, created_at, updated_at, avatar, description, name, public, starters, uid, repo_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) + + if name: + query = query.filter("name", "like", f"%{name}%") + + query = query.order("updated_at", desc=True) + data = query.execute() + 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 4bd489ed..8eb0e6ee 100644 --- a/server/bot/router.py +++ b/server/bot/router.py @@ -3,6 +3,7 @@ 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 @@ -32,42 +33,9 @@ def get_bot_list( 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, repo_name" - ) - - if personal == "true": - if not user or not user.access_token: - return {"data": [], "personal": personal} - - 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] - ) - - 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) - - if name: - query = query.filter("name", "like", f"%{name}%") - - query = query.order("updated_at", desc=True) - data = query.execute() + data = query_list(name, user.id, user.access_token, personal) - return {"data": data.data if data and data.data else [], "personal": personal} + return {"data": data if data else [], "personal": personal} except Exception as e: return JSONResponse( From 4c0d36a0dbb1428103b89aa40ece81cd6e760960 Mon Sep 17 00:00:00 2001 From: yingying Date: Wed, 18 Dec 2024 17:17:31 +0800 Subject: [PATCH 40/46] feat: add join selection for the list query --- server/bot/list.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/bot/list.py b/server/bot/list.py index 788e39f4..b6e76207 100644 --- a/server/bot/list.py +++ b/server/bot/list.py @@ -14,8 +14,12 @@ def query_list( ): try: supabase = get_client() - query = supabase.table("bots").select( - "id, created_at, updated_at, avatar, description, name, public, starters, uid, repo_name" + 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 personal == "true": @@ -47,6 +51,12 @@ def query_list( 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}") From fd509b761a97fbc3440d598f328e3ee6699b239e Mon Sep 17 00:00:00 2001 From: yingying Date: Wed, 18 Dec 2024 17:28:26 +0800 Subject: [PATCH 41/46] refactory: remove usless request --- client/app/factory/list/components/BotCard.tsx | 17 +++++++++-------- server/bot/list.py | 5 ++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/app/factory/list/components/BotCard.tsx b/client/app/factory/list/components/BotCard.tsx index 6533480f..45bf6814 100644 --- a/client/app/factory/list/components/BotCard.tsx +++ b/client/app/factory/list/components/BotCard.tsx @@ -17,11 +17,7 @@ import { Tooltip, } from '@nextui-org/react'; import { useRouter } from 'next/navigation'; -import { - useBotDelete, - useGetBotBoundRepos, - useGetBotRagTask, -} from '@/app/hooks/useBot'; +import { useBotDelete, useGetBotRagTask } from '@/app/hooks/useBot'; import { TaskStatus } from '@/types/task'; import ErrorBadgeIcon from '@/public/icons/ErrorBadgeIcon'; import KnowledgeTaskCompleteIcon from '@/public/icons/CheckBadgeIcon'; @@ -33,12 +29,17 @@ import CardCartIcon from '@/public/icons/CardCartIcon'; declare type Bot = Tables<'bots'>; -const BotInfoIconList = (props: { bot: Bot }) => { +interface BotInfo extends Bot { + github_installed?: boolean; + picture?: string; + nickname?: string; +} + +const BotInfoIconList = (props: { bot: BotInfo }) => { const { bot } = props; - const { data } = useGetBotBoundRepos(bot.id); const showHomeIcon = bot.domain_whitelist && bot.domain_whitelist.length > 0; const showCartIcon = bot.public; - const showGithubIcon = data && Array.isArray(data) && data.length > 0; + const showGithubIcon = bot.github_installed; const texts = [ showGithubIcon ? I18N.components.BotCard.gITHU : '', showHomeIcon ? I18N.components.BotCard.guanWang : undefined, diff --git a/server/bot/list.py b/server/bot/list.py index b6e76207..f0b5638e 100644 --- a/server/bot/list.py +++ b/server/bot/list.py @@ -21,6 +21,8 @@ def query_list( "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: @@ -46,9 +48,6 @@ def query_list( else: query = query.eq("public", True) - if name: - query = query.filter("name", "like", f"%{name}%") - query = query.order("updated_at", desc=True) data = query.execute() if data.data: From 320fa715e10a66a388fcef5286a84cdf43ce0074 Mon Sep 17 00:00:00 2001 From: yingying Date: Wed, 18 Dec 2024 17:38:23 +0800 Subject: [PATCH 42/46] chore: sync the db --- .../20241218093541_remote_schema.sql | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 migrations/supabase/migrations/20241218093541_remote_schema.sql 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$ +; + + From eb794c84b28e1aa0fe1f141f2f980f8def01d572 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Wed, 18 Dec 2024 20:24:07 +0800 Subject: [PATCH 43/46] refactor: support run without aws fix: typo fix: typo --- .github/workflows/aws-preview.yml | 10 +++++----- .github/workflows/aws-prod.yml | 10 +++++----- README.en-US.md | 10 +++++----- README.ja-JP.md | 10 +++++----- README.md | 10 +++++----- docs/guides/self_hosted_aws.md | 2 +- docs/guides/self_hosted_aws_cn.md | 2 +- petercat_utils/README.md | 10 +++++----- server/.env.example | 10 +++++----- server/.env.local.example | 10 +++++----- server/aws/service.py | 14 +++++++------- server/bot/list.py | 3 +-- server/core/service/user_llm_token.py | 12 ++++++------ server/github_app/router.py | 8 +++----- server/github_app/utils.py | 8 ++++---- server/utils/get_private_key.py | 14 -------------- server/utils/private_key/__init__.py | 9 +++++++++ server/utils/private_key/base.py | 4 ++++ server/utils/private_key/local.py | 6 ++++++ server/utils/private_key/s3.py | 20 ++++++++++++++++++++ template.yml | 10 +++++----- 21 files changed, 107 insertions(+), 85 deletions(-) delete mode 100644 server/utils/get_private_key.py create mode 100644 server/utils/private_key/__init__.py create mode 100644 server/utils/private_key/base.py create mode 100644 server/utils/private_key/local.py create mode 100644 server/utils/private_key/s3.py diff --git a/.github/workflows/aws-preview.yml b/.github/workflows/aws-preview.yml index 8d2a3d76..14b0549c 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 }} \ + AWSGithubSecretName=${{ secrets.X_GITHUB_SECRET_NAME }} \ + AWSStaticSecretName=${{ secrets.STATIC_SECRET_NAME }} \ + AWSLLMTokenSecretName=${{ vars.LLM_TOKEN_SECRET_NAME }} \ + AWSLLMTokenPublicName=${{ vars.LLM_TOKEN_PUBLIC_NAME }} \ + AWSStaticKeyPairId=${{ 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..a84ba7fd 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 }} \ + AWSGithubSecretName=${{ secrets.X_GITHUB_SECRET_NAME }} \ + AWSStaticSecretName=${{ secrets.STATIC_SECRET_NAME }} \ + AWSLLMTokenSecretName=${{ vars.LLM_TOKEN_SECRET_NAME }} \ + AWSLLMTokenPublicName=${{ vars.LLM_TOKEN_PUBLIC_NAME }} \ + AWSStaticKeyPairId=${{ 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/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/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/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/server/.env.example b/server/.env.example index 09b1373b..b5e86f1f 100644 --- a/server/.env.example +++ b/server/.env.example @@ -38,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 index 390407c6..a0354e2d 100644 --- a/server/.env.local.example +++ b/server/.env.local.example @@ -39,9 +39,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/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 index f0b5638e..040582a8 100644 --- a/server/bot/list.py +++ b/server/bot/list.py @@ -1,9 +1,8 @@ from typing import Optional -from github import Github +from github import Github, Auth from core.dao.repositoryConfigDAO import RepositoryConfigDAO from petercat_utils import get_client -from github import Github, Auth def query_list( 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/github_app/router.py b/server/github_app/router.py index 981ddf94..a05fb1d7 100644 --- a/server/github_app/router.py +++ b/server/github_app/router.py @@ -25,8 +25,8 @@ ) 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") logger = logging.getLogger() @@ -64,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: diff --git a/server/github_app/utils.py b/server/github_app/utils.py index 2b727fed..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() ) 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/template.yml b/template.yml index 70133959..19f05650 100644 --- a/template.yml +++ b/template.yml @@ -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 AWSGithubSecretName + STATIC_SECRET_NAME: !Ref AWSStaticSecretName + LLM_TOKEN_SECRET_NAME: !Ref AWSLLMTokenSecretName + LLM_TOKEN_PUBLIC_NAME: !Ref AWSLLMTokenPublicName + STATIC_KEYPAIR_ID: !Ref AWSStaticKeyPairId S3_TEMP_BUCKET_NAME: !Ref S3TempBucketName API_URL: !Ref APIUrl WEB_URL: !Ref WebUrl From 6259c272e31b1a007d6103276d238415c3ecacb2 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Thu, 19 Dec 2024 11:40:53 +0800 Subject: [PATCH 44/46] refactor: env names --- .github/workflows/aws-preview.yml | 10 +++++----- .github/workflows/aws-prod.yml | 10 +++++----- template.yml | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/aws-preview.yml b/.github/workflows/aws-preview.yml index 14b0549c..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.X_GITHUB_SECRET_NAME }} \ - AWSStaticSecretName=${{ secrets.STATIC_SECRET_NAME }} \ - AWSLLMTokenSecretName=${{ vars.LLM_TOKEN_SECRET_NAME }} \ - AWSLLMTokenPublicName=${{ vars.LLM_TOKEN_PUBLIC_NAME }} \ - AWSStaticKeyPairId=${{ secrets.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 a84ba7fd..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.X_GITHUB_SECRET_NAME }} \ - AWSStaticSecretName=${{ secrets.STATIC_SECRET_NAME }} \ - AWSLLMTokenSecretName=${{ vars.LLM_TOKEN_SECRET_NAME }} \ - AWSLLMTokenPublicName=${{ vars.LLM_TOKEN_PUBLIC_NAME }} \ - AWSStaticKeyPairId=${{ secrets.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/template.yml b/template.yml index 19f05650..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 - X_GITHUB_SECRET_NAME: !Ref AWSGithubSecretName - STATIC_SECRET_NAME: !Ref AWSStaticSecretName - LLM_TOKEN_SECRET_NAME: !Ref AWSLLMTokenSecretName - LLM_TOKEN_PUBLIC_NAME: !Ref AWSLLMTokenPublicName - 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 From de7c8900cd56883223ff3b5965788157cd5e204d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Thu, 19 Dec 2024 15:45:18 +0800 Subject: [PATCH 45/46] Update quick_ assistant_start_cn.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 缨缨 --- docs/guides/quick_ assistant_start_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() { From f5443bafc338e164a953e057a0cafbb532e84151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=A8=E7=BC=A8?= Date: Thu, 19 Dec 2024 15:45:46 +0800 Subject: [PATCH 46/46] Update quick_ assistant_start.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 缨缨 --- docs/guides/quick_ assistant_start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() {