Skip to content

Commit 11fedad

Browse files
authored
Merge pull request #187 from CJackHwang/dev
推送一次
2 parents aee9412 + 833ec19 commit 11fedad

18 files changed

Lines changed: 4271 additions & 168 deletions

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,10 @@ errors_py/
218218
logs/
219219

220220
# Authentication Profiles (Sensitive)
221-
auth_profiles/active/
222-
auth_profiles/saved/
221+
auth_profiles/active/*
222+
!auth_profiles/active/.gitkeep
223+
auth_profiles/saved/*
224+
!auth_profiles/saved/.gitkeep
223225

224226
# Camoufox/Playwright Profile Data (Assume these are generated/temporary)
225227
camoufox_profile/

README.md

Lines changed: 103 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ This project is generously sponsored by ZMTO. Visit their website: [https://zmto
2828

2929
## 系统要求
3030

31-
* **Python**: 3.9+ (推荐 3.10+ 或 3.11+)
31+
* **Python**: >=3.9, <4.0 (推荐 3.10+ 以获得最佳性能)
32+
* **依赖管理**: [Poetry](https://python-poetry.org/) (现代化Python依赖管理工具)
33+
* **类型检查**: [Pyright](https://github.com/microsoft/pyright) (可选,用于开发时类型检查)
3234
* **操作系统**: Windows, macOS, Linux (完全跨平台支持)
3335
* **内存**: 建议 2GB+ 可用内存
3436
* **网络**: 稳定的互联网连接访问 Google AI Studio
@@ -47,6 +49,7 @@ This project is generously sponsored by ZMTO. Visit their website: [https://zmto
4749
* **灵活认证系统**: 支持可选的API密钥认证,完全兼容OpenAI标准的Bearer token格式
4850
* **模块化架构**: 采用清晰的模块化设计,便于维护和扩展
4951
* **统一配置管理**: 基于 `.env` 文件的统一配置方式,支持环境变量覆盖
52+
* **现代化开发工具**: 集成 Poetry 依赖管理和 Pyright 类型检查,提供优秀的开发体验
5053

5154
## 系统架构
5255

@@ -158,26 +161,102 @@ python launch_camoufox.py --headless
158161

159162
### 快速开始
160163

161-
1. **安装**: 参见 [安装指南](docs/installation-guide.md)
162-
2. **配置**: 参见 [环境变量配置指南](docs/environment-configuration.md) - **推荐先配置**
163-
3. **首次认证**: 参见 [认证设置指南](docs/authentication-setup.md)
164-
4. **日常运行**: 参见 [日常运行指南](docs/daily-usage.md)
165-
5. **API使用**: 参见 [API使用指南](docs/api-usage.md)
166-
6. **Web界面**: 参见 [Web UI使用指南](docs/webui-guide.md)
164+
本项目采用现代化的 Python 开发工具链,使用 [Poetry](https://python-poetry.org/) 进行依赖管理,[Pyright](https://github.com/microsoft/pyright) 进行类型检查。
167165

168-
### 详细文档
166+
#### 🚀 一键安装脚本 (推荐)
169167

168+
```bash
169+
# macOS/Linux 用户
170+
curl -sSL https://raw.githubusercontent.com/CJackHwang/AIstudioProxyAPI/main/scripts/install.sh | bash
171+
172+
# Windows 用户 (PowerShell)
173+
iwr -useb https://raw.githubusercontent.com/CJackHwang/AIstudioProxyAPI/main/scripts/install.ps1 | iex
174+
```
175+
176+
#### 📋 手动安装步骤
177+
178+
1. **安装 Poetry** (如果尚未安装):
179+
```bash
180+
# macOS/Linux
181+
curl -sSL https://install.python-poetry.org | python3 -
182+
183+
# Windows (PowerShell)
184+
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
185+
186+
# 或使用包管理器
187+
# macOS: brew install poetry
188+
# Ubuntu/Debian: apt install python3-poetry
189+
```
190+
191+
2. **克隆项目**:
192+
```bash
193+
git clone https://github.com/CJackHwang/AIstudioProxyAPI.git
194+
cd AIstudioProxyAPI
195+
```
196+
197+
3. **安装依赖**:
198+
Poetry 会自动创建虚拟环境并安装所有依赖:
199+
```bash
200+
poetry install
201+
```
202+
203+
4. **激活虚拟环境**:
204+
```bash
205+
# 方式1: 激活 shell (推荐日常开发)
206+
poetry env activate
207+
208+
# 方式2: 直接运行命令 (推荐自动化脚本)
209+
poetry run python gui_launcher.py
210+
```
211+
212+
#### 🔧 后续配置步骤
213+
214+
5. **环境配置**: 参见 [环境变量配置指南](docs/environment-configuration.md) - **推荐先配置**
215+
6. **首次认证**: 参见 [认证设置指南](docs/authentication-setup.md)
216+
7. **日常运行**: 参见 [日常运行指南](docs/daily-usage.md)
217+
8. **API使用**: 参见 [API使用指南](docs/api-usage.md)
218+
9. **Web界面**: 参见 [Web UI使用指南](docs/webui-guide.md)
219+
220+
#### 🛠️ 开发者选项
221+
222+
如果您是开发者,还可以:
223+
224+
```bash
225+
# 安装开发依赖 (包含类型检查、测试工具等)
226+
poetry install --with dev
227+
228+
# 启用类型检查 (需要安装 pyright)
229+
npm install -g pyright
230+
pyright
231+
232+
# 查看项目依赖树
233+
poetry show --tree
234+
235+
# 更新依赖
236+
poetry update
237+
```
238+
239+
### 📚 详细文档
240+
241+
#### 🚀 快速上手
170242
- [安装指南](docs/installation-guide.md) - 详细的安装步骤和环境配置
171243
- [环境变量配置指南](docs/environment-configuration.md) - **.env 文件配置管理**
172244
- [认证设置指南](docs/authentication-setup.md) - 首次运行与认证文件设置
173245
- [日常运行指南](docs/daily-usage.md) - 日常使用和配置选项
246+
247+
#### 🔧 功能使用
174248
- [API使用指南](docs/api-usage.md) - API端点和客户端配置
175249
- [Web UI使用指南](docs/webui-guide.md) - Web界面功能说明
176-
- [故障排除指南](docs/troubleshooting.md) - 常见问题解决方案
177-
- [高级配置指南](docs/advanced-configuration.md) - 高级功能和配置选项
178250
- [脚本注入指南](docs/script_injection_guide.md) - 油猴脚本动态挂载功能使用指南 (v3.0) 🆕
251+
252+
#### ⚙️ 高级配置
253+
- [高级配置指南](docs/advanced-configuration.md) - 高级功能和配置选项
179254
- [日志控制指南](docs/logging-control.md) - 日志系统配置和调试
180-
- [依赖版本说明](docs/dependency-versions.md) - Python版本要求和依赖兼容性详解
255+
- [故障排除指南](docs/troubleshooting.md) - 常见问题解决方案
256+
257+
#### 🛠️ 开发相关
258+
- [开发者指南](docs/development-guide.md) - Poetry、Pyright 和开发工作流程 🆕
259+
- [依赖版本说明](docs/dependency-versions.md) - Poetry 依赖管理和版本控制详解
181260

182261
## 客户端配置示例
183262

@@ -193,13 +272,13 @@ python launch_camoufox.py --headless
193272

194273
---
195274

196-
## Docker 部署
275+
## 🐳 Docker 部署
197276

198-
本项目支持通过 Docker 进行部署,**现在完全支持 `.env` 配置文件**
277+
本项目支持通过 Docker 进行部署,使用 **Poetry** 进行依赖管理,**完全支持 `.env` 配置文件**
199278

200279
> 📁 **注意**: 所有 Docker 相关文件已移至 `docker/` 目录,保持项目根目录整洁。
201280

202-
### 快速 Docker 部署
281+
### 🚀 快速 Docker 部署
203282

204283
```bash
205284
# 1. 准备配置文件
@@ -210,20 +289,26 @@ nano .env # 编辑配置
210289
# 2. 使用 Docker Compose 启动
211290
docker compose up -d
212291
213-
# 3. 版本更新 (在 docker 目录下)
292+
# 3. 查看日志
293+
docker compose logs -f
294+
295+
# 4. 版本更新 (在 docker 目录下)
214296
bash update.sh
215297
```
216298

217-
### 详细文档
299+
### 📚 详细文档
218300

219-
- [Docker 部署指南 (docker/README-Docker.md)](docker/README-Docker.md) - 包含完整的 `.env` 配置说明
301+
- [Docker 部署指南 (docker/README-Docker.md)](docker/README-Docker.md) - 包含完整的 Poetry + `.env` 配置说明
220302
- [Docker 快速指南 (docker/README.md)](docker/README.md) - 快速开始指南
221303

222-
### 重要说明
304+
### ✨ Docker 特性
223305

306+
- ✅ **Poetry 依赖管理**: 使用现代化的 Python 依赖管理工具
307+
- ✅ **多阶段构建**: 优化镜像大小和构建速度
224308
- ✅ **配置统一**: 使用 `.env` 文件管理所有配置
225309
- ✅ **版本更新**: `bash update.sh` 即可完成更新
226310
- ✅ **目录整洁**: Docker 文件已移至 `docker/` 目录
311+
- ✅ **跨平台支持**: 支持 x86_64 和 ARM64 架构
227312
- ⚠️ **认证文件**: 首次运行需要在主机上获取认证文件,然后挂载到容器中
228313

229314
---

auth_profiles/active/.gitkeep

Whitespace-only changes.

auth_profiles/saved/.gitkeep

Whitespace-only changes.

browser_utils/page_controller.py

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
封装了所有与Playwright页面直接交互的复杂逻辑。
44
"""
55
import asyncio
6-
from typing import Callable, List, Dict, Any
6+
from typing import Callable, List, Dict, Any, Optional
77

88
from playwright.async_api import Page as AsyncPage, expect as expect_async, TimeoutError
99

@@ -12,7 +12,9 @@
1212
MAT_CHIP_REMOVE_BUTTON_SELECTOR, TOP_P_INPUT_SELECTOR, SUBMIT_BUTTON_SELECTOR,
1313
CLEAR_CHAT_BUTTON_SELECTOR, CLEAR_CHAT_CONFIRM_BUTTON_SELECTOR, OVERLAY_SELECTOR,
1414
PROMPT_TEXTAREA_SELECTOR, RESPONSE_CONTAINER_SELECTOR, RESPONSE_TEXT_SELECTOR,
15-
EDIT_MESSAGE_BUTTON_SELECTOR,USE_URL_CONTEXT_SELECTOR,UPLOAD_BUTTON_SELECTOR
15+
EDIT_MESSAGE_BUTTON_SELECTOR,USE_URL_CONTEXT_SELECTOR,UPLOAD_BUTTON_SELECTOR,
16+
SET_THINKING_BUDGET_TOGGLE_SELECTOR, THINKING_BUDGET_INPUT_SELECTOR,
17+
GROUNDING_WITH_GOOGLE_SEARCH_TOGGLE_SELECTOR
1618
)
1719
from config import (
1820
CLICK_TIMEOUT_MS, WAIT_FOR_ELEMENT_TIMEOUT_MS, CLEAR_CHAT_VERIFY_TIMEOUT_MS,
@@ -62,6 +64,116 @@ async def adjust_parameters(self, request_params: Dict[str, Any], page_params_ca
6264
# 调整URL CONTEXT
6365
await self._open_url_content(check_client_disconnected)
6466

67+
# 调整“思考预算”开关
68+
await self._set_thinking_budget_toggle_checked(check_client_disconnected)
69+
70+
# 在函数末尾,await self._set_thinking_budget_toggle_checked(...) 之后添加:
71+
self.logger.info(f"[{self.req_id}] 检查预算...{request_params.get('reasoning_effort')}")
72+
if 'reasoning_effort' in request_params:
73+
await self._adjust_thinking_budget(request_params.get('reasoning_effort'), check_client_disconnected)
74+
75+
# 调整 Google Search 开关
76+
await self._adjust_google_search(request_params, check_client_disconnected)
77+
78+
async def _adjust_thinking_budget(self, reasoning_effort: Optional[str], check_client_disconnected: Callable):
79+
"""根据 reasoning_effort 调整思考预算。"""
80+
self.logger.info(f"[{self.req_id}] 检查并调整思考预算...")
81+
82+
token_budget = None
83+
if reasoning_effort is None or reasoning_effort.lower() == 'none':
84+
token_budget = 8192
85+
self.logger.info(f"[{self.req_id}] 'reasoning_effort' 为空或 'none',使用默认思考预算: {token_budget}")
86+
else:
87+
effort_map = {
88+
"low": 1000,
89+
"medium": 8000,
90+
"high": 24000
91+
}
92+
token_budget = effort_map.get(reasoning_effort.lower())
93+
94+
if not token_budget:
95+
self.logger.warning(f"[{self.req_id}] 无效的 reasoning_effort 值: '{reasoning_effort}'。跳过调整。")
96+
return
97+
98+
budget_input_locator = self.page.locator(THINKING_BUDGET_INPUT_SELECTOR)
99+
100+
try:
101+
await expect_async(budget_input_locator).to_be_visible(timeout=5000)
102+
await self._check_disconnect(check_client_disconnected, "思考预算调整 - 输入框可见后")
103+
104+
self.logger.info(f"[{self.req_id}] 设置思考预算为: {token_budget}")
105+
await budget_input_locator.fill(str(token_budget), timeout=5000)
106+
await self._check_disconnect(check_client_disconnected, "思考预算调整 - 填充输入框后")
107+
108+
# 验证
109+
await asyncio.sleep(0.1)
110+
new_value_str = await budget_input_locator.input_value(timeout=3000)
111+
if int(new_value_str) == token_budget:
112+
self.logger.info(f"[{self.req_id}] ✅ 思考预算已成功更新为: {new_value_str}")
113+
else:
114+
self.logger.warning(f"[{self.req_id}] ⚠️ 思考预算更新后验证失败。页面显示: {new_value_str}, 期望: {token_budget}")
115+
116+
except Exception as e:
117+
self.logger.error(f"[{self.req_id}] ❌ 调整思考预算时出错: {e}")
118+
if isinstance(e, ClientDisconnectedError):
119+
raise
120+
121+
async def _adjust_google_search(self, request_params: Dict[str, Any], check_client_disconnected: Callable):
122+
"""根据请求参数中的 'googleSearch' 工具存在与否,双向控制 Google Search 开关。"""
123+
self.logger.info(f"[{self.req_id}] 检查并调整 Google Search 开关...")
124+
125+
tools = request_params.get('tools')
126+
has_google_search = False
127+
if isinstance(tools, list):
128+
for tool in tools:
129+
if isinstance(tool, dict) and tool.get('function', {}).get('name') == 'googleSearch':
130+
has_google_search = True
131+
break
132+
133+
toggle_selector = GROUNDING_WITH_GOOGLE_SEARCH_TOGGLE_SELECTOR
134+
135+
try:
136+
toggle_locator = self.page.locator(toggle_selector)
137+
await expect_async(toggle_locator).to_be_visible(timeout=5000)
138+
await self._check_disconnect(check_client_disconnected, "Google Search 开关 - 元素可见后")
139+
140+
is_checked_str = await toggle_locator.get_attribute("aria-checked")
141+
self.logger.info(f"[{self.req_id}] Google Search 开关 'aria-checked' 状态: '{is_checked_str}'。是否需要搜索: {has_google_search}")
142+
143+
if has_google_search:
144+
# 需要搜索,但开关是关闭的,则打开它
145+
if is_checked_str == "false":
146+
self.logger.info(f"[{self.req_id}] 'googleSearch' 工具存在,但开关关闭。正在点击以打开...")
147+
await toggle_locator.click(timeout=CLICK_TIMEOUT_MS)
148+
await self._check_disconnect(check_client_disconnected, "Google Search 开关 - 点击打开后")
149+
await asyncio.sleep(0.5) # 等待UI更新
150+
new_state = await toggle_locator.get_attribute("aria-checked")
151+
if new_state == "true":
152+
self.logger.info(f"[{self.req_id}] ✅ Google Search 开关已成功打开。")
153+
else:
154+
self.logger.warning(f"[{self.req_id}] ⚠️ Google Search 开关打开失败。当前状态: '{new_state}'")
155+
else:
156+
self.logger.info(f"[{self.req_id}] 'googleSearch' 工具存在,且开关已打开,无需操作。")
157+
else:
158+
# 不需要搜索,但开关是打开的,则关闭它
159+
if is_checked_str == "true":
160+
self.logger.info(f"[{self.req_id}] 'googleSearch' 工具不存在,但开关打开。正在点击以关闭...")
161+
await toggle_locator.click(timeout=CLICK_TIMEOUT_MS)
162+
await self._check_disconnect(check_client_disconnected, "Google Search 开关 - 点击关闭后")
163+
await asyncio.sleep(0.5) # 等待UI更新
164+
new_state = await toggle_locator.get_attribute("aria-checked")
165+
if new_state == "false":
166+
self.logger.info(f"[{self.req_id}] ✅ Google Search 开关已成功关闭。")
167+
else:
168+
self.logger.warning(f"[{self.req_id}] ⚠️ Google Search 开关关闭失败。当前状态: '{new_state}'")
169+
else:
170+
self.logger.info(f"[{self.req_id}] 'googleSearch' 工具不存在,且开关已关闭,无需操作。")
171+
172+
except Exception as e:
173+
self.logger.error(f"[{self.req_id}] ❌ 操作 'Google Search toggle' 开关 '{toggle_selector}' 时发生错误: {e}")
174+
if isinstance(e, ClientDisconnectedError):
175+
raise
176+
65177
async def _open_url_content(self,check_client_disconnected: Callable):
66178
try:
67179
collapse_tools_locator = self.page.locator('button[aria-label="Expand or collapse tools"]')
@@ -84,6 +196,53 @@ async def _open_url_content(self,check_client_disconnected: Callable):
84196
except Exception as e:
85197
self.logger.error(f"[{self.req_id}] ❌ 操作USE_URL_CONTEXT_SELECTOR时发生错误:{e}。")
86198

199+
async def _set_thinking_budget_toggle_checked(self, check_client_disconnected: Callable):
200+
"""
201+
确保 "Thinking Budget" 滑块开关处于选中状态。
202+
"""
203+
toggle_selector = SET_THINKING_BUDGET_TOGGLE_SELECTOR
204+
self.logger.info(f"[{self.req_id}] 检查并设置 'Thinking Budget toggle' 滑块开关 '{toggle_selector}' 的状态...")
205+
206+
try:
207+
toggle_locator = self.page.locator(toggle_selector)
208+
209+
# 等待元素在 DOM 中可见
210+
await expect_async(toggle_locator).to_be_visible(timeout=5000)
211+
await self._check_disconnect(check_client_disconnected, "思考预算开关 - 元素可见后")
212+
213+
# 1. 检查当前 'aria-checked' 属性
214+
is_checked = await toggle_locator.get_attribute("aria-checked")
215+
self.logger.info(f"[{self.req_id}] 思考预算开关当前 'aria-checked' 状态: {is_checked}")
216+
217+
# 2. 如果开关未选中 (aria-checked="false"),则点击它
218+
if is_checked == "false":
219+
self.logger.info(f"[{self.req_id}] 思考预算开关当前未选中,正在点击以启用...")
220+
await toggle_locator.click(timeout=CLICK_TIMEOUT_MS)
221+
await self._check_disconnect(check_client_disconnected, "思考预算开关 - 点击后")
222+
223+
# 3. 验证操作是否成功
224+
await asyncio.sleep(0.5) # 短暂等待,让 UI 状态更新
225+
new_state = await toggle_locator.get_attribute("aria-checked")
226+
if new_state == "true":
227+
self.logger.info(f"[{self.req_id}] ✅ 'Thinking Budget toggle' 已成功启用。新状态: {new_state}")
228+
else:
229+
self.logger.warning(f"[{self.req_id}] ⚠️ 'Thinking Budget toggle' 点击后验证失败。期望状态: 'true', 实际状态: '{new_state}'")
230+
# 如果需要,可以在此处添加错误快照
231+
# await save_error_snapshot(f"thinking_budget_toggle_verify_fail_{self.req_id}")
232+
233+
elif is_checked == "true":
234+
self.logger.info(f"[{self.req_id}] 'Thinking Budget toggle' 已处于选中状态,无需操作。")
235+
else:
236+
# 处理 'aria-checked' 属性不存在或值无效的情况
237+
self.logger.warning(f"[{self.req_id}] 无法确定 'Thinking Budget toggle' 的状态,'aria-checked' 值为: '{is_checked}'。")
238+
239+
except Exception as e:
240+
self.logger.error(f"[{self.req_id}] ❌ 操作 'Thinking Budget toggle' 滑块开关 '{toggle_selector}' 时发生错误: {e}")
241+
# 如果需要,可以在此处添加错误快照
242+
# await save_error_snapshot(f"thinking_budget_toggle_error_{self.req_id}")
243+
# 如果是客户端断开连接的特定错误,则重新抛出
244+
if isinstance(e, ClientDisconnectedError):
245+
raise
87246
async def _adjust_temperature(self, temperature: float, page_params_cache: dict, params_cache_lock: asyncio.Lock, check_client_disconnected: Callable):
88247
"""调整温度参数。"""
89248
async with params_cache_lock:

config/selectors.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,9 @@
4141
MAT_CHIP_REMOVE_BUTTON_SELECTOR = 'mat-chip-set mat-chip-row button[aria-label*="Remove"]'
4242
TOP_P_INPUT_SELECTOR = 'div.settings-item-column:has(h3:text-is("Top P")) input[type="number"].slider-input'
4343
TEMPERATURE_INPUT_SELECTOR = 'div[data-test-id="temperatureSliderContainer"] input[type="number"].slider-input'
44-
USE_URL_CONTEXT_SELECTOR = 'button[aria-label="Browse the url context"]'
44+
USE_URL_CONTEXT_SELECTOR = 'button[aria-label="Browse the url context"]'
45+
SET_THINKING_BUDGET_TOGGLE_SELECTOR = 'button[aria-label="Toggle thinking budget between auto and manual"]'
46+
# Thinking budget slider input
47+
THINKING_BUDGET_INPUT_SELECTOR = 'xpath=//div[contains(@class, "settings-item") and .//p[normalize-space()="Set thinking budget"]]/following-sibling::div[contains(@class, "item-input")]//input[@type="number"]'
48+
# --- Google Search Grounding ---
49+
GROUNDING_WITH_GOOGLE_SEARCH_TOGGLE_SELECTOR = 'div[data-test-id="searchAsAToolTooltip"] mat-slide-toggle button'

0 commit comments

Comments
 (0)