├── server ├── __init__.py ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── test_main.py │ └── mock_session.py ├── agent │ ├── llm │ │ ├── clients │ │ │ ├── __init__.py │ │ │ └── openai.py │ │ └── base.py │ ├── tools │ │ ├── helper.py │ │ ├── auth.py │ │ └── git_info.py │ └── bot │ │ ├── bot_builder.py │ │ ├── __init__.py │ │ └── get_bot.py ├── requirements-dev.txt ├── utils │ ├── private_key │ │ ├── base.py │ │ ├── local.py │ │ ├── __init__.py │ │ └── s3.py │ ├── random_str.py │ ├── supabase.py │ ├── env.py │ ├── fuzzy_match.py │ ├── path_to_hunk.py │ ├── rsa.py │ └── sanitize_token.py ├── core │ ├── dao │ │ ├── BaseDAO.py │ │ ├── botDAO.py │ │ └── llmTokenDAO.py │ ├── models │ │ ├── profiles.py │ │ ├── user.py │ │ ├── user_llm_token.py │ │ ├── llm_token.py │ │ ├── repository.py │ │ ├── bot.py │ │ ├── authorization.py │ │ ├── bot_approval.py │ │ └── user_token_usage.py │ └── type_class │ │ └── bot.py ├── README.md ├── i18n │ ├── zh-CN.json │ ├── ko.json │ ├── ja.json │ ├── en.json │ └── zh-TW.json ├── pytest.ini ├── aws │ ├── exceptions.py │ ├── schemas.py │ ├── dependencies.py │ ├── constants.py │ └── router.py ├── setup_python.sh ├── env.py ├── github_app │ └── purchased.py ├── auth │ ├── verify_admin.py │ ├── clients │ │ └── __init__.py │ ├── get_user_info.py │ └── cors_middleware.py ├── requirements.txt ├── insight │ └── service │ │ ├── overview.py │ │ └── pr.py ├── .env.local.example └── .env.example ├── migrations ├── roles.sql ├── supabase │ ├── seed.sql │ ├── database.types.ts │ ├── .gitignore │ └── migrations │ │ ├── 20241015090438_remote_schema.sql │ │ ├── 20240913114522_remote_schema.sql │ │ ├── 20240912023152_remote_schema.sql │ │ └── 20241224030938_remote_schema.sql └── .env.example ├── client ├── database.types.ts ├── .eslintrc.json ├── .kiwi │ ├── ja │ │ ├── chunk.ts │ │ ├── utils.ts │ │ ├── release.ts │ │ ├── index.ts │ │ ├── edit.ts │ │ ├── DeployBotModal.ts │ │ └── app.ts │ ├── en │ │ ├── chunk.ts │ │ ├── utils.ts │ │ ├── index.ts │ │ ├── release.ts │ │ └── edit.ts │ ├── ko │ │ ├── chunk.ts │ │ ├── utils.ts │ │ ├── release.ts │ │ ├── index.ts │ │ ├── edit.ts │ │ └── DeployBotModal.ts │ ├── zh-CN │ │ ├── chunk.ts │ │ ├── utils.ts │ │ ├── release.ts │ │ ├── index.ts │ │ ├── edit.ts │ │ ├── DeployBotModal.ts │ │ └── app.ts │ └── zh-TW │ │ ├── chunk.ts │ │ ├── utils.ts │ │ ├── release.ts │ │ ├── index.ts │ │ ├── edit.ts │ │ ├── DeployBotModal.ts │ │ └── app.ts ├── public │ ├── images │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── footer-contribution.png │ │ ├── chevron-up.svg │ │ ├── chevron-down.svg │ │ ├── icon-gemini.svg │ │ ├── icon-menu.svg │ │ ├── icon-close.svg │ │ ├── x.svg │ │ ├── bg.svg │ │ ├── create.svg │ │ ├── statistic.svg │ │ ├── icon-tavily.svg │ │ ├── icon-supabase.svg │ │ ├── chat.svg │ │ └── knowledge.svg │ └── icons │ │ ├── PlusIcon.tsx │ │ ├── MinusIcon.tsx │ │ ├── AppendixIcon.tsx │ │ ├── BackIcon.tsx │ │ ├── ChevronDownIcon.tsx │ │ ├── PlayIcon.tsx │ │ ├── CloudIcon.tsx │ │ ├── StarIcon.tsx │ │ ├── MinusCircleIcon.tsx │ │ ├── MenuIcon.tsx │ │ ├── AddIcon.tsx │ │ ├── SaveIcon.tsx │ │ ├── GitHubIcon.tsx │ │ ├── LoadingIcon.tsx │ │ ├── ErrorBadgeIcon.tsx │ │ ├── BookIcon.tsx │ │ ├── ChatIcon.tsx │ │ ├── HomeIcon.tsx │ │ ├── BulbIcon.tsx │ │ ├── SearchIcon.tsx │ │ ├── LangIcon.tsx │ │ ├── AIBtnIcon.tsx │ │ ├── SpaceIcon.tsx │ │ ├── ConfigIcon.tsx │ │ ├── CheckBadgeIcon.tsx │ │ ├── CardHomeIcon.tsx │ │ ├── LoginIcon.tsx │ │ └── DeleteIcon.tsx ├── types │ ├── declarations.d.ts │ └── task.ts ├── .env.example ├── postcss.config.js ├── .env.local.example ├── app │ ├── utils │ │ ├── url.ts │ │ ├── time.ts │ │ ├── I18N.ts │ │ └── tools.ts │ ├── hooks │ │ ├── useAvailableLLMs.ts │ │ ├── useFingerprint.ts │ │ ├── useAgreement.ts │ │ ├── useAnalyze.ts │ │ ├── useToken.ts │ │ └── useUser.ts │ ├── interface │ │ └── index.ts │ ├── factory │ │ └── edit │ │ │ └── components │ │ │ ├── DeployBotModal │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ │ ├── Collapse.tsx │ │ │ ├── TaskContext.tsx │ │ │ └── PublicSwitcher.tsx │ ├── global-error.tsx │ ├── knowledge │ │ ├── page.tsx │ │ └── chunk │ │ │ └── page.tsx │ ├── constant │ │ └── avatar.ts │ ├── user │ │ ├── tokens │ │ │ └── components │ │ │ │ └── CreateButton.tsx │ │ └── login │ │ │ └── page.tsx │ ├── policy │ │ └── page.tsx │ ├── contexts │ │ └── BotContext.tsx │ ├── agreement │ │ └── page.tsx │ └── services │ │ ├── TokensController.ts │ │ └── UserController.ts ├── instrumentation.ts ├── share │ └── supabas-client.ts ├── .prettierrc.js ├── components │ ├── GitHubStars.tsx │ ├── BaseBotCard.tsx │ ├── AddBotCard.tsx │ ├── Crash.tsx │ ├── PublishBotCard.tsx │ ├── Spinner.tsx │ ├── Markdown.tsx │ ├── BotItem.tsx │ └── FullPageSkeleton.tsx ├── kiwi-config.json ├── sentry.edge.config.js ├── sentry.server.config.js ├── tsconfig.json ├── sentry.client.config.js ├── next.config.js └── tailwind.config.js ├── .vercelignore ├── assistant ├── .prettierignore ├── .stylelintrc ├── src │ ├── mock │ │ ├── index.ts │ │ ├── bot.mock.ts │ │ └── inputArea.mock.ts │ ├── utils │ │ ├── index.ts │ │ ├── material.ts │ │ └── popcenter.ts │ ├── interface │ │ ├── index.ts │ │ └── model.interface.ts │ ├── types │ │ └── declarations.d.ts │ ├── Chat │ │ ├── components │ │ │ ├── MarkdownRender.tsx │ │ │ ├── LoadingStart.tsx │ │ │ ├── MySpinner.tsx │ │ │ ├── LoadingEnd.tsx │ │ │ ├── UserContent.tsx │ │ │ └── ChatItemRender.tsx │ │ └── template │ │ │ ├── GitInsightCard.tsx │ │ │ ├── index.tsx │ │ │ └── LoginCard.tsx │ ├── icons │ │ ├── StopMessageIcon.tsx │ │ ├── DeleteIcon.tsx │ │ ├── ThunderIcon.tsx │ │ ├── GitHubIcon.tsx │ │ ├── UploadImageIcon.tsx │ │ └── NewMessageIcon.tsx │ ├── ChartHeader │ │ └── index.tsx │ ├── services │ │ └── UserController.ts │ ├── GitInsight │ │ ├── components │ │ │ ├── CountCard.tsx │ │ │ └── GitInsightIcon.tsx │ │ └── index.md │ ├── Theme │ │ └── index.ts │ ├── Markdown │ │ ├── CopyButton.tsx │ │ ├── index.tsx │ │ └── CodeBlock.tsx │ ├── Assistant │ │ └── InitAssistant.tsx │ ├── index.ts │ ├── StarterList │ │ ├── index.md │ │ └── index.tsx │ └── BoxChart │ │ └── index.md ├── .eslintrc.js ├── .gitignore ├── tailwind.config.js ├── scripts │ └── prepend-tailwind-loader.js ├── postcss.config.js ├── .dumirc.ts ├── .editorconfig ├── .prettierrc.js ├── tsconfig.json ├── .fatherrc.ts └── LICENSE ├── extension ├── .eslintrc.json ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── page.tsx │ │ ├── layout.tsx │ │ └── globals.css │ └── components │ │ └── ChatPanel.tsx ├── public │ ├── icons │ │ ├── logo-16.png │ │ ├── logo-192.png │ │ ├── logo-32.png │ │ └── logo-48.png │ ├── background.js │ └── manifest.json ├── postcss.config.js ├── next.config.js ├── webpack.config.js ├── .gitignore ├── tailwind.config.ts ├── export.js ├── tsconfig.json ├── package.json └── README.md ├── .aws ├── petercat-example.toml ├── petercat-prod.toml └── petercat-preview.toml ├── .vscode └── settings.json ├── docs └── guides │ └── data_migration.md ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── docker └── Dockerfile.aws.lambda ├── .gitignore ├── package.json └── LICENSE /server/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /migrations/roles.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/database.types.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /migrations/supabase/seed.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /migrations/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL= -------------------------------------------------------------------------------- /migrations/supabase/database.types.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/agent/llm/clients/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | docker/ 2 | lui/ 3 | server/ -------------------------------------------------------------------------------- /assistant/.prettierignore: -------------------------------------------------------------------------------- 1 | /dist 2 | *.yaml 3 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /extension/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /migrations/supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | .env 5 | -------------------------------------------------------------------------------- /server/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest-cov 2 | pytest 3 | pytest-asyncio 4 | twine 5 | -------------------------------------------------------------------------------- /assistant/.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@umijs/lint/dist/config/stylelint" 3 | } 4 | -------------------------------------------------------------------------------- /assistant/src/mock/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bot.mock'; 2 | export * from './inputArea.mock'; 3 | -------------------------------------------------------------------------------- /assistant/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './chatTranslator'; 2 | export * from './material'; 3 | -------------------------------------------------------------------------------- /client/.kiwi/ja/chunk.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | fanHui: '返回', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/.kiwi/en/chunk.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | fanHui: 'Back', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/.kiwi/ko/chunk.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | fanHui: '돌아가기', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/.kiwi/zh-CN/chunk.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | fanHui: '返回', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/.kiwi/zh-TW/chunk.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | fanHui: '返回', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/.kiwi/zh-CN/utils.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | tools: { 3 | jieXiJSO: '解析JSON时发生错误:', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /assistant/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@umijs/lint/dist/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /client/.kiwi/ja/utils.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | tools: { 3 | jieXiJSO: 'JSONを解析中にエラーが発生しました:', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/.kiwi/ko/utils.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | tools: { 3 | jieXiJSO: 'JSON을 해석하는 중 오류 발생:', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/.kiwi/zh-TW/utils.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | tools: { 3 | jieXiJSO: '解析 JSON 時發生錯誤:', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petercat-ai/petercat/HEAD/client/public/images/logo.png -------------------------------------------------------------------------------- /extension/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petercat-ai/petercat/HEAD/extension/src/app/favicon.ico -------------------------------------------------------------------------------- /assistant/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | .dumi/tmp 4 | .dumi/tmp-test 5 | .dumi/tmp-production 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /assistant/src/interface/index.ts: -------------------------------------------------------------------------------- 1 | export * from './contentMessage.interface'; 2 | export * from './model.interface'; 3 | -------------------------------------------------------------------------------- /client/public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petercat-ai/petercat/HEAD/client/public/images/favicon.ico -------------------------------------------------------------------------------- /client/types/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /assistant/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], 4 | }; 5 | -------------------------------------------------------------------------------- /extension/public/icons/logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petercat-ai/petercat/HEAD/extension/public/icons/logo-16.png -------------------------------------------------------------------------------- /extension/public/icons/logo-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petercat-ai/petercat/HEAD/extension/public/icons/logo-192.png -------------------------------------------------------------------------------- /extension/public/icons/logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petercat-ai/petercat/HEAD/extension/public/icons/logo-32.png -------------------------------------------------------------------------------- /extension/public/icons/logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petercat-ai/petercat/HEAD/extension/public/icons/logo-48.png -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | 3 | NEXT_PUBLIC_API_DOMAIN=http://localhost:8001 4 | 5 | NEXT_STANDALONE="true" 6 | -------------------------------------------------------------------------------- /client/.kiwi/en/utils.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | tools: { 3 | jieXiJSO: 'Error occurred while parsing JSON:', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /extension/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/.env.local.example: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | 3 | NEXT_PUBLIC_API_DOMAIN=http://localhost:8001 4 | 5 | NEXT_STANDALONE="true" 6 | -------------------------------------------------------------------------------- /server/utils/private_key/base.py: -------------------------------------------------------------------------------- 1 | 2 | class BasePrivateKeyProvider(): 3 | def get_private_key(self, name: str) -> str: 4 | pass 5 | -------------------------------------------------------------------------------- /assistant/scripts/prepend-tailwind-loader.js: -------------------------------------------------------------------------------- 1 | module.exports = (content) => { 2 | return `import '../.dumi/tmp/global.css';\n${content}`; 3 | } 4 | -------------------------------------------------------------------------------- /client/public/images/footer-contribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petercat-ai/petercat/HEAD/client/public/images/footer-contribution.png -------------------------------------------------------------------------------- /server/core/dao/BaseDAO.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | class BaseDAO: 4 | @abstractmethod 5 | def get_client(): 6 | ... -------------------------------------------------------------------------------- /extension/public/background.js: -------------------------------------------------------------------------------- 1 | chrome.sidePanel 2 | .setPanelBehavior({ openPanelOnActionClick: true }) 3 | .catch((error) => console.error(error)); 4 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | 2 | English | [简体中文](./README.zh-CN.md) 3 | -------------------------------------------------------------------------------- /server/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # tests/conftest.py 2 | import sys 3 | import os 4 | 5 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 6 | -------------------------------------------------------------------------------- /server/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "starter0": "介绍一下这个项目", 3 | "starter1": "查看贡献指南", 4 | "starter2": "我该怎样快速上手", 5 | "hello_message": "我是你专属的答疑机器人,你可以问我关于当前项目的任何问题~" 6 | } 7 | -------------------------------------------------------------------------------- /server/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests 3 | rootdir=server 4 | consider_namespace_packages = True 5 | python_files = test_*.py 6 | cov=com 7 | cov-report=xml,html 8 | -------------------------------------------------------------------------------- /assistant/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss'), 4 | require('autoprefixer'), 5 | require("postcss-nested"), 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /server/aws/exceptions.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | 3 | 4 | class UploadError(HTTPException): 5 | def __init__(self, detail: str): 6 | super().__init__(status_code=500, detail=detail) 7 | -------------------------------------------------------------------------------- /server/aws/schemas.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import Optional 3 | 4 | 5 | class ImageMetaData(BaseModel): 6 | title: Optional[str] = None 7 | description: Optional[str] = None 8 | -------------------------------------------------------------------------------- /server/i18n/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "starter0": "이 프로젝트를 소개합니다", 3 | "starter1": "기여 가이드라인 보기", 4 | "starter2": "어떻게 빠르게 시작할 수 있나요?", 5 | "hello_message": "저는 여러분의 전용 Q&A 봇입니다. 현재 프로젝트에 대해 무엇이든 물어보세요~" 6 | } 7 | -------------------------------------------------------------------------------- /client/app/utils/url.ts: -------------------------------------------------------------------------------- 1 | export function isUrl(url?: string): boolean { 2 | if (!url) { 3 | return false; 4 | } 5 | const regex = /^https:\/\/[^\s/$.?#].[^\s]*$/; 6 | return regex.test(url); 7 | } -------------------------------------------------------------------------------- /server/i18n/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "starter0": "このプロジェクトを紹介します", 3 | "starter1": "コントリビューションガイドラインを確認する", 4 | "starter2": "どうやって素早く始められますか?", 5 | "hello_message": "私はあなた専用のQ&Aボットです。現在のプロジェクトについて何でも聞いてくださいね~" 6 | } 7 | -------------------------------------------------------------------------------- /server/setup_python.sh: -------------------------------------------------------------------------------- 1 | python3.12 -m venv venv 2 | source venv/bin/activate 3 | python3 -m pip install --upgrade pip 4 | pip3 install --no-cache-dir -r requirements.txt 5 | pip3 install --no-cache-dir -r requirements-dev.txt 6 | -------------------------------------------------------------------------------- /server/agent/tools/helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def need_github_login(): 4 | return json.dumps({ 5 | "type": "card", 6 | "template_id": "LOGIN_INVITE", 7 | "error_info": "你必须先登录 petercat 才能使用此功能。请点击下方登录按钮登录。" 8 | }) 9 | -------------------------------------------------------------------------------- /server/utils/random_str.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | def random_str(N: int = 8): 5 | return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(N)) 6 | -------------------------------------------------------------------------------- /client/types/task.ts: -------------------------------------------------------------------------------- 1 | export enum TaskStatus { 2 | NOT_STARTED = "NOT_STARTED", 3 | IN_PROGRESS = "IN_PROGRESS", 4 | COMPLETED = "COMPLETED", 5 | ON_HOLD = "ON_HOLD", 6 | CANCELLED = "CANCELLED", 7 | ERROR = "ERROR" 8 | } -------------------------------------------------------------------------------- /server/env.py: -------------------------------------------------------------------------------- 1 | # list all env variables 2 | from utils.env import get_env_variable 3 | 4 | 5 | WEB_URL = get_env_variable("WEB_URL") 6 | ENVIRONMENT = get_env_variable("PETERCAT_ENV", "development") 7 | API_URL = get_env_variable("API_URL") 8 | -------------------------------------------------------------------------------- /server/aws/dependencies.py: -------------------------------------------------------------------------------- 1 | from .constants import AWS_REGION 2 | import boto3 3 | 4 | 5 | def get_s3_client(): 6 | session = boto3.session.Session() 7 | client = session.client(service_name="s3", region_name=AWS_REGION) 8 | return client 9 | -------------------------------------------------------------------------------- /server/github_app/purchased.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from fastapi.responses import JSONResponse 4 | 5 | 6 | class PurchaseServer(): 7 | def purchased(self, payload: dict): 8 | print(f"purchased={payload}") 9 | return JSONResponse(content={"status": "ok"}, status_code=200) -------------------------------------------------------------------------------- /assistant/.dumirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'dumi'; 2 | 3 | export default defineConfig({ 4 | outputPath: 'docs-dist', 5 | themeConfig: { 6 | name: 'lui', 7 | }, 8 | tailwindcss: {}, 9 | plugins: ['@umijs/plugins/dist/tailwindcss'], 10 | }); 11 | -------------------------------------------------------------------------------- /server/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "starter0": "Introduce the project", 3 | "starter1": "Review the contribution guidelines", 4 | "starter2": "How can I quickly get started?", 5 | "hello_message": "I'm your dedicated Q&A bot. Feel free to ask me anything about the current project~" 6 | } 7 | -------------------------------------------------------------------------------- /server/i18n/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "starter0": "Introduce the project", 3 | "starter1": "Review the contribution guidelines", 4 | "starter2": "How can I quickly get started?", 5 | "hello_message": "I'm your dedicated Q&A bot. Feel free to ask me anything about the current project~" 6 | } 7 | -------------------------------------------------------------------------------- /assistant/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /server/utils/private_key/local.py: -------------------------------------------------------------------------------- 1 | from utils.private_key.base import BasePrivateKeyProvider 2 | 3 | class LocalPrivateKeyProvider(BasePrivateKeyProvider): 4 | def get_private_key(self, secret_id: str) -> str: 5 | # if local, use secret_id itself as private key 6 | return secret_id -------------------------------------------------------------------------------- /.aws/petercat-example.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | [default.deploy.parameters] 3 | stack_name = "{{YOUR_STACK_NAME}}" 4 | resolve_s3 = true 5 | s3_prefix = "{{YOUR_STACK_NAME}}" 6 | region = "{{YOUR REGION}}" 7 | confirm_changeset = true 8 | capabilities = "CAPABILITY_IAM" 9 | disable_rollback = true 10 | -------------------------------------------------------------------------------- /client/instrumentation.ts: -------------------------------------------------------------------------------- 1 | export async function register() { 2 | if (process.env.NEXT_RUNTIME === "nodejs") { 3 | await import("./sentry.server.config"); 4 | } 5 | 6 | if (process.env.NEXT_RUNTIME === "edge") { 7 | await import("./sentry.edge.config"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.configPath": "./client/.prettierrc.js", 3 | "python.analysis.extraPaths": [ 4 | "./server" 5 | ], 6 | "python.testing.pytestArgs": [ 7 | "server" 8 | ], 9 | "python.testing.unittestEnabled": false, 10 | "python.testing.pytestEnabled": true 11 | } 12 | -------------------------------------------------------------------------------- /client/public/icons/PlusIcon.tsx: -------------------------------------------------------------------------------- 1 | const PlusIcon = () => ( 2 | 10 | 11 | 12 | ); 13 | export default PlusIcon; 14 | -------------------------------------------------------------------------------- /client/public/icons/MinusIcon.tsx: -------------------------------------------------------------------------------- 1 | const MinusIcon = () => ( 2 | 8 | 9 | 10 | ); 11 | 12 | export default MinusIcon; 13 | -------------------------------------------------------------------------------- /migrations/supabase/migrations/20241015090438_remote_schema.sql: -------------------------------------------------------------------------------- 1 | drop function if exists "public"."match_antd_doc"(query_embedding vector, filter jsonb); 2 | drop function if exists "public"."match_antd_documents"(query_embedding vector, filter jsonb); 3 | drop function if exists "public"."match_antd_knowledge"(query_embedding vector, filter jsonb); 4 | -------------------------------------------------------------------------------- /client/.kiwi/zh-CN/release.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | zuiXinChanPinXin: '最新产品新闻, 以便在新版能力发布时获取最新信息', 4 | guanZhu: '关注', 5 | xiaoMaoMiJiQi: '小猫咪机器人首次亮相上海外滩大会,助力开源社区答疑场景', 6 | waiTanDaHuiShou: '外滩大会首次发布', 7 | quanLianLuGuoJi: 8 | '全链路国际化支持,增加官网移动端适配,多平台集成链路产品化', 9 | guoJiHuaZhiChi: '国际化支持', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /client/.kiwi/zh-TW/release.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | zuiXinChanPinXin: '最新產品新聞,以便在新版能力發布時取得最新資訊', 4 | guanZhu: '關注', 5 | xiaoMaoMiJiQi: '小貓咪機器人首次亮相上海外灘大會,助力開源社群答疑場景', 6 | waiTanDaHuiShou: '外灘大會首次發布', 7 | quanLianLuGuoJi: 8 | '全鏈路國際化支援,增加官網移動端適配,多平臺整合鏈路產品化', 9 | guoJiHuaZhiChi: '國際化支援', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /server/aws/constants.py: -------------------------------------------------------------------------------- 1 | from utils.env import get_env_variable 2 | 3 | 4 | SUCCESS_CODE = "UPLOAD_SUCCESS" 5 | ERROR_CODES = {"credentials_error": "CREDENTIALS_ERROR", "upload_error": "UPLOAD_ERROR"} 6 | S3_TEMP_BUCKET_NAME = get_env_variable("S3_TEMP_BUCKET_NAME") 7 | STATIC_URL = get_env_variable("STATIC_URL") 8 | AWS_REGION = get_env_variable("AWS_REGION") 9 | -------------------------------------------------------------------------------- /client/app/hooks/useAvailableLLMs.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query'; 2 | import { getAvailableLLMs } from '../services/UserController'; 3 | 4 | export function useAvailableLLMs() { 5 | return useQuery({ 6 | queryKey: [`availableLLMS.llms`], 7 | queryFn: async () => getAvailableLLMs(), 8 | retry: true, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /assistant/src/utils/material.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 等待time ms时间, 默认2000ms 3 | * @param time ms 4 | */ 5 | export async function sleep(time: number = 2000): Promise { 6 | console.log('Start of sleep'); 7 | // eslint-disable-next-line no-promise-executor-return 8 | await new Promise((resolve) => setTimeout(resolve, time)); 9 | console.log('End of sleep'); 10 | } 11 | -------------------------------------------------------------------------------- /client/app/hooks/useFingerprint.ts: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr'; 2 | import FingerprintJS from '@fingerprintjs/fingerprintjs' 3 | 4 | export function useFingerprint() { 5 | return useSWR(['client.fingerprint'], async () => { 6 | const fpPromise = FingerprintJS.load(); 7 | const fp = await fpPromise; 8 | return fp.get(); 9 | }, { suspense: false }) 10 | } 11 | -------------------------------------------------------------------------------- /server/utils/supabase.py: -------------------------------------------------------------------------------- 1 | from supabase.client import Client, create_client 2 | 3 | from utils.env import get_env_variable 4 | 5 | supabase_url = get_env_variable("SUPABASE_URL") 6 | supabase_key = get_env_variable("SUPABASE_SERVICE_KEY") 7 | 8 | 9 | def get_client(): 10 | supabase: Client = create_client(supabase_url, supabase_key) 11 | return supabase 12 | -------------------------------------------------------------------------------- /assistant/src/interface/model.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IBotParams { 2 | repetition_penalty: number; 3 | temperature: number; 4 | top_k: number; 5 | top_p: number; 6 | do_sample: boolean; 7 | } 8 | 9 | export interface IBot { 10 | name: string; 11 | avatar: string; 12 | displayName: string; 13 | out_seq_length?: number; 14 | params?: IBotParams; 15 | } 16 | -------------------------------------------------------------------------------- /client/app/interface/index.ts: -------------------------------------------------------------------------------- 1 | export interface BotProfile { 2 | id: string; 3 | avatar?: string; 4 | name: string; 5 | description?: string; 6 | prompt?: string; 7 | starters?: string[]; 8 | public?: boolean; 9 | helloMessage?: string; 10 | repoName?: string; 11 | gitAvatar?: string; 12 | llm?: string; 13 | token_id?: string; 14 | domain_whitelist: string[]; 15 | } 16 | -------------------------------------------------------------------------------- /client/share/supabas-client.ts: -------------------------------------------------------------------------------- 1 | import { Database } from '@/types/database.types'; 2 | import { SupabaseClient, createClient } from '@supabase/supabase-js'; 3 | 4 | const supabaseUrl = process.env.SUPABASE_URL!; 5 | const supabaseAnonKey = process.env.SUPABASE_SERVICE_KEY!; 6 | export const supabase: SupabaseClient = createClient( 7 | supabaseUrl, 8 | supabaseAnonKey, 9 | ); 10 | -------------------------------------------------------------------------------- /client/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | endOfLine: 'lf', 3 | semi: true, 4 | singleQuote: true, 5 | tabWidth: 2, 6 | trailingComma: 'all', 7 | printWidth: 80, 8 | arrowParens: 'always', 9 | proseWrap: 'never', 10 | overrides: [ 11 | { files: '.eslintrc', options: { 'parser': 'json' } }, 12 | { files: '.prettierrc', options: { 'parser': 'json' } } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /extension/src/components/ChatPanel.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Chat } from '@petercatai/assistant'; 3 | import React from 'react'; 4 | const ChatPanel = () => { 5 | return ( 6 | 11 | ); 12 | }; 13 | 14 | export default ChatPanel; 15 | -------------------------------------------------------------------------------- /assistant/src/types/ declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-syntax-highlighter' { 2 | import { ComponentType, ReactNode } from 'react'; 3 | 4 | interface SyntaxHighlighterProps { 5 | language: string; 6 | style: any; 7 | children: ReactNode; 8 | className?: string; 9 | } 10 | 11 | export const Prism: ComponentType; 12 | export const vs: any; 13 | } 14 | -------------------------------------------------------------------------------- /client/public/icons/AppendixIcon.tsx: -------------------------------------------------------------------------------- 1 | const AppendixIcon = () => ( 2 | 3 | 7 | 8 | ); 9 | export default AppendixIcon; 10 | -------------------------------------------------------------------------------- /extension/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | export default function Home() { 4 | return ( 5 |
15 |

