├── requirements.txt ├── static ├── logo.png ├── styles.css └── styles_index.css ├── templates ├── results.html └── index.html ├── README.md ├── .gitignore └── app.py /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamikip/GitGPT/HEAD/requirements.txt -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamikip/GitGPT/HEAD/static/logo.png -------------------------------------------------------------------------------- /templates/results.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 搜索结果 7 | 8 | 9 | 10 | 11 |
12 |

搜索结果

13 |
14 | {% if results %} 15 | {% for repo in results %} 16 |
17 |

{{ repo.name }}

18 |

{{ repo.description or 'No description available.' }}

19 |

⭐: {{ repo.stars }}

20 |

Language: {{ repo.language }}

21 |
22 | {% endfor %} 23 | {% else %} 24 |

No repositories found.

25 | {% endif %} 26 |
27 | 返回搜索 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitGPT 2 | 3 | GitGPT 是一个基于 Flask 构建的 Web 应用程序,旨在通过 GPT 模型和 GitHub API 帮助用户根据关键词查找和筛选 GitHub 仓库。它可以根据用户输入的关键词,提取出中文和英文的关键词,并根据这些关键词在 GitHub 上搜索相关的仓库,最终根据匹配度对结果进行排序和展示。 4 | 5 | ## 功能 6 | 7 | - **关键词提取**:通过 GPT 模型从用户输入中提取出中英文关键词。 8 | - **仓库搜索**:根据关键词在 GitHub 上搜索仓库,支持根据仓库名、描述和 README 文件进行搜索。 9 | - **去重合并**:将多个来源的搜索结果去重并合并。 10 | - **匹配度计算**:根据仓库名、描述与关键词的匹配度以及仓库的星标数,计算仓库的匹配度得分。 11 | - **结果展示**:将排序后的仓库结果以友好的方式展示给用户。 12 | 13 | ## 环境要求 14 | 15 | - Python 3.x 16 | - Flask 17 | - requests 18 | 19 | ## 安装 20 | 21 | 1. 克隆本仓库到本地: 22 | 23 | ```bash 24 | git clone https://github.com/tamikip/GitGPT.git 25 | cd GitGPT 26 | ``` 27 | 28 | 2. 创建并激活虚拟环境(可选): 29 | 30 | ```bash 31 | python3 -m venv venv 32 | source venv/bin/activate # Linux/macOS 33 | venv\Scripts\activate # Windows 34 | ``` 35 | 36 | 3. 安装依赖: 37 | 38 | ```bash 39 | pip install -r requirements.txt 40 | ``` 41 | 42 | 4. 配置环境变量: 43 | 44 | 在项目根目录下创建一个 `.env` 文件,添加以下内容: 45 | 46 | ```bash 47 | API_KEY=your_gpt_api_key 48 | BASE_URL=your_gpt_api_base_url 49 | GITHUB_TOKEN=your_github_token # 可选 50 | IS_GITHUB_API_VERIFY=number # 此处填写 1 或 0 (是/否) 51 | ``` 52 | 53 | 请将 `your_gpt_api_key`、`your_gpt_api_base_url`、`your_github_token` 和 `number` 替换为你的实际 API 密钥和 GitHub Token。 54 | - `API_KEY` GPT的api密钥 55 | - `BASE_URL` GPT的api请求地址 56 | - `GITHUB_TOKEN` github申请的token值(可选) 57 | - `IS_GITHUB_API_VERIFY` 是否跳过ssl检查 58 | 59 | ## 使用 60 | 61 | 1. 启动 Flask 应用: 62 | 63 | ```bash 64 | python app.py 65 | ``` 66 | 67 | 2. 在浏览器中打开 `http://127.0.0.1:5000/`,进入应用主页。 68 | 69 | 3. 在搜索框中输入你想要查找的关键词,点击搜索按钮,应用将显示与关键词匹配的 GitHub 仓库列表。 70 | 71 | ## API 说明 72 | 73 | - `GET /search?q=&if_md=`:根据关键词搜索 GitHub 仓库。 74 | - `q`:用户输入的关键词。 75 | - `if_md`:是否启用 README 文件搜索,`off` 表示不启用,`on` 表示启用。 76 | 77 | ## 贡献 78 | 79 | 欢迎对本项目进行贡献!你可以通过以下方式参与: 80 | 81 | 1. 提交问题(Issues)和功能请求。 82 | 2. 提交 Pull Request 以修复问题或添加新功能。 83 | 3. 改进文档。 84 | 85 | ## 许可证 86 | 87 | 本项目采用 [MIT 许可证](LICENSE)。 88 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GITGPT 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 |

