1 line
6.9 KiB
JSON
1 line
6.9 KiB
JSON
{"repo": "blindcreeper/counter-strike-2-skin-trading-system", "files": {"src/aihelp_vision.py": {"62959fda8a350b18ff98876dbb3c1efddc07aa84": {"t": 1785674241.6323755, "n": 5940, "h": "86d38665d7a3b4ca", "c": "import os\nimport base64\nfrom io import BytesIO\nfrom pathlib import Path\nimport tkinter as tk\nfrom concurrent.futures import ThreadPoolExecutor\nimport queue\nimport threading\nimport re\nimport time\n\nimport keyboard\nimport pyautogui\nfrom openai import OpenAI\n\n# ================= 配置区域 =================\n\n# 替换为您的支持 Vision (视觉) 的 API Key\n# 推荐使用:\n# 1. OpenAI (gpt-4o-mini, gpt-4o) - 最稳\n# 2. DashScope (qwen-vl-max) - 中文最强\n# 3. Google Gemini (gemini-1.5-flash) - 免费且快\nAPI_KEY = \"sk-cfa012bef6554f65a635f21e2cbe9cd1\" # 请填写您的 API Key\nAI_BASE_URL = \"https://dashscope.aliyuncs.com/compatible-mode/v1\" # 请填写对应的 Base URL\nAI_MODEL = \"qwen-vl-plus\" # 请填写对应的模型名称\n\nOBSIDIAN_DIR = Path(os.getenv(\"OBSIDIAN_DIR\", r\"D:\\obsidian\\obsidiandata\\englishnote\"))\n\nHOTKEY_CAPTURE = \"F8\"\nHOTKEY_SELECT_REGION = \"F7\"\n\n# ===========================================\n\nREGION = None\ntask_queue = queue.Queue()\nprocessing_count = 0\nmax_concurrent_tasks = 2\n\nif not API_KEY or API_KEY.startswith(\"sk-...\"):\n raise RuntimeError(\"请在代码中配置真实的支持 Vision 的 API Key!\")\n\nclient = OpenAI(\n api_key=API_KEY,\n base_url=AI_BASE_URL,\n)\n\ndef encode_image(image):\n \"\"\"将 PIL Image 转换为 Base64 字符串\"\"\"\n buffered = BytesIO()\n image.save(buffered, format=\"JPEG\", quality=85)\n return base64.b64encode(buffered.getvalue()).decode('utf-8')\n\ndef select_region():\n \"\"\"手动框选截图区域\"\"\"\n result = {\"region\": None}\n root = tk.Tk()\n root.attributes(\"-fullscreen\", True)\n root.attributes(\"-alpha\", 0.25)\n root.configure(bg=\"black\")\n \n canvas = tk.Canvas(root, cursor=\"cross\", bg=\"gray\", highlightthickness=0)\n canvas.pack(fill=tk.BOTH, expand=True)\n\n start_x, start_y = 0, 0\n rect_id = None\n\n def on_press(event):\n nonlocal start_x, start_y, rect_id\n start_x, start_y = event.x, event.y\n if rect_id: canvas.delete(rect_id)\n rect_id = canvas.create_rectangle(start_x, start_y, start_x, start_y, outline=\"red\", width=2)\n\n def on_drag(event):\n if rect_id: canvas.coords(rect_id, start_x, start_y, event.x, event.y)\n\n def on_release(event):\n x1, y1, x2, y2 = start_x, start_y, event.x, event.y\n left, top = min(x1, x2), min(y1, y2)\n width, height = abs(x2 - x1), abs(y2 - y1)\n if width > 10 and height > 10:\n result[\"region\"] = (left, top, width, height)\n root.destroy()\n\n canvas.bind(\"<ButtonPress-1>\", on_press)\n canvas.bind(\"<B1-Motion>\", on_drag)\n canvas.bind(\"<ButtonRelease-1>\", on_release)\n root.bind(\"<Escape>\", lambda e: root.destroy())\n root.mainloop()\n return result[\"region\"]\n\ndef set_region_hotkey():\n global REGION\n print(\"[INFO] 请框选区域...\")\n region = select_region()\n if region:\n REGION = region\n print(f\"[OK] 区域已设置: {REGION}\")\n add_task_to_queue()\n\ndef capture_hotkey():\n global REGION\n if not REGION:\n print(\"[INFO] 请先框选区域...\")\n region = select_region()\n if not region: return\n REGION = region\n add_task_to_queue()\n\ndef add_task_to_queue():\n global processing_count\n if processing_count >= max_concurrent_tasks:\n print(f\"[INFO] 任务队列已满,请稍候...\")\n return\n \n # 立即截图(在主线程截图,避免 UI 变化)\n screenshot = pyautogui.screenshot(region=REGION)\n task_queue.put(screenshot)\n print(f\"[INFO] 截图已添加,队列长度: {task_queue.qsize()}\")\n \n if not hasattr(add_task_to_queue, 'worker_started'):\n threading.Thread(target=worker_loop, daemon=True).start()\n add_task_to_queue.worker_started = True\n\ndef worker_loop():\n global processing_count\n while True:\n try:\n image = task_queue.get(timeout=1)\n processing_count += 1\n print(f\"[INFO] 开始处理图片...\")\n try:\n process_image(image)\n except Exception as e:\n print(f\"[ERROR] 处理失败: {e}\")\n finally:\n processing_count -= 1\n task_queue.task_done()\n except queue.Empty:\n continue\n\ndef process_image(image):\n base64_image = encode_image(image)\n \n # 构造 Vision API 请求\n # 提示词要求输出 JSON 或特定 Markdown 格式\n prompt = \"\"\"\n 请识别这张图片中的英语题目。\n \n 请输出 Markdown 格式解析,包含以下部分:\n 1. # 重点单词\n (仅输出这道题最核心的一个单词)\n 2. # 题目\n (识别出的完整题目文本)\n 3. # 英语题目解析\n (尽量简洁)\n 4.# 相关单词\n \n 请严格按照上述标题输出,不要标注markdown\n \"\"\"\n\n try:\n response = client.chat.completions.create(\n model=AI_MODEL,\n messages=[\n {\n \"role\": \"user\", \n \"content\": [\n {\"type\": \"text\", \"text\": prompt},\n {\n \"type\": \"image_url\",\n \"image_url\": {\"url\": f\"data:image/jpeg;base64,{base64_image}\"}\n }\n ]\n }\n ],\n max_tokens=1000\n )\n content = response.choices[0].message.content\n save_note(content)\n \n except Exception as e:\n print(f\"[ERROR] API 调用失败: {e}\")\n\ndef save_note(content):\n OBSIDIAN_DIR.mkdir(parents=True, exist_ok=True)\n \n # 解析重点单词作为文件名\n keyword = \"english_note\"\n match = re.search(r\"# 重点单词\\s*\\n+([^\\n]+)\", content)\n if match:\n keyword = match.group(1).strip().split()[0] # 取第一行第一个词\n keyword = re.sub(r'[^\\w\\s-]', '', keyword).strip()\n\n file_path = OBSIDIAN_DIR / f\"{keyword}.md\"\n \n # 处理重名\n counter = 1\n original_kw = keyword\n while file_path.exists():\n file_path = OBSIDIAN_DIR / f\"{original_kw}_{counter}.md\"\n counter += 1\n \n file_path.write_text(content, encoding=\"utf-8\")\n print(f\"[SUCCESS] 笔记已保存: {file_path.name}\")\n\ndef main():\n keyboard.add_hotkey(HOTKEY_SELECT_REGION, set_region_hotkey)\n keyboard.add_hotkey(HOTKEY_CAPTURE, capture_hotkey)\n print(f\"Ready! 按 {HOTKEY_SELECT_REGION} 框选,按 {HOTKEY_CAPTURE} 识别。\")\n keyboard.wait()\n\nif __name__ == \"__main__\":\n main()"}}}} |