PeterCat

16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /client/components/GitHubStars.tsx: -------------------------------------------------------------------------------- 1 | async function fetchGitHubStars() { 2 | const res = await fetch('https://api.github.com/repos/petercat-ai/petercat', { 3 | cache: 'no-store', 4 | }); 5 | const { stargazers_count: stars = 0 } = await res.json(); 6 | return stars; 7 | } 8 | 9 | export default async function GitHubStars() { 10 | const stars = await fetchGitHubStars(); 11 | return stars; 12 | } 13 | -------------------------------------------------------------------------------- /.aws/petercat-prod.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | [default.deploy.parameters] 3 | stack_name = "sam-app" 4 | resolve_s3 = true 5 | s3_prefix = "sam-app" 6 | region = "ap-northeast-1" 7 | confirm_changeset = true 8 | capabilities = "CAPABILITY_IAM" 9 | disable_rollback = true 10 | image_repositories = [ 11 | "FastAPIFunction=654654285942.dkr.ecr.ap-northeast-1.amazonaws.com/samapp7427b055/fastapifunctionead79d0drepo", 12 | ] 13 | -------------------------------------------------------------------------------- /assistant/src/Chat/components/MarkdownRender.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Markdown from '../../Markdown'; 3 | 4 | interface IProps { 5 | className?: string; 6 | content: string; 7 | } 8 | 9 | const MarkdownRender = React.memo((props: IProps) => ( 10 | 14 | )); 15 | export default MarkdownRender; 16 | -------------------------------------------------------------------------------- /assistant/src/mock/bot.mock.ts: -------------------------------------------------------------------------------- 1 | export const BOT_INFO = { 2 | avatar: 3 | 'https://mdn.alipayobjects.com/huamei_yhboz9/afts/img/A*li7ySppF7TYAAAAAAAAAAAAADlDCAQ/original', 4 | name: 'peterCat', 5 | helloMessage: 6 | '👋🏻 你好,我是 PeterCat 初次见面,先自我介绍一下:我是一个开源项目的机器人。你可以通过和我对话配置一个答疑机器人。下面是一些快捷选项,你可以选择开始配置!', //开场白 7 | starters: [ 8 | '答疑机器人需要完成哪几步的配置?', 9 | '帮我创建一个 Ant Design 答疑机器人', 10 | ], //预制问题 11 | }; 12 | -------------------------------------------------------------------------------- /server/auth/verify_admin.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from fastapi import Depends, HTTPException, Request 4 | 5 | from auth.get_user_info import get_user 6 | from core.models.user import User 7 | 8 | async def verify_admin(request: Request, user: Annotated[User | None, Depends(get_user)] = None,): 9 | if not user or not user.is_admin: 10 | raise HTTPException(status_code=403, detail="Must Login") 11 | -------------------------------------------------------------------------------- /client/.kiwi/ja/release.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | zuiXinChanPinXin: 4 | '新しい機能がリリースされたときに最新情報を取得するための最新の製品ニュース', 5 | guanZhu: 'フォロー', 6 | xiaoMaoMiJiQi: 7 | '子猫ロボットが上海外灘カンファレンスで初登場し、オープンソースコミュニティのQ&Aシナリオを支援', 8 | waiTanDaHuiShou: '外灘カンファレンスで初公開', 9 | quanLianLuGuoJi: 10 | 'フルリンク国際化サポート、公式サイトのモバイル端末対応を追加し、マルチプラットフォーム統合リンクの製品化', 11 | guoJiHuaZhiChi: '国際化サポート', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /client/.kiwi/ko/release.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | zuiXinChanPinXin: 4 | '새로운 기능이 출시될 때 최신 정보를 얻을 수 있는 최신 제품 뉴스', 5 | guanZhu: '팔로우', 6 | xiaoMaoMiJiQi: 7 | '작은 고양이 로봇이 상하이 와이탄 컨퍼런스에서 처음으로 모습을 드러내며 오픈 소스 커뮤니티의 Q&A 시나리오를 지원', 8 | waiTanDaHuiShou: '와이탄 컨퍼런스에서 최초 공개', 9 | quanLianLuGuoJi: 10 | '전체 링크 국제화 지원, 공식 웹사이트의 모바일 버전 지원 추가, 다중 플랫폼 통합 링크 제품화', 11 | guoJiHuaZhiChi: '국제화 지원', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /server/core/models/profiles.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from datetime import datetime 3 | from typing import Optional 4 | 5 | class Profiles(BaseModel): 6 | id: str 7 | created_at: datetime = datetime.now() 8 | nickname: Optional[str] = "" 9 | name: Optional[str] = "" 10 | picture: Optional[str] = "" 11 | sid: Optional[str] = "" 12 | sub: Optional[str] = "" 13 | agreement_accepted: Optional[bool] = False 14 | -------------------------------------------------------------------------------- /extension/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import './globals.css'; 3 | 4 | export const metadata: Metadata = { 5 | title: 'PeterCat', 6 | description: 'Create your own Q&A bot', 7 | }; 8 | 9 | export default function RootLayout({ 10 | children, 11 | }: Readonly<{ 12 | children: React.ReactNode; 13 | }>) { 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /server/auth/clients/__init__.py: -------------------------------------------------------------------------------- 1 | from auth.clients.auth0 import Auth0Client 2 | from auth.clients.base import BaseAuthClient 3 | from auth.clients.local import LocalClient 4 | 5 | from utils.env import get_env_variable 6 | 7 | PETERCAT_AUTH0_ENABLED = get_env_variable("PETERCAT_AUTH0_ENABLED", "True") == "True" 8 | 9 | def get_auth_client() -> BaseAuthClient: 10 | if PETERCAT_AUTH0_ENABLED: 11 | return Auth0Client() 12 | return LocalClient() 13 | -------------------------------------------------------------------------------- /server/core/models/user.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from pydantic import BaseModel 3 | 4 | class User(BaseModel): 5 | id: Optional[str] = None 6 | sub: str 7 | sid: str 8 | nickname: str 9 | avatar: Optional[str] = None 10 | picture: Optional[str] 11 | agreement_token: Optional[bool] = False 12 | is_admin: Optional[bool] = False 13 | anonymous: Optional[bool] = True 14 | access_token: Optional[str] = None 15 | -------------------------------------------------------------------------------- /server/core/models/user_llm_token.py: -------------------------------------------------------------------------------- 1 | 2 | from datetime import datetime 3 | from typing import Optional 4 | from pydantic import BaseModel 5 | class UserLLMToken(BaseModel): 6 | id: Optional[str] = None 7 | user_id: Optional[str] = None 8 | created_at: Optional[datetime] = datetime.now().isoformat() 9 | slug: Optional[str] = None 10 | llm: str 11 | encrypted_token: Optional[str] = None 12 | sanitized_token: Optional[str] = None 13 | -------------------------------------------------------------------------------- /.aws/petercat-preview.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | [default.deploy.parameters] 3 | stack_name = "petercat-api-preview" 4 | resolve_s3 = true 5 | s3_prefix = "petercat-api-preview" 6 | region = "ap-northeast-1" 7 | confirm_changeset = true 8 | capabilities = "CAPABILITY_IAM" 9 | disable_rollback = true 10 | image_repositories = [ 11 | "FastAPIFunction=654654285942.dkr.ecr.ap-northeast-1.amazonaws.com/petercatapipreview49199518/fastapifunctionead79d0drepo", 12 | ] 13 | -------------------------------------------------------------------------------- /assistant/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pluginSearchDirs: false, 3 | plugins: [ 4 | require.resolve('prettier-plugin-organize-imports'), 5 | require.resolve('prettier-plugin-packagejson'), 6 | ], 7 | printWidth: 80, 8 | proseWrap: 'never', 9 | singleQuote: true, 10 | trailingComma: 'all', 11 | overrides: [ 12 | { 13 | files: '*.md', 14 | options: { 15 | proseWrap: 'preserve', 16 | }, 17 | }, 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /assistant/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "declaration": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "jsx": "react", 8 | "baseUrl": "./", 9 | "paths": { 10 | "@@/*": [".dumi/tmp/*"], 11 | "assistant": ["src"], 12 | "assistant/*": ["src/*", "*"] 13 | }, 14 | "resolveJsonModule": true 15 | }, 16 | "include": [".dumirc.ts", "src/**/*", "tailwind.config.js"] 17 | } 18 | -------------------------------------------------------------------------------- /client/.kiwi/en/index.ts: -------------------------------------------------------------------------------- 1 | import components from './components'; 2 | import edit from './edit'; 3 | import utils from './utils'; 4 | import chunk from './chunk' 5 | import app from './app'; 6 | import release from './release'; 7 | import DeployBotModal from './DeployBotModal'; 8 | 9 | export default Object.assign( 10 | {}, 11 | { 12 | components, 13 | app, 14 | utils, 15 | edit, 16 | chunk, 17 | release, 18 | DeployBotModal, 19 | }, 20 | ); 21 | -------------------------------------------------------------------------------- /client/.kiwi/ja/index.ts: -------------------------------------------------------------------------------- 1 | import components from './components'; 2 | import edit from './edit'; 3 | import utils from './utils'; 4 | import chunk from './chunk'; 5 | import app from './app'; 6 | import release from './release'; 7 | import DeployBotModal from './DeployBotModal'; 8 | 9 | export default Object.assign( 10 | {}, 11 | { 12 | chunk, 13 | components, 14 | app, 15 | utils, 16 | edit, 17 | release, 18 | DeployBotModal, 19 | }, 20 | ); 21 | -------------------------------------------------------------------------------- /client/.kiwi/ko/index.ts: -------------------------------------------------------------------------------- 1 | import components from './components'; 2 | import edit from './edit'; 3 | import utils from './utils'; 4 | import app from './app'; 5 | import chunk from './chunk'; 6 | import release from './release'; 7 | import DeployBotModal from './DeployBotModal'; 8 | 9 | export default Object.assign( 10 | {}, 11 | { 12 | chunk, 13 | components, 14 | app, 15 | utils, 16 | edit, 17 | release, 18 | DeployBotModal, 19 | }, 20 | ); 21 | -------------------------------------------------------------------------------- /server/core/models/llm_token.py: -------------------------------------------------------------------------------- 1 | 2 | from datetime import datetime 3 | from typing import Optional 4 | from pydantic import BaseModel 5 | class LLMToken(BaseModel): 6 | id: Optional[int] = None 7 | user_id: Optional[str] = None 8 | created_at: Optional[datetime] = None 9 | slug: Optional[str] = None 10 | limit: Optional[int] = None 11 | usage: Optional[int] = None 12 | free: Optional[bool] = False 13 | llm: str 14 | token: Optional[str] = None -------------------------------------------------------------------------------- /client/.kiwi/zh-CN/index.ts: -------------------------------------------------------------------------------- 1 | import components from './components'; 2 | import chunk from './chunk'; 3 | import release from './release'; 4 | import DeployBotModal from './DeployBotModal'; 5 | import edit from './edit'; 6 | import utils from './utils'; 7 | import app from './app'; 8 | 9 | export default Object.assign( 10 | {}, 11 | { 12 | components, 13 | app, 14 | utils, 15 | edit, 16 | DeployBotModal, 17 | release, 18 | chunk 19 | }, 20 | ); 21 | -------------------------------------------------------------------------------- /client/.kiwi/zh-TW/index.ts: -------------------------------------------------------------------------------- 1 | import components from './components'; 2 | import edit from './edit'; 3 | import utils from './utils'; 4 | import chunk from './chunk' 5 | import app from './app'; 6 | import release from './release'; 7 | import DeployBotModal from './DeployBotModal'; 8 | 9 | export default Object.assign( 10 | {}, 11 | { 12 | chunk, 13 | components, 14 | app, 15 | utils, 16 | edit, 17 | release, 18 | DeployBotModal, 19 | }, 20 | ); 21 | -------------------------------------------------------------------------------- /client/public/icons/BackIcon.tsx: -------------------------------------------------------------------------------- 1 | const BackIcon = () => ( 2 | 8 | 14 | 15 | ) 16 | export default BackIcon 17 | -------------------------------------------------------------------------------- /server/utils/private_key/__init__.py: -------------------------------------------------------------------------------- 1 | from utils.env import get_env_variable 2 | from utils.private_key.local import LocalPrivateKeyProvider 3 | from utils.private_key.s3 import S3PrivateKeyProvider 4 | 5 | PETERCAT_SECRETS_PROVIDER = get_env_variable("PETERCAT_SECRETS_PROVIDER", "aws") 6 | 7 | def get_private_key(secret_id: str): 8 | provider = S3PrivateKeyProvider() if PETERCAT_SECRETS_PROVIDER == 'aws' else LocalPrivateKeyProvider() 9 | return provider.get_private_key(secret_id) 10 | -------------------------------------------------------------------------------- /server/agent/tools/auth.py: -------------------------------------------------------------------------------- 1 | from langchain.tools import tool 2 | 3 | from agent.tools.helper import need_github_login 4 | 5 | def factory(token: str): 6 | @tool 7 | def check_login(): 8 | """ 9 | Get user login status 10 | 11 | :return: True if login, or a "LOGIN_INVITE" card 12 | """ 13 | if token is None: 14 | return need_github_login() 15 | return True 16 | 17 | return { 18 | "check_login": check_login 19 | } -------------------------------------------------------------------------------- /server/utils/env.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os 3 | 4 | def load_env(): 5 | load_dotenv(verbose=True, override=True) 6 | load_dotenv(dotenv_path=".env.local", verbose=True, override=True) 7 | 8 | load_env() 9 | 10 | # Define a method to load an environmental variable and return its value 11 | def get_env_variable(key: str, default=None): 12 | # Get the environment variable, returning the default value if it does not exist 13 | return os.getenv(key, default) 14 | -------------------------------------------------------------------------------- /client/app/factory/edit/components/DeployBotModal/types.ts: -------------------------------------------------------------------------------- 1 | export interface DeployState { 2 | publicMarket?: { 3 | checked: boolean; 4 | }; 5 | deployWebsite?: { 6 | checked: boolean; 7 | targetUrl?: string; 8 | }; 9 | appInstalledRepo?: RepoBindBotConfig[]; 10 | } 11 | 12 | export interface RepoBindBotConfig { 13 | repo_id: string; 14 | checked: boolean; 15 | } 16 | 17 | export interface BotApproval { 18 | approval_path: string; 19 | approval_status: 'open' | 'close'; 20 | } 21 | -------------------------------------------------------------------------------- /client/public/images/chevron-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/public/images/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /extension/next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('next').NextConfig} 3 | */ 4 | const nextConfig = { 5 | output: 'export', 6 | 7 | // Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html` 8 | // trailingSlash: true, 9 | 10 | // Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href` 11 | // skipTrailingSlashRedirect: true, 12 | 13 | // Optional: Change the output directory `out` -> `dist` 14 | // distDir: 'dist', 15 | } 16 | 17 | module.exports = nextConfig 18 | -------------------------------------------------------------------------------- /assistant/src/icons/StopMessageIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export const StopMessageIcon = () => { 3 | return ( 4 | 5 | 6 | 7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /client/public/icons/ChevronDownIcon.tsx: -------------------------------------------------------------------------------- 1 | const ChevronDownIcon = (props: any) => ( 2 | 13 | 18 | 19 | ); 20 | export default ChevronDownIcon; 21 | -------------------------------------------------------------------------------- /migrations/supabase/migrations/20240913114522_remote_schema.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."bots" alter column "token_id" set default ''::text; 2 | set check_function_bodies = off; 3 | CREATE OR REPLACE FUNCTION public.get_bot_stats(filter_bot_id text) 4 | RETURNS TABLE(call_cnt bigint) 5 | LANGUAGE plpgsql 6 | AS $function$ 7 | BEGIN 8 | RETURN QUERY 9 | SELECT 10 | COUNT(1)::BIGINT AS call_cnt 11 | FROM user_token_usage u 12 | WHERE u.bot_id = filter_bot_id -- 使用别名来引用参数 13 | GROUP BY u.bot_id; 14 | END; 15 | $function$; 16 | -------------------------------------------------------------------------------- /server/core/models/repository.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | from pydantic import BaseModel, field_serializer 4 | 5 | 6 | class RepositoryConfig(BaseModel): 7 | id: Optional[str] = None 8 | repo_name: str 9 | robot_id: Optional[str] = None 10 | created_at: datetime 11 | owner_id: Optional[str] = None 12 | repo_id: Optional[str] = None 13 | 14 | @field_serializer("created_at") 15 | def serialize_created_at(self, created_at: datetime): 16 | return created_at.isoformat() 17 | -------------------------------------------------------------------------------- /server/tests/test_main.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | 3 | from server.env import ENVIRONMENT, WEB_URL, API_URL 4 | from server.main import app 5 | 6 | client = TestClient(app) 7 | 8 | 9 | def test_health_checker(): 10 | response = client.get("/api/health_checker") 11 | assert response.status_code == 200 12 | assert response.json() == { 13 | "ENVIRONMENT": ENVIRONMENT, 14 | "API_URL": API_URL, 15 | "CALLBACK_URL": f"{API_URL}/api/auth/callback", 16 | "WEB_URL": WEB_URL, 17 | } 18 | -------------------------------------------------------------------------------- /docs/guides/data_migration.md: -------------------------------------------------------------------------------- 1 | ## Database migrations 2 | Database changes are managed through "migrations." Database migrations are a common way of tracking changes to your database over time. 3 | 4 | 5 | For this guide, we'll create a table called employees and see how we can make changes to it. 6 | 7 | > Link your project 8 | $ supabase link --project-ref {{YOUR_SUPBASE_PROJECT_ID}} 9 | 10 | > Sync schema diffs from remote project to local 11 | $ supabase db pull 12 | 13 | > Run all migrations against this project 14 | $ supabase db push 15 | 16 | -------------------------------------------------------------------------------- /extension/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: { 5 | background: './src/background/background.ts', 6 | }, 7 | output: { 8 | path: path.resolve(__dirname, 'dist'), 9 | filename: '[name].js', 10 | }, 11 | resolve: { 12 | extensions: ['.ts', '.tsx', '.js'], 13 | }, 14 | mode: "production", 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.tsx?$/, 19 | use: 'ts-loader', 20 | exclude: /node_modules/, 21 | }, 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /assistant/src/ChartHeader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface ChartHeaderProps { 4 | title: string; 5 | operation?: React.ReactNode; 6 | } 7 | 8 | const ChartHeader: React.FC = ({ title, operation }) => { 9 | return ( 10 |
11 |
12 |
{title}
13 | <>{operation} 14 |
15 |
16 | ); 17 | }; 18 | 19 | export default ChartHeader; 20 | -------------------------------------------------------------------------------- /assistant/src/services/UserController.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // Get the public bot profile by id 4 | export async function getUserInfo(apiDomain: string, { clientId }: { clientId?: string }) { 5 | const response = await axios.get(`${apiDomain}/api/auth/userinfo?clientId=${clientId}`, { withCredentials: true }); 6 | return response.data.data; 7 | } 8 | 9 | 10 | export async function requestLogout(apiDomain: string) { 11 | const response = await axios.get(`${apiDomain}/api/auth/logout`, { withCredentials: true }); 12 | return response.data; 13 | } -------------------------------------------------------------------------------- /assistant/src/Chat/template/GitInsightCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import GitInsight, { GitInsightProps } from '../../GitInsight'; 3 | 4 | const GitInsightCard = (props: GitInsightProps) => { 5 | const { forkCount, starCount, commitCount } = props; 6 | return ( 7 |
8 | 13 |
14 | ); 15 | }; 16 | 17 | export default GitInsightCard; 18 | -------------------------------------------------------------------------------- /extension/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /client/public/icons/PlayIcon.tsx: -------------------------------------------------------------------------------- 1 | const PlayIcon = () => ( 2 | 16 | ); 17 | export default PlayIcon; 18 | -------------------------------------------------------------------------------- /server/utils/fuzzy_match.py: -------------------------------------------------------------------------------- 1 | import difflib 2 | 3 | 4 | def contains_keyword_fuzzy(text, keywords, cutoff=0.8): 5 | text_lower = text.lower() 6 | for keyword in keywords: 7 | keyword_lower = keyword.lower() 8 | len_keyword = len(keyword_lower) 9 | 10 | for i in range(len(text_lower) - len_keyword + 1): 11 | substring = text_lower[i : i + len_keyword] 12 | matcher = difflib.SequenceMatcher(None, keyword_lower, substring) 13 | if matcher.ratio() >= cutoff: 14 | return True 15 | return False 16 | -------------------------------------------------------------------------------- /client/components/BaseBotCard.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react'; 3 | import { Card } from '@nextui-org/react'; 4 | 5 | const BaseBotCard = (props: { 6 | onPress: Function; 7 | children: React.ReactNode; 8 | }) => { 9 | return ( 10 | { 15 | props.onPress(); 16 | }} 17 | > 18 | {props.children} 19 | 20 | ); 21 | }; 22 | 23 | export default BaseBotCard; 24 | -------------------------------------------------------------------------------- /client/public/images/icon-gemini.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assistant/src/GitInsight/components/CountCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import GitInsightIcon from './GitInsightIcon'; 3 | import ScrollNumber from './ScrollNumber'; 4 | 5 | export interface CountCardProps { 6 | type: 'fork' | 'commit' | 'star'; 7 | count: number; 8 | } 9 | const CountCard: React.FC = (props) => { 10 | const { type, count } = props; 11 | 12 | return ( 13 |
14 | 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default CountCard; 21 | -------------------------------------------------------------------------------- /client/app/utils/time.ts: -------------------------------------------------------------------------------- 1 | export function convertToLocalTime(utcDateString:string) { 2 | // 创建日期对象 3 | const utcDate = new Date(utcDateString); 4 | 5 | // 获取当地时间的年份、月份、日期、小时和分钟 6 | const year = utcDate.getFullYear(); 7 | const month = String(utcDate.getMonth() + 1).padStart(2, '0'); // 月份是0基的,要加1 8 | const day = String(utcDate.getDate()).padStart(2, '0'); 9 | const hours = String(utcDate.getHours()).padStart(2, '0'); 10 | const minutes = String(utcDate.getMinutes()).padStart(2, '0'); 11 | 12 | // 格式化返回当地时间 13 | return `${year}-${month}-${day} ${hours}:${minutes}`; 14 | } 15 | -------------------------------------------------------------------------------- /client/kiwi-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileType": "ts", 3 | "srcLang": "zh-CN", 4 | "distLangs": ["en-US"], 5 | "googleApiKey": "", 6 | "baiduApiKey": { 7 | "appId": "", 8 | "appKey": "" 9 | }, 10 | "baiduLangMap": { 11 | "en-US": "en" 12 | }, 13 | "translateOptions": { 14 | "concurrentLimit": 10, 15 | "requestOptions": {} 16 | }, 17 | "defaultTranslateKeyApi": "Pinyin", 18 | "importI18N": "import I18N from '@/app/utils/I18N';", 19 | "ignoreDir": [], 20 | "ignoreFile": [ 21 | "client/components/LangSwitcher.tsx", 22 | "client/app/contexts/GlobalContext.tsx" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /client/public/images/icon-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /extension/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /extension/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "PeterCat", 4 | "short_name": "petercat", 5 | "version": "0.0.1", 6 | "description": "petercat, your github Q&A assitant", 7 | "icons": { 8 | "16": "/icons/logo-16.png", 9 | "32": "/icons/logo-32.png", 10 | "48": "/icons/logo-48.png", 11 | "192": "/icons/logo-192.png" 12 | }, 13 | "background": { 14 | "service_worker": "background.js" 15 | }, 16 | "action": { 17 | "default_title": "点击打开侧边栏" 18 | }, 19 | "side_panel": { 20 | "default_path": "index.html" 21 | }, 22 | "permissions": ["sidePanel"] 23 | } 24 | -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.100.1 2 | starlette==0.27.0 3 | uvicorn[standard]==0.23.2 4 | python-dotenv==1.0.0 5 | openai 6 | mangum 7 | langserve 8 | langchain_community>=0.3,<0.4 9 | langchain>=0.3,<0.4 10 | langchain_google_genai 11 | PyGithub==2.3.0 12 | GitPython>=3.1.43 13 | python-multipart 14 | supabase 15 | authlib==0.14.3 16 | boto3>=1.34.84 17 | PyJWT 18 | pydantic>=2.7.0 19 | unstructured>=0.15.9 20 | python-jose>=3.3.0 21 | six>=1.16.0 22 | jose>=1.0.0 23 | itsdangerous==2.2.0 24 | fastapi_auth0==0.5.0 25 | requests 26 | httpx==0.27.2 27 | urllib3>=2.2.2 28 | toolz 29 | # -e ../../whiskerrag_toolkit 30 | whiskerrag>=0.1.3 -------------------------------------------------------------------------------- /client/sentry.edge.config.js: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | Sentry.init({ 4 | dsn: "https://25cd1bf180d345e9c926589cd0ca0353@o4507910955597824.ingest.us.sentry.io/4507910957432832", 5 | 6 | // Set tracesSampleRate to 1.0 to capture 100% 7 | // of transactions for tracing. 8 | // We recommend adjusting this value in production 9 | tracesSampleRate: 1.0, 10 | 11 | // ... 12 | 13 | // Note: if you want to override the automatic release value, do not set a 14 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so 15 | // that it will also get attached to your source maps 16 | }); 17 | -------------------------------------------------------------------------------- /client/sentry.server.config.js: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | Sentry.init({ 4 | dsn: "https://25cd1bf180d345e9c926589cd0ca0353@o4507910955597824.ingest.us.sentry.io/4507910957432832", 5 | 6 | // Set tracesSampleRate to 1.0 to capture 100% 7 | // of transactions for tracing. 8 | // We recommend adjusting this value in production 9 | tracesSampleRate: 1.0, 10 | 11 | // ... 12 | 13 | // Note: if you want to override the automatic release value, do not set a 14 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so 15 | // that it will also get attached to your source maps 16 | }); 17 | -------------------------------------------------------------------------------- /client/public/images/icon-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /server/insight/service/overview.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from github import Github 3 | 4 | g = Github() 5 | 6 | 7 | def get_overview(repo_name: Optional[str] = ""): 8 | if not repo_name: 9 | return None 10 | 11 | try: 12 | repo = g.get_repo(repo_name) 13 | 14 | commit_count = repo.get_commits().totalCount 15 | 16 | info = { 17 | "stars": repo.stargazers_count, 18 | "forks": repo.forks_count, 19 | "commits": commit_count, 20 | } 21 | 22 | return info 23 | except Exception as e: 24 | print(f"An error occurred: {e}") 25 | return None 26 | -------------------------------------------------------------------------------- /client/public/images/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/.kiwi/en/release.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | zuiXinChanPinXin: 4 | 'Get the latest product news to stay updated when new features are released', 5 | guanZhu: 'Follow', 6 | xiaoMaoMiJiQi: 7 | 'The Little Kitty robot made its debut at the Shanghai Bund Conference, supporting the open-source community with Q&A scenarios', 8 | waiTanDaHuiShou: 'First release at the Bund Conference', 9 | quanLianLuGuoJi: 10 | 'Full-link internationalization support, added mobile adaptation for the official website, and productization of multi-platform integration links', 11 | guoJiHuaZhiChi: 'Internationalization Support', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /assistant/src/Theme/index.ts: -------------------------------------------------------------------------------- 1 | const defaultTheme = { 2 | transitionDuration: '0.3s', 3 | chatBoxBackgroundColor: '#FCFCFC', 4 | userBubbleBackgroundColor: 'rgba(37, 99, 235, 1)', 5 | systemBubbleBackgroundColor: 'rgba(255, 255, 255, 1)', 6 | stopButtonBackgroundColor: 'rgba(254, 226, 226, 1)', 7 | stopButtonBackgroundColorHover: 'rgba(254, 226, 226, 0.7)', 8 | stopButtonIconColor: 'rgba(220, 38, 38, 1)', 9 | stopButtonIconColorHover: 'rgba(239, 68, 68, 1)', 10 | }; 11 | 12 | export const theme = { 13 | getDesignToken: () => { 14 | return defaultTheme; 15 | }, 16 | 17 | useToken: () => { 18 | return { 19 | token: defaultTheme, 20 | }; 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /extension/export.js: -------------------------------------------------------------------------------- 1 | // create out directory for static Chrome Extension 2 | 3 | const fs = require('fs'); 4 | const glob = require('glob'); 5 | 6 | const files = glob.sync('out/**/*.html'); 7 | files.forEach((file) => { 8 | const content = fs.readFileSync(file, 'utf-8'); 9 | const modifiedContent = content.replace(/\/_next/g, './next'); 10 | fs.writeFileSync(file, modifiedContent, 'utf-8'); 11 | }); 12 | 13 | const sourcePath = 'out/_next'; 14 | const destinationPath = 'out/next'; 15 | 16 | fs.rename(sourcePath, destinationPath, (err) => { 17 | if (err) { 18 | console.error('Rename failed:', err); 19 | } else { 20 | console.log('Rename successfully.'); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /server/core/dao/botDAO.py: -------------------------------------------------------------------------------- 1 | from core.dao.BaseDAO import BaseDAO 2 | from supabase.client import Client 3 | 4 | from core.models.bot import BotModel 5 | from utils.supabase import get_client 6 | 7 | 8 | class BotDAO(BaseDAO): 9 | client: Client 10 | 11 | def __init__(self): 12 | super().__init__() 13 | self.client = get_client() 14 | 15 | def get_bot(self, bot_id: str): 16 | resp = self.client.table("bots").select("*").eq("id", bot_id).execute() 17 | bot = resp.data[0] 18 | return BotModel(**bot) 19 | 20 | def update_bot(self, bot_id: str, data: dict): 21 | resp = self.client.table("bots").update(data).eq("id", bot_id).execute() 22 | return resp 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: "⚡️ feature" 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /client/components/AddBotCard.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react'; 3 | import { CardBody } from '@nextui-org/react'; 4 | import { AddBotIcon } from '@/public/icons/AddBotIcon'; 5 | import BaseBotCard from './BaseBotCard'; 6 | 7 | const AddBotCard = (props: { onPress: Function }) => { 8 | return ( 9 | { 11 | props.onPress(); 12 | }} 13 | > 14 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default AddBotCard; 22 | -------------------------------------------------------------------------------- /client/components/Crash.tsx: -------------------------------------------------------------------------------- 1 | import I18N from '@/app/utils/I18N'; 2 | import React from 'react'; 3 | 4 | const Crash: React.FC = () => { 5 | return ( 6 |
7 |
8 | Error Illustration 14 |

15 | {I18N.components.Crash.woMenSiHuYu}
16 | {I18N.components.Crash.qingChangShiShuaXin}

17 |
18 |
19 | ); 20 | }; 21 | 22 | export default Crash; 23 | -------------------------------------------------------------------------------- /extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "outDir": "./dist", 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /client/app/hooks/useAgreement.ts: -------------------------------------------------------------------------------- 1 | import { useMutation, useQuery } from '@tanstack/react-query'; 2 | import { 3 | acceptAgreement, 4 | getAgreementStatus, 5 | } from '@/app/services/UserController'; 6 | export function useAgreement() { 7 | const mutation = useMutation({ 8 | mutationFn: acceptAgreement, 9 | }); 10 | 11 | return { 12 | data: mutation.data, 13 | acceptAgreement: mutation.mutate, 14 | isLoading: mutation.isPending, 15 | error: mutation.error, 16 | isSuccess: mutation.isSuccess, 17 | }; 18 | } 19 | 20 | export const useAgreementStatus = () => { 21 | return useQuery({ 22 | queryKey: [`agreement`], 23 | queryFn: async () => getAgreementStatus(), 24 | retry: false, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /client/components/PublishBotCard.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react'; 3 | import { CardBody } from '@nextui-org/react'; 4 | import BaseBotCard from './BaseBotCard'; 5 | import { MarketIcon } from '@/public/icons/MarketIcon'; 6 | 7 | const PublishBotCard = (props: { onPress: Function }) => { 8 | return ( 9 | { 11 | props.onPress(); 12 | }} 13 | > 14 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default PublishBotCard; 22 | -------------------------------------------------------------------------------- /assistant/src/Chat/components/LoadingStart.tsx: -------------------------------------------------------------------------------- 1 | import Lottie from 'lottie-react'; 2 | import React from 'react'; 3 | import LoadingAnimationStart from '../../assets/bubble-start.json'; 4 | 5 | interface LoadingStartProps { 6 | loop?: boolean; 7 | onComplete?: () => void; 8 | className?: string; 9 | } 10 | 11 | const LoadingStart: React.FC = (props) => { 12 | const { onComplete, loop = true } = props; 13 | 14 | return ( 15 |
16 | 22 |
23 | ); 24 | }; 25 | 26 | export default LoadingStart; 27 | -------------------------------------------------------------------------------- /server/aws/router.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, File, UploadFile, Form 2 | from .schemas import ImageMetaData 3 | from .dependencies import get_s3_client 4 | from .service import upload_image_to_s3 5 | 6 | router = APIRouter( 7 | prefix="/api/aws", 8 | tags=["aws"], 9 | responses={404: {"description": "Not found"}}, 10 | ) 11 | 12 | 13 | @router.post("/upload") 14 | async def upload_image( 15 | file: UploadFile = File(...), 16 | title: str = Form(None), 17 | description: str = Form(None), 18 | s3_client=Depends(get_s3_client), 19 | ): 20 | 21 | metadata = ImageMetaData(title=title, description=description) 22 | result = upload_image_to_s3(file, metadata, s3_client) 23 | return {"status": "success", "data": result} 24 | -------------------------------------------------------------------------------- /extension/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assistant/src/Chat/components/MySpinner.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Spin } from 'antd'; 3 | import React from 'react'; 4 | 5 | export interface ISpinnerProps { 6 | loading?: boolean; 7 | spinner?: React.ReactNode; 8 | children?: React.ReactNode; 9 | } 10 | 11 | const MySpinner = (props: ISpinnerProps) => { 12 | const { loading } = props; 13 | if (loading) { 14 | return ( 15 |
16 |
17 | {props.spinner ?? } 18 |
19 | {props.children} 20 |
21 | ); 22 | } 23 | return props.children; 24 | }; 25 | 26 | export default MySpinner; 27 | -------------------------------------------------------------------------------- /assistant/src/Markdown/CopyButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const CopyButton: React.FC<{ content: string }> = ({ content }) => { 4 | const [copied, setCopied] = useState(false); 5 | 6 | const handleCopy = async () => { 7 | try { 8 | await navigator.clipboard.writeText(content); 9 | setCopied(true); 10 | setTimeout(() => setCopied(false), 2000); 11 | } catch (error) { 12 | console.error('copy', error); 13 | } 14 | }; 15 | 16 | return ( 17 | 23 | ); 24 | }; 25 | 26 | export default CopyButton; 27 | -------------------------------------------------------------------------------- /assistant/src/Markdown/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import remarkGfm from 'remark-gfm'; 4 | import CodeBlock from './CodeBlock'; 5 | export interface MarkdownProps { 6 | text: string; 7 | style?: React.CSSProperties; 8 | } 9 | 10 | const Markdown: React.FC = ({ text, style }) => { 11 | return ( 12 |
16 | 23 | {text} 24 | 25 |
26 | ); 27 | }; 28 | 29 | export default Markdown; 30 | -------------------------------------------------------------------------------- /client/app/global-error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Crash from "@/components/Crash"; 4 | import * as Sentry from "@sentry/nextjs"; 5 | import { useEffect } from "react"; 6 | 7 | export default function GlobalError({ 8 | error, 9 | }: { 10 | error: Error & { digest?: string }; 11 | }) { 12 | useEffect(() => { 13 | Sentry.captureException(error); 14 | }, [error]); 15 | 16 | return ( 17 | 18 | 19 | {/* `NextError` is the default Next.js error page component. Its type 20 | definition requires a `statusCode` prop. However, since the App Router 21 | does not expose status codes for errors, we simply pass 0 to render a 22 | generic error message. */} 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /client/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react'; 3 | import { Spinner } from '@nextui-org/spinner'; 4 | 5 | export interface ISpinnerProps { 6 | loading?: boolean; 7 | spinner?: React.ReactNode; 8 | children?: React.ReactNode; 9 | } 10 | 11 | const MySpinner = (props: ISpinnerProps) => { 12 | const { loading } = props; 13 | if (loading) { 14 | return ( 15 |
16 |
17 | {props.spinner ?? } 18 |
19 | {props.children} 20 |
21 | ); 22 | } 23 | return props.children; 24 | }; 25 | 26 | export default MySpinner; 27 | -------------------------------------------------------------------------------- /client/public/icons/CloudIcon.tsx: -------------------------------------------------------------------------------- 1 | const CloudIcon = () => ( 2 | 9 | 13 | 14 | ); 15 | export default CloudIcon; 16 | -------------------------------------------------------------------------------- /client/public/icons/StarIcon.tsx: -------------------------------------------------------------------------------- 1 | const StarIcon = (props: { className?: string }) => ( 2 | 9 | 15 | 16 | ); 17 | export default StarIcon; 18 | -------------------------------------------------------------------------------- /client/public/images/bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/public/icons/MinusCircleIcon.tsx: -------------------------------------------------------------------------------- 1 | const MinusCircleIcon = () => ( 2 | 9 | 15 | 16 | ); 17 | export default MinusCircleIcon; 18 | -------------------------------------------------------------------------------- /client/app/utils/I18N.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 多语言工具 3 | * @author https://github.com/alibaba/kiwi/blob/master/kiwi-demo/src/I18N.ts 4 | */ 5 | 6 | import kiwiIntl from 'kiwi-intl'; 7 | import enLangs from '../../.kiwi/en'; 8 | import zhCNLangs from '../../.kiwi/zh-CN'; 9 | import zhWTLangs from '../../.kiwi/zh-TW'; 10 | import jaLangs from '../../.kiwi/ja'; 11 | import koLangs from '../../.kiwi/ko'; 12 | 13 | export enum LangEnum { 14 | 'zh-CN' = 'zh-CN', 15 | 'zh-TW' = 'zh-TW', 16 | 'en' = 'en', 17 | 'ja' = 'ja', 18 | 'ko' = 'ko', 19 | } 20 | 21 | const langs = { 22 | en: enLangs, 23 | 'zh-CN': zhCNLangs, 24 | 'zh-TW': zhWTLangs, 25 | ja: jaLangs, 26 | ko: koLangs, 27 | }; 28 | 29 | const defaultLang = 'zh-CN'; 30 | 31 | const I18N = kiwiIntl.init(defaultLang, langs); 32 | 33 | export default I18N; 34 | -------------------------------------------------------------------------------- /client/public/icons/MenuIcon.tsx: -------------------------------------------------------------------------------- 1 | const MenuIcon = (props: { className?: string }) => ( 2 | 9 | 15 | 16 | ); 17 | export default MenuIcon; 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /assistant/.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'father'; 2 | 3 | export default defineConfig({ 4 | // more father config: https://github.com/umijs/father/blob/master/docs/config.md 5 | esm: {}, 6 | umd: { 7 | name: 'PetercatLUI', 8 | externals: { 9 | react: 'React', 10 | 'react-dom/client': 'ReactDOM', 11 | antd: 'antd', 12 | 'lottie-web': 'lottie', 13 | }, 14 | // 该项目借助 Umi 应用的能力来应用 tailwindcss,但该方案无法用于组件库 15 | // 所以此处先用黑科技绕一下,后续需要改成 tailwindcss 生成与框架解耦的方案 16 | chainWebpack(memo) { 17 | memo.module 18 | .rule('tailwindcss') 19 | .pre() 20 | .test(/src\/index\.ts$/) 21 | .use('prepend-tailwind-loader') 22 | .loader(require.resolve('./scripts/prepend-tailwind-loader.js')); 23 | 24 | return memo; 25 | }, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /assistant/src/Chat/template/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import GitInsightCard from './GitInsightCard'; 3 | import LoginCard from './LoginCard'; 4 | 5 | export const UITemplateRender = ({ templateId, apiDomain, webDomain, token, cardData }: { templateId: string, apiDomain: string; webDomain?: string; token: string; cardData: any }) => { 6 | if (templateId === 'GIT_INSIGHT') { 7 | return ( 8 | 13 | ); 14 | } 15 | 16 | if (templateId === 'LOGIN_INVITE') { 17 | return ( 18 | 23 | ); 24 | } 25 | return null; 26 | }; 27 | -------------------------------------------------------------------------------- /client/public/images/create.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assistant/src/icons/DeleteIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const DeleteIcon = () => ( 4 | 11 | 15 | 16 | ); 17 | 18 | -------------------------------------------------------------------------------- /assistant/src/Assistant/InitAssistant.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import Assistant, { AssistantProps } from './index'; 4 | 5 | /** 6 | * 命令式初始化 Assistant 组件 7 | */ 8 | export function initAssistant(props: AssistantProps) { 9 | if (typeof document === 'undefined') { 10 | return; 11 | } 12 | 13 | // 创建挂载点 14 | const elm = document.createElement('div'); 15 | document.body.appendChild(elm); 16 | 17 | const AssistantContainer = () => { 18 | React.useEffect(() => { 19 | return () => { 20 | if (document.body.contains(elm)) { 21 | document.body.removeChild(elm); 22 | } 23 | }; 24 | }, []); 25 | 26 | return ; 27 | }; 28 | 29 | // 渲染 React 组件 30 | createRoot(elm).render(); 31 | } 32 | -------------------------------------------------------------------------------- /assistant/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AreaChart } from './AreaChart'; 2 | export { default as Assistant } from './Assistant'; 3 | export { initAssistant } from './Assistant/InitAssistant'; 4 | export { default as BoxChart } from './BoxChart'; 5 | export { default as ChartHeader } from './ChartHeader'; 6 | export { default as Chat } from './Chat'; 7 | export { default as GitInsight } from './GitInsight'; 8 | export { default as GitInsightIcon } from './GitInsight/components/GitInsightIcon'; 9 | export { default as Heatmap } from './Heatmap'; 10 | export { default as useUser } from './hooks/useUser'; 11 | export { default as LineChart } from './LineChart'; 12 | export { default as RankChart } from './RankChart'; 13 | export { default as StarterList } from './StarterList'; 14 | export { default as ThoughtChain } from './ThoughtChain'; 15 | export * from './utils'; 16 | -------------------------------------------------------------------------------- /migrations/supabase/migrations/20240912023152_remote_schema.sql: -------------------------------------------------------------------------------- 1 | set check_function_bodies = off; 2 | CREATE OR REPLACE FUNCTION public.get_user_stats(filter_user_id text, start_date date, end_date date) 3 | RETURNS TABLE(usage_date date, input_tokens bigint, output_tokens bigint, total_tokens bigint) 4 | LANGUAGE plpgsql 5 | AS $function$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT 9 | u.date AS usage_date, -- 使用别名来避免歧义 10 | SUM(u.input_token)::BIGINT AS input_tokens, -- 将结果转换为 BIGINT 11 | SUM(u.output_token)::BIGINT AS output_tokens, -- 将结果转换为 BIGINT 12 | SUM(u.total_token)::BIGINT AS total_tokens -- 将结果转换为 BIGINT 13 | FROM user_token_usage u 14 | WHERE u.user_id = filter_user_id -- 使用别名来引用参数 15 | AND u.date >= start_date 16 | AND u.date <= end_date 17 | GROUP BY u.date; 18 | END; 19 | $function$; 20 | -------------------------------------------------------------------------------- /server/core/models/bot.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | from pydantic import BaseModel 4 | 5 | 6 | class BotModel(BaseModel): 7 | id: str 8 | uid: str 9 | repo_name: str 10 | avatar: Optional[str] = "" 11 | description: Optional[str] 12 | prompt: Optional[str] = "" 13 | name: str 14 | public: bool = False 15 | llm: Optional[str] = "openai" 16 | token_id: Optional[str] = "" 17 | created_at: datetime = datetime.now() 18 | domain_whitelist: Optional[list[str]] = [] 19 | temperature: Optional[float] = 0.2 20 | n: Optional[int] = 1 21 | top_p: Optional[float] = None 22 | 23 | 24 | class RepoBindBotConfigVO(BaseModel): 25 | repo_id: str 26 | robot_id: Optional[str] = "" 27 | 28 | 29 | class RepoBindBotRequest(BaseModel): 30 | repos: list[RepoBindBotConfigVO] 31 | -------------------------------------------------------------------------------- /client/public/icons/AddIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export const AddIcon = ({ 3 | fill = 'currentColor', 4 | filled = false, 5 | size = 'normal', 6 | height = '16px', 7 | width = '16px', 8 | label = '', 9 | ...props 10 | }) => ( 11 | 12 | 13 | 14 | ); 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/.kiwi/zh-CN/edit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | jiQiRenShangWei: '机器人尚未配置任何内容 请在完成配置后进行对话测试', 4 | baoCun: '保存', 5 | yuLanYuCeShi: '预览与测试', 6 | shouDongPeiZhi: '手动配置', 7 | duiHuaTiaoShi: '对话调试', 8 | chongXinShengChengPei: '重新生成配置', 9 | ziDongShengChengPei: '自动生成配置', 10 | diZhiYouWu: '地址有误', 11 | qingShuRuGI: '请输入或选择 GitHub 项目名称', 12 | fuZhiTOK: '复制 Token', 13 | tOKEN: 'Token 已复制到剪贴板', 14 | gITHU: 'Github 项目名称', 15 | bangWoPeiZhiYi: '帮我配置一个答疑机器人', 16 | chuCiJianMianXian: 17 | '👋🏻 初次见面,先自我介绍一下:我是 PeterCat,一个开源项目的机器人。你可以通过和我对话配置一个答疑机器人。', 18 | shengChengShiBai: '生成失败', 19 | shengChengChengGongE: '生成成功', 20 | baoCunShiBaiE: '保存失败', 21 | baoCunChengGong: '保存成功', 22 | shengChengShiBaiC: '生成失败', 23 | shengChengChengGong: '生成成功', 24 | baoCunYuBuShu: '保存与部署', 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /client/.kiwi/zh-TW/edit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | jiQiRenShangWei: '機器人尚未配置任何內容 請在完成配置後進行對話測試', 4 | baoCun: '儲存', 5 | yuLanYuCeShi: '預覽與測試', 6 | shouDongPeiZhi: '手動配置', 7 | duiHuaTiaoShi: '對話除錯', 8 | chongXinShengChengPei: '重新生成配置', 9 | ziDongShengChengPei: '自動生成配置', 10 | diZhiYouWu: '地址有誤', 11 | qingShuRuGI: '請輸入或選擇 GitHub 專案名稱', 12 | fuZhiTOK: '複製 Token', 13 | tOKEN: 'Token 已複製到剪貼簿', 14 | gITHU: 'GitHub 專案名稱', 15 | bangWoPeiZhiYi: '幫我配置一個答疑機器人', 16 | chuCiJianMianXian: 17 | '👋🏻 初次見面,先自我介紹一下:我是 PeterCat,一個開源專案的機器人。你可以透過和我對話配置一個答疑機器人。', 18 | shengChengShiBai: '生成失敗', 19 | shengChengChengGongE: '生成成功', 20 | baoCunShiBaiE: '儲存失敗', 21 | baoCunChengGong: '儲存成功', 22 | shengChengShiBaiC: '生成失敗', 23 | shengChengChengGong: '生成成功', 24 | baoCunYuBuShu: '儲存與部署', 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /server/core/models/authorization.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import json 3 | from pydantic import BaseModel, field_serializer 4 | from typing import Dict, Optional 5 | 6 | class Authorization(BaseModel): 7 | token: str 8 | installation_id: str 9 | code: str 10 | created_at: datetime 11 | expires_at: Optional[datetime] = datetime.now().isoformat() 12 | 13 | permissions: Optional[Dict] = {} 14 | 15 | @field_serializer('created_at') 16 | def serialize_created_at(self, created_at: datetime): 17 | return created_at.isoformat() 18 | 19 | @field_serializer('expires_at') 20 | def serialize_expires_at(self, expires_at: datetime): 21 | return expires_at.isoformat() 22 | 23 | @field_serializer('permissions') 24 | def serialize_permissions(self, permissions: Dict): 25 | return json.dumps(permissions) 26 | -------------------------------------------------------------------------------- /assistant/src/Chat/components/LoadingEnd.tsx: -------------------------------------------------------------------------------- 1 | import Lottie from 'lottie-react'; 2 | import React, { useState } from 'react'; 3 | 4 | import LoadingAnimationEnd from '../../assets/bubble-end.json'; 5 | 6 | interface LoadingEndProps { 7 | children?: React.ReactNode; 8 | onComplete?: () => void; 9 | } 10 | 11 | const LoadingEnd: React.FC = (props) => { 12 | const { children } = props; 13 | 14 | const [complete, setComplete] = useState(false); 15 | 16 | if (complete) { 17 | return children; 18 | } 19 | 20 | return ( 21 | <> 22 |
23 | setComplete(true)} 28 | /> 29 |
30 | 31 | ); 32 | }; 33 | 34 | export default LoadingEnd; 35 | -------------------------------------------------------------------------------- /assistant/src/GitInsight/index.md: -------------------------------------------------------------------------------- 1 | 2 | # GitInsight 3 | 4 | GitInsight 是一个展示 Git 仓库统计信息(包括 Star 数量、Fork 数量和 Commit 数量)的组件。每个统计信息通过 CountCard 组件展示,并带有渐进的动画效果。 5 | 6 | ## 安装 7 | 8 | 确保你已经安装了必要的依赖: 9 | 10 | ```bash 11 | npm install @petercatai/assistant 12 | ``` 13 | 14 | ## 使用示例 15 | 16 | ```tsx 17 | import React from 'react'; 18 | import { GitInsight } from '@petercatai/assistant'; 19 | 20 | export default () => ( 21 | 26 | ); 27 | ``` 28 | ## API 29 | 30 | | 属性名 | 类型 | 默认值 | 描述 | 31 | | ------------- | -------- | ------ | ------------------------ | 32 | | `forkCount` | `number` | `0` | Git 仓库的 Fork 数量。 | 33 | | `commitCount` | `number` | `0` | Git 仓库的 Commit 数量。 | 34 | | `starCount` | `number` | `0` | Git 仓库的 Star 数量。 | 35 | -------------------------------------------------------------------------------- /client/components/Markdown.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { remark } from 'remark'; 4 | import html from 'remark-html'; 5 | 6 | const Markdown: React.FC<{ markdownContent: string; theme?: boolean }> = ({ 7 | markdownContent, 8 | theme = true, 9 | }) => { 10 | const [content, setContent] = useState(''); 11 | 12 | useEffect(() => { 13 | const processMarkdown = async () => { 14 | const processedContent = await remark() 15 | .use(html) 16 | .process(markdownContent); 17 | setContent(processedContent.toString()); 18 | }; 19 | processMarkdown(); 20 | }, [markdownContent]); 21 | return ( 22 |
26 | ); 27 | }; 28 | 29 | export default Markdown; 30 | -------------------------------------------------------------------------------- /assistant/src/Chat/template/LoginCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | 4 | import GitHubIcon from '../../icons/GitHubIcon'; 5 | import useUser from '../../hooks/useUser'; 6 | 7 | const LoginCard = ({ apiDomain, webDomain, token }: { apiDomain: string; webDomain?: string; token: string; }) => { 8 | const { user, isLoading, actions } = useUser({ apiDomain, webDomain, fingerprint: token }); 9 | 10 | if (isLoading) { 11 | return 12 | } 13 | 14 | if (!user || user.id.startsWith('client|')) { 15 | return ( 16 | 23 | ); 24 | } 25 | 26 | return

你已完成登录。可以继续输入了。

27 | 28 | } 29 | 30 | export default LoginCard; 31 | -------------------------------------------------------------------------------- /client/public/images/statistic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /migrations/supabase/migrations/20241224030938_remote_schema.sql: -------------------------------------------------------------------------------- 1 | set check_function_bodies = off; 2 | 3 | CREATE OR REPLACE FUNCTION public.analyze_user_token_usage(start_date date, end_date date) 4 | RETURNS TABLE(bot_id text, usage_date date, input_tokens bigint, output_tokens bigint, total_tokens bigint) 5 | LANGUAGE plpgsql 6 | AS $function$ 7 | BEGIN 8 | RETURN QUERY 9 | SELECT 10 | u.bot_id AS bot_id, 11 | u.date AS usage_date, -- 使用别名来避免歧义 12 | SUM(u.input_token)::BIGINT AS input_tokens, -- 将结果转换为 BIGINT 13 | SUM(u.output_token)::BIGINT AS output_tokens, -- 将结果转换为 BIGINT 14 | SUM(u.total_token)::BIGINT AS total_tokens -- 将结果转换为 BIGINT 15 | FROM user_token_usage u 16 | WHERE 17 | u.date >= start_date AND 18 | u.date <= end_date 19 | GROUP BY u.date, u.bot_id; 20 | END; 21 | $function$ 22 | ; 23 | 24 | 25 | -------------------------------------------------------------------------------- /server/agent/bot/bot_builder.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncIterator 2 | from agent.llm.clients.openai import OpenAIClient 3 | from core.type_class.data_class import ChatData 4 | 5 | from agent.base import AgentBuilder, dict_to_sse 6 | from agent.prompts.bot_builder import generate_prompt_by_user_id 7 | from agent.tools import bot_builder 8 | 9 | TOOL_MAPPING = { 10 | "create_bot": bot_builder.create_bot, 11 | "edit_bot": bot_builder.edit_bot, 12 | } 13 | 14 | 15 | def agent_stream_chat( 16 | input_data: ChatData, 17 | user_id: str, 18 | bot_id: str, 19 | ) -> AsyncIterator[str]: 20 | prompt = generate_prompt_by_user_id(user_id, bot_id) 21 | agent = AgentBuilder( 22 | chat_model=OpenAIClient(), 23 | prompt=prompt, 24 | tools=TOOL_MAPPING, 25 | enable_tavily=False, 26 | ) 27 | return dict_to_sse(agent.run_stream_chat(input_data)) 28 | -------------------------------------------------------------------------------- /server/utils/private_key/s3.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | from botocore.exceptions import ClientError 3 | from utils.env import get_env_variable 4 | from utils.private_key.base import BasePrivateKeyProvider 5 | 6 | REGION_NAME = get_env_variable("AWS_REGION") 7 | 8 | 9 | class S3PrivateKeyProvider(BasePrivateKeyProvider): 10 | def get_private_key(self, secret_id: str) -> str: 11 | session = boto3.session.Session() 12 | client = session.client(service_name="secretsmanager", region_name=REGION_NAME) 13 | try: 14 | get_secret_value_response = client.get_secret_value(SecretId=secret_id) 15 | except ClientError as e: 16 | # For a list of exceptions thrown, see 17 | # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html 18 | raise e 19 | 20 | return get_secret_value_response["SecretString"] -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "paths": { 26 | "@/*": [ 27 | "./*" 28 | ] 29 | }, 30 | "resolveJsonModule": true 31 | }, 32 | "include": [ 33 | "next-env.d.ts", 34 | "**/*.ts", 35 | "**/*.tsx", 36 | ".next/types/**/*.ts" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /client/public/icons/SaveIcon.tsx: -------------------------------------------------------------------------------- 1 | const SaveIcon = () => ( 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | export default SaveIcon; 15 | -------------------------------------------------------------------------------- /assistant/src/icons/ThunderIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export const ThunderIcon = () => { 3 | return ( 4 | 11 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /docker/Dockerfile.aws.lambda: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/docker/library/python:3.12.0-slim-bullseye 2 | 3 | LABEL org.opencontainers.image.source=https://github.com/petercat-ai/petercat 4 | LABEL org.opencontainers.image.description="Petercat Container Image" 5 | LABEL org.opencontainers.image.licenses=MIT 6 | 7 | # Copy aws-lambda-adapter for Steaming response 8 | COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.1 /lambda-adapter /opt/extensions/lambda-adapter 9 | 10 | # Copy nltk_lambda_layer for using nltk in lambda 11 | COPY --from=petercatai/nltk-layer:1.0.1 /nltk_data /opt/nltk_data 12 | 13 | # Copy function code 14 | COPY . ${LAMBDA_TASK_ROOT} 15 | # from your project folder. 16 | COPY requirements.txt . 17 | RUN pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}" -U --no-cache-dir 18 | 19 | # Set NLTK_DATA to load nltk_data 20 | ENV NLTK_DATA=/opt/nltk_data 21 | 22 | CMD ["python", "main.py"] 23 | -------------------------------------------------------------------------------- /server/tests/mock_session.py: -------------------------------------------------------------------------------- 1 | import json 2 | from base64 import b64encode 3 | from itsdangerous import TimestampSigner 4 | from core.models.user import User 5 | from utils.env import get_env_variable 6 | 7 | session_secret_key = get_env_variable("FASTAPI_SECRET_KEY") 8 | 9 | 10 | def create_session_cookie(data) -> str: 11 | signer = TimestampSigner(str(session_secret_key)) 12 | 13 | return signer.sign( 14 | b64encode(json.dumps(data).encode("utf-8")), 15 | ).decode("utf-8") 16 | 17 | 18 | mock_user = User( 19 | id="1", 20 | sub="1", 21 | sid="1", 22 | avatar="1", 23 | picture="1", 24 | nickname="1", 25 | access_token="1", 26 | anonymous=False, 27 | agreement_accepted=False, 28 | ) 29 | 30 | 31 | def get_mock_user(): 32 | return mock_user 33 | 34 | 35 | def mock_session(): 36 | return {"session": create_session_cookie({"user": dict(mock_user)})} 37 | -------------------------------------------------------------------------------- /client/sentry.client.config.js: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | Sentry.init({ 4 | dsn: "https://25cd1bf180d345e9c926589cd0ca0353@o4507910955597824.ingest.us.sentry.io/4507910957432832", 5 | // Replay may only be enabled for the client-side 6 | integrations: [Sentry.replayIntegration()], 7 | 8 | // Set tracesSampleRate to 1.0 to capture 100% 9 | // of transactions for tracing. 10 | // We recommend adjusting this value in production 11 | tracesSampleRate: 1.0, 12 | 13 | // Capture Replay for 10% of all sessions, 14 | // plus for 100% of sessions with an error 15 | replaysSessionSampleRate: 0.1, 16 | replaysOnErrorSampleRate: 1.0, 17 | 18 | // ... 19 | 20 | // Note: if you want to override the automatic release value, do not set a 21 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so 22 | // that it will also get attached to your source maps 23 | }); 24 | -------------------------------------------------------------------------------- /assistant/src/Chat/components/UserContent.tsx: -------------------------------------------------------------------------------- 1 | import { Image } from 'antd'; 2 | import React, { type FC } from 'react'; 3 | import { ImageURLContentBlock } from '../../interface'; 4 | import MarkdownRender from './MarkdownRender'; 5 | 6 | interface IProps { 7 | images: ImageURLContentBlock[]; 8 | text: string; 9 | } 10 | 11 | const UserContent: FC = ({ images, text }) => { 12 | return ( 13 |
14 | {images.map((image, index) => ( 15 | img 25 | ))} 26 | {text && } 27 |
28 | ); 29 | }; 30 | 31 | export default UserContent; 32 | -------------------------------------------------------------------------------- /server/agent/llm/base.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import Any, Dict, List, Optional 3 | from langchain_core.language_models import BaseChatModel 4 | from core.type_class.data_class import MessageContent 5 | 6 | 7 | class BaseLLMClient: 8 | def __init__( 9 | self, 10 | temperature: Optional[float] = 0.2, 11 | n: Optional[int] = 1, 12 | top_p: Optional[float] = None, 13 | max_tokens: Optional[int] = 1500, 14 | streaming: Optional[bool] = False, 15 | api_key: Optional[str] = "", 16 | ): 17 | pass 18 | 19 | @abstractmethod 20 | def get_client() -> BaseChatModel: 21 | pass 22 | 23 | @abstractmethod 24 | def get_tools(self, tool: List[Any]) -> list[Dict[str, Any]]: 25 | pass 26 | 27 | @abstractmethod 28 | def parse_content(self, content: List[MessageContent]) -> List[MessageContent]: 29 | pass 30 | -------------------------------------------------------------------------------- /server/core/type_class/bot.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List 2 | from pydantic import BaseModel, HttpUrl 3 | 4 | 5 | class BotCreateRequest(BaseModel): 6 | repo_name: str 7 | starters: Optional[List[str]] = None 8 | hello_message: Optional[str] = None 9 | lang: Optional[str] = "en" 10 | 11 | 12 | class BotDeployRequest(BaseModel): 13 | bot_id: str 14 | website_url: Optional[HttpUrl] = None 15 | 16 | 17 | class BotUpdateRequest(BaseModel): 18 | id: str 19 | avatar: Optional[str] = None 20 | description: Optional[str] = None 21 | prompt: Optional[str] = None 22 | name: Optional[str] = None 23 | starters: Optional[List[str]] = None 24 | public: Optional[bool] = None 25 | hello_message: Optional[str] = None 26 | llm: Optional[str] = "openai" 27 | token_id: Optional[str] = "" 28 | 29 | 30 | class ErrorResponse(BaseModel): 31 | error: str 32 | status: int 33 | -------------------------------------------------------------------------------- /client/app/factory/edit/components/DeployBotModal/utils.ts: -------------------------------------------------------------------------------- 1 | import type { BindBotToRepoConfig } from '@/app/services/BotsController'; 2 | import { RepoBindBotConfig } from './types'; 3 | 4 | export const diffRepoBindResult = ( 5 | origin: RepoBindBotConfig[], 6 | current: RepoBindBotConfig[], 7 | BOT_ID: string, 8 | ): BindBotToRepoConfig[] => { 9 | return origin 10 | .map((item) => { 11 | const currentItem = current.find((cur) => cur.repo_id === item.repo_id); 12 | if (currentItem) { 13 | const originChecked = item.checked; 14 | const curChecked = currentItem.checked; 15 | 16 | if (originChecked !== curChecked) { 17 | return { 18 | repo_id: item.repo_id, 19 | robot_id: curChecked ? BOT_ID : '', 20 | }; 21 | } 22 | } 23 | return null; 24 | }) 25 | .filter((item) => item !== null) as BindBotToRepoConfig[]; 26 | }; 27 | -------------------------------------------------------------------------------- /client/public/icons/GitHubIcon.tsx: -------------------------------------------------------------------------------- 1 | const GitHubIcon = (props: { className?: string }) => ( 2 | 3 | 7 | 8 | ); 9 | export default GitHubIcon; 10 | -------------------------------------------------------------------------------- /client/app/hooks/useAnalyze.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query'; 2 | import { analyzeTokenUsage, analyzeTopBots, analyzeTopUsers } from '../services/TokensController'; 3 | 4 | export interface BotUsage { 5 | bot_name: string; 6 | total_tokens: number; 7 | usage_date: Date; 8 | } 9 | 10 | export function useAnalyze() { 11 | return useQuery({ 12 | queryKey: [`usage.analyze`], 13 | queryFn: async () => analyzeTokenUsage(), 14 | retry: false, 15 | }); 16 | } 17 | 18 | export function useTopBots() { 19 | return useQuery({ 20 | queryKey: [`usage.top.bots`], 21 | queryFn: async () => analyzeTopBots(), 22 | retry: false, 23 | }); 24 | } 25 | 26 | export function useTopUsers() { 27 | return useQuery<{ user_name: string; total_tokens: number;}[]>({ 28 | queryKey: [`usage.top.users`], 29 | queryFn: async () => analyzeTopUsers(), 30 | retry: false, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /client/app/knowledge/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useSearchParams } from 'next/navigation'; 4 | import React from 'react'; 5 | import KnowledgePageHeader from './components/Header'; 6 | import KnowledgeList from './components/KnowledgeList'; 7 | import { ToastContainer } from 'react-toastify'; 8 | 9 | import 'react-toastify/dist/ReactToastify.css'; 10 | 11 | export default function KnowledgePage() { 12 | const searchParams = useSearchParams(); 13 | const repo_name = searchParams.get('repo_name'); 14 | const bot_id = searchParams.get('bot_id'); 15 | return ( 16 |
17 | 18 | 22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /assistant/src/mock/inputArea.mock.ts: -------------------------------------------------------------------------------- 1 | import { IBot } from '../interface'; 2 | 3 | export const DEFAULT_HELLO_MESSAGE = 4 | '我是你的私人助理Kate, 我有许多惊人的能力,比如你可以对我说我想创建一个机器人'; 5 | 6 | export const DEFAULT_PROMPTS = [ 7 | '什么是克苏鲁神话', 8 | '洛夫克拉特的作品有哪些', 9 | '他为什么被认为是最具影响力的恐怖小说家之一', 10 | ]; 11 | 12 | export const BOTS: IBot[] = [ 13 | { 14 | displayName: 'bailing65b', 15 | name: 'bailing65b_trtint4_stream', 16 | avatar: 17 | 'https://mdn.alipayobjects.com/huamei_yhboz9/afts/img/A*li7ySppF7TYAAAAAAAAAAAAADlDCAQ/original', 18 | }, 19 | { 20 | displayName: 'gpt', 21 | name: 'qwen72b_stream', 22 | avatar: 23 | 'https://mdn.alipayobjects.com/huamei_yhboz9/afts/img/A*li7ySppF7TYAAAAAAAAAAAAADlDCAQ/original', 24 | }, 25 | { 26 | displayName: 'qwen', 27 | name: 'qwen14b_stream', 28 | avatar: 29 | 'https://mdn.alipayobjects.com/huamei_yhboz9/afts/img/A*li7ySppF7TYAAAAAAAAAAAAADlDCAQ/original', 30 | }, 31 | ]; 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Docker volumnes 2 | docker/volumes/db/data 3 | docker/volumes/storage 4 | 5 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 6 | __pycache__/ 7 | *.pyc 8 | **/*.pyc 9 | # dependencies 10 | node_modules/ 11 | 12 | server/temp/ 13 | 14 | venv 15 | .venv/ 16 | 17 | # testing 18 | /coverage 19 | .coverage 20 | 21 | # next.js 22 | .next/ 23 | out/ 24 | 25 | .ruff_cache/ 26 | 27 | # production 28 | build 29 | 30 | # misc 31 | .DS_Store 32 | *.pem 33 | 34 | # debug 35 | npm-debug.log* 36 | yarn-debug.log* 37 | yarn-error.log* 38 | .yarnrc.yml 39 | 40 | # local env files 41 | .env*.local 42 | .env*.pre 43 | .env 44 | *.env 45 | *.env.local 46 | # vercel 47 | .vercel 48 | 49 | # typescript 50 | *.tsbuildinfo 51 | next-env.d.ts 52 | 53 | .yarn 54 | /server/.aws-sam/* 55 | .aws-sam/* 56 | 57 | # IDE 58 | .idea 59 | **/.vscode/ 60 | 61 | # Pytest 62 | .pytest_cache 63 | 64 | dist/ 65 | assistant/src/style/global.css 66 | -------------------------------------------------------------------------------- /assistant/src/icons/GitHubIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const GitHubIcon = (props: { className?: string }) => ( 4 | 5 | 9 | 10 | ); 11 | export default GitHubIcon; 12 | -------------------------------------------------------------------------------- /client/public/icons/LoadingIcon.tsx: -------------------------------------------------------------------------------- 1 | const KnowledgeTaskRunningIcon = (props: { className?: string }) => ( 2 |
3 | 10 | 17 | 18 |
19 | ); 20 | export default KnowledgeTaskRunningIcon; 21 | -------------------------------------------------------------------------------- /client/.kiwi/ko/edit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | jiQiRenShangWei: 4 | '봇이 아직 아무 내용도 구성되지 않았습니다. 구성이 완료된 후 대화 테스트를 진행하십시오.', 5 | baoCun: '저장', 6 | yuLanYuCeShi: '미리보기 및 테스트', 7 | shouDongPeiZhi: '수동 구성', 8 | duiHuaTiaoShi: '대화 디버깅', 9 | chongXinShengChengPei: '구성 다시 생성', 10 | ziDongShengChengPei: '구성 자동 생성', 11 | diZhiYouWu: '주소 오류', 12 | qingShuRuGI: 'GitHub 프로젝트 이름을 입력하거나 선택하세요', 13 | fuZhiTOK: '토큰 복사', 14 | tOKEN: '토큰이 클립보드에 복사되었습니다.', 15 | gITHU: 'GitHub 프로젝트 이름', 16 | bangWoPeiZhiYi: 'Q&A 봇 설정을 도와주세요.', 17 | chuCiJianMianXian: 18 | '👋🏻 안녕하세요! 처음 뵙겠습니다. 제 소개를 하겠습니다: 저는 PeterCat입니다, 오픈소스 프로젝트의 로봇입니다. 저와 대화를 통해 질의응답 로봇을 구성할 수 있습니다.', 19 | shengChengShiBai: '생성 실패', 20 | shengChengChengGongE: '생성 성공', 21 | baoCunShiBaiE: '저장 실패', 22 | baoCunChengGong: '저장 성공', 23 | shengChengShiBaiC: '생성 실패', 24 | shengChengChengGong: '생성 성공', 25 | baoCunYuBuShu: '저장 및 배포', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /client/public/icons/ErrorBadgeIcon.tsx: -------------------------------------------------------------------------------- 1 | const ErrorBadgeIcon = () => ( 2 | 9 | 15 | 16 | ); 17 | export default ErrorBadgeIcon; 18 | -------------------------------------------------------------------------------- /client/.kiwi/ja/edit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | jiQiRenShangWei: 4 | 'ボットがまだ何も設定されていません。設定完了後に対話テストを行ってください。', 5 | baoCun: '保存', 6 | yuLanYuCeShi: 'プレビューとテスト', 7 | shouDongPeiZhi: '手動設定', 8 | duiHuaTiaoShi: '対話デバッグ', 9 | chongXinShengChengPei: '設定を再生成', 10 | ziDongShengChengPei: '設定を自動生成', 11 | diZhiYouWu: 'アドレスに誤りがあります', 12 | qingShuRuGI: 'GitHubプロジェクト名を入力または選択してください', 13 | fuZhiTOK: 'トークンをコピー', 14 | tOKEN: 'トークンがクリップボードにコピーされました', 15 | gITHU: 'GitHubプロジェクト名', 16 | bangWoPeiZhiYi: 'Q&Aボットの設定を手伝ってください', 17 | chuCiJianMianXian: 18 | '👋🏻 こんにちは!初めまして、自己紹介させていただきます。私はPeterCatと申します。オープンソースプロジェクトのロボットです。私と対話することで、Q&Aロボットを設定できます。', 19 | shengChengShiBai: '生成に失敗しました', 20 | shengChengChengGongE: '生成に成功しました', 21 | baoCunShiBaiE: '保存に失敗しました', 22 | baoCunChengGong: '保存に成功しました', 23 | shengChengShiBaiC: '生成に失敗しました', 24 | shengChengChengGong: '生成に成功しました', 25 | baoCunYuBuShu: '保存と展開', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /client/public/icons/BookIcon.tsx: -------------------------------------------------------------------------------- 1 | const BookIcon = () => ( 2 | 9 | 13 | 17 | 18 | ); 19 | export default BookIcon; 20 | -------------------------------------------------------------------------------- /assistant/src/Markdown/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 3 | // @ts-ignore 4 | import { vs } from 'react-syntax-highlighter/dist/cjs/styles/prism'; 5 | 6 | import CopyButton from './CopyButton'; 7 | 8 | const CodeBlock: React.FC<{ 9 | inline?: boolean; 10 | className?: string; 11 | children?: React.ReactNode; 12 | }> = ({ inline, className, children }) => { 13 | const language = className?.replace('language-', '') || ''; // 提取语言信息 14 | const codeContent = String(children).trim(); 15 | 16 | if (inline) { 17 | return {children}; 18 | } 19 | 20 | return ( 21 |
22 | 23 | {codeContent} 24 | 25 | 26 |
27 | ); 28 | }; 29 | 30 | export default CodeBlock; 31 | -------------------------------------------------------------------------------- /client/next.config.js: -------------------------------------------------------------------------------- 1 | const { withSentryConfig } = require("@sentry/nextjs"); 2 | 3 | const nextConfig = { 4 | ...process.env.NEXT_STANDALONE ? { output: "standalone" } : {}, // Standalone configuration 5 | webpack: (config, { dev }) => { 6 | config.resolve.fallback = { http: false, https: false, net: false, tls: false }; 7 | 8 | if (dev) { 9 | config.watchOptions = { 10 | followSymlinks: true, 11 | }; 12 | 13 | config.snapshot.managedPaths = []; 14 | } 15 | 16 | // Add markdown loader 17 | config.module.rules.push({ 18 | test: /\.md$/, 19 | use: 'raw-loader', 20 | }); 21 | 22 | return config; 23 | }, 24 | }; 25 | 26 | 27 | const sentryOptions = { 28 | org: "petercat", 29 | project: "petercat", 30 | authToken: process.env.SENTRY_AUTH_TOKEN, 31 | silent: false, 32 | hideSourceMaps: true, 33 | sourcemaps: { 34 | disable: true, 35 | }, 36 | }; 37 | 38 | module.exports = withSentryConfig(nextConfig, sentryOptions); 39 | -------------------------------------------------------------------------------- /server/insight/service/pr.py: -------------------------------------------------------------------------------- 1 | from utils.insight import get_data 2 | 3 | 4 | def get_pr_data(repo_name): 5 | metrics_mapping = { 6 | "change_requests": "open", 7 | "change_requests_accepted": "merge", 8 | "change_requests_reviews": "reviews", 9 | } 10 | return get_data(repo_name, metrics_mapping) 11 | 12 | 13 | def get_code_frequency(repo_name): 14 | metrics_mapping = { 15 | "code_change_lines_add": "add", 16 | "code_change_lines_remove": "remove", 17 | } 18 | data = get_data(repo_name, metrics_mapping) 19 | 20 | def process_entries(entries): 21 | """Convert 'remove' entries to negative values.""" 22 | return [ 23 | {**entry, "value": -entry["value"]} if entry["type"] == "remove" else entry 24 | for entry in entries 25 | ] 26 | 27 | for key in ["year", "quarter", "month"]: 28 | if key in data: 29 | data[key] = process_entries(data[key]) 30 | 31 | return data 32 | -------------------------------------------------------------------------------- /client/public/icons/ChatIcon.tsx: -------------------------------------------------------------------------------- 1 | const ChatIcon = () => ( 2 | 8 | 12 | 16 | 17 | ) 18 | export default ChatIcon 19 | -------------------------------------------------------------------------------- /client/public/icons/HomeIcon.tsx: -------------------------------------------------------------------------------- 1 | const HomeIcon = () => ( 2 | 9 | 15 | 16 | ); 17 | 18 | export default HomeIcon; 19 | -------------------------------------------------------------------------------- /client/public/icons/BulbIcon.tsx: -------------------------------------------------------------------------------- 1 | const BulbIcon = () => ( 2 | 3 | 7 | 13 | 14 | ); 15 | export default BulbIcon; 16 | -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | const {nextui} = require("@nextui-org/react"); 3 | module.exports = { 4 | mode: 'jit', 5 | content: [ 6 | './app/pages/**/*.{js,ts,jsx,tsx,mdx}', 7 | './components/**/*.{js,ts,jsx,tsx,mdx}', 8 | './app/**/*.{js,ts,jsx,tsx,mdx}', 9 | "./node_modules/@nextui-org/**/dist/**/*.{js,ts,jsx,tsx,mjs}", 10 | "./node_modules/@petercatai/assistant/dist/**/*.{js,jsx}" 11 | ], 12 | theme: { 13 | extend: { 14 | backgroundImage: { 15 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 16 | 'gradient-conic': 17 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 18 | }, 19 | boxShadow: { 20 | 'large': '0px 20px 25px -5px rgba(0, 0, 0, 0.10), 0px 10px 10px -5px rgba(0, 0, 0, 0.04);' 21 | }, 22 | fontFamily: { 23 | solitreo: ['Solitreo', 'sans-serif'], 24 | }, 25 | }, 26 | }, 27 | darkMode: "class", 28 | plugins: [nextui()], 29 | } 30 | -------------------------------------------------------------------------------- /client/app/utils/tools.ts: -------------------------------------------------------------------------------- 1 | import I18N from '@/app/utils/I18N'; 2 | export const extractParametersByTools = (content: string) => { 3 | const regex = /\$\$TOOLS\$\$ (.+?) \$\$END\$\$/; 4 | const match = content.match(regex); 5 | if (match && match[1]) { 6 | try { 7 | const json = JSON.parse(match[1]); 8 | return json.parameters; 9 | } catch (error) { 10 | console.error(I18N.utils.tools.jieXiJSO, error); 11 | } 12 | } 13 | return null; 14 | }; 15 | 16 | export const extractFullRepoNameFromGitHubUrl = (input: string) => { 17 | try { 18 | // Use a regex that matches both full URLs and `username/reponame` format. 19 | const regex = /^(?:https:\/\/github\.com\/)?([^\/]+)\/([^\/]+)(?:\/.*)?$/; 20 | const match = input.match(regex); 21 | 22 | if (match && match[1] && match[2]) { 23 | return `${match[1]}/${match[2]}`; 24 | } else { 25 | return null; 26 | } 27 | } catch (error) { 28 | console.error('Error parsing input:', error); 29 | return null; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /client/public/icons/SearchIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export const SearchIcon = ({ 3 | fill = 'currentColor', 4 | filled = false, 5 | size = 'normal', 6 | height = '16px', 7 | width = '16px', 8 | label = '', 9 | ...props 10 | }) => ( 11 | 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /server/core/dao/llmTokenDAO.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from typing import Optional 4 | from core.dao.BaseDAO import BaseDAO 5 | from supabase.client import Client 6 | from utils.supabase import get_client 7 | from core.models.llm_token import LLMToken 8 | 9 | logger = logging.getLogger() 10 | logger.setLevel("INFO") 11 | 12 | class LLMTokenDAO(BaseDAO): 13 | client: Client 14 | 15 | def __init__(self): 16 | super().__init__() 17 | self.client = get_client() 18 | 19 | def get_llm_token(self, llm: Optional[str | None] = None, free: Optional[bool | None] = None) -> LLMToken: 20 | query = self.client.table("llm_tokens") \ 21 | .select('*') 22 | if llm is not None: 23 | query = query.eq("llm", llm) 24 | 25 | if free: 26 | query = query.eq("free", free) 27 | 28 | response = query.execute() 29 | 30 | if not response.data or not response.data[0]: 31 | return [] 32 | 33 | return LLMToken(**response.data[0]) -------------------------------------------------------------------------------- /server/utils/path_to_hunk.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | def convert_patch_to_hunk(diff): 4 | if not diff: 5 | return '' 6 | old_line, new_line = 0, 0 7 | result = [] 8 | 9 | for line in diff.splitlines(): 10 | if line.startswith('@@'): 11 | # 使用正则表达式提取旧文件和新文件的起始行号 12 | match = re.search(r'@@ -(\d+),?\d* \+(\d+),?\d* @@', line) 13 | if match: 14 | old_line = int(match.group(1)) 15 | new_line = int(match.group(2)) 16 | continue # 跳过 @@ 行的输出 17 | elif line.startswith('-'): 18 | result.append(f" {old_line:<5} {line}") # 仅旧文件有内容 19 | old_line += 1 20 | elif line.startswith('+'): 21 | result.append(f"{new_line:<5} {line}") # 仅新文件有内容 22 | new_line += 1 23 | else: 24 | result.append(f"{new_line:<5} {old_line:<5} {line}") # 两边都有的内容 25 | old_line += 1 26 | new_line += 1 27 | 28 | return "NewFile OldFile SourceCode \n" + "\n".join(result) -------------------------------------------------------------------------------- /server/agent/bot/__init__.py: -------------------------------------------------------------------------------- 1 | from core.models.bot import BotModel 2 | from agent.llm import LLM, LLMTokenLike 3 | 4 | 5 | class Bot: 6 | _bot: BotModel 7 | _llm: LLM 8 | _llm_token: LLMTokenLike 9 | 10 | def __init__(self, bot: BotModel, llm_token: LLMTokenLike): 11 | self._bot = bot 12 | self._llm_token = llm_token 13 | self._llm = LLM( 14 | llm_token=llm_token, 15 | temperature=bot.temperature, 16 | n=bot.n, 17 | top_p=bot.top_p, 18 | ) 19 | 20 | @property 21 | def id(self): 22 | return self._bot.id 23 | 24 | @property 25 | def prompt(self): 26 | return self._bot.prompt 27 | 28 | @property 29 | def llm_token(self): 30 | return self._llm_token 31 | 32 | @property 33 | def llm(self): 34 | return self._llm._client 35 | 36 | @property 37 | def token_id(self): 38 | return self._bot.token_id 39 | 40 | @property 41 | def repo_name(self): 42 | return self._bot.repo_name 43 | -------------------------------------------------------------------------------- /client/app/hooks/useToken.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query'; 2 | import { getTokenList, getLLMList } from '../services/TokensController'; 3 | import { Tables, Database } from '@/types/database.types'; 4 | import { Updater, useImmer } from 'use-immer'; 5 | 6 | export declare type LLMToken = Tables<'user_llm_tokens'>; 7 | export declare type LLMTokenInsert = Database['public']['Tables']['user_llm_tokens']['Insert']; 8 | 9 | export function useTokenList() { 10 | return useQuery({ 11 | queryKey: [`token.list`], 12 | queryFn: async () => getTokenList(), 13 | retry: false, 14 | }); 15 | } 16 | 17 | const defaultLLMToken = {} 18 | 19 | export function useCreateToken(): [LLMTokenInsert, Updater] { 20 | const [llmToken, setLLMToken] = useImmer(defaultLLMToken); 21 | 22 | return [llmToken, setLLMToken] 23 | } 24 | 25 | export function useListLLMs() { 26 | return useQuery({ 27 | queryKey: [`llm.list`], 28 | queryFn: async () => getLLMList(), 29 | retry: false, 30 | }); 31 | } -------------------------------------------------------------------------------- /server/auth/get_user_info.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, Request 2 | from auth.clients import get_auth_client 3 | from auth.clients.base import BaseAuthClient 4 | from core.models.user import User 5 | 6 | from utils.env import get_env_variable 7 | 8 | AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") 9 | 10 | async def get_user_id(request: Request): 11 | user_info = request.session.get("user") 12 | try: 13 | if user_info is None: 14 | return None 15 | return user_info["sub"] 16 | 17 | except Exception: 18 | return None 19 | 20 | 21 | async def get_user(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)) -> User | None: 22 | user_info = request.session.get("user") 23 | if user_info is None: 24 | return None 25 | 26 | if user_info["sub"].startswith("client|"): 27 | return User(**user_info, anonymous=True) 28 | 29 | access_token = await auth_client.get_access_token(user_id=user_info["sub"]) 30 | return User(**user_info, access_token=access_token, anonymous=False) 31 | -------------------------------------------------------------------------------- /assistant/src/icons/UploadImageIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export const UploadImageIcon = () => { 3 | return ( 4 | 5 | 6 | 7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /client/app/constant/avatar.ts: -------------------------------------------------------------------------------- 1 | export const AVATARS = [ 2 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*cK4XTpUP9ZMAAAAAAAAAAAAADrPSAQ/original', 3 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*DkUHTL5J7TwAAAAAAAAAAAAADrPSAQ/original', 4 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*8iAgQ4GjWFwAAAAAAAAAAAAADrPSAQ/original', 5 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*Qb3IRYxiiwAAAAAAAAAAAAAADrPSAQ/original', 6 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*RIJKQb5KlHUAAAAAAAAAAAAADrPSAQ/original', 7 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*zKvKQYWth78AAAAAAAAAAAAADrPSAQ/original', 8 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*P4SpQq-kBIIAAAAAAAAAAAAADrPSAQ/original', 9 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*L24SQpx9RrYAAAAAAAAAAAAADrPSAQ/original', 10 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*5JwzTIq2VOIAAAAAAAAAAAAADrPSAQ/original', 11 | 'https://mdn.alipayobjects.com/huamei_j8gzmo/afts/img/A*vvOsTrFCXskAAAAAAAAAAAAADrPSAQ/original', 12 | ]; 13 | -------------------------------------------------------------------------------- /client/app/hooks/useUser.ts: -------------------------------------------------------------------------------- 1 | import { useUser as useAssistUser } from '@petercatai/assistant'; 2 | import { useFingerprint } from './useFingerprint'; 3 | import { useQuery } from '@tanstack/react-query'; 4 | import { getUserRepos } from '../services/UserController'; 5 | import { map } from 'lodash'; 6 | 7 | const API_DOMAIN = process.env.NEXT_PUBLIC_API_DOMAIN!; 8 | 9 | export const useUser = () => { 10 | const { data: fingerprint } = useFingerprint(); 11 | const { user, isLoading, actions } = useAssistUser({ 12 | apiDomain: API_DOMAIN, 13 | webDomain: '', 14 | fingerprint: fingerprint?.visitorId!, 15 | }); 16 | 17 | return { 18 | user, 19 | isLoading, 20 | actions, 21 | status: isLoading ? 'pending' : 'success', 22 | }; 23 | }; 24 | 25 | export const useUserRepos = (enabled: boolean) => { 26 | return useQuery({ 27 | queryKey: [`user.repos`], 28 | queryFn: async () => getUserRepos(), 29 | enabled, 30 | select: (data) => 31 | map(data.data, (item) => ({ label: item.name, key: item.name })), 32 | retry: true, 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /assistant/src/GitInsight/components/GitInsightIcon.tsx: -------------------------------------------------------------------------------- 1 | import Lottie from 'lottie-react'; 2 | import React, { useMemo } from 'react'; 3 | 4 | import * as Commit from '../../assets/commit.json'; 5 | import * as Fork from '../../assets/fork.json'; 6 | import * as Star from '../../assets/star.json'; 7 | 8 | interface GitInsightIconProps { 9 | type?: 'fork' | 'commit' | 'star'; 10 | } 11 | 12 | const GitInsightIcon: React.FC = (props) => { 13 | const { type } = props; 14 | 15 | const animationData = useMemo(() => { 16 | if (type === 'fork') { 17 | return Fork; 18 | } 19 | if (type === 'commit') { 20 | return Commit; 21 | } 22 | return Star; 23 | }, [type]); 24 | 25 | return ( 26 | <> 27 |
28 | 37 |
38 | 39 | ); 40 | }; 41 | 42 | export default GitInsightIcon; 43 | -------------------------------------------------------------------------------- /server/utils/rsa.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.primitives import hashes 2 | from cryptography.hazmat.primitives.asymmetric import padding 3 | from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key 4 | 5 | # 加密 token 6 | def encrypt_token(public_key_pem: str, token): 7 | public_key = load_pem_public_key(public_key_pem) 8 | encrypted_token = public_key.encrypt( 9 | token.encode(), 10 | padding.OAEP( 11 | mgf=padding.MGF1(algorithm=hashes.SHA256()), 12 | algorithm=hashes.SHA256(), 13 | label=None 14 | ) 15 | ) 16 | return encrypted_token 17 | 18 | # 解密 token 19 | def decrypt_token(private_key_pem, encrypted_token): 20 | private_key = load_pem_private_key(private_key_pem, password=None) 21 | decrypted_token = private_key.decrypt( 22 | encrypted_token, 23 | padding.OAEP( 24 | mgf=padding.MGF1(algorithm=hashes.SHA256()), 25 | algorithm=hashes.SHA256(), 26 | label=None 27 | ) 28 | ) 29 | return decrypted_token.decode() 30 | -------------------------------------------------------------------------------- /client/components/BotItem.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Tables } from '@/types/database.types'; 3 | import React from 'react'; 4 | declare type Bot = Tables<'bots'>; 5 | 6 | const BotItem = (props: {bot: Bot, selectedId: string, onPress: Function}) => { 7 | const { 8 | bot, 9 | onPress, 10 | selectedId 11 | } = props; 12 | const className = `cursor-pointer flex h-[68px] rounded-[8px] bg-gray-100 px-[12px] py-[10px] border-2 border-gray-100 ${selectedId === bot.id ? ' border-gray-700 bg-gradient-to-r from-[#FAE4CB80] to-[#F3F4F680]' : ''} ` 13 | return ( 14 |
{ 15 | onPress(bot.id); 16 | }} className={className}> 17 | 18 |
19 |
{bot.name}
20 |
{bot.description}
21 |
22 |
23 | 24 | ); 25 | }; 26 | 27 | export default BotItem; 28 | -------------------------------------------------------------------------------- /server/core/models/bot_approval.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from enum import Enum 3 | from typing import Optional 4 | from pydantic import BaseModel, field_serializer 5 | 6 | 7 | class TaskType(Enum): 8 | WEBSITE = "website" 9 | MARKET = "market" 10 | 11 | 12 | class ApprovalStatus(Enum): 13 | OPEN = "open" 14 | CLOSED = "closed" 15 | 16 | 17 | class BotApproval(BaseModel): 18 | id: Optional[str] = None 19 | created_at: datetime 20 | bot_id: Optional[str] = None 21 | task_type: Optional[TaskType] = None 22 | approval_status: Optional[ApprovalStatus] = None 23 | approval_path: Optional[str] = None 24 | 25 | @field_serializer("created_at") 26 | def serialize_created_at(self, created_at: datetime): 27 | return created_at.isoformat() 28 | 29 | @field_serializer("task_type") 30 | def serialize_task_type(self, task_type: TaskType) -> str: 31 | return task_type.value 32 | 33 | @field_serializer("approval_status") 34 | def serialize_approval_status(self, approval_status: ApprovalStatus) -> str: 35 | return approval_status.value 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "petercat", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "bootstrap": "cd client && yarn && cd ../assistant && yarn && cd ../server && bash setup_python.sh", 7 | "client": "cd client && yarn run dev", 8 | "assistant": "cd assistant && yarn run dev", 9 | "server": "cd server && ./venv/bin/python3 -m uvicorn main:app --reload", 10 | "server:local": "cd server && ./venv/bin/python3 -m uvicorn main:app --port 8001 --reload", 11 | "env:pull": "cd server && ./venv/bin/python3 scripts/envs.py pull", 12 | "client:server": "concurrently \"yarn run server\" \"yarn run client\"", 13 | "assistant:server": "concurrently \"yarn run server\" \"yarn run assistant\"", 14 | "build:docker": "docker build -t petercat .", 15 | "build:pypi": "rm -rf dist && python3 -m build", 16 | "publish:pypi": "python3 -m twine upload --repository pypi dist/* " 17 | }, 18 | "engines": { 19 | "node": "^18 || >=20" 20 | }, 21 | "repository": "https://github.com/petercat-ai/petercat.git", 22 | "devDependencies": { 23 | "concurrently": "^8.2.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 PeterCat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assistant/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/app/factory/edit/components/Collapse.tsx: -------------------------------------------------------------------------------- 1 | import ChevronDownIcon from '@/public/icons/ChevronDownIcon'; 2 | import React, { useState, ReactNode } from 'react'; 3 | 4 | interface CollapseProps { 5 | title: string; 6 | children: ReactNode; 7 | } 8 | 9 | const Collapse: React.FC = ({ title, children }) => { 10 | const [isOpen, setIsOpen] = useState(true); 11 | 12 | return ( 13 |
14 |
setIsOpen(!isOpen)} 17 | > 18 | {isOpen ? ( 19 | 20 | ) : ( 21 | 22 | )} 23 | {title} 24 |
25 |
30 |
{children}
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default Collapse; 37 | -------------------------------------------------------------------------------- /client/app/factory/edit/components/TaskContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from 'react'; 2 | import { Updater, useImmer } from 'use-immer'; 3 | 4 | interface TaskContext { 5 | running: boolean; 6 | } 7 | const DefaultTaskContext: TaskContext = { 8 | running: true, 9 | }; 10 | 11 | const BotTaskContext = createContext< 12 | | { 13 | taskProfile: TaskContext; 14 | setTaskProfile: Updater; 15 | } 16 | | undefined 17 | >(undefined); 18 | 19 | const BotTaskProvider: React.FC<{ children: React.ReactNode }> = ({ 20 | children, 21 | }) => { 22 | const [taskProfile, setTaskProfile] = 23 | useImmer(DefaultTaskContext); 24 | 25 | return ( 26 | 27 | {children} 28 | 29 | ); 30 | }; 31 | 32 | const useBotTask = () => { 33 | const context = useContext(BotTaskContext); 34 | if (context === undefined) { 35 | throw new Error('useBotTask must be used within a BotTaskProvider'); 36 | } 37 | return context; 38 | }; 39 | 40 | export { BotTaskProvider, useBotTask }; 41 | -------------------------------------------------------------------------------- /client/public/images/icon-tavily.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/.kiwi/zh-TW/DeployBotModal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | DeployContent: { 3 | buShuDaoQiTa: '部署到其他平臺', 4 | shuaXin: '重新整理', 5 | qianWangAnZhuang: '前往安裝', 6 | weiNengHuoQuNin: 7 | '未能取得您在 GITHUB 平臺上已經安裝 petercat assistant 助手的倉庫,請', 8 | zheJiangTiHuanCang: '這將替換倉庫原有機器人', 9 | dianJiKaiShiPei: '點選開始配置', 10 | xuanZeYaoGuanLian: '選擇要關聯的 GitHub 倉庫', 11 | qingShuRuYuMing: '請輸入正確網域名稱,以 https:// 開始', 12 | muBiaoWangZhanYu: '目標網站域名', 13 | shenHeZhong: '審核中...', 14 | buShuDaoWoDe: '部署到我的網站', 15 | zheJiangTiJiaoYi: 16 | '這將提交一個 issue 到 PeterCat 倉庫,待我們人工審核透過後即可完成公開。', 17 | ninDeJiQiRen: '您的機器人已經公開到了市場,請前往市場檢視。', 18 | gongKaiDaoPE: '公開到 PeterCat 市場', 19 | meiZhaoDaoXiangYao:"找不到已安裝 PeterCat Assistant 的倉庫", 20 | dianJiCiChu:'點選此處', 21 | shouQuanAnZhuangG:"授權組織給 PeterCat" 22 | }, 23 | DeployItem: { 24 | shouQi: '收起', 25 | }, 26 | index: { 27 | tiaoGuo: '跳過', 28 | baoCunChengGong: '儲存成功!', 29 | buShuChengGong: '部署成功', 30 | wanCheng: '完成', 31 | tiJiaoChengGong: '提交成功', 32 | shouQiBuShu: '收起', 33 | buShu: '部署', 34 | daKaiBuShu: '開啟部署', 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /client/public/icons/LangIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const LangIcon = (props: { 3 | className?: string; 4 | style?: React.CSSProperties; 5 | }) => ( 6 | 14 | 18 | 19 | ); 20 | export default LangIcon; 21 | -------------------------------------------------------------------------------- /client/public/images/icon-supabase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/components/FullPageSkeleton.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import dynamic from 'next/dynamic'; 3 | import Walk from '../public/loading.json'; 4 | import WalkJump from '../public/opening.json'; 5 | 6 | const Lottie = dynamic(() => import('lottie-react'), { ssr: false }); 7 | 8 | export const SKELETON_MAP = { 9 | LOADING: Walk, 10 | OPENING: WalkJump, 11 | }; 12 | 13 | export interface FullPageSkeletonProps { 14 | type?: 'LOADING' | 'OPENING'; 15 | loop?: boolean; 16 | onComplete?: () => void; 17 | } 18 | 19 | const FullPageSkeleton = (props: FullPageSkeletonProps) => { 20 | const { type = 'LOADING', onComplete, loop = true } = props; 21 | return ( 22 |
23 | 35 |
36 | ); 37 | }; 38 | 39 | export default FullPageSkeleton; 40 | -------------------------------------------------------------------------------- /extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extension", 3 | "version": "0.1.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "npm run prep", 8 | "prep": "next build && node export.js", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "autoprefixer": "^10.4.19", 14 | "css-loader": "^7.1.2", 15 | "mini-css-extract-plugin": "^2.9.0", 16 | "next": "14.2.5", 17 | "next-compose-plugins": "^2.2.1", 18 | "@petercatai/assistant": "^1.0.0", 19 | "postcss-loader": "^8.1.1", 20 | "react": "^18", 21 | "react-dom": "^18", 22 | "style-loader": "^4.0.0" 23 | }, 24 | "devDependencies": { 25 | "@types/chrome": "^0.0.268", 26 | "@types/node": "^20", 27 | "@types/react": "^18", 28 | "@types/react-dom": "^18", 29 | "eslint": "^8", 30 | "eslint-config-next": "14.2.5", 31 | "next-transpile-modules": "^10.0.1", 32 | "postcss": "^8", 33 | "tailwindcss": "^3.4.6", 34 | "ts-loader": "^9.5.1", 35 | "typescript": "^5", 36 | "webpack": "^5.93.0", 37 | "webpack-cli": "^5.1.4", 38 | "webpack-dev-server": "^5.0.4" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/.kiwi/zh-CN/DeployBotModal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | DeployContent: { 3 | buShuDaoQiTa: '部署到其它平台', 4 | shuaXin: '刷新', 5 | qianWangAnZhuang: '前往安装', 6 | weiNengHuoQuNin: 7 | '未能获取您在 GITHUB 平台上已经安装 petercat assistant\n 助手的仓库,请', 8 | zheJiangTiHuanCang: '这将替换仓库原有机器人', 9 | dianJiKaiShiPei: '点击开始配置', 10 | xuanZeYaoGuanLian: '选择要关联的 GitHub 仓库', 11 | qingShuRuYuMing: '请输入正确域名,以 https:// 开始', 12 | muBiaoWangZhanYu: '目标网站域名', 13 | shenHeZhong: '审核中...', 14 | buShuDaoWoDe: '部署到我的网站', 15 | zheJiangTiJiaoYi: 16 | '这将提交一个 issue 到 PeterCat\n 仓库,待我们人工审核通过后即可完成公开。', 17 | ninDeJiQiRen: '您的机器人已经公开到了市场,请前往市场查看。', 18 | gongKaiDaoPE: '公开到 PeterCat 市场', 19 | meiZhaoDaoXiangYao:"没找到已安装 PeterCat Assistant 机器人的仓库?", 20 | dianJiCiChu:'点击此处', 21 | shouQuanAnZhuangG:"授权组织给 PeterCat" 22 | }, 23 | DeployItem: { 24 | shouQi: '收起', 25 | }, 26 | index: { 27 | tiaoGuo: '跳过', 28 | baoCunChengGong: '保存成功!', 29 | buShuChengGong: '部署成功', 30 | wanCheng: '完成', 31 | tiJiaoChengGong: '提交成功', 32 | shouQiBuShu: '收起部署', 33 | buShu: '部署', 34 | daKaiBuShu: '打开部署', 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /client/app/user/tokens/components/CreateButton.tsx: -------------------------------------------------------------------------------- 1 | import I18N from '@/app/utils/I18N'; 2 | import { Button, ButtonProps, useDisclosure } from '@nextui-org/react'; 3 | import CreateModal from './CreateModal'; 4 | import { useTokenList } from '@/app/hooks/useToken'; 5 | import { useMutation } from '@tanstack/react-query'; 6 | import { createToken } from '@/app/services/TokensController'; 7 | 8 | export default function CreateButton(props: ButtonProps) { 9 | const { refetch } = useTokenList(); 10 | const { isOpen: createIsOpen, onOpen: onCreateOpen, onClose: onCreateClose } = useDisclosure(); 11 | 12 | const createMutation = useMutation({ 13 | mutationFn: createToken, 14 | onSuccess() { 15 | onCreateClose(); 16 | refetch(); 17 | } 18 | }); 19 | 20 | return ( 21 | <> 22 | 23 | createMutation.mutate(data)} 28 | isLoading={createMutation.isPending} 29 | /> 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /client/public/icons/AIBtnIcon.tsx: -------------------------------------------------------------------------------- 1 | const AIBtnIcon = () => ( 2 | 3 | 9 | 10 | ); 11 | export default AIBtnIcon; 12 | -------------------------------------------------------------------------------- /assistant/src/StarterList/index.md: -------------------------------------------------------------------------------- 1 | # StarterList 2 | 3 | StarterList 是一个用于展示机器人开场白的组件。 4 | 5 | ## 安装 6 | 7 | 确保你已经安装了必要的依赖: 8 | 9 | ```bash 10 | npm install @petercatai/assistant 11 | ``` 12 | 13 | ## 使用示例 14 | 15 | ```jsx 16 | import React, { useEffect, useState } from 'react'; 17 | import { StarterList } from '@petercatai/assistant'; 18 | 19 | export default () => { 20 | return ( 21 | 22 | ); 23 | }; 24 | ``` 25 | 26 | ## API 27 | 28 | | 属性名 | 类型 | 默认值 | 描述 | 29 | | ----------- | ------------------------------ | ----------- | -------------------------------------------------------------------- | 30 | | `starters` | `string[]` | `[]` | 启动器字符串数组,用于展示启动器按钮。 | 31 | | `onClick` | `(msg: string) => void` | `undefined` | 点击启动器按钮时的回调函数,参数为被点击的启动器字符串。 | 32 | | `style` | `React.CSSProperties` | `undefined` | 自定义内联样式,应用于启动器列表的容器。 | 33 | | `className` | `string` | `undefined` | 自定义类名,应用于启动器列表的容器。 | 34 | 35 | -------------------------------------------------------------------------------- /client/public/images/chat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/.kiwi/ko/DeployBotModal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | DeployContent: { 3 | buShuDaoQiTa: '다른 플랫폼으로 배포', 4 | shuaXin: '새로 고침', 5 | qianWangAnZhuang: '설치로 이동', 6 | weiNengHuoQuNin: 7 | 'GITHUB 플랫폼에서 이미 설치된 petercat assistant 도우미를 찾을 수 없습니다. ', 8 | zheJiangTiHuanCang: '이것은 기존 봇을 대체할 것입니다.', 9 | dianJiKaiShiPei: '설정을 시작하려면 클릭하세요.', 10 | xuanZeYaoGuanLian: '연결할 GitHub 저장소를 선택하세요.', 11 | qingShuRuYuMing: '도메인을 입력하세요, 일반적으로 https:// 로 시작합니다.', 12 | muBiaoWangZhanYu: '목표 웹사이트 도메인', 13 | shenHeZhong: '검토 중...', 14 | buShuDaoWoDe: '내 웹사이트에 배포', 15 | zheJiangTiJiaoYi: 16 | '이것은 PeterCat 저장소에 issue를 제출하게 되며, 우리 인공 검토를 통과하면 공개될 수 있습니다.', 17 | ninDeJiQiRen: '당신의 봇이 시장에 공개되었습니다. 시장을 확인해 보세요.', 18 | gongKaiDaoPE: 'PeterCat 시장에 공개', 19 | meiZhaoDaoXiangYao:'PeterCat Assistant가 설치된 저장소를 찾을 수 없습니까?', 20 | dianJiCiChu:'여기를 클릭하세요', 21 | shouQuanAnZhuangG:'PeterCat에 조직 권한 부여' 22 | }, 23 | DeployItem: { 24 | shouQi: '접기', 25 | }, 26 | index: { 27 | tiaoGuo: '건너뛰기', 28 | baoCunChengGong: '성공적으로 저장!', 29 | buShuChengGong: '배포 성공', 30 | wanCheng: '완료', 31 | tiJiaoChengGong: '제출이 성공했습니다', 32 | shouQiBuShu: '접기', 33 | buShu: '배포', 34 | daKaiBuShu: '배포 열기', 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /client/public/icons/SpaceIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export const SpaceIcon = ({ 3 | fill = 'currentColor', 4 | filled = false, 5 | size = 'normal', 6 | height = '18px', 7 | width = '18px', 8 | label = '', 9 | ...props 10 | }) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /client/app/user/login/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { useRouter } from 'next/navigation'; 5 | import FullPageSkeleton from '@/components/FullPageSkeleton'; 6 | import { useUser } from '@petercatai/assistant'; 7 | import { useFingerprint } from '@/app/hooks/useFingerprint'; 8 | 9 | const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN!; 10 | 11 | export default function Login() { 12 | const { data: fingerprint } = useFingerprint(); 13 | const { user: currentUser, isLoading } = useUser({ 14 | apiDomain, 15 | fingerprint: fingerprint?.visitorId || '', 16 | }); 17 | const router = useRouter(); 18 | 19 | const finishLogin = () => { 20 | if (window.opener) { 21 | window.opener.postMessage({ status: 'success' }, '*'); 22 | window.close(); 23 | } else { 24 | router.push('/factory/list'); 25 | } 26 | }; 27 | 28 | useEffect(() => { 29 | if (!isLoading) { 30 | if (currentUser.id.startsWith('client|')) { 31 | location.href = `${process.env.NEXT_PUBLIC_API_DOMAIN}/api/auth/login`; 32 | } else { 33 | finishLogin(); 34 | } 35 | } 36 | }, [currentUser, isLoading]); 37 | 38 | return ( 39 | <> 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /client/app/policy/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useMemo } from 'react'; 4 | import HomeHeader from '@/components/HomeHeader'; 5 | import Markdown from '@/components/Markdown'; 6 | import { useGlobal } from '@/app/contexts/GlobalContext'; 7 | import PolicyZhCN from '../../.kiwi/zh-CN/policy.md'; 8 | import PolicyEN from '../../.kiwi/en/policy.md'; 9 | import PolicyJA from '../../.kiwi/ja/policy.md'; 10 | import PolicyKO from '../../.kiwi/ko/policy.md'; 11 | import PolicyZhTW from '../../.kiwi/zh-TW/policy.md'; 12 | 13 | export default function Policy() { 14 | const { language } = useGlobal(); 15 | 16 | const markdownContent = useMemo(() => { 17 | switch (language) { 18 | case 'zh-CN': 19 | return PolicyZhCN; 20 | case 'zh-TW': 21 | return PolicyZhTW; 22 | case 'ja': 23 | return PolicyJA; 24 | case 'ko': 25 | return PolicyKO; 26 | case 'en': 27 | return PolicyEN; 28 | default: 29 | return PolicyEN; 30 | } 31 | }, [language]); 32 | return ( 33 |
34 | 35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /client/app/contexts/BotContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from 'react'; 2 | import { Updater, useImmer } from 'use-immer'; 3 | import { BotProfile } from '@/app/interface'; 4 | 5 | export const defaultBotProfile: BotProfile = { 6 | id: '', 7 | avatar: '', 8 | gitAvatar: '', 9 | name: 'Untitled', 10 | description: '', 11 | prompt: '', 12 | starters: [''], 13 | public: false, 14 | repoName: '', 15 | helloMessage: '', 16 | llm: 'openai', 17 | token_id: '', 18 | domain_whitelist: [], 19 | }; 20 | 21 | const BotContext = createContext< 22 | | { 23 | botProfile: BotProfile; 24 | setBotProfile: Updater; 25 | } 26 | | undefined 27 | >(undefined); 28 | 29 | const BotProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { 30 | const [botProfile, setBotProfile] = useImmer(defaultBotProfile); 31 | 32 | return ( 33 | 34 | {children} 35 | 36 | ); 37 | }; 38 | 39 | const useBot = () => { 40 | const context = useContext(BotContext); 41 | if (context === undefined) { 42 | throw new Error('useBot must be used within a BotProvider'); 43 | } 44 | return context; 45 | }; 46 | 47 | export { BotProvider, useBot }; 48 | -------------------------------------------------------------------------------- /server/utils/sanitize_token.py: -------------------------------------------------------------------------------- 1 | def sanitize_token(token: str, visible_start: int = 4, visible_end: int = 4, max_mask_length: int = 12) -> str: 2 | """ 3 | Sanitizes a token by masking its middle characters with asterisks (*) while keeping 4 | a specified number of characters visible at the beginning and end. The masked section 5 | will be limited to a maximum length. 6 | 7 | Args: 8 | token (str): The token to be sanitized. 9 | visible_start (int): Number of characters to keep visible at the start. Default is 4. 10 | visible_end (int): Number of characters to keep visible at the end. Default is 4. 11 | max_mask_length (int): Maximum number of asterisks to use for the masked section. Default is 12. 12 | 13 | Returns: 14 | str: The sanitized token. 15 | """ 16 | if not token or len(token) <= visible_start + visible_end: 17 | return token 18 | 19 | # Calculate the length of the masked section 20 | masked_length = len(token) - visible_start - visible_end 21 | 22 | # Limit the masked section to the maximum mask length 23 | if masked_length > max_mask_length: 24 | masked_length = max_mask_length 25 | 26 | masked_section = '*' * masked_length 27 | return token[:visible_start] + masked_section + token[-visible_end:] 28 | -------------------------------------------------------------------------------- /client/public/icons/ConfigIcon.tsx: -------------------------------------------------------------------------------- 1 | const ConfigIcon = () => ( 2 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | 24 | export default ConfigIcon; 25 | -------------------------------------------------------------------------------- /client/.kiwi/en/edit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | jiQiRenShangWei: 4 | 'The bot has not been configured with any content yet. Please proceed with conversation testing after completing the configuration.', 5 | baoCun: 'Save', 6 | yuLanYuCeShi: 'Preview and Test', 7 | shouDongPeiZhi: 'Manual Configuration', 8 | duiHuaTiaoShi: 'Conversation Debugging', 9 | chongXinShengChengPei: 'Regenerate Configuration', 10 | ziDongShengChengPei: 'Automatically Generate Configuration', 11 | diZhiYouWu: 'Invalid Address', 12 | qingShuRuGI: 'Please enter or select the GitHub project name', 13 | fuZhiTOK: 'Copy Token', 14 | tOKEN: 'Token has been copied to clipboard', 15 | gITHU: 'GitHub project name', 16 | bangWoPeiZhiYi: 'Help me create a Q&A bot', 17 | chuCiJianMianXian: 18 | '👋🏻 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.', 19 | shengChengShiBai: 'Generation Failed', 20 | shengChengChengGongE: 'Generation Successful', 21 | baoCunShiBaiE: 'Save Failed', 22 | baoCunChengGong: 'Save Successful', 23 | shengChengShiBaiC: 'Generation Failed', 24 | shengChengChengGong: 'Generation Successful', 25 | baoCunYuBuShu: 'Save and Deploy', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /assistant/src/StarterList/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FC } from 'react'; 2 | import { ThunderIcon } from '../icons/ThunderIcon'; 3 | 4 | export interface IProps { 5 | starters: string[]; 6 | onClick?: (msg: string) => void; 7 | style?: React.CSSProperties; 8 | className?: string; 9 | } 10 | 11 | const StarterList: FC = ({ starters, onClick, style, className }) => { 12 | return ( 13 |
14 |
18 | {starters?.map((starterStr) => { 19 | return ( 20 |
24 | { 27 | onClick?.(starterStr); 28 | }} 29 | > 30 | {starterStr} 31 | 32 | 33 |
34 | ); 35 | })} 36 |
37 |
38 | ); 39 | }; 40 | 41 | export default StarterList; 42 | -------------------------------------------------------------------------------- /client/.kiwi/ja/DeployBotModal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | DeployContent: { 3 | buShuDaoQiTa: '他のプラットフォームにデプロイ', 4 | shuaXin: 'リフレッシュ', 5 | qianWangAnZhuang: 'インストールに行く', 6 | weiNengHuoQuNin: 7 | 'あなたがGITHUBプラットフォームですでに petercat assistant 助手をインストールしたリポジトリを取得できませんでした。それについて、', 8 | zheJiangTiHuanCang: 'これは元のロボットを置き換えるでしょう。', 9 | dianJiKaiShiPei: '設定を開始するためにはクリックしてください。', 10 | xuanZeYaoGuanLian: '関連付けるGitHubリポジトリを選択してください。', 11 | qingShuRuYuMing: 12 | 'ドメイン名を入力してください。通常、https://で始まります。', 13 | muBiaoWangZhanYu: '目標ウェブサイトのドメイン', 14 | shenHeZhong: '審査中...', 15 | buShuDaoWoDe: '私のウェブサイトにデプロイ', 16 | zheJiangTiJiaoYi: 17 | 'これはPeter Cat リポジトリに問題を提出し、私達が人間による審査を通った後で公開が完了します。', 18 | ninDeJiQiRen: 19 | 'あなたのロボットはすでに市場に公開されています。市場をご覧ください。', 20 | gongKaiDaoPE: 'Peter Cat市場に公開', 21 | meiZhaoDaoXiangYao:'PeterCat Assistant がインストールされたリポジトリが見つかりませんか?', 22 | dianJiCiChu:'こちらをクリック', 23 | shouQuanAnZhuangG:'PeterCat に組織を承認' 24 | }, 25 | DeployItem: { 26 | shouQi: '折りたたむ', 27 | }, 28 | index: { 29 | tiaoGuo: 'スキップ', 30 | baoCunChengGong: '保存が成功しました!', 31 | buShuChengGong: 'デプロイが成功しました', 32 | wanCheng: 'かんりょう', 33 | tiJiaoChengGong: '提出が成功しました', 34 | shouQiBuShu: '折りたたむ', 35 | buShu: 'デプロイ', 36 | daKaiBuShu: 'デプロイを開', 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /assistant/src/icons/NewMessageIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export const NewMessageIcon = () => { 3 | return ( 4 | 5 | 6 | 7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /client/app/agreement/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useMemo } from 'react'; 4 | import HomeHeader from '@/components/HomeHeader'; 5 | import Markdown from '@/components/Markdown'; 6 | import { useGlobal } from '@/app/contexts/GlobalContext'; 7 | import AgreementZhCN from '../../.kiwi/zh-CN/agreement.md'; 8 | import AgreementEN from '../../.kiwi/en/agreement.md'; 9 | import AgreementJA from '../../.kiwi/ja/agreement.md'; 10 | import AgreementKO from '../../.kiwi/ko/agreement.md'; 11 | import AgreementZhTW from '../../.kiwi/zh-TW/agreement.md'; 12 | 13 | export default function Agreement() { 14 | const { language } = useGlobal(); 15 | 16 | const markdownContent = useMemo(() => { 17 | switch (language) { 18 | case 'zh-CN': 19 | return AgreementZhCN; 20 | case 'zh-TW': 21 | return AgreementZhTW; 22 | case 'ja': 23 | return AgreementJA; 24 | case 'ko': 25 | return AgreementKO; 26 | case 'en': 27 | return AgreementEN; 28 | default: 29 | return AgreementEN; 30 | } 31 | }, [language]); 32 | return ( 33 |
34 | 35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /client/app/knowledge/chunk/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import I18N from '@/app/utils/I18N'; 4 | import { useRouter, useSearchParams } from 'next/navigation'; 5 | import React from 'react'; 6 | import ChunkList from '../components/ChunkList'; 7 | 8 | export default function ChunkPage() { 9 | const searchParams = useSearchParams(); 10 | const knowledge_id = searchParams.get('knowledge_id'); 11 | const router = useRouter(); 12 | if (!knowledge_id) { 13 | router.push('/'); 14 | } 15 | return ( 16 |
17 |
{ 19 | window.history.back(); 20 | }} 21 | className="p-2 flex gap-2 cursor-pointer" 22 | > 23 | 31 | 36 | 37 | {I18N.chunk.page.fanHui} 38 |
39 | 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /server/agent/bot/get_bot.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from fastapi import Depends 4 | from agent.bot import Bot 5 | from agent.llm import import_clients 6 | from auth.get_user_info import get_user 7 | from core.dao.botDAO import BotDAO 8 | from core.dao.llmTokenDAO import LLMTokenDAO 9 | 10 | from core.models.user import User 11 | from core.service.user_llm_token import UserLLMTokenService, get_llm_token_service 12 | from core.type_class.data_class import ChatData 13 | 14 | def get_bot( 15 | input_data: ChatData, 16 | user: Annotated[User | None, Depends(get_user)] = None, 17 | llm_service: Annotated[UserLLMTokenService | None, Depends(get_llm_token_service)] = None 18 | ) -> Bot: 19 | 20 | import_clients() 21 | 22 | bot_dao = BotDAO() 23 | llm_token_dao = LLMTokenDAO() 24 | 25 | bot = bot_dao.get_bot(input_data.bot_id) 26 | 27 | if bot.token_id: 28 | llm_token = llm_service.get_llm_token(id=bot.token_id) 29 | else: 30 | llm_token = llm_token_dao.get_llm_token(bot.llm) 31 | 32 | return Bot(bot=bot, llm_token=llm_token) 33 | 34 | def get_bot_by_id(bot_id: str) -> Bot: 35 | bot_dao = BotDAO() 36 | llm_token_dao = LLMTokenDAO() 37 | 38 | bot = bot_dao.get_bot(bot_id) 39 | llm_token = llm_token_dao.get_llm_token(bot.llm) 40 | return Bot(bot=bot, llm_token=llm_token) 41 | -------------------------------------------------------------------------------- /server/auth/cors_middleware.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import Awaitable, Callable 3 | from fastapi import Request, Response 4 | from starlette.middleware.base import BaseHTTPMiddleware 5 | from fastapi.responses import PlainTextResponse 6 | 7 | from auth.middleware import ANONYMOUS_USER_ALLOW_LIST 8 | 9 | 10 | class AuthCORSMiddleWare(BaseHTTPMiddleware): 11 | 12 | async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response: 13 | response = await call_next(request) 14 | requested_origin = request.headers.get('origin') 15 | if requested_origin is None: 16 | return response 17 | 18 | if request.url.path in ANONYMOUS_USER_ALLOW_LIST: 19 | if request.method == "OPTIONS": 20 | headers = self.mutate_cors_headers(request, response) 21 | return PlainTextResponse("OK", status_code=200, headers=headers) 22 | if response.status_code == 200: 23 | self.mutate_cors_headers(request, response) 24 | 25 | return response 26 | return response 27 | 28 | def mutate_cors_headers(self, request: Request, response: Response): 29 | requested_origin = request.headers.get('origin') 30 | headers = response.headers 31 | headers["Access-Control-Allow-Origin"] = requested_origin 32 | return headers 33 | -------------------------------------------------------------------------------- /client/public/icons/CheckBadgeIcon.tsx: -------------------------------------------------------------------------------- 1 | const KnowledgeTaskCompleteIcon = () => ( 2 | 9 | 13 | 17 | 18 | ); 19 | export default KnowledgeTaskCompleteIcon; 20 | -------------------------------------------------------------------------------- /server/core/models/user_token_usage.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | from pydantic import BaseModel 4 | 5 | class UserTokenUsage(BaseModel): 6 | id: Optional[str] = None 7 | user_id: Optional[str] = None 8 | token_id: Optional[str] = None 9 | bot_id: Optional[str] = None 10 | created_at: Optional[datetime] = datetime.now().isoformat() 11 | date: Optional[datetime] = datetime.now().date().isoformat() 12 | input_token: Optional[int] = 0 13 | output_token: Optional[int] = 0 14 | total_token: Optional[int] = 0 15 | 16 | 17 | class UserTokenUsageStats(BaseModel): 18 | usage_date: datetime 19 | input_tokens: Optional[int] = 0 20 | output_tokens: Optional[int] = 0 21 | total_tokens: Optional[int] = 0 22 | 23 | 24 | class BotTokenUsageStats(BaseModel): 25 | bot_id: str 26 | bot_name: str 27 | usage_date: datetime 28 | input_tokens: Optional[int] = 0 29 | output_tokens: Optional[int] = 0 30 | total_tokens: Optional[int] = 0 31 | 32 | class BotTokenUsageRate(BaseModel): 33 | bot_id: str 34 | bot_name: str 35 | input_tokens: Optional[int] = 0 36 | output_tokens: Optional[int] = 0 37 | total_tokens: Optional[int] = 0 38 | 39 | class UserTokenUsageRate(BaseModel): 40 | user_id: str 41 | user_name: str 42 | input_tokens: Optional[int] = 0 43 | output_tokens: Optional[int] = 0 44 | total_tokens: Optional[int] = 0 -------------------------------------------------------------------------------- /client/app/services/TokensController.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { LLMTokenInsert } from "../hooks/useToken"; 3 | 4 | const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN; 5 | axios.defaults.withCredentials = true; 6 | 7 | export async function getTokenList() { 8 | const response = await axios.get(`${apiDomain}/api/user/llm_tokens`); 9 | return response.data.data; 10 | } 11 | 12 | export async function getLLMList() { 13 | const response = await axios.get(`${apiDomain}/api/user/llms`); 14 | return response.data; 15 | } 16 | 17 | 18 | export async function deleteToken(id: string) { 19 | const response = await axios.delete(`${apiDomain}/api/user/llm_token/${id}`); 20 | return response.data; 21 | } 22 | 23 | export async function createToken(data: LLMTokenInsert) { 24 | const response = await axios.post(`${apiDomain}/api/user/llm_token`, data); 25 | return response.data; 26 | } 27 | 28 | export async function analyzeTokenUsage() { 29 | const response = await axios.get(`${apiDomain}/api/user/llm_token_usages/analyzer`); 30 | return response.data; 31 | } 32 | 33 | export async function analyzeTopBots() { 34 | const response = await axios.get(`${apiDomain}/api/user/llm_token_usages/top_bots`); 35 | return response.data; 36 | } 37 | 38 | export async function analyzeTopUsers() { 39 | const response = await axios.get(`${apiDomain}/api/user/llm_token_usages/top_users`); 40 | return response.data; 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/.kiwi/zh-CN/app.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | woMenLaiZiMa: 4 | '我们来自蚂蚁集团支付宝体验技术部,致力于创造美而实用的产品,AI\n 只是手段,为你的工作提供更多愉悦的价值才是我们的唯一目标。', 5 | xiangMuXiangXiXin: '项目详细信息请进文档查阅', 6 | chaKanGengDuo: '查看更多', 7 | maYiKaiYuan: '蚂蚁开源', 8 | agreement: '用户协议', 9 | agreementLabel: '我已阅读并同意用户协议', 10 | policy: '隐私政策', 11 | pETER: 'PeterCat 社区', 12 | liaoJieGengDuo: '了解更多', 13 | deYiYuQiangDa: 14 | '得益于强大的底层能力,您可以将任意感兴趣的代码仓库转换为答疑机器人,或体验社区中其它机器人。它们不仅能推荐优质代码仓库,还能协助用户自动提交\n issue。', 15 | duoZhongJiChengFang: 16 | '多种集成方式自由选择,如对话应用 SDK\n 集成至官网,Github APP一键安装至 Github 仓库等', 17 | duoPingTaiJiCheng: '多平台集成', 18 | jiQiRenChuangJian: 19 | '机器人创建后,所有相关Github 文档和 issue\n 将自动入库,作为机器人的知识依据', 20 | zhiShiZiDongRu: '知识自动入库', 21 | jinXuYaoGaoZhi: 22 | '仅需要告知你的仓库地址或名称,PeterCat\n 即可自动完成创建机器人的全部流程', 23 | duiHuaJiChuangZao: '对话即创造', 24 | woMenTiGongDui: 25 | '我们提供对话式答疑 Agent\n 配置系统、自托管部署方案和便捷的一体化应用\n SDK,让您能够为自己的 GitHub\n 仓库一键创建智能答疑机器人,并快速集成到各类官网或项目中,\n 为社区提供更高效的技术支持生态。', 26 | liJiChangShi: '立即尝试', 27 | shiZhuanWeiSheQu: '是专为社区维护者和开发者打造的智能答疑机器人解决方案', 28 | xiaoMaoMiZhuNi: '小猫咪助你征服 Github', 29 | wenDang: '文档', 30 | release: '更新日志', 31 | gongZuoTai: '工作台', 32 | yanShiAnLi: '演示案例', 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /client/app/services/UserController.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN; 4 | 5 | // Get the public bot profile by id 6 | export async function getUserInfo({ clientId }: { clientId?: string }) { 7 | const response = await axios.get( 8 | `${apiDomain}/api/auth/userinfo?clientId=${clientId}`, 9 | { withCredentials: true }, 10 | ); 11 | return response.data.data; 12 | } 13 | 14 | export async function acceptAgreement() { 15 | const response = await axios.post( 16 | `${apiDomain}/api/auth/accept/agreement`, 17 | {}, 18 | { withCredentials: true }, 19 | ); 20 | return response.data; 21 | } 22 | 23 | export async function getAgreementStatus() { 24 | const response = await axios.get(`${apiDomain}/api/auth/agreement/status`, { 25 | withCredentials: true, 26 | }); 27 | return response.data; 28 | } 29 | 30 | export async function getUserRepos() { 31 | const response = await axios.get(`${apiDomain}/api/auth/repos`, { 32 | withCredentials: true, 33 | }); 34 | return response.data; 35 | } 36 | 37 | export async function getAvailableLLMs() { 38 | const response = await axios.get(`${apiDomain}/api/user/llms`, { 39 | withCredentials: true, 40 | }); 41 | return response.data; 42 | } 43 | 44 | export async function requestLogout() { 45 | const response = await axios.get(`${apiDomain}/api/auth/logout`, { 46 | withCredentials: true, 47 | }); 48 | return response.data; 49 | } 50 | -------------------------------------------------------------------------------- /client/.kiwi/zh-TW/app.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | woMenLaiZiMa: 4 | '我們來自螞蟻集團支付寶體驗技術部,致力於創造美而實用的產品,AI\n 只是手段,為你的工作提供更多愉悅的價值才是我們的唯一目標。', 5 | xiangMuXiangXiXin: '專案詳細資訊請進文件查閱', 6 | chaKanGengDuo: '檢視更多', 7 | maYiKaiYuan: '螞蟻開源', 8 | agreement: '使用者協議', 9 | agreementLabel: '我已閱讀並同意使用者協議', 10 | policy: '隱私政策', 11 | pETER: 'PeterCat 社群', 12 | liaoJieGengDuo: '了解更多', 13 | deYiYuQiangDa: 14 | '得益於強大的底層能力,您可以將任意感興趣的程式碼倉庫轉換為答疑機器人,或體驗社群中其它機器人。它們不僅能推薦優質程式碼倉庫,還能協助使用者自動提交\n issue。', 15 | duoZhongJiChengFang: 16 | '多種整合方式自由選擇,如對話應用 SDK\n 整合至官網,Github APP 一鍵安裝至 Github 倉庫等', 17 | duoPingTaiJiCheng: '多平臺整合', 18 | jiQiRenChuangJian: 19 | '機器人建立後,所有相關 Github 文件和 issue\n 將自動入庫,作為機器人的知識依據', 20 | zhiShiZiDongRu: '知識自動入庫', 21 | jinXuYaoGaoZhi: 22 | '僅需要告知你的倉庫地址或名稱,PeterCat\n 即可自動完成建立機器人的全部流程', 23 | duiHuaJiChuangZao: '對話即創造', 24 | woMenTiGongDui: 25 | '我們提供對話式答疑 Agent\n 配置系統、自託管部署方案和便捷的一體化應用\n SDK,讓您能夠為自己的 GitHub\n 倉庫一鍵建立智慧答疑機器人,並快速整合到各類官網或專案中,\n 為社群提供更高效的技術支援生態。', 26 | liJiChangShi: '立即嘗試', 27 | shiZhuanWeiSheQu: '是專為社群維護者和開發者打造的智慧答疑機器人解決方案', 28 | xiaoMaoMiZhuNi: '小貓咪助你征服 Github', 29 | wenDang: '文件', 30 | release: '更新日誌', 31 | gongZuoTai: '工作臺', 32 | yanShiAnLi: '演示案例', 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /server/agent/tools/git_info.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | from github import Github 3 | from github.ContentFile import ContentFile 4 | from langchain.tools import tool 5 | import json 6 | 7 | 8 | g = Github() 9 | 10 | 11 | @tool 12 | def search_repo( 13 | repo_name: Optional[str] = "", 14 | ) -> List[ContentFile]: 15 | """ 16 | Get basic information of a GitHub repository including star count, fork count, and commit count. 17 | 18 | :param repo_name: Name of the repository in the format 'owner/repo' 19 | :return: A object with basic repo information. 20 | """ 21 | if not repo_name: 22 | return None 23 | try: 24 | repo = g.get_repo(repo_name) 25 | 26 | # Get the latest commit count 27 | commit_count = repo.get_commits().totalCount 28 | 29 | info = { 30 | "type": "card", 31 | "template_id": "GIT_INSIGHT", 32 | "card_data": { 33 | "name": repo.name, 34 | "full_name": repo.full_name, 35 | "description": repo.description, 36 | "language": repo.language, 37 | "stars": repo.stargazers_count, 38 | "forks": repo.forks_count, 39 | "commits": commit_count, 40 | "url": repo.html_url, 41 | }, 42 | } 43 | 44 | return json.dumps(info) 45 | except Exception as e: 46 | print(f"An error occurred: {e}") 47 | return None 48 | -------------------------------------------------------------------------------- /assistant/src/utils/popcenter.ts: -------------------------------------------------------------------------------- 1 | export const popupCenter = ({ 2 | url, 3 | title, 4 | w, 5 | h, 6 | }: { 7 | url: string; 8 | title: string; 9 | w: number; 10 | h: number; 11 | }) => { 12 | if (typeof window === 'undefined' || typeof document === 'undefined') { 13 | return; 14 | } 15 | const dualScreenLeft = 16 | window.screenLeft !== undefined ? window.screenLeft : window.screenX; 17 | const dualScreenTop = 18 | window.screenTop !== undefined ? window.screenTop : window.screenY; 19 | 20 | const width = window.innerWidth 21 | ? window.innerWidth 22 | : document.documentElement.clientWidth 23 | ? document.documentElement.clientWidth 24 | : screen.width; 25 | const height = window.innerHeight 26 | ? window.innerHeight 27 | : document.documentElement.clientHeight 28 | ? document.documentElement.clientHeight 29 | : screen.height; 30 | 31 | const systemZoom = width / window.screen.availWidth; 32 | 33 | // 修正计算 left 和 top,使窗口能够居中 34 | const left = (width - w / systemZoom) / 2 + dualScreenLeft; 35 | const top = (height - h / systemZoom) / 2 + dualScreenTop; 36 | 37 | const newWindow = window.open( 38 | url, 39 | title, 40 | ` 41 | scrollbars=none, 42 | width=${w / systemZoom}, 43 | height=${h / systemZoom}, 44 | top=${top}, 45 | left=${left} 46 | `, 47 | ); 48 | if (newWindow && newWindow.focus) { 49 | newWindow.focus(); 50 | } 51 | 52 | return newWindow; 53 | }; 54 | -------------------------------------------------------------------------------- /client/public/icons/CardHomeIcon.tsx: -------------------------------------------------------------------------------- 1 | const CardHomeIcon = () => ( 2 | 9 | 13 | 17 | 23 | 24 | ); 25 | export default CardHomeIcon; 26 | -------------------------------------------------------------------------------- /client/public/icons/LoginIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export const LoginIcon = ({ 3 | fill = 'currentColor', 4 | filled = false, 5 | size = 'normal', 6 | height = '16px', 7 | width = '16px', 8 | label = '', 9 | ...props 10 | }) => ( 11 | 12 | 13 | 14 | ); 15 | 16 | 17 | -------------------------------------------------------------------------------- /assistant/src/BoxChart/index.md: -------------------------------------------------------------------------------- 1 | 2 | # BoxChart 3 | 4 | 箱线图通常用来展示一组数据分布情况的统计图,一般包括几种数据:最小值、下四分位数、中位数、上四分位数、最大值 5 | 6 | ## 安装 7 | 8 | 确保你已经安装了必要的依赖: 9 | 10 | ```bash 11 | npm install @petercatai/assistant 12 | ``` 13 | 14 | ## 使用示例 15 | 16 | ```tsx 17 | import React from 'react'; 18 | import { BoxChart } from '@petercatai/assistant'; 19 | 20 | export default () => { 21 | const data ={ 22 | "year": [ 23 | { 24 | "date": "2024", 25 | "value": [10,23,44,55,102] 26 | }, 27 | 28 | ], 29 | "quarter": [ 30 | { 31 | "date": "2024Q3", 32 | "value": [3,22,32,44,54] 33 | }, 34 | 35 | { 36 | "date": "2024Q4", 37 | "value": [2,34,44,55,102] 38 | }, 39 | 40 | ], 41 | "month": [ 42 | { "date": "2024-03","value": [2, 4, 6, 15, 20] }, 43 | { "date": "2024-04","value": [3, 5, 7, 16, 21] }, 44 | { "date": "2024-05","value": [1, 3, 5, 14, 19] }, 45 | { "date": "2024-06","value": [2, 4, 6, 15, 20] }, 46 | { "date": "2024-07","value": [3, 5, 7, 16, 21] }, 47 | { "date": "2024-08","value": [4, 6, 8, 17, 22] }, 48 | { "date": "2024-09","value": [1, 3, 5, 14, 19] }, 49 | { "date": "2024-10","value": [2, 4, 6, 15, 20] }, 50 | { "date": "2024-11","value": [3, 5, 7, 16, 21] }, 51 | { "date": "2024-12","value": [4, 6, 8, 17, 22] }, 52 | { "date": "2025-01","value": [5, 7, 9, 18, 23] }, 53 | ] 54 | }; 55 | 56 | 57 | return ; 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /extension/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /server/agent/llm/clients/openai.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Optional 2 | from langchain_openai import ChatOpenAI 3 | from langchain_core.utils.function_calling import convert_to_openai_tool 4 | 5 | from agent.llm import register_llm_client 6 | from agent.llm.base import BaseLLMClient 7 | from core.type_class.data_class import MessageContent 8 | from utils.env import get_env_variable 9 | 10 | 11 | OPEN_API_KEY = get_env_variable("OPENAI_API_KEY") 12 | 13 | 14 | @register_llm_client("openai") 15 | class OpenAIClient(BaseLLMClient): 16 | _client: ChatOpenAI 17 | 18 | def __init__( 19 | self, 20 | temperature: Optional[float] = 0.2, 21 | n: Optional[int] = 1, 22 | top_p: Optional[float] = None, 23 | max_tokens: Optional[int] = 1500, 24 | streaming: Optional[bool] = False, 25 | api_key: Optional[str] = OPEN_API_KEY, 26 | ): 27 | self._client = ChatOpenAI( 28 | model_name="gpt-4o", 29 | temperature=temperature, 30 | n=n, 31 | top_p=top_p, 32 | streaming=streaming, 33 | max_tokens=max_tokens, 34 | openai_api_key=api_key, 35 | stream_usage=True, 36 | ) 37 | 38 | def get_client(self): 39 | return self._client 40 | 41 | def get_tools(self, tools: List[Any]): 42 | return [convert_to_openai_tool(tool) for tool in tools] 43 | 44 | def parse_content(self, content: List[MessageContent]): 45 | return [c.model_dump() for c in content] 46 | -------------------------------------------------------------------------------- /client/public/images/knowledge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/app/factory/edit/components/PublicSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import I18N from '@/app/utils/I18N'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { Switch, cn } from '@nextui-org/react'; 4 | import type { Updater } from 'use-immer'; 5 | import { BotProfile } from '@/app/interface'; 6 | 7 | interface PublicSwitcherProps { 8 | isSelected: boolean; 9 | setBotProfile?: Updater; 10 | } 11 | 12 | const PublicSwitcher = (props: PublicSwitcherProps) => { 13 | const { isSelected, setBotProfile } = props; 14 | 15 | const onChange = (e: React.ChangeEvent) => { 16 | const value = e.target.checked; 17 | 18 | setBotProfile?.((draft: BotProfile) => { 19 | draft.public = value; 20 | }); 21 | }; 22 | 23 | return ( 24 | 43 | {I18N.components.PublicSwitcher.shiChangZhongGongKai} 44 | ); 45 | }; 46 | 47 | export default PublicSwitcher; 48 | -------------------------------------------------------------------------------- /server/.env.local.example: -------------------------------------------------------------------------------- 1 | # App Base Configures 2 | API_URL=http://localhost:8001 3 | WEB_URL=http://localhost:3000 4 | # OPTIONAL - standalong static url if required 5 | STATIC_URL=STATIC_URL 6 | 7 | FASTAPI_SECRET_KEY=fastapi_secret_key 8 | # `Project URL`: 9 | SUPABASE_URL=http://localhost:8001 10 | # `Project API keys`: SERVICE_ROLE_KEY from supabase .env file 11 | SUPABASE_SERVICE_KEY=${SERVICE_ROLE_KEY} 12 | 13 | SUPABASE_PASSWORD=aABCDEFG 14 | # OpenAI API KEY 15 | OPENAI_API_KEY=sk-xxxx 16 | # OPTIONAL - Gemini 17 | GEMINI_API_KEY=gemini_api_key 18 | # Tavily Api Key 19 | TAVILY_API_KEY=tavily_api_key 20 | 21 | # OPTIONAL - Github Apps Configures 22 | X_GITHUB_APP_ID=github_app_id 23 | X_GITHUB_APPS_CLIENT_ID=github_apps_client_id 24 | X_GITHUB_APPS_CLIENT_SECRET=github_apps_client_secret 25 | 26 | # OPTIONAL - Local Authorization Configures 27 | PETERCAT_LOCAL_UID="petercat|001" 28 | PETERCAT_LOCAL_UNAME="petercat" 29 | PETERCAT_LOCAL_GITHUB_TOKEN="github_pat_xxxx" 30 | 31 | # OPTIONAL - SKIP AUTH0 Authorization 32 | PETERCAT_AUTH0_ENABLED=True 33 | 34 | # OPTIONAL - AUTH0 Configures 35 | API_IDENTIFIER=api_identifier 36 | AUTH0_DOMAIN=auth0_domain 37 | AUTH0_CLIENT_ID=auth0_client_id 38 | AUTH0_CLIENT_SECRET=auth0_client_secret 39 | 40 | # OPTIONAL - AWS Configures 41 | X_GITHUB_SECRET_NAME="prod/githubapp/petercat/pem" 42 | STATIC_SECRET_NAME="prod/petercat/static" 43 | LLM_TOKEN_SECRET_NAME="prod/petercat/llm" 44 | LLM_TOKEN_PUBLIC_NAME="petercat/prod/llm/pub" 45 | STATIC_KEYPAIR_ID="xxxxxx" 46 | S3_TEMP_BUCKET_NAME=S3_TEMP_BUCKET_NAME 47 | -------------------------------------------------------------------------------- /client/.kiwi/ja/app.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | page: { 3 | woMenLaiZiMa: 4 | '私たちはアントグループのアリペイ体験技術部から来ました。美しく実用的な製品を作ることに専念しています。AIは単なる手段であり、あなたの仕事にもっと喜びをもたらすことが私たちの唯一の目標です。', 5 | xiangMuXiangXiXin: 'プロジェクトの詳細情報はドキュメントでご確認ください', 6 | chaKanGengDuo: 'もっと見る', 7 | maYiKaiYuan: 'アントオープンソース', 8 | agreement: 'ユーザー契約', 9 | agreementLabel: '私は利用規約を読み、同意しました', 10 | policy: 'プライバシーポリシー', 11 | pETER: 'PeterCat コミュニティ', 12 | liaoJieGengDuo: '詳細を知る', 13 | deYiYuQiangDa: 14 | '強力な基盤により、気になるコードリポジトリをQ&Aボットに変換したり、他のコミュニティボットを体験できます。これらは優れたコードリポジトリを推薦するだけでなく、ユーザーが自動的にissueを提出するのも支援します。', 15 | duoZhongJiChengFang: 16 | '会話アプリケーションSDKを公式サイトに統合したり、Github APPをワンクリックでGithubリポジトリにインストールするなど、さまざまな統合方法を自由に選択できます。', 17 | duoPingTaiJiCheng: '多プラットフォーム統合', 18 | jiQiRenChuangJian: 19 | 'ボット作成後、すべての関連Githubドキュメントやissueが自動的にデータベースに保存され、ボットの知識ベースとなります。', 20 | zhiShiZiDongRu: '知識自動登録', 21 | jinXuYaoGaoZhi: 22 | 'リポジトリのアドレスまたは名前を教えていただければ、Peter Catはボットの全作成プロセスを自動的に完了します。', 23 | duiHuaJiChuangZao: '対話が創造です', 24 | woMenTiGongDui: 25 | '私たちは対話式Q&Aエージェントの設定システム、自分でホスティングするデプロイメントソリューション、そして統合アプリケーションSDKを提供しており、GitHubリポジトリのQ&Aボットをワンクリックで作成し、さまざまな公式サイトやプロジェクトに迅速に統合できます。これにより、コミュニティにより効率的な技術サポートのエコシステムを提供します。', 26 | liJiChangShi: '今すぐ試す', 27 | shiZhuanWeiSheQu: 28 | 'これはコミュニティのメンテナーと開発者のために作られたQ&Aボットソリューションです', 29 | xiaoMaoMiZhuNi: 'Peter CatがあなたのGithub攻略を手助けします', 30 | wenDang: 'ドキュメント', 31 | release: '更新ログ', 32 | gongZuoTai: '作業台', 33 | yanShiAnLi: 'デモケース', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | # App Base Configures 2 | API_URL=http://localhost:8000 3 | WEB_URL=http://localhost:3000 4 | # OPTIONAL - standalong static url if required 5 | STATIC_URL=STATIC_URL 6 | 7 | FASTAPI_SECRET_KEY=fastapi_secret_key 8 | # `Project URL` field of https://supabase.com/dashboard/project/_/settings/database 9 | SUPABASE_URL=${SUPABASE_PUBLIC_URL} 10 | # `Project API keys`: `anon public` field of https://supabase.com/dashboard/project/_/settings/database 11 | SUPABASE_SERVICE_KEY=${SERVICE_ROLE_KEY} 12 | 13 | SUPABASE_PASSWORD=aABCDEFG 14 | # OpenAI API KEY 15 | OPENAI_API_KEY=sk-xxxx 16 | # OPTIONAL - Gemini 17 | GEMINI_API_KEY=gemini_api_key 18 | # Tavily Api Key 19 | TAVILY_API_KEY=tavily_api_key 20 | 21 | # OPTIONAL - Github Apps Configures 22 | X_GITHUB_APP_ID=github_app_id 23 | X_GITHUB_APPS_CLIENT_ID=github_apps_client_id 24 | X_GITHUB_APPS_CLIENT_SECRET=github_apps_client_secret 25 | 26 | PETERCAT_AUTH0_ENABLED=False 27 | # OPTIONAL - Local Authorization Configures 28 | 29 | PETERCAT_LOCAL_UID="petercat|001" 30 | PETERCAT_LOCAL_UNAME="petercat" 31 | 32 | # OPTIONAL - AUTH0 Configures 33 | 34 | API_IDENTIFIER=api_identifier 35 | AUTH0_DOMAIN=auth0_domain 36 | AUTH0_CLIENT_ID=auth0_client_id 37 | AUTH0_CLIENT_SECRET=auth0_client_secret 38 | 39 | # OPTIONAL - AWS Configures 40 | X_GITHUB_SECRET_NAME="prod/githubapp/petercat/pem" 41 | STATIC_SECRET_NAME="prod/petercat/static" 42 | LLM_TOKEN_SECRET_NAME="prod/petercat/llm" 43 | LLM_TOKEN_PUBLIC_NAME="petercat/prod/llm/pub" 44 | STATIC_KEYPAIR_ID="xxxxxx" 45 | S3_TEMP_BUCKET_NAME=S3_TEMP_BUCKET_NAME 46 | -------------------------------------------------------------------------------- /assistant/src/Chat/components/ChatItemRender.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FC } from 'react'; 2 | import '../index.css'; 3 | 4 | interface IProps { 5 | direction: 'start' | 'end'; 6 | avatar?: any; 7 | title: React.ReactNode; 8 | content: React.ReactNode; 9 | starter?: React.ReactNode; 10 | } 11 | 12 | const ChatItemRender: FC = ({ 13 | direction, 14 | avatar, 15 | title, 16 | content, 17 | starter = <>, 18 | }) => { 19 | return ( 20 | <> 21 |
28 | {direction === 'start' && ( 29 |
38 | )} 39 |
40 | {title} 41 | {content} 42 |
43 |
44 | {starter} 45 | 46 | ); 47 | }; 48 | 49 | export default ChatItemRender; 50 | -------------------------------------------------------------------------------- /client/public/icons/DeleteIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export const DeleteIcon = () => ( 3 | 48 | ); 49 | --------------------------------------------------------------------------------