GITGPT

17 |
18 |
19 | 20 | 24 | 25 |
26 | 30 | 精确模式 31 |
32 |
33 | 34 |
{{ error_message }}
35 |
36 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /static/styles.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | 3 | /* 通用样式 */ 4 | body { 5 | margin: 0; 6 | padding: 0; 7 | font-family: Arial, sans-serif; 8 | background-color: #f5f5f5; 9 | } 10 | 11 | /* index.html 特定样式 */ 12 | .index-body { 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | height: 100vh; 17 | } 18 | 19 | .container { 20 | text-align: center; 21 | width: 100%; /* 拉满宽度 */ 22 | max-width: 1200px; /* 设置最大宽度 */ 23 | margin: 20px auto; /* 添加顶部间距,避免被遮挡 */ 24 | padding: 20px; 25 | background-color: #fff; 26 | border-radius: 10px; 27 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 28 | } 29 | 30 | h1 { 31 | font-size: 36px; 32 | margin-bottom: 20px; 33 | color: #333; 34 | } 35 | 36 | .search-box { 37 | width: 60%; 38 | padding: 15px; 39 | font-size: 24px; 40 | border: 2px solid #ccc; 41 | border-radius: 30px; 42 | outline: none; 43 | transition: border-color 0.3s; 44 | } 45 | 46 | .search-box:focus { 47 | border-color: #007BFF; 48 | } 49 | 50 | .search-button { 51 | padding: 15px 30px; 52 | font-size: 24px; 53 | margin-left: 10px; 54 | border: none; 55 | background-color: #007BFF; 56 | color: white; 57 | border-radius: 30px; 58 | cursor: pointer; 59 | transition: background-color 0.3s; 60 | position: relative; 61 | display: inline-flex; 62 | align-items: center; 63 | justify-content: center; 64 | } 65 | 66 | .search-button:hover { 67 | background-color: #0056b3; 68 | } 69 | 70 | .loader { 71 | border: 3px solid #f3f3f3; 72 | border-radius: 50%; 73 | border-top: 3px solid #fff; 74 | width: 20px; 75 | height: 20px; 76 | animation: spin 1s linear infinite; 77 | margin-left: 10px; 78 | } 79 | 80 | @keyframes spin { 81 | 0% { transform: rotate(0deg); } 82 | 100% { transform: rotate(360deg); } 83 | } 84 | 85 | /* results.html 特定样式 */ 86 | .results-container { 87 | margin-top: 20px; 88 | display: flex; 89 | flex-direction: column; 90 | align-items: stretch; /* 确保子元素拉满容器宽度 */ 91 | } 92 | 93 | .repo { 94 | background-color: #f9f9f9; 95 | border: 2px solid #ccc; 96 | padding: 20px; 97 | margin-bottom: 15px; 98 | border-radius: 30px; 99 | cursor: pointer; 100 | transition: background-color 0.3s ease, box-shadow 0.3s ease; 101 | width: 100%; /* 拉满容器宽度 */ 102 | box-sizing: border-box; /* 包含内边距和边框在内的宽度计算 */ 103 | } 104 | 105 | .repo:hover { 106 | background-color: #e9e9e9; 107 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 108 | } 109 | 110 | .repo h2 { 111 | margin: 0 0 10px; 112 | font-size: 24px; 113 | color: #007bff; 114 | } 115 | 116 | .repo p { 117 | margin: 5px 0; 118 | color: #555; 119 | font-size: 18px; 120 | } 121 | 122 | .back-button { 123 | display: inline-block; 124 | margin-top: 20px; 125 | padding: 15px 30px; 126 | font-size: 24px; 127 | background-color: #007bff; 128 | color: white; 129 | text-decoration: none; 130 | border-radius: 30px; 131 | text-align: center; 132 | transition: background-color 0.3s; 133 | } 134 | 135 | .back-button:hover { 136 | background-color: #0056b3; 137 | } 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /static/styles_index.css: -------------------------------------------------------------------------------- 1 | /* 通用样式 */ 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | font-family: Arial, sans-serif; 6 | background-color: #f5f5f5; 7 | } 8 | 9 | /* index.html 特定样式 */ 10 | .index-body { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | height: 100vh; 15 | flex-direction: column; 16 | } 17 | 18 | .container { 19 | text-align: center; 20 | width: 100%; /* 拉满宽度 */ 21 | max-width: 1200px; /* 设置最大宽度 */ 22 | margin-top: -50px; /* 将标题稍微向上移动 */ 23 | } 24 | 25 | .header { 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; /* 居中对齐整个标题和图片 */ 29 | margin-bottom: 20px; /* 与下面的内容保持一定距离 */ 30 | } 31 | 32 | .logo { 33 | width: 80px; /* 设置宽度 */ 34 | height: auto; /* 高度自动调整 */ 35 | margin-right: 20px; /* 图片与标题之间的间距 */ 36 | } 37 | 38 | h1 { 39 | font-size: 60px; 40 | color: #333; 41 | } 42 | 43 | .search-box { 44 | width: 60%; 45 | padding: 15px; 46 | font-size: 24px; 47 | border: 2px solid #ccc; 48 | border-radius: 30px; 49 | outline: none; 50 | transition: border-color 0.3s; 51 | } 52 | 53 | .search-box:focus { 54 | border-color: #007BFF; 55 | } 56 | 57 | .search-button { 58 | padding: 15px 30px; 59 | font-size: 24px; 60 | margin-left: 10px; 61 | border: none; 62 | background-color: #007BFF; 63 | color: white; 64 | border-radius: 30px; 65 | cursor: pointer; 66 | transition: background-color 0.3s; 67 | position: relative; 68 | display: inline-flex; 69 | align-items: center; 70 | justify-content: center; 71 | } 72 | 73 | .search-button:hover { 74 | background-color: #0056b3; 75 | } 76 | 77 | .loader { 78 | border: 3px solid #f3f3f3; 79 | border-radius: 50%; 80 | border-top: 3px solid #fff; 81 | width: 20px; 82 | height: 20px; 83 | animation: spin 1s linear infinite; 84 | margin-left: 10px; 85 | } 86 | 87 | @keyframes spin { 88 | 0% { transform: rotate(0deg); } 89 | 100% { transform: rotate(360deg); } 90 | } 91 | 92 | /* results.html 特定样式 */ 93 | .results-container { 94 | margin-top: 20px; 95 | display: flex; 96 | flex-direction: column; 97 | align-items: stretch; /* 确保子元素拉满容器宽度 */ 98 | } 99 | 100 | .repo { 101 | background-color: #f9f9f9; 102 | border: 2px solid #ccc; 103 | padding: 20px; 104 | margin-bottom: 15px; 105 | border-radius: 30px; 106 | cursor: pointer; 107 | transition: background-color 0.3s ease, box-shadow 0.3s ease; 108 | width: 100%; /* 拉满容器宽度 */ 109 | box-sizing: border-box; /* 包含内边距和边框在内的宽度计算 */ 110 | } 111 | 112 | .repo:hover { 113 | background-color: #e9e9e9; 114 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 115 | } 116 | 117 | .repo h2 { 118 | margin: 0 0 10px; 119 | font-size: 24px; 120 | color: #007bff; 121 | } 122 | 123 | .repo p { 124 | margin: 5px 0; 125 | color: #555; 126 | font-size: 18px; 127 | } 128 | 129 | .back-button { 130 | display: inline-block; 131 | margin-top: 20px; 132 | padding: 15px 30px; 133 | font-size: 24px; 134 | background-color: #007bff; 135 | color: white; 136 | text-decoration: none; 137 | border-radius: 30px; 138 | text-align: center; 139 | transition: background-color 0.3s; 140 | } 141 | 142 | .back-button:hover { 143 | background-color: #0056b3; 144 | } 145 | 146 | .switch { 147 | position: relative; 148 | display: inline-block; 149 | width: 60px; 150 | height: 34px; 151 | margin-top: 10px; /* 调整开关按钮与搜索框之间的距离 */ 152 | } 153 | 154 | .error-message { 155 | color: red; 156 | font-size: 20px; 157 | display: none; 158 | } 159 | 160 | .switch input { 161 | opacity: 0; 162 | width: 0; 163 | height: 0; 164 | } 165 | 166 | .slider { 167 | position: absolute; 168 | cursor: pointer; 169 | top: 0; 170 | left: 0; 171 | right: 0; 172 | bottom: 0; 173 | background-color: #ccc; 174 | transition: .4s; 175 | border-radius: 34px; 176 | } 177 | 178 | .slider:before { 179 | position: absolute; 180 | content: ""; 181 | height: 26px; 182 | width: 26px; 183 | left: 4px; 184 | bottom: 4px; 185 | background-color: white; 186 | transition: .4s; 187 | border-radius: 50%; 188 | } 189 | 190 | input:checked + .slider { 191 | background-color: #2196F3; 192 | } 193 | 194 | input:checked + .slider:before { 195 | transform: translateX(26px); 196 | } 197 | 198 | .label-text { 199 | margin-left: 10px; 200 | font-size: 16px; 201 | vertical-align: middle; 202 | } 203 | 204 | .switch-container { 205 | display: flex; 206 | align-items: center; 207 | justify-content: center; 208 | margin-top: 10px; 209 | } 210 | /* 针对小屏幕的调整 */ 211 | @media (max-width: 600px) { 212 | .container { 213 | width: 90%; /* 减少页面左右留白 */ 214 | margin-top: -30px; /* 调整容器的位置 */ 215 | } 216 | 217 | .header { 218 | flex-direction: column; /* 垂直排列图片和标题 */ 219 | } 220 | 221 | .logo { 222 | width: 60px; /* 缩小 logo 尺寸 */ 223 | margin-right: 0; /* 去除 logo 与标题之间的间距 */ 224 | margin-bottom: 10px; /* 在 logo 和标题之间增加垂直间距 */ 225 | } 226 | 227 | h1 { 228 | font-size: 40px; /* 缩小标题字体 */ 229 | } 230 | 231 | .search-box { 232 | width: 100%; /* 搜索框拉满宽度 */ 233 | font-size: 18px; /* 缩小字体 */ 234 | padding: 10px; /* 减小内边距 */ 235 | } 236 | 237 | .search-button { 238 | margin-top: 20px; 239 | font-size: 18px; /* 缩小按钮字体 */ 240 | padding: 10px 20px; /* 调整按钮大小 */ 241 | } 242 | 243 | .switch-container { 244 | margin-top: 20px; /* 增加开关按钮与其他元素的间距 */ 245 | } 246 | 247 | .repo h2 { 248 | font-size: 20px; /* 缩小结果标题字体 */ 249 | } 250 | 251 | .repo p { 252 | font-size: 16px; /* 缩小结果描述字体 */ 253 | } 254 | 255 | .back-button { 256 | font-size: 18px; /* 缩小返回按钮字体 */ 257 | padding: 10px 20px; /* 调整返回按钮大小 */ 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template 2 | import requests 3 | import json 4 | from math import sqrt 5 | import re 6 | import os 7 | from dotenv import load_dotenv 8 | 9 | app = Flask(__name__) 10 | 11 | load_dotenv() 12 | API_KEY = os.getenv('API_KEY') 13 | BASE_URL = os.getenv('BASE_URL') 14 | token = os.getenv('GITHUB_TOKEN') 15 | if_md = False 16 | 17 | # 是否打开github api 请求时 是否跳过ssl检查 18 | IS_GITHUB_API_VERIFY = os.getenv('IS_GITHUB_API_VERIFY') 19 | if IS_GITHUB_API_VERIFY: 20 | if int(IS_GITHUB_API_VERIFY) == 1: 21 | IS_GITHUB_API_VERIFY = True 22 | else: 23 | IS_GITHUB_API_VERIFY = False 24 | else: 25 | IS_GITHUB_API_VERIFY = True 26 | 27 | 28 | def standardized_json(prompt): 29 | """此函数用于标准化ai生成的json""" 30 | json_pattern = re.compile(r'\{.*\}', re.DOTALL) 31 | match = json_pattern.search(prompt) 32 | 33 | json_string = match.group(0) if match else None 34 | return json_string 35 | 36 | 37 | def merge_and_remove_duplicates(json1, json2): 38 | """此函数用于合并中文和英文关键词的仓库并且去重""" 39 | combined = json1 + json2 40 | seen_names = set() 41 | unique_repositories = [] 42 | 43 | for repo in combined: 44 | if repo['name'] not in seen_names: 45 | unique_repositories.append(repo) 46 | seen_names.add(repo['name']) 47 | 48 | return unique_repositories 49 | 50 | 51 | def merge_and_remove_duplicates2(json1, json2, json3): 52 | """此函数用于合并仓库名,仓库描述,readme关键词的仓库并且去重""" 53 | combined = json1 + json2 + json3 54 | seen_names = set() 55 | unique_repositories = [] 56 | 57 | for repo in combined: 58 | if repo['name'] not in seen_names: 59 | unique_repositories.append(repo) 60 | seen_names.add(repo['name']) 61 | 62 | return unique_repositories 63 | 64 | 65 | def calculate_score(project, keyword): 66 | """核心,此函数用于计算仓库与用户需求的匹配度,计算公式为仓库名匹配(15)+仓库描述匹配(15)+开根号(star/100)""" 67 | name_score = 15 if keyword.lower() in project['name'].lower() else 0 68 | description = project.get('description') or "" 69 | description_score = 15 if keyword.lower() in description.lower() else 0 70 | 71 | star_score = sqrt(project['stars']) / 100 72 | 73 | total_score = name_score + description_score + star_score 74 | total_score = round(total_score, 2) 75 | return total_score 76 | 77 | 78 | def sort_projects_by_score(projects, keyword): 79 | """根据匹配度给json排序,由高到低""" 80 | scored_projects = [] 81 | 82 | for project in projects: 83 | score = calculate_score(project, keyword) 84 | project_with_score = project.copy() 85 | project_with_score['score'] = score 86 | scored_projects.append(project_with_score) 87 | 88 | sorted_projects = sorted(scored_projects, key=lambda x: x['score'], reverse=True) 89 | return sorted_projects 90 | 91 | 92 | def gpt(prompt): 93 | global API_KEY, BASE_URL 94 | key = API_KEY 95 | url = BASE_URL 96 | model = "GLM-4-0520" 97 | 98 | payload = json.dumps({ 99 | "model": model, 100 | "messages": [ 101 | { 102 | "role": "system", 103 | "content": "你是一个关键词提取助手,会提取用户的要求里面的关键词,给出该关键词的中文和英文,关键词用逗号隔开。只返回json,不要使用markdown语法,例如 [{cn_keyword:苹果,香蕉},{en_keyword:apple,banana},{language:None}] ,language为用户指定的编程语言,没指定则返回None,以下内容不属于关键词[开源,软件,应用,app]" 104 | }, 105 | { 106 | "role": "user", 107 | "content": prompt 108 | } 109 | ] 110 | }) 111 | 112 | headers = { 113 | 'Accept': 'application/json', 114 | 'Authorization': f'Bearer {key}', 115 | 'User-Agent': 'Apifox/1.0.0 (https://apifox.com)', 116 | 'Content-Type': 'application/json' 117 | } 118 | response = requests.request("POST", url, headers=headers, data=payload) 119 | if response.status_code == 200: 120 | content = json.loads(response.text)['choices'][0]['message']['content'] 121 | return content 122 | else: 123 | return None 124 | 125 | 126 | def search_repositories(keyword, model="name"): 127 | """根据关键词查找匹配的仓库名和描述""" 128 | global token 129 | if not keyword: 130 | return [] 131 | 132 | if model == "name": 133 | url = f"https://api.github.com/search/repositories?q={keyword}" 134 | elif model == "describe": 135 | url = f"https://api.github.com/search/repositories?q={keyword}+in:description" 136 | 137 | headers = { 138 | "Accept": "application/vnd.github.v3+json" 139 | } 140 | if token: 141 | headers["Authorization"] = f"token {token}" 142 | 143 | response = requests.get(url, headers=headers, verify=IS_GITHUB_API_VERIFY) 144 | 145 | if response.status_code == 200: 146 | return response.json().get("items", []) 147 | else: 148 | app.logger.error(f"Error: {response.status_code}") 149 | return [] 150 | 151 | 152 | def search_repos_by_keyword(keyword): 153 | """根据关键词查找匹配的readme""" 154 | search_url = f"https://api.github.com/search/repositories?q={keyword}+in:readme" 155 | headers = { 156 | "Accept": "application/vnd.github.v3+json" 157 | } 158 | if token: 159 | headers["Authorization"] = f"token {token}" 160 | 161 | response = requests.get(search_url, headers=headers, verify=IS_GITHUB_API_VERIFY) 162 | 163 | if response.status_code == 200: 164 | search_results = response.json() 165 | matched_repos = [] 166 | 167 | for repo in search_results['items']: 168 | repo_info = { 169 | "name": repo['name'], 170 | "description": repo['description'], 171 | "stars": repo['stargazers_count'], 172 | "url": repo['html_url'], 173 | "language": repo['language'] 174 | } 175 | matched_repos.append(repo_info) 176 | matched_repos = json.dumps(matched_repos, indent=4, ensure_ascii=False) 177 | return matched_repos 178 | else: 179 | app.logger.info(f"Failed to search repositories: {response.status_code}") 180 | return [] 181 | 182 | 183 | def display_repositories(repositories): 184 | """处理原始json""" 185 | if repositories: 186 | repo_list = [] 187 | for repo in repositories: 188 | repo_info = { 189 | "name": repo['name'], 190 | "description": repo['description'], 191 | "stars": repo['stargazers_count'], 192 | "url": repo['html_url'], 193 | "language": repo['language'] 194 | } 195 | # 排除垃圾仓库 196 | if repo['description']: 197 | if len(repo['description']) < 200: 198 | repo_list.append(repo_info) 199 | 200 | json_output = json.dumps(repo_list, indent=4, ensure_ascii=False) 201 | return json_output 202 | else: 203 | return json.dumps({"message": "No repositories found."}, indent=4) 204 | 205 | 206 | @app.route('/') 207 | def index(): 208 | return render_template('index.html') 209 | 210 | 211 | @app.route('/search', methods=['GET']) 212 | def search(): 213 | user_input = request.args.get('q') 214 | if_md = request.args.get('if_md') == 'off' 215 | 216 | keyword_json = gpt(user_input) 217 | try: 218 | keyword_json = standardized_json(keyword_json) 219 | dict_list = json.loads(keyword_json) 220 | cn_keyword = dict_list['cn_keyword'] 221 | en_keyword = dict_list['en_keyword'] 222 | 223 | name_repositories = json.loads(display_repositories(search_repositories(cn_keyword))) 224 | describe_repositories = json.loads(display_repositories(search_repositories(cn_keyword, model="describe"))) 225 | try: 226 | if if_md: 227 | readme_repositories = json.loads(search_repos_by_keyword(cn_keyword)) 228 | repositories = merge_and_remove_duplicates2(name_repositories, describe_repositories, 229 | readme_repositories) 230 | else: 231 | 232 | repositories = merge_and_remove_duplicates(name_repositories, describe_repositories) 233 | json_output = json.dumps(repositories, ensure_ascii=False, indent=4) 234 | json_output = json.loads(json_output) 235 | sorted_projects1 = sort_projects_by_score(json_output, cn_keyword) 236 | 237 | name_repositories = json.loads(display_repositories(search_repositories(en_keyword))) 238 | describe_repositories = json.loads(display_repositories(search_repositories(en_keyword, model="describe"))) 239 | if if_md: 240 | readme_repositories = json.loads(search_repos_by_keyword(en_keyword)) 241 | repositories = merge_and_remove_duplicates2(name_repositories, describe_repositories, 242 | readme_repositories) 243 | else: 244 | repositories = merge_and_remove_duplicates(name_repositories, describe_repositories) 245 | json_output = json.dumps(repositories, ensure_ascii=False, indent=4) 246 | json_output = json.loads(json_output) 247 | sorted_projects2 = sort_projects_by_score(json_output, en_keyword) 248 | 249 | final_json = merge_and_remove_duplicates(sorted_projects1, sorted_projects2) 250 | except Exception as e: 251 | app.logger.error(e) 252 | final_json = [{"name": "没有找到匹配的仓库", 253 | 'description': '请你尝试换一种说法进行查找', 254 | 'url': 'index.html', 'score': 0}, ] 255 | except requests.exceptions.SSLError as e: 256 | app.logger.error("requests ssl error!") 257 | return render_template( 258 | "index.html", 259 | error_message="向github发送请求时,出现ssl错误!", 260 | user_input=user_input 261 | ) 262 | except Exception as e: 263 | app.logger.error(e) 264 | return render_template( 265 | 'index.html', 266 | error_message='内容无法识别!请重新输入!', 267 | user_input=user_input # 返回用户上次输入,以免刷新导致数据丢失 268 | ) 269 | app.logger.info(f"Number of returned results: {len(final_json)}") 270 | return render_template('results.html', results=final_json) 271 | 272 | 273 | if __name__ == "__main__": 274 | app.run(debug=True) 275 | 276 | --------------------------------------------------------------------------------