feat: 拆出 FastAPI 独立后端 + MQTT 控制 + 阿里云 AI
后端(backend/,FastAPI :10085): - 全部数据 REST 接口:dashboard 快照 / 企业分区 / 播放列表 / 媒体 / 设置 - MQTT 控制通道:opc/display/command(切页/播放/卡片/通知), 管理端 POST /api/display/command → MQTT 广播 → 所有大屏同步响应 - 阿里云 DashScope:通义千问 LLM + 函数调用(工具经 MQTT 广播), paraformer-realtime-v2 语音识别 /api/ai/asr - 媒体资源统一由后端存储返回(上传/列表/静态服务) - SSE /api/events 保留作 MQTT 不可用时的兼容回退 前端: - config.js + .env.local 配置后端地址与 MQTT 账号(前端 dpm / 服务端 dpmserver) - mqtt.js 客户端 + useMqttControl(MQTT 驱动切页/媒体/卡片/通知) - DpmOverlays 全局覆盖层(通知 toast + 企业/分区/总览卡片) - useParkSim 改为后端 API 数据源(离线回退本地模拟) - AiChatPanel:对话走后端 LLM(工具调用),语音走本地录音 + 后端 ASR - MediaScreen:媒体控制走 MQTT(保留 SSE 回退),修复 useEffect TDZ Rust:lib.rs 移除内嵌 HTTP 服务器,只保留薄壳(窗口/权限/自启/npc 隧道) 安全:backend/.env、.env.local、media、data.json 已 gitignore
This commit is contained in:
+13
@@ -12,6 +12,14 @@ dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# 后端密钥 / 前端本地配置(含阿里云密钥、MQTT 凭据,不入库)
|
||||
backend/.env
|
||||
.env.local
|
||||
|
||||
# 后端运行时数据(媒体上传、持久化配置)
|
||||
backend/media/
|
||||
backend/data.json
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
@@ -25,3 +33,8 @@ dist-ssr
|
||||
|
||||
# 本地数据资料(不入库)
|
||||
数据资料/
|
||||
|
||||
# Python
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# OPC 智能园区后端环境配置(复制为 .env 或直接 export)
|
||||
# 注意:backend/.env 已 gitignore,含阿里云密钥与 MQTT 凭据,勿提交
|
||||
|
||||
# HTTP 监听(也可用 FASTAPI_HOST/FASTAPI_PORT)
|
||||
DPM_HOST=0.0.0.0
|
||||
DPM_PORT=10085
|
||||
|
||||
# MQTT Broker(局域网 EMQX)
|
||||
DPM_MQTT_ENABLED=1
|
||||
DPM_MQTT_HOST=192.168.1.3
|
||||
DPM_MQTT_PORT=1883
|
||||
# 服务端发布账号(在 EMQX 认证中创建)
|
||||
DPM_MQTT_USERNAME=dpmserver
|
||||
DPM_MQTT_PASSWORD=123456
|
||||
# 前端 WebSocket 接入地址(前端在项目根 .env.local 配置,账号 dpm)
|
||||
DPM_MQTT_WS=ws://192.168.1.3:8083/mqtt
|
||||
DPM_MQTT_TICK_INTERVAL=2.2
|
||||
|
||||
# 阿里云 DashScope(LLM + 语音识别)
|
||||
DASHSCOPE_API_KEY=sk-xxx
|
||||
DPM_LLM_MODEL=qwen-plus
|
||||
DPM_ASR_MODEL=paraformer-realtime-v2
|
||||
@@ -0,0 +1 @@
|
||||
3.12
|
||||
@@ -0,0 +1,87 @@
|
||||
# OPC 智能园区后端(FastAPI + MQTT)
|
||||
|
||||
独立后端服务:**所有数据通过 REST API 提供**,**页面/媒体控制通过 MQTT 下发**,媒体资源由后端统一存储与返回。
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
┌──────────────┐ REST(全部数据/媒体/设置) ┌────────────────┐
|
||||
│ Tauri 大屏 │ ◄────────────────────────── │ FastAPI :10085 │
|
||||
│ (React 前端) │ └───────┬────────┘
|
||||
│ │ MQTT 订阅 opc/display/command │ MQTT 发布
|
||||
│ 账号: dpm │ ◄────────────────────────────────────│ 账号: dpmserver
|
||||
└──────┬───────┘ MQTT Broker (EMQX) │
|
||||
│ 192.168.1.3:1883 │
|
||||
└───────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **数据**:`GET /api/dashboard/snapshot`(每 2.2s 刷新,模拟引擎在后端运行)等
|
||||
- **控制**:`POST /api/display/command` → MQTT `opc/display/command` → 所有大屏订阅同步响应
|
||||
- **媒体**:上传/列表/播放列表/`/file` 静态返回(图片/视频统一由后端托管)
|
||||
- **AI**:通义千问(DashScope)+ 工具调用(切页/控制/卡片/通知,经 MQTT 广播);语音识别用阿里云 paraformer
|
||||
|
||||
## 启动
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
cp .env.example .env # 按需修改(含 DashScope Key、MQTT 账号)
|
||||
.venv/bin/python main.py
|
||||
```
|
||||
|
||||
启动后:API 与前端页面均在 `http://0.0.0.0:10085`(浏览器直接打开即大屏)。
|
||||
|
||||
## 环境变量(backend/.env)
|
||||
|
||||
| 变量 | 说明 |
|
||||
|---|---|
|
||||
| `DPM_HOST` / `DPM_PORT`(或 `FASTAPI_HOST`/`FASTAPI_PORT`) | 监听地址,默认 `0.0.0.0:10085` |
|
||||
| `DPM_MQTT_HOST` / `DPM_MQTT_PORT` | MQTT broker 地址,默认读 `MQTT_BROKER_HOST/PORT` |
|
||||
| `MQTT_USERNAME` / `MQTT_PASSWORD` | **服务端发布账号:dpmserver / 123456** |
|
||||
| `DASHSCOPE_API_KEY` | 阿里云 DashScope(LLM + 语音识别) |
|
||||
| `DPM_LLM_MODEL` | 默认 `qwen-plus` |
|
||||
| `DPM_ASR_MODEL` | 默认 `paraformer-realtime-v2` |
|
||||
|
||||
> 前端连接配置在项目根 `.env.local`(已 gitignore):`VITE_API_BASE`、`VITE_MQTT_URL`、`VITE_MQTT_USERNAME=dpm`、`VITE_MQTT_PASSWORD=123456`。
|
||||
|
||||
## MQTT 主题
|
||||
|
||||
| 主题 | 方向 | 说明 |
|
||||
|---|---|---|
|
||||
| `opc/display/command` | 后端→前端 | `{cmd_id, ts, action, params}`,action: `navigate` / `navigate_rel` / `play` / `pause` / `next` / `prev` / `alert` / `show_card` / `set_mode` |
|
||||
| `opc/dashboard/tick` | 后端→前端 | 数据快照(`{ts, snapshot}`) |
|
||||
| `opc/display/ack` | 前端→后端 | 指令回执(预留) |
|
||||
|
||||
## 控制示例
|
||||
|
||||
```bash
|
||||
# 切换页面(数据大屏 / 数字孪生 / AI 助手 / 媒体轮播)
|
||||
curl -X POST http://localhost:10085/api/display/command \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"action":"navigate","params":{"page":"/twin"}}'
|
||||
|
||||
# 媒体控制
|
||||
curl -X POST http://localhost:10085/api/display/command \
|
||||
-d '{"action":"next","params":{}}'
|
||||
|
||||
# 大屏通知
|
||||
curl -X POST http://localhost:10085/api/display/command \
|
||||
-d '{"action":"alert","params":{"title":"园区通知","content":"..."}}'
|
||||
```
|
||||
|
||||
## 关键接口
|
||||
|
||||
- `GET /api/dashboard/snapshot` — 大屏全量数据快照
|
||||
- `GET /api/park/companies` · `GET /api/park/zones` — 企业分布
|
||||
- `POST /api/ai/chat` — AI 对话(`{messages:[{role,content}]}` → `{reply, tools, mqtt_published}`)
|
||||
- `POST /api/ai/asr` — 语音识别(multipart 上传录音 + `format` 参数)
|
||||
- `GET/POST /api/playlist` · `POST /upload` · `GET /media` — 媒体与播放列表
|
||||
- `POST /api/display/command` — 管理端控制指令(转 MQTT 广播)
|
||||
- `GET /api/events` — SSE 兼容通道(MQTT 不可用时的前端回退)
|
||||
|
||||
## 说明
|
||||
|
||||
- 大屏数据由后端模拟引擎产生(`sim_engine.py`,与原前端 `parkData.js` 逻辑一致);前端离线时也有本地兜底
|
||||
- 媒体文件存放于 `backend/media/`,`/file/...` 直接返回
|
||||
- 安全:`backend/.env` 与根 `.env.local` 已加入 `.gitignore`,含阿里云密钥与 MQTT 凭据,勿提交
|
||||
@@ -0,0 +1,174 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""AI 助手工具调用引擎
|
||||
后端收到对话 → 规则引擎判断意图 → 产出文字回复 + 工具调用列表
|
||||
工具执行时通过 MQTT 广播 opc/display/command,前端(所有大屏)同步响应
|
||||
工具:navigate(切页) / control(媒体控制) / alert(通知) / show_card(展示卡片)
|
||||
"""
|
||||
|
||||
from .mqtt import hub
|
||||
|
||||
# ---------------- 工具执行 ----------------
|
||||
|
||||
def execute_tool(tool):
|
||||
"""执行一个工具调用:发布 MQTT 控制指令,返回是否发布成功"""
|
||||
t = tool.get("type")
|
||||
params = tool.get("params") or {}
|
||||
if t == "navigate":
|
||||
return hub.publish_command("navigate", {"page": params.get("page", "home")}).get("published", False)
|
||||
if t == "control":
|
||||
action = params.get("action")
|
||||
if action in ("play", "pause", "next", "prev"):
|
||||
return hub.publish_command(action).get("published", False)
|
||||
if action == "set_mode":
|
||||
return hub.publish_command("set_mode", {"mode": params.get("mode", "auto")}).get("published", False)
|
||||
return False
|
||||
if t == "alert":
|
||||
return hub.publish_command("alert", {"title": params.get("title", "提示"), "content": params.get("content", "")}).get("published", False)
|
||||
if t == "show_card":
|
||||
return hub.publish_command("show_card", params).get("published", False)
|
||||
return False
|
||||
|
||||
|
||||
def run_tools(tools):
|
||||
"""执行一组工具,返回 [{type, ok}] 并汇总是否全部发布成功"""
|
||||
results = []
|
||||
all_ok = True
|
||||
for tool in tools or []:
|
||||
ok = bool(execute_tool(tool))
|
||||
results.append({"type": tool.get("type"), "ok": ok})
|
||||
all_ok = all_ok and ok
|
||||
return results, all_ok
|
||||
|
||||
|
||||
# ---------------- 意图 → 回复 + 工具 ----------------
|
||||
|
||||
PAGES = {
|
||||
"home": "/", "数据大屏": "/", "首页": "/",
|
||||
"twin": "/twin", "数字孪生": "/twin", "3d": "/twin", "孪生": "/twin",
|
||||
"ai": "/ai", "ai助手": "/ai", "智能助手": "/ai",
|
||||
"screen": "/screen", "媒体": "/screen", "轮播": "/screen", "媒体轮播": "/screen",
|
||||
}
|
||||
|
||||
RULES = [
|
||||
{
|
||||
"match": ["入驻", "申请", "入孵", "入园", "如何加入", "怎样加入"],
|
||||
"reply": "入驻流程如下:\n\n1. 提交入驻申请(线上表单或到园区服务中心现场办理)\n2. 项目评审(园区组织专家对项目可行性、团队背景进行评估)\n3. 签订入驻协议,办理入驻手续\n4. 安排办公场地,对接创业导师与孵化服务\n\n目前园区在园项目 158 家,累计孵化企业 208 余家。如需协助,可直接联系园区服务中心。",
|
||||
"tools": [{"type": "show_card", "params": {"card": "overview"}}],
|
||||
},
|
||||
{
|
||||
"match": ["政策", "补贴", "优惠", "扶持", "减免", "奖励", "资助"],
|
||||
"reply": "园区政策服务覆盖「七位一体」中的政策维度:\n\n· 场地租金减免:符合条件的大学生创业项目可享受场地优惠\n· 创业补贴:协助对接省、市两级创业补贴与一次性创业资助\n· 税费优惠:对接小微企业所得税减免、增值税优惠等政策\n· 项目申报:指导高企认定(目前在园高新技术企业 32 家)、科技型中小企业备案\n\n更多政策细节可咨询园区政策服务窗口。",
|
||||
"tools": [],
|
||||
},
|
||||
{
|
||||
"match": ["场地", "办公室", "工位", "租金", "收费", "面积", "多少钱", "价格"],
|
||||
"reply": "园区总建筑面积 1.2 万㎡,按功能划分为三个区域:\n\n· 加速区(A1–A12):成长型项目办公室,共 11 间独立办公室 + 1 间园区管理办公室\n· 国际区(I1–I7):面向国际合作的办公空间(7 间)\n· 成长区(G1–G8):初创团队成长空间(8 间)\n\n租金按创业项目阶段与政策给予差异化优惠,具体可到园区服务中心咨询。",
|
||||
"tools": [{"type": "show_card", "params": {"card": "zones"}}],
|
||||
},
|
||||
{
|
||||
"match": ["企业", "公司", "名录", "名单", "入驻企业", "有哪些企业", "查企业"],
|
||||
"reply": "园区目前有 39 家入驻企业,分布在三个区域:\n\n· 加速区:云南派音人工智能科技、米勒克尔蓝宝石珠宝、云南宸中低空经济等 9 家\n· 国际区:仰光客厅、中越生物医疗、酷享野农AI农业等 7 家\n· 成长区:Facebook越南跨境电商、昆明云韵体育、云南星瑞航空等 8 家\n\n我已在屏幕上为您展示企业分布卡片。",
|
||||
"tools": [{"type": "show_card", "params": {"card": "companies"}}],
|
||||
},
|
||||
{
|
||||
"match": ["融资", "投资", "资金", "贷款", "估值", "资本"],
|
||||
"reply": "园区融资服务包含:\n\n· 融资对接:定期组织路演与投资机构对接活动\n· 孵化估值:累计孵化企业估值约 5.2 亿元\n· 金融支持:协助对接银行创业贷款、政府创业担保贷款\n· 财务辅导:提供融资方案设计与商业计划书辅导",
|
||||
"tools": [],
|
||||
},
|
||||
{
|
||||
"match": ["就业", "招聘", "岗位", "人才", "用工"],
|
||||
"reply": "园区累计带动就业 2,186 人(其中高校毕业生 1,680 人,实习岗位 506 个)。\n\n· 企业招聘:在驻企业持续发布岗位\n· 人才对接:与在昆高校共建实习实训基地\n· 创业带动:园区鼓励以创业带动就业",
|
||||
"tools": [],
|
||||
},
|
||||
{
|
||||
"match": ["导师", "培训", "课程", "辅导", "孵化服务", "孵化器"],
|
||||
"reply": "园区配备创业导师 58 位,提供全周期孵化服务:\n\n· 一对一辅导:创业导师定期坐诊,覆盖商业模式、法律财税、市场营销\n· 培训课程:创业训练营、行业专题沙龙、政策解读会\n· 资源对接:技术、供应链、渠道等产业资源链接\n· 公共服务:工商注册、知识产权、法律咨询一站式代办",
|
||||
"tools": [],
|
||||
},
|
||||
{
|
||||
"match": ["AI", "模型", "算力", "tokens", "token", "工具", "智能", "赋能", "大模型"],
|
||||
"reply": "园区 AI 赋能服务是「七位一体」的核心特色:\n\n· 大模型推理:接入 DeepSeek-V3、通义千问、智谱 GLM-4、豆包、讯飞星火等多模型\n· 工具调用:文档生成、数据查询、图像创作、代码执行、语音合成等智能工具\n· 算力与成本:实时提供 token 消耗统计与速率监控\n\n今日园区 AI 推理任务持续运行,服务节点 12/12 在线。",
|
||||
"tools": [],
|
||||
},
|
||||
{
|
||||
"match": ["加速区", "国际区", "成长区", "分区", "区域", "区别", "布局"],
|
||||
"reply": "园区按企业成长阶段划分三个区域:\n\n· 加速区(蓝色):成长型项目独立办公室(A1–A12),在驻 9 家\n· 国际区(青色):国际合作与跨境业务(I1–I7),在驻 7 家\n· 成长区(绿色):初创团队共享空间(G1–G8),在驻 8 家\n\n园区管理办公室位于加速区 M 房间。",
|
||||
"tools": [{"type": "show_card", "params": {"card": "zones"}}],
|
||||
},
|
||||
{
|
||||
"match": ["切", "去", "打开", "显示", "进入"],
|
||||
"reply": None, # 由下方页面/控制规则补充
|
||||
},
|
||||
{
|
||||
"match": ["你好", "您好", "hi", "hello", "嗨", "在吗"],
|
||||
"reply": "您好!我是 OPC 园区 AI 智能助手。\n\n可以问我:入驻流程、政策扶持、场地收费、AI 赋能、企业名录、融资服务等问题;也可以让我「切换到数字孪生页」「打开媒体轮播」或「展示企业卡片」。",
|
||||
"tools": [],
|
||||
},
|
||||
{
|
||||
"match": ["谢谢", "感谢", "好的", "明白"],
|
||||
"reply": "不客气,随时为您服务。您也可以让我展示园区数据卡片,或切换到任意展示页面。",
|
||||
"tools": [],
|
||||
},
|
||||
]
|
||||
|
||||
DEFAULT_REPLY = "园区提供「空间 + 孵化 + 融资 + 政策 + 资源 + AI 赋能 + 综合服务」七位一体服务,在园项目 158 家,累计带动就业 2,186 人,累计营收 2.08 亿元。\n\n您可以这样问我:\n· 如何申请入驻园区?\n· 展示入驻企业卡片\n· 切换到数字孪生页面"
|
||||
|
||||
|
||||
def _detect_page(text):
|
||||
for key, path in PAGES.items():
|
||||
if key in text:
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def _detect_control(text):
|
||||
if any(k in text for k in ["播放", "继续播放", "开始播放"]):
|
||||
return "play"
|
||||
if any(k in text for k in ["暂停", "停止播放"]):
|
||||
return "pause"
|
||||
if any(k in text for k in ["下一个", "下一张", "下一屏", "切换下一"]):
|
||||
return "next"
|
||||
if any(k in text for k in ["上一个", "上一张", "上一屏", "切换上一"]):
|
||||
return "prev"
|
||||
return None
|
||||
|
||||
|
||||
def run_chat(messages):
|
||||
"""输入对话消息列表([{"role","content"}]),返回 {reply, tools}"""
|
||||
text = ""
|
||||
for m in reversed(messages or []):
|
||||
if m.get("role") in ("user", "me"):
|
||||
text = (m.get("content") or "").strip()
|
||||
break
|
||||
|
||||
reply = None
|
||||
tools = []
|
||||
|
||||
# 1) 页面切换意图(优先级最高)
|
||||
page = _detect_page(text)
|
||||
if page:
|
||||
label = {"/": "数据大屏", "/twin": "数字孪生", "/ai": "AI 助手", "/screen": "媒体轮播"}.get(page, page)
|
||||
reply = f"好的,已为您切换到「{label}」页面。"
|
||||
tools = [{"type": "navigate", "params": {"page": page}}]
|
||||
return {"reply": reply, "tools": tools}
|
||||
|
||||
# 2) 媒体控制意图
|
||||
ctrl = _detect_control(text)
|
||||
if ctrl:
|
||||
names = {"play": "播放", "pause": "暂停", "next": "切换下一项", "prev": "切换上一项"}
|
||||
reply = f"好的,已执行:{names[ctrl]}。"
|
||||
tools = [{"type": "control", "params": {"action": ctrl}}]
|
||||
return {"reply": reply, "tools": tools}
|
||||
|
||||
# 3) 知识库规则
|
||||
for rule in RULES:
|
||||
if any(k in text for k in rule["match"]):
|
||||
reply = rule["reply"]
|
||||
tools = rule["tools"]
|
||||
break
|
||||
|
||||
if reply is None:
|
||||
reply = DEFAULT_REPLY
|
||||
|
||||
# 工具已在后端通过 MQTT 广播;返回值供请求端本地同步执行
|
||||
return {"reply": reply, "tools": tools}
|
||||
@@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""语音识别 —— 阿里云 DashScope paraformer-realtime-v2(流式)
|
||||
前端录音上传(m4a/wav/ogg/opus/pcm)→ 这里转写为文本
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
import certifi
|
||||
|
||||
# macOS 系统 Python 无系统 CA:必须在导入 dashscope 之前设置,
|
||||
# aiohttp 才会使用 certifi 根证书(否则 WS 连接报 SSL 证书错误)
|
||||
os.environ.setdefault("SSL_CERT_FILE", certifi.where())
|
||||
|
||||
import dashscope # noqa: E402
|
||||
from dashscope.audio.asr import Recognition, RecognitionCallback # noqa: E402
|
||||
|
||||
from .config import settings # noqa: E402
|
||||
|
||||
log = logging.getLogger("dpm.asr")
|
||||
|
||||
|
||||
def _err_text(result):
|
||||
"""安全提取 RecognitionResult 的错误描述(其 __str__ 有缺陷)"""
|
||||
for attr in ("message", "code"):
|
||||
v = getattr(result, attr, None)
|
||||
if v:
|
||||
return f"{attr}={v}"
|
||||
return repr(result)
|
||||
|
||||
# 前端 MediaRecorder 可能产生的容器/编码 → paraformer 格式名
|
||||
FORMAT_ALIASES = {
|
||||
"mp4": "m4a", "m4a": "m4a", "aac": "m4a",
|
||||
"webm": "ogg", "opus": "opus", "ogg": "ogg",
|
||||
"wav": "wav", "pcm": "pcm", "mp3": "mp3",
|
||||
}
|
||||
|
||||
# m4a/mp3 等压缩格式采样率由服务端自动识别
|
||||
_AUTO_SAMPLE_FORMATS = {"m4a", "mp3", "ogg", "opus", "wav"}
|
||||
|
||||
|
||||
class _Callback(RecognitionCallback):
|
||||
def __init__(self):
|
||||
self.sentences = []
|
||||
self.error = None
|
||||
|
||||
def on_message(self, message):
|
||||
try:
|
||||
header = message.get("header", {})
|
||||
if header.get("action") != "result":
|
||||
return
|
||||
payload = message.get("payload", {})
|
||||
sentences = payload.get("sentence", {}).get("sentences") or []
|
||||
for s in sentences:
|
||||
if s.get("sentence_end"):
|
||||
self.sentences.append(s.get("text", ""))
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("ASR 消息解析异常: %s", e)
|
||||
|
||||
def on_error(self, result):
|
||||
self.error = result
|
||||
log.warning("ASR 错误: %s", _err_text(result))
|
||||
|
||||
|
||||
def transcribe(audio_bytes: bytes, fmt: str = "m4a") -> str:
|
||||
"""上传音频字节 → 返回转写文本(空串表示未识别到内容)"""
|
||||
if not settings.DASHSCOPE_API_KEY:
|
||||
raise RuntimeError("未配置 DASHSCOPE_API_KEY,无法使用阿里云语音识别")
|
||||
|
||||
dashscope.api_key = settings.DASHSCOPE_API_KEY
|
||||
fmt = FORMAT_ALIASES.get((fmt or "m4a").lower().lstrip("."), "m4a")
|
||||
|
||||
cb = _Callback()
|
||||
rec = Recognition(
|
||||
model=settings.ASR_MODEL,
|
||||
format=fmt,
|
||||
sample_rate=16000 if fmt in ("pcm", "wav") else 0, # 压缩格式自动识别
|
||||
callback=cb,
|
||||
)
|
||||
|
||||
# 分块发送,模拟流式(适当间隔,避免触发服务端 batching 报错)
|
||||
rec.start()
|
||||
chunk = 16 * 1024
|
||||
for i in range(0, len(audio_bytes), chunk):
|
||||
rec.send_audio_frame(audio_bytes[i:i + chunk])
|
||||
time.sleep(0.08)
|
||||
time.sleep(0.5) # 等待尾部识别
|
||||
rec.stop()
|
||||
|
||||
if cb.error:
|
||||
raise RuntimeError(f"阿里云语音识别失败: {_err_text(cb.error)}")
|
||||
return "".join(cb.sentences).strip()
|
||||
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""OPC 智能园区后端 —— 全局配置(backend/.env 可覆盖,未配置时用默认值)"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent # backend/
|
||||
|
||||
|
||||
def _load_dotenv(path):
|
||||
"""极简 .env 解析:KEY=VALUE,支持 # 注释与引号"""
|
||||
if not Path(path).exists():
|
||||
return
|
||||
for raw in Path(path).read_text("utf-8").splitlines():
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, _, value = line.partition("=")
|
||||
key = key.strip()
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if key and key not in os.environ:
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
_load_dotenv(BASE_DIR / ".env")
|
||||
|
||||
|
||||
def _env(key, default):
|
||||
return os.getenv(key, default)
|
||||
|
||||
|
||||
class Settings:
|
||||
# ---- HTTP ----
|
||||
HOST = _env("DPM_HOST", _env("FASTAPI_HOST", "0.0.0.0"))
|
||||
PORT = int(_env("DPM_PORT", _env("FASTAPI_PORT", "8000")))
|
||||
|
||||
# ---- 数据目录 ----
|
||||
MEDIA_DIR = Path(_env("DPM_MEDIA_DIR", str(BASE_DIR / "media")))
|
||||
DATA_FILE = Path(_env("DPM_DATA_FILE", str(BASE_DIR / "data.json")))
|
||||
DIST_DIR = Path(_env("DPM_DIST_DIR", str(BASE_DIR.parent / "dist"))) # 前端构建产物(可选托管)
|
||||
|
||||
# ---- MQTT(优先使用 .env 中的局域网 Broker 配置) ----
|
||||
MQTT_ENABLED = _env("DPM_MQTT_ENABLED", "1") == "1"
|
||||
MQTT_HOST = _env("DPM_MQTT_HOST", _env("MQTT_BROKER_HOST", "127.0.0.1"))
|
||||
MQTT_PORT = int(_env("DPM_MQTT_PORT", _env("MQTT_BROKER_PORT", "1883")))
|
||||
MQTT_USERNAME = _env("DPM_MQTT_USERNAME", _env("MQTT_USERNAME", "")) or None
|
||||
MQTT_PASSWORD = _env("DPM_MQTT_PASSWORD", _env("MQTT_PASSWORD", "")) or None
|
||||
MQTT_CLIENT_ID = _env("DPM_MQTT_CLIENT_ID", "dpm-backend")
|
||||
# 前端 WebSocket 接入地址(默认按 .env 的 broker 主机 + EMQX 常规 ws 端口)
|
||||
MQTT_WS_URL = _env(
|
||||
"DPM_MQTT_WS",
|
||||
f"ws://{_env('MQTT_PUBLIC_HOST', _env('MQTT_BROKER_HOST', 'localhost'))}:8083/mqtt",
|
||||
)
|
||||
MQTT_TICK_INTERVAL = float(_env("DPM_MQTT_TICK_INTERVAL", "2.2"))
|
||||
|
||||
# ---- 主题 ----
|
||||
TOPIC_COMMAND = _env("DPM_TOPIC_COMMAND", "opc/display/command")
|
||||
TOPIC_TICK = _env("DPM_TOPIC_TICK", "opc/dashboard/tick")
|
||||
TOPIC_ACK = _env("DPM_TOPIC_ACK", "opc/display/ack")
|
||||
|
||||
# ---- 阿里云 DashScope(LLM + 语音识别) ----
|
||||
DASHSCOPE_API_KEY = _env("DASHSCOPE_API_KEY", "")
|
||||
LLM_MODEL = _env("DPM_LLM_MODEL", "qwen-plus") # 通义千问
|
||||
ASR_MODEL = _env("DPM_ASR_MODEL", "paraformer-realtime-v2") # 流式语音识别
|
||||
LLM_BASE_URL = _env("DPM_LLM_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
|
||||
ASR_TIMEOUT = float(_env("DPM_ASR_TIMEOUT", "30"))
|
||||
|
||||
|
||||
settings = Settings()
|
||||
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""SSE 事件总线 —— 兼容旧通道(前端 MQTT 不可用时回退)
|
||||
所有 MQTT 控制指令同时推送到 SSE /api/events
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
|
||||
class EventBus:
|
||||
def __init__(self):
|
||||
self._queues = set()
|
||||
self._loop = None
|
||||
|
||||
def set_loop(self, loop):
|
||||
self._loop = loop
|
||||
|
||||
def subscribe(self):
|
||||
q = asyncio.Queue(maxsize=256)
|
||||
self._queues.add(q)
|
||||
return q
|
||||
|
||||
def unsubscribe(self, q):
|
||||
self._queues.discard(q)
|
||||
|
||||
def emit(self, payload: dict):
|
||||
"""线程安全:从任意线程广播事件"""
|
||||
if not self._loop:
|
||||
return
|
||||
try:
|
||||
asyncio.run_coroutine_threadsafe(self._emit(payload), self._loop)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
|
||||
async def _emit(self, payload):
|
||||
data = json.dumps(payload, ensure_ascii=False)
|
||||
for q in list(self._queues):
|
||||
try:
|
||||
q.put_nowait(data)
|
||||
except asyncio.QueueFull:
|
||||
pass
|
||||
|
||||
|
||||
bus = EventBus()
|
||||
@@ -0,0 +1,197 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""AI 对话引擎 —— 阿里云 DashScope(通义千问)+ 函数调用
|
||||
工具调用执行后通过 MQTT 广播,所有大屏同步响应
|
||||
DashScope 不可用时自动回退本地规则引擎(离线兜底)
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import ssl
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import certifi
|
||||
|
||||
from .config import settings
|
||||
|
||||
log = logging.getLogger("dpm.llm")
|
||||
|
||||
# macOS 系统 Python 无系统 CA,使用 certifi 提供的根证书
|
||||
_SSL_CTX = ssl.create_default_context(cafile=certifi.where())
|
||||
|
||||
SYSTEM_PROMPT = (
|
||||
"你是「昆明市大学生创业园 · OPC 智能园区」的 AI 智能助手,运行在大屏展播系统上。"
|
||||
"园区提供空间、孵化、融资、政策、资源、AI 赋能、综合服务七位一体服务,"
|
||||
"在园项目 158 家,累计带动就业 2,186 人,累计营收 2.08 亿元。"
|
||||
"当用户要求「切换页面 / 控制播放 / 展示卡片 / 弹出通知」时,必须调用对应工具;"
|
||||
"其余园区相关问题用简洁、专业的中文回答,可适当使用列表。"
|
||||
)
|
||||
|
||||
# 暴露给大模型的工具定义(执行时经 MQTT 广播到前端)
|
||||
TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "navigate_page",
|
||||
"description": "切换大屏展示页面(数据大屏 / 数字孪生 / AI 助手 / 媒体轮播)",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {"type": "string", "enum": ["/", "/twin", "/ai", "/screen"],
|
||||
"description": "目标页面路径"},
|
||||
},
|
||||
"required": ["page"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "media_control",
|
||||
"description": "控制媒体播放(播放/暂停/下一项/上一项)",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {"type": "string", "enum": ["play", "pause", "next", "prev"]},
|
||||
},
|
||||
"required": ["action"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "show_card",
|
||||
"description": "在大屏上展示信息卡片(企业分布 / 分区介绍 / 园区总览 / 自定义内容)",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"card": {"type": "string", "enum": ["companies", "zones", "overview", "custom"]},
|
||||
"title": {"type": "string"},
|
||||
"content": {"type": "string"},
|
||||
},
|
||||
"required": ["card"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "send_alert",
|
||||
"description": "在大屏上弹出通知提示",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"content": {"type": "string"},
|
||||
},
|
||||
"required": ["title", "content"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
_TOOL_MAP = {
|
||||
"navigate_page": "navigate",
|
||||
"media_control": "control",
|
||||
"show_card": "show_card",
|
||||
"send_alert": "alert",
|
||||
}
|
||||
|
||||
|
||||
def _chat_once(messages, with_tools=True):
|
||||
payload = {
|
||||
"model": settings.LLM_MODEL,
|
||||
"messages": messages,
|
||||
"temperature": 0.6,
|
||||
}
|
||||
if with_tools:
|
||||
payload["tools"] = TOOLS
|
||||
req = urllib.request.Request(
|
||||
f"{settings.LLM_BASE_URL}/chat/completions",
|
||||
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {settings.DASHSCOPE_API_KEY}",
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=60, context=_SSL_CTX) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
|
||||
def _fallback(messages):
|
||||
"""DashScope 不可用时回退本地规则引擎"""
|
||||
from .ai_tools import run_chat as rule_run
|
||||
|
||||
result = rule_run(messages)
|
||||
# 规则引擎产出的工具同样执行(MQTT 广播),并汇总发布结果
|
||||
_, all_ok = _exec_all(result.get("tools", []))
|
||||
result["model"] = "rule-engine"
|
||||
result["mqtt_published"] = all_ok
|
||||
return result
|
||||
|
||||
|
||||
def _exec_all(tools):
|
||||
"""执行工具列表,返回 (results, all_ok)"""
|
||||
from .ai_tools import run_tools
|
||||
|
||||
results, all_ok = run_tools(tools)
|
||||
return results, all_ok
|
||||
|
||||
|
||||
def run_chat(messages):
|
||||
"""入口:{reply, tools, model}
|
||||
tools 已在后端执行(MQTT 广播),返回值供请求端本地同步执行"""
|
||||
if not settings.DASHSCOPE_API_KEY:
|
||||
log.warning("未配置 DASHSCOPE_API_KEY,使用本地规则引擎")
|
||||
return _fallback(messages)
|
||||
|
||||
msgs = [{"role": "system", "content": SYSTEM_PROMPT}] + [
|
||||
{"role": m.get("role") == "me" and "user" or m.get("role", "user"), "content": m.get("content", "")}
|
||||
for m in (messages or [])
|
||||
]
|
||||
|
||||
try:
|
||||
data = _chat_once(msgs)
|
||||
choice = data["choices"][0]["message"]
|
||||
reply = choice.get("content") or ""
|
||||
tool_calls = choice.get("tool_calls") or []
|
||||
executed = []
|
||||
|
||||
if tool_calls:
|
||||
for tc in tool_calls:
|
||||
fn = tc.get("function", {})
|
||||
name = fn.get("name", "")
|
||||
try:
|
||||
args = json.loads(fn.get("arguments") or "{}")
|
||||
except json.JSONDecodeError:
|
||||
args = {}
|
||||
tool = {"type": _TOOL_MAP.get(name, name), "params": args}
|
||||
executed.append(tool)
|
||||
msgs.append({"role": "assistant", "content": None, "tool_calls": tool_calls})
|
||||
msgs.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.get("id", ""),
|
||||
"content": json.dumps({"ok": True}, ensure_ascii=False),
|
||||
})
|
||||
# 工具执行(MQTT 广播),并汇总发布结果
|
||||
_, all_ok = _exec_all(executed)
|
||||
# 二次调用:携带工具结果生成最终回复
|
||||
try:
|
||||
data2 = _chat_once(msgs, with_tools=False)
|
||||
reply = data2["choices"][0]["message"].get("content") or reply
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("LLM 二次调用失败(保留工具回复): %s", e)
|
||||
else:
|
||||
all_ok = True
|
||||
|
||||
if not reply:
|
||||
reply = "已完成操作。您还可以让我切换页面、控制播放或展示园区卡片。"
|
||||
|
||||
return {"reply": reply, "tools": executed, "model": settings.LLM_MODEL, "mqtt_published": all_ok}
|
||||
except urllib.error.HTTPError as e:
|
||||
log.warning("DashScope HTTP %s: %s", e.code, e.read()[:300])
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("DashScope 调用失败,回退规则引擎: %s", e)
|
||||
|
||||
return _fallback(messages)
|
||||
@@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""OPC 智能园区后端 —— FastAPI 入口
|
||||
启动:uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
或:python main.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .config import settings
|
||||
from .event_bus import bus
|
||||
from .mqtt import hub
|
||||
from .routers import router
|
||||
from .sim_engine import sim_engine
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s")
|
||||
log = logging.getLogger("dpm.main")
|
||||
|
||||
DIST = Path(settings.DIST_DIR)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# 启动准备
|
||||
settings.MEDIA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
bus.set_loop(asyncio.get_running_loop())
|
||||
hub.start()
|
||||
|
||||
stop = threading.Event()
|
||||
|
||||
def tick_loop():
|
||||
"""数据引擎每 2.2s 推进一次;MQTT 连接时同步推送快照"""
|
||||
while not stop.is_set():
|
||||
try:
|
||||
snap = sim_engine.tick()
|
||||
hub.publish_tick(snap)
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("数据 tick 异常: %s", e)
|
||||
stop.wait(settings.MQTT_TICK_INTERVAL)
|
||||
|
||||
threading.Thread(target=tick_loop, daemon=True).start()
|
||||
log.info("OPC 智能园区后端启动于 http://%s:%s", settings.HOST, settings.PORT)
|
||||
if settings.MQTT_ENABLED:
|
||||
log.info("MQTT Broker: %s:%s(前端 WS: %s)", settings.MQTT_HOST, settings.MQTT_PORT, settings.MQTT_WS_URL)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
stop.set()
|
||||
hub.stop()
|
||||
log.info("OPC 智能园区后端已停止")
|
||||
|
||||
|
||||
app = FastAPI(title="OPC 智能园区后端", version="1.0.0", lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
# 媒体资源静态服务(上传/播放的文件)
|
||||
settings.MEDIA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
app.mount("/file", StaticFiles(directory=str(settings.MEDIA_DIR)), name="media")
|
||||
|
||||
|
||||
# ---------- 前端静态托管 + SPA 回退(浏览器直接访问 :8000 即可) ----------
|
||||
|
||||
@app.get("/{path:path}", include_in_schema=False)
|
||||
async def spa_fallback(path: str):
|
||||
if not DIST.exists():
|
||||
return JSONResponse({"detail": "前端未构建(dist 目录不存在)"}, status_code=404)
|
||||
dist = DIST.resolve()
|
||||
if not path:
|
||||
target = dist / "index.html"
|
||||
else:
|
||||
target = (dist / path).resolve()
|
||||
if target.is_file() and target.is_relative_to(dist):
|
||||
return FileResponse(target)
|
||||
index = dist / "index.html"
|
||||
if index.exists():
|
||||
return FileResponse(index)
|
||||
return JSONResponse({"detail": "Not Found"}, status_code=404)
|
||||
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""MQTT 发布中心 —— 后端 → 前端控制通道
|
||||
页面控制 / 媒体控制 / 卡片展示指令统一走 opc/display/command
|
||||
数据快照按 DPM_MQTT_TICK_INTERVAL 推送 opc/dashboard/tick
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
from .config import settings
|
||||
from .event_bus import bus
|
||||
|
||||
log = logging.getLogger("dpm.mqtt")
|
||||
|
||||
|
||||
def new_cmd_id():
|
||||
return uuid.uuid4().hex[:12]
|
||||
|
||||
|
||||
class MqttHub:
|
||||
def __init__(self):
|
||||
self.client = None
|
||||
self.connected = False
|
||||
self._lock = threading.RLock()
|
||||
|
||||
# ---------- 生命周期 ----------
|
||||
def start(self):
|
||||
if not settings.MQTT_ENABLED:
|
||||
log.info("MQTT 已禁用(DPM_MQTT_ENABLED=0)")
|
||||
return
|
||||
try:
|
||||
self.client = mqtt.Client(
|
||||
client_id=settings.MQTT_CLIENT_ID,
|
||||
clean_session=True,
|
||||
protocol=mqtt.MQTTv311,
|
||||
)
|
||||
if settings.MQTT_USERNAME:
|
||||
self.client.username_pw_set(settings.MQTT_USERNAME, settings.MQTT_PASSWORD)
|
||||
self.client.on_connect = self._on_connect
|
||||
self.client.on_disconnect = self._on_disconnect
|
||||
self.client.connect_async(settings.MQTT_HOST, settings.MQTT_PORT, keepalive=30)
|
||||
self.client.loop_start()
|
||||
log.info("MQTT 连接中 %s:%s ...", settings.MQTT_HOST, settings.MQTT_PORT)
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("MQTT 初始化失败(数据 API 不受影响): %s", e)
|
||||
|
||||
def stop(self):
|
||||
if self.client:
|
||||
try:
|
||||
self.client.loop_stop()
|
||||
self.client.disconnect()
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
self.client = None
|
||||
|
||||
def _on_connect(self, client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
self.connected = True
|
||||
log.info("MQTT 已连接 %s:%s", settings.MQTT_HOST, settings.MQTT_PORT)
|
||||
else:
|
||||
log.warning("MQTT 连接失败 rc=%s", rc)
|
||||
|
||||
def _on_disconnect(self, client, userdata, rc):
|
||||
self.connected = False
|
||||
if rc != 0:
|
||||
log.warning("MQTT 断开(rc=%s),自动重连中...", rc)
|
||||
|
||||
# ---------- 发布 ----------
|
||||
def publish(self, topic, payload, qos=1, retain=False):
|
||||
if not self.client or not self.connected:
|
||||
log.debug("MQTT 未连接,丢弃发布 %s", topic)
|
||||
return False
|
||||
try:
|
||||
info = self.client.publish(topic, json.dumps(payload, ensure_ascii=False), qos=qos, retain=retain)
|
||||
return info.rc == mqtt.MQTT_ERR_SUCCESS
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("MQTT 发布失败: %s", e)
|
||||
return False
|
||||
|
||||
def publish_command(self, action, params=None):
|
||||
"""统一指令信封:{cmd_id, ts, action, params, published}
|
||||
MQTT 广播 + SSE 兼容推送;published 标记是否真正发布成功"""
|
||||
payload = {
|
||||
"cmd_id": new_cmd_id(),
|
||||
"ts": int(time.time() * 1000),
|
||||
"action": action,
|
||||
"params": params or {},
|
||||
}
|
||||
ok = self.publish(settings.TOPIC_COMMAND, payload)
|
||||
payload["published"] = ok
|
||||
if ok:
|
||||
bus.emit(payload) # SSE 兼容通道(仅发布成功时推送)
|
||||
return payload
|
||||
|
||||
def publish_tick(self, snapshot):
|
||||
payload = {"ts": int(time.time() * 1000), "snapshot": snapshot}
|
||||
self.publish(settings.TOPIC_TICK, payload, qos=0)
|
||||
|
||||
|
||||
hub = MqttHub()
|
||||
@@ -0,0 +1,383 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""全部 REST 路由 —— 数据 / 媒体 / 播放列表 / 设置 / AI / 展示控制"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Request, UploadFile, File
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .ai_tools import run_chat, run_tools
|
||||
from .asr import transcribe as asr_transcribe
|
||||
from .config import settings
|
||||
from .event_bus import bus
|
||||
from .llm import run_chat as llm_run_chat
|
||||
from .mqtt import hub
|
||||
from .sim_engine import sim_engine
|
||||
from .storage import storage
|
||||
|
||||
log = logging.getLogger("dpm.api")
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ALLOWED_EXT = {".mp4", ".mkv", ".avi", ".jpg", ".jpeg", ".png"}
|
||||
|
||||
|
||||
def _media_type(path):
|
||||
lower = path.lower()
|
||||
if lower.endswith((".mp4", ".mkv", ".avi")):
|
||||
return "video"
|
||||
return "image"
|
||||
|
||||
|
||||
# ==================== 数据模型 ====================
|
||||
|
||||
class SettingsBody(BaseModel):
|
||||
volume: int | None = None
|
||||
play_mode: str | None = None
|
||||
image_duration: int | None = None
|
||||
fullscreen: bool | None = None
|
||||
autostart: bool | None = None
|
||||
username: str | None = None
|
||||
password: str | None = None
|
||||
|
||||
|
||||
class ActionBody(BaseModel):
|
||||
action: str
|
||||
|
||||
|
||||
class PathBody(BaseModel):
|
||||
path: str
|
||||
|
||||
|
||||
class UrlBody(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
class StatePayload(BaseModel):
|
||||
status: str = ""
|
||||
index: int = 0
|
||||
name: str = ""
|
||||
media_type: str = ""
|
||||
|
||||
|
||||
class StateBody(BaseModel):
|
||||
state: StatePayload
|
||||
|
||||
|
||||
class DisplayCommandBody(BaseModel):
|
||||
action: str
|
||||
params: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
role: str
|
||||
content: str
|
||||
|
||||
|
||||
class ChatBody(BaseModel):
|
||||
messages: list[ChatMessage]
|
||||
|
||||
|
||||
# ==================== 认证 / 设置 ====================
|
||||
|
||||
@router.post("/api/login")
|
||||
async def login(request: Request):
|
||||
form = await request.form()
|
||||
s = storage.get_settings()
|
||||
return JSONResponse({"success": form.get("username") == s["username"] and form.get("password") == s["password"]})
|
||||
|
||||
|
||||
@router.get("/api/settings")
|
||||
async def get_settings():
|
||||
s = storage.get_settings()
|
||||
return {k: s[k] for k in ("volume", "play_mode", "image_duration", "fullscreen", "autostart")}
|
||||
|
||||
|
||||
@router.post("/api/settings")
|
||||
async def update_settings(body: SettingsBody):
|
||||
storage.update_settings(**body.model_dump(exclude_none=True))
|
||||
s = storage.get_settings()
|
||||
hub.publish_command("settings_changed", {"volume": s["volume"], "play_mode": s["play_mode"]})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ==================== 媒体 ====================
|
||||
|
||||
def _list_media():
|
||||
files = []
|
||||
media_dir = Path(settings.MEDIA_DIR)
|
||||
if media_dir.exists():
|
||||
for p in sorted(media_dir.iterdir()):
|
||||
if not p.is_file() or p.suffix.lower() not in ALLOWED_EXT:
|
||||
continue
|
||||
files.append({
|
||||
"name": p.name, "relative_path": p.name,
|
||||
"type": _media_type(p.name), "url": f"/file/{p.name}", "source": "local",
|
||||
})
|
||||
for item in storage.get_url_media():
|
||||
files.append({
|
||||
"name": item.get("name", item.get("url", "")),
|
||||
"relative_path": item.get("url", ""),
|
||||
"type": item.get("type", "image"),
|
||||
"url": item.get("url", ""),
|
||||
"source": "url",
|
||||
})
|
||||
return {"files": files}
|
||||
|
||||
|
||||
@router.get("/media")
|
||||
async def list_media():
|
||||
return _list_media()
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
async def upload(file: UploadFile = File(...)):
|
||||
name = file.filename or "file"
|
||||
ext = Path(name).suffix.lower()
|
||||
if ext not in ALLOWED_EXT:
|
||||
return JSONResponse({"ok": False, "error": "不支持的文件类型"}, status_code=400)
|
||||
settings.MEDIA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
stem = Path(name).stem
|
||||
new_name = f"{stem}_{time.strftime('%Y%m%d%H%M%S')}{ext}"
|
||||
save_path = settings.MEDIA_DIR / new_name
|
||||
with save_path.open("wb") as f:
|
||||
while True:
|
||||
chunk = await file.read(1024 * 1024)
|
||||
if not chunk:
|
||||
break
|
||||
f.write(chunk)
|
||||
hub.publish_command("playlist_changed")
|
||||
return {"ok": True, "name": new_name}
|
||||
|
||||
|
||||
@router.post("/api/delete")
|
||||
async def delete_media(body: PathBody):
|
||||
path = body.path
|
||||
if not path.startswith(("http://", "https://")):
|
||||
target = settings.MEDIA_DIR / path
|
||||
try:
|
||||
if target.exists() and target.is_file():
|
||||
target.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
storage.delete_media(path)
|
||||
hub.publish_command("playlist_changed")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/api/media/add-url")
|
||||
async def add_url(body: UrlBody):
|
||||
url = body.url.strip()
|
||||
if not url:
|
||||
return {"ok": False}
|
||||
name = url.rsplit("/", 1)[-1].split("?", 1)[0] or url
|
||||
t = _media_type(url)
|
||||
is_new = storage.add_url_media(url, name, t)
|
||||
return {"ok": True, "duplicate": None if is_new else True}
|
||||
|
||||
|
||||
# ==================== 播放列表 ====================
|
||||
|
||||
def _playlist_files():
|
||||
files = []
|
||||
media_dir = Path(settings.MEDIA_DIR)
|
||||
for item in storage.get_playlist():
|
||||
path = item.get("path", "")
|
||||
if item.get("source") == "url":
|
||||
files.append({
|
||||
"name": item.get("name") or path,
|
||||
"relative_path": path, "type": _media_type(path), "url": path, "source": "url",
|
||||
})
|
||||
else:
|
||||
fp = media_dir / path
|
||||
if fp.exists():
|
||||
files.append({
|
||||
"name": fp.name, "relative_path": path,
|
||||
"type": _media_type(path), "url": f"/file/{path}", "source": "local",
|
||||
})
|
||||
s = storage.get_settings()
|
||||
return {"files": files, "volume": s["volume"], "play_mode": s["play_mode"], "image_duration": s["image_duration"]}
|
||||
|
||||
|
||||
@router.get("/api/playlist")
|
||||
async def get_playlist():
|
||||
return _playlist_files()
|
||||
|
||||
|
||||
@router.post("/api/playlist/add")
|
||||
async def add_playlist(body: PathBody):
|
||||
storage.add_to_playlist(body.path)
|
||||
hub.publish_command("playlist_changed")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/api/playlist/remove")
|
||||
async def remove_playlist(body: PathBody):
|
||||
storage.remove_from_playlist(body.path)
|
||||
hub.publish_command("playlist_changed")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ==================== 播放控制 / 状态 ====================
|
||||
|
||||
_CONTROL_ACTIONS = {"play", "pause", "next", "prev"}
|
||||
|
||||
|
||||
@router.post("/api/control")
|
||||
async def control(body: ActionBody):
|
||||
if body.action not in _CONTROL_ACTIONS:
|
||||
return JSONResponse({"ok": False, "error": "action 必须是 play/pause/next/prev"}, status_code=400)
|
||||
hub.publish_command(body.action)
|
||||
return {"ok": True, "action": body.action}
|
||||
|
||||
|
||||
@router.get("/api/state")
|
||||
async def get_state():
|
||||
return {"state": {"status": "unknown", "index": 0, "name": "", "media_type": ""}}
|
||||
|
||||
|
||||
@router.post("/api/state")
|
||||
async def update_state(body: StateBody):
|
||||
hub.publish_command("state_update", body.state.model_dump())
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/api/display-command")
|
||||
async def display_command(body: ActionBody):
|
||||
if body.action == "minimize":
|
||||
hub.publish_command("minimize")
|
||||
return {"ok": True, "action": "minimize"}
|
||||
return JSONResponse({"ok": False, "error": "不支持的显示命令"}, status_code=400)
|
||||
|
||||
|
||||
# ==================== 数据(大屏全部数据来自后端) ====================
|
||||
|
||||
@router.get("/api/dashboard/snapshot")
|
||||
async def dashboard_snapshot():
|
||||
return sim_engine.snapshot()
|
||||
|
||||
|
||||
@router.get("/api/dashboard/overview")
|
||||
async def dashboard_overview():
|
||||
return sim_engine.snapshot()
|
||||
|
||||
|
||||
# ==================== 园区企业 ====================
|
||||
|
||||
_COMPANIES = [
|
||||
{"name": "云南派音人工智能科技", "zone": "加速区", "room": "A1", "color": "#4c8dff"},
|
||||
{"name": "米勒克尔蓝宝石珠宝", "zone": "加速区", "room": "A2", "color": "#4c8dff"},
|
||||
{"name": "中泰研学合作", "zone": "加速区", "room": "A3", "color": "#4c8dff"},
|
||||
{"name": "云南宸中低空经济", "zone": "加速区", "room": "A4", "color": "#4c8dff"},
|
||||
{"name": "昆明智海银高文化科技", "zone": "加速区", "room": "A5", "color": "#4c8dff"},
|
||||
{"name": "AI机器人大模型训练", "zone": "加速区", "room": "A6", "color": "#4c8dff"},
|
||||
{"name": "云南廷秀文旅康养", "zone": "加速区", "room": "A7", "color": "#4c8dff"},
|
||||
{"name": "瀚颖AI+教育信息咨询", "zone": "加速区", "room": "A8", "color": "#4c8dff"},
|
||||
{"name": "云南大学AI+创业平台", "zone": "加速区", "room": "A9", "color": "#4c8dff"},
|
||||
{"name": "仰光客厅", "zone": "国际区", "room": "I1", "color": "#22d3ee"},
|
||||
{"name": "云南上古绝学文化", "zone": "国际区", "room": "I2", "color": "#22d3ee"},
|
||||
{"name": "中越生物医疗", "zone": "国际区", "room": "I3", "color": "#22d3ee"},
|
||||
{"name": "酷享野农AI农业", "zone": "国际区", "room": "I4", "color": "#22d3ee"},
|
||||
{"name": "滇缅国际设计", "zone": "国际区", "room": "I5", "color": "#22d3ee"},
|
||||
{"name": "昆明舒诺生物科技", "zone": "国际区", "room": "I6", "color": "#22d3ee"},
|
||||
{"name": "达岸教育管理", "zone": "国际区", "room": "I7", "color": "#22d3ee"},
|
||||
{"name": "Facebook越南跨境电商", "zone": "成长区", "room": "G1", "color": "#34d399"},
|
||||
{"name": "研X同行者网络", "zone": "成长区", "room": "G2", "color": "#34d399"},
|
||||
{"name": "朵哈·玫瑰特色产业链", "zone": "成长区", "room": "G3", "color": "#34d399"},
|
||||
{"name": "昆明云韵体育", "zone": "成长区", "room": "G4", "color": "#34d399"},
|
||||
{"name": "南菌优培食用菌", "zone": "成长区", "room": "G5", "color": "#34d399"},
|
||||
{"name": "五华区丽裳文化", "zone": "成长区", "room": "G6", "color": "#34d399"},
|
||||
{"name": "云南星瑞航空", "zone": "成长区", "room": "G7", "color": "#34d399"},
|
||||
{"name": "综合直播私域平台", "zone": "成长区", "room": "G8", "color": "#34d399"},
|
||||
{"name": "园区管理办公室", "zone": "园区管理", "room": "M", "color": "#fbbf24"},
|
||||
]
|
||||
|
||||
|
||||
@router.get("/api/park/companies")
|
||||
async def park_companies():
|
||||
return {"companies": _COMPANIES}
|
||||
|
||||
|
||||
@router.get("/api/park/zones")
|
||||
async def park_zones():
|
||||
zones = ["加速区", "国际区", "成长区"]
|
||||
counts = {z: sum(1 for c in _COMPANIES if c["zone"] == z) for z in zones}
|
||||
colors = {"加速区": "#4c8dff", "国际区": "#22d3ee", "成长区": "#34d399"}
|
||||
return {"zones": [{"name": z, "count": counts[z], "color": colors[z]} for z in zones]}
|
||||
|
||||
|
||||
# ==================== AI 对话(通义千问 + 工具调用) ====================
|
||||
|
||||
@router.post("/api/ai/chat")
|
||||
async def ai_chat(body: ChatBody):
|
||||
"""大模型对话:工具调用(切页/控制/卡片/通知)经 MQTT 广播到所有大屏
|
||||
返回 {reply, tools, model};tools 供请求端本地同步执行"""
|
||||
messages = [m.model_dump() for m in body.messages]
|
||||
result = llm_run_chat(messages)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/api/ai/asr")
|
||||
async def ai_asr(file: UploadFile = File(...), format: str = "m4a"):
|
||||
"""语音识别:上传录音 → 阿里云 paraformer 转写为文本"""
|
||||
data = await file.read()
|
||||
if not data:
|
||||
return JSONResponse({"ok": False, "error": "空音频"}, status_code=400)
|
||||
try:
|
||||
text = asr_transcribe(data, fmt=format)
|
||||
return {"ok": True, "text": text}
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("ASR 转写失败: %s", e)
|
||||
return JSONResponse({"ok": False, "error": str(e)}, status_code=502)
|
||||
|
||||
|
||||
# ==================== 展示控制(管理端 → MQTT) ====================
|
||||
|
||||
_VALID_DISPLAY_ACTIONS = {"navigate", "navigate_rel", "play", "pause", "next", "prev",
|
||||
"alert", "show_card", "set_mode", "minimize"}
|
||||
|
||||
|
||||
@router.post("/api/display/command")
|
||||
async def display_command_publish(body: DisplayCommandBody):
|
||||
"""管理端/任意客户端通过 REST 发控制指令 → 后端转 MQTT 广播给所有大屏"""
|
||||
if body.action not in _VALID_DISPLAY_ACTIONS:
|
||||
return JSONResponse({"ok": False, "error": f"不支持的指令: {body.action}"}, status_code=400)
|
||||
cmd = hub.publish_command(body.action, body.params)
|
||||
return {"ok": cmd.get("published", False), "cmd_id": cmd["cmd_id"], "action": body.action,
|
||||
"mqtt_connected": hub.connected}
|
||||
|
||||
|
||||
@router.get("/api/display/state")
|
||||
async def display_state():
|
||||
return {
|
||||
"mqtt_connected": hub.connected,
|
||||
"mqtt_host": f"{settings.MQTT_HOST}:{settings.MQTT_PORT}",
|
||||
"page": None, # 前端可通过 MQTT 回执上报当前页(后续扩展)
|
||||
}
|
||||
|
||||
|
||||
# ==================== SSE 兼容通道(MQTT 不可用时前端回退) ====================
|
||||
|
||||
@router.get("/api/events")
|
||||
async def sse_events(request: Request):
|
||||
q = bus.subscribe()
|
||||
|
||||
async def gen():
|
||||
try:
|
||||
while True:
|
||||
if await request.is_disconnected():
|
||||
break
|
||||
try:
|
||||
data = await asyncio.wait_for(q.get(), timeout=15)
|
||||
yield f"data: {data}\n\n"
|
||||
except asyncio.TimeoutError:
|
||||
yield ": keepalive\n\n"
|
||||
finally:
|
||||
bus.unsubscribe(q)
|
||||
|
||||
return StreamingResponse(gen(), media_type="text/event-stream")
|
||||
@@ -0,0 +1,156 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""园区数据模拟引擎 —— 由前端 parkData.js 移植,后端统一产生数据
|
||||
前端通过 GET /api/dashboard/snapshot 获取,或订阅 MQTT opc/dashboard/tick
|
||||
"""
|
||||
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
|
||||
MODEL_NAMES = ["DeepSeek-V3", "通义千问", "智谱 GLM-4", "豆包", "讯飞星火"]
|
||||
TOOL_NAMES = ["文档生成", "数据查询", "图像创作", "代码执行", "语音合成"]
|
||||
COMPANY_NAMES = [
|
||||
"云南派音人工智能科技", "米勒克尔蓝宝石珠宝", "中泰研学合作", "云南宸中低空经济",
|
||||
"昆明智海银高文化科技", "云南廷秀文旅康养", "瀚颖AI+教育信息咨询",
|
||||
"仰光客厅", "云南上古绝学文化", "中越生物医疗", "酷享野农AI农业",
|
||||
"滇缅国际设计", "昆明舒诺生物科技", "达岸教育管理", "花仙子园艺肥料",
|
||||
"研X同行者网络", "鬼才明AI创意工作室", "朵哈·玫瑰特色产业链", "昆明云韵体育",
|
||||
"南菌优培食用菌", "五华区丽裳文化", "云南星瑞航空", "综合直播私域平台",
|
||||
"昆明屿澈电商", "蓝智科技", "云品出滇·纸享万家", "启元人工智能科技",
|
||||
]
|
||||
|
||||
|
||||
def _pick(arr):
|
||||
return arr[random.randrange(len(arr))]
|
||||
|
||||
|
||||
def _now_time():
|
||||
return time.strftime("%H:%M:%S")
|
||||
|
||||
|
||||
def make_series(base, growth, noise, n=30):
|
||||
out = []
|
||||
v = base
|
||||
for _ in range(n):
|
||||
v = v * (1 + growth) + (random.random() - 0.5) * noise
|
||||
out.append(round(max(1, v * 100) / 100, 2))
|
||||
return out
|
||||
|
||||
|
||||
def gen_event():
|
||||
r = random.random
|
||||
pool = [
|
||||
{"icon": "bolt", "text": f"AI 推理任务完成 · 消耗 {round(800 + r() * 9000):,} tokens({_pick(MODEL_NAMES)})"},
|
||||
{"icon": "wrench", "text": f"工具「{_pick(TOOL_NAMES)}」被调用 {round(10 + r() * 90)} 次"},
|
||||
{"icon": "building", "text": f"「{_pick(COMPANY_NAMES)}」提交入驻申请 · 进入评审流程"},
|
||||
{"icon": "users", "text": f"「{_pick(COMPANY_NAMES)}」新增招聘岗位 {round(1 + r() * 5)} 个"},
|
||||
{"icon": "coin", "text": f"园区企业完成一笔 ¥{(0.5 + r() * 9):.1f}万 交易"},
|
||||
{"icon": "robot", "text": f"「{_pick(MODEL_NAMES)}」模型完成一次微调任务"},
|
||||
]
|
||||
e = _pick(pool)
|
||||
return {"id": f"{int(time.time()*1000)}-{random.random()}", "icon": e["icon"], "text": e["text"], "time": _now_time()}
|
||||
|
||||
|
||||
def init_feed():
|
||||
return [
|
||||
{"id": 1, "icon": "bolt", "text": "云南派音AI 完成音频向量嵌入任务 · 消耗 12,480 tokens", "time": _now_time()},
|
||||
{"id": 2, "icon": "building", "text": "「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间", "time": _now_time()},
|
||||
{"id": 3, "icon": "users", "text": "「昆明舒护安养老服务」新增招聘岗位 2 个", "time": _now_time()},
|
||||
{"id": 4, "icon": "coin", "text": "园区企业完成一笔 ¥3.6万 交易", "time": _now_time()},
|
||||
]
|
||||
|
||||
|
||||
def init_snapshot():
|
||||
return {
|
||||
"t": 0,
|
||||
"token": {"today": 128.64, "total": 12840, "rate": 84.6, "series": make_series(82, 0.012, 9)},
|
||||
"tools": {"today": 3568, "total": 365204, "success": 98.7},
|
||||
"projects": {"inPark": 158, "cum": 208, "todayNew": 2},
|
||||
"jobs": {"total": 2186, "todayNew": 3},
|
||||
"revenue": {"today": 38.6, "total": 20800, "growth": 8.2, "series": make_series(30, 0.006, 4)},
|
||||
"park": {"devices": 98.6, "energy": 386, "people": 127, "desk": 76, "meeting": 3, "nodes": 12},
|
||||
"feed": init_feed(),
|
||||
}
|
||||
|
||||
|
||||
def next_snapshot(s):
|
||||
r = random.random
|
||||
t_delta = round(0.26 + r() * 0.34, 2)
|
||||
|
||||
token = {
|
||||
"today": round(s["token"]["today"] + t_delta, 2),
|
||||
"total": round(s["token"]["total"] + t_delta, 2),
|
||||
"rate": round(76 + r() * 20, 1),
|
||||
}
|
||||
tool_delta = round(13 + r() * 22)
|
||||
tools = {
|
||||
"today": s["tools"]["today"] + tool_delta,
|
||||
"total": s["tools"]["total"] + tool_delta,
|
||||
"success": round(98.1 + r() * 1.2, 1),
|
||||
}
|
||||
rev_delta = round(0.6 + r() * 1.7, 1)
|
||||
revenue = {
|
||||
"today": round(s["revenue"]["today"] + rev_delta, 1),
|
||||
"total": round(s["revenue"]["total"] + rev_delta, 1),
|
||||
"growth": round(7.2 + r() * 2.2, 1),
|
||||
}
|
||||
park = {
|
||||
"devices": round(97.6 + r() * 1.6, 1),
|
||||
"energy": round(320 + r() * 130),
|
||||
"people": round(80 + r() * 95),
|
||||
"desk": round(62 + r() * 24),
|
||||
"meeting": round(2 + r() * 4),
|
||||
"nodes": 12,
|
||||
}
|
||||
|
||||
projects = dict(s["projects"])
|
||||
if r() < 0.055:
|
||||
projects["todayNew"] += 1
|
||||
if r() < 0.035:
|
||||
projects["inPark"] += 1
|
||||
projects["cum"] += 1
|
||||
jobs = dict(s["jobs"])
|
||||
if r() < 0.08:
|
||||
jobs["todayNew"] += 1
|
||||
if r() < 0.05:
|
||||
jobs["total"] += 1
|
||||
|
||||
t_series = list(s["token"]["series"])
|
||||
t_series[-1] = round(t_series[-1] + t_delta, 2)
|
||||
if s["t"] % 12 == 11:
|
||||
t_series = t_series[1:] + [round(72 + r() * 30, 2)]
|
||||
token["series"] = t_series
|
||||
|
||||
r_series = list(s["revenue"]["series"])
|
||||
r_series[-1] = round(r_series[-1] + rev_delta, 1)
|
||||
if s["t"] % 12 == 11:
|
||||
r_series = r_series[1:] + [round(26 + r() * 9, 1)]
|
||||
revenue["series"] = r_series
|
||||
|
||||
feed = [gen_event()] + s["feed"][:6] if s["t"] % 3 == 2 else s["feed"]
|
||||
|
||||
return {
|
||||
"t": s["t"] + 1,
|
||||
"token": token, "tools": tools, "revenue": revenue,
|
||||
"park": park, "projects": projects, "jobs": jobs, "feed": feed,
|
||||
}
|
||||
|
||||
|
||||
class SimEngine:
|
||||
"""带锁的快照引擎:单例供 API 与 MQTT tick 共用"""
|
||||
|
||||
def __init__(self):
|
||||
self._lock = threading.RLock()
|
||||
self._snap = init_snapshot()
|
||||
|
||||
def snapshot(self):
|
||||
with self._lock:
|
||||
return dict(self._snap)
|
||||
|
||||
def tick(self):
|
||||
with self._lock:
|
||||
self._snap = next_snapshot(self._snap)
|
||||
return dict(self._snap)
|
||||
|
||||
|
||||
sim_engine = SimEngine()
|
||||
@@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""JSON 持久化:设置 / 播放列表 / URL 媒体(对齐原 Rust storage 语义)"""
|
||||
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from .config import settings
|
||||
|
||||
DEFAULT_SETTINGS = {
|
||||
"username": "admin",
|
||||
"password": "123456",
|
||||
"volume": 80,
|
||||
"auto_play": True,
|
||||
"play_mode": "sequential",
|
||||
"image_duration": 5,
|
||||
"fullscreen": True,
|
||||
"autostart": False,
|
||||
}
|
||||
|
||||
|
||||
class Storage:
|
||||
def __init__(self, data_file: Path):
|
||||
self.data_file = data_file
|
||||
self._lock = threading.RLock()
|
||||
self._data = self._load()
|
||||
|
||||
# ---------- 底层 ----------
|
||||
def _load(self):
|
||||
if self.data_file.exists():
|
||||
try:
|
||||
return json.loads(self.data_file.read_text("utf-8"))
|
||||
except Exception:
|
||||
pass
|
||||
return {"settings": dict(DEFAULT_SETTINGS), "playlist": [], "url_media": []}
|
||||
|
||||
def _save(self):
|
||||
self.data_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp = self.data_file.with_suffix(".tmp")
|
||||
tmp.write_text(json.dumps(self._data, ensure_ascii=False, indent=2), "utf-8")
|
||||
tmp.replace(self.data_file)
|
||||
|
||||
# ---------- 设置 ----------
|
||||
def get_settings(self):
|
||||
with self._lock:
|
||||
s = dict(DEFAULT_SETTINGS)
|
||||
s.update(self._data.get("settings", {}))
|
||||
return s
|
||||
|
||||
def update_settings(self, **kw):
|
||||
with self._lock:
|
||||
s = dict(DEFAULT_SETTINGS)
|
||||
s.update(self._data.get("settings", {}))
|
||||
for k, v in kw.items():
|
||||
if v is not None and k in s:
|
||||
s[k] = v
|
||||
self._data["settings"] = s
|
||||
self._save()
|
||||
|
||||
# ---------- 播放列表 ----------
|
||||
def get_playlist(self):
|
||||
with self._lock:
|
||||
return list(self._data.get("playlist", []))
|
||||
|
||||
def add_to_playlist(self, path):
|
||||
with self._lock:
|
||||
items = self._data.setdefault("playlist", [])
|
||||
if not any(it.get("path") == path for it in items):
|
||||
items.append({"path": path, "source": None, "name": None})
|
||||
self._save()
|
||||
|
||||
def remove_from_playlist(self, path):
|
||||
with self._lock:
|
||||
self._data.setdefault("playlist", [])
|
||||
self._data["playlist"] = [it for it in self._data["playlist"] if it.get("path") != path]
|
||||
self._save()
|
||||
|
||||
# ---------- URL 媒体 ----------
|
||||
def get_url_media(self):
|
||||
with self._lock:
|
||||
return list(self._data.get("url_media", []))
|
||||
|
||||
def add_url_media(self, url, name, media_type):
|
||||
"""返回 True 表示新增,False 表示已存在(重复)"""
|
||||
with self._lock:
|
||||
items = self._data.setdefault("url_media", [])
|
||||
if any(it.get("url") == url for it in items):
|
||||
return False
|
||||
items.append({
|
||||
"url": url, "name": name, "type": media_type,
|
||||
"added_at": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
})
|
||||
self._save()
|
||||
return True
|
||||
|
||||
def delete_media(self, path):
|
||||
with self._lock:
|
||||
self._data.setdefault("url_media", [])
|
||||
self._data["url_media"] = [it for it in self._data["url_media"] if it.get("url") != path]
|
||||
self._data.setdefault("playlist", [])
|
||||
self._data["playlist"] = [it for it in self._data["playlist"] if it.get("path") != path]
|
||||
self._save()
|
||||
|
||||
|
||||
storage = Storage(settings.DATA_FILE)
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""开发入口:python main.py(或 uvicorn app.main:app)"""
|
||||
import uvicorn
|
||||
|
||||
from app.config import settings
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("app.main:app", host=settings.HOST, port=settings.PORT, reload=False)
|
||||
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"certifi>=2024.2",
|
||||
"fastapi>=0.110",
|
||||
"paho-mqtt>=1.6,<2",
|
||||
"python-multipart>=0.0.9",
|
||||
"uvicorn[standard]>=0.29",
|
||||
"dashscope>=1.26",
|
||||
]
|
||||
Generated
+579
@@ -0,0 +1,579 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-doc"
|
||||
version = "0.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.14.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "paho-mqtt" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "certifi", specifier = ">=2024.2" },
|
||||
{ name = "fastapi", specifier = ">=0.110" },
|
||||
{ name = "paho-mqtt", specifier = ">=1.6,<2" },
|
||||
{ name = "python-multipart", specifier = ">=0.0.9" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.29" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.7.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.141.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paho-mqtt"
|
||||
version = "1.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/dd/4b75dcba025f8647bc9862ac17299e0d7d12d3beadbf026d8c8d74215c12/paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f", size = 99373, upload-time = "2021-10-21T10:33:59.864Z" }
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.46.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.32"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.52.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2e/28/64ca011edf31c715b4fad359c587ea52391aaffa125065695590241ff617/uvicorn-0.52.3.tar.gz", hash = "sha256:18857b9e6579300be55c91c0a1cfd37d9a2cf0cabea33b88275f199eb73b8b58", size = 100621, upload-time = "2026-08-13T16:50:02.899Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/2b/ebd108734a8204c6b4b93c681c9a38c5273b3ccd5d129fee4ffc1d97772c/uvicorn-0.52.3-py3-none-any.whl", hash = "sha256:116af2710dbf47c80f463cd20ee4884b6662f4c9f227d797ddc7279d2fcc2c7c", size = 79859, upload-time = "2026-08-13T16:50:01.323Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
standard = [
|
||||
{ name = "httptools" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
|
||||
{ name = "watchfiles" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.22.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "17.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f7/96/e01084f83a64bcb3a27994bd0cb0db68ff29d9c6707fae37ec19b18ba990/websockets-17.0.1.tar.gz", hash = "sha256:5baa9bc0dfbae8c507e51c8cf1b6d4628086f7a87bbd3a9952bd5f035451f1cc", size = 183298, upload-time = "2026-07-31T11:31:27.665Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ff/6199a52d864215750af8668d84b0274775011a90052081f5a9495807a92b/websockets-17.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10f461191125c63902ea7394ae9e752b1b5785641850c1d365bb30b0f88bc53f", size = 212603, upload-time = "2026-07-31T11:29:30.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/7e/439a962bcada88dcf586da77a1b2385f91e2d2910e9359540934c827156b/websockets-17.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cffc84ddec6da7f447677266fee2a3c40ecc78172f00752aa1150b8a8d65df1d", size = 210286, upload-time = "2026-07-31T11:29:32.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/82/123660edc759c225626b3b91952c7625f85c77a8362acbc35a4623120f7d/websockets-17.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c23e532c8a2325a1e7486de8763a60dc43e83f01bcaeca07e3ba79652c156db1", size = 210549, upload-time = "2026-07-31T11:29:33.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/2f/2940e57080cf56f28190287516400126d5a76b52b9a61dc10ba6f6400dbe/websockets-17.0.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c09e097d0e46e3c289bedab9a475ae344b70c30ff5646e46af22b4e6fdc97b21", size = 219874, upload-time = "2026-07-31T11:29:34.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/28/9ec976c16d63cc51c28dfec74b66854048c0b8b6579946e902f91b69e8bf/websockets-17.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f47b0815af3948ec6a440b3afa02f05b18cc0939549e91b5c677b5d9c2c8472a", size = 220150, upload-time = "2026-07-31T11:29:35.831Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/a4/850c699a16bbc451723856360c59bd997bec075e637154f3fa96e80d5760/websockets-17.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8848c207049ad49d318e5f64a3d4d7bb189f8328d0d98e65647788f2a085785c", size = 221389, upload-time = "2026-07-31T11:29:37.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/e1/f60a891c1a4b3420d5052333a84eb7241e1fb4a71866dec1562f5fa30027/websockets-17.0.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2604de7228506b13a44a256a9d223943340c0e725af5d367dc068e192b027761", size = 224169, upload-time = "2026-07-31T11:29:38.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/fb/e2a893be6fae4fddfe50ddc3035a331d3f381103d5467b7900026bdb3a64/websockets-17.0.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07abc3bd196a48af476a82fd47f3f79a6a3f70937a9f930cef703cfa0c9d83b6", size = 222025, upload-time = "2026-07-31T11:29:39.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/72/e3144b2d79276fab9798ed7d4aea2f0847434f186800b6f56a1eddcb3114/websockets-17.0.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:769ce7e2acfd9a89f2bed3a9c0da229459516bbc00bd4c9e2ca492c613ae4861", size = 220779, upload-time = "2026-07-31T11:29:41.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/5e/69c02174fbcf1c40c6adc45d3c316a401558392fe7bab8969ef8c46f1689/websockets-17.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07d78a509c3333f5908c83d7f78144ea68a6c9ec28110f5c54d81d8fcdc262c4", size = 218053, upload-time = "2026-07-31T11:29:42.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/09/7574778b095b99cfa0856583462f56568df784f9b41485145169b2ec9c64/websockets-17.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ffad64ce7ad3703d652a3fd9af26238377d24ce52c6ad8ff35d26d82f61f493f", size = 220825, upload-time = "2026-07-31T11:29:43.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/e6/f46571f38765dbc4cbc0d0b47de8db65768006dbbd4340e6f5f51bc1d895/websockets-17.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e95e321d0d763f2b6633512605f6112ebd70d5746f3ce05c941909d4a25233f2", size = 219427, upload-time = "2026-07-31T11:29:44.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/31/6ff1fee057bd7e9dd5237fc064a749615378d003aa045b5bfc2d12b2f4f7/websockets-17.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd526c8228e759c1006c4b7c9ac71dc4e925ced1a6a6a5a8e94643709738f63e", size = 220198, upload-time = "2026-07-31T11:29:45.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/4f/d41847227a44b9ad87c3d5a9fddbfad8b7c4d6032878d8460d9d37c2d44f/websockets-17.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd3369e42c0246afaf9d669cfc19797e3a49e8c0a639544459c57597108b966", size = 221304, upload-time = "2026-07-31T11:29:47.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/30/21a7e326c6ad2eb526cd5b816383d59cdeb28b8805b65a543c3cfbd8e8ce/websockets-17.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b580794e926cab7ff42ee4371ef14e0b22cb2bb722a607f77769136468f49a3f", size = 218858, upload-time = "2026-07-31T11:29:48.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/48/55b0331cd5bec9ce29748f79edc00075805450a47011d0c8e3b1c61dbf04/websockets-17.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5033ffe6804dd53afafa7d08e8c3eef2d2431f34d58ca30507a8442dd04a033a", size = 219840, upload-time = "2026-07-31T11:29:49.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/6e/2e8bc06e546f49b32a58a2bc2957902d1809ecc37552d3d7ccd6639a126e/websockets-17.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6db9e5bf3649ab506c6ae8a3ac85a00fb1ae3816d75962771b2df8adbc5d40d2", size = 220116, upload-time = "2026-07-31T11:29:51.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/ad/4bac01fa41aca54307157b9c9f68b066a6bb51fb18716ba618078a67b283/websockets-17.0.1-cp312-cp312-win32.whl", hash = "sha256:bc0bca48ba24c6c866847fd20478a51dd547fa0ad258dab9615c414ec534bbc0", size = 213050, upload-time = "2026-07-31T11:29:52.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/d8/c3a78cccc74a554780e9e76e323d5cde891048627025f0f82623e22dc3df/websockets-17.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2b3f3020171202b135ca078e20434977c6b2b02af647130d6980c9e39b9462e3", size = 213348, upload-time = "2026-07-31T11:29:53.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/25/e1b8824bd632c8a5a62d504b61e9e35e470b67e4be0206f5c28f90c7f86d/websockets-17.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:41d6aa06b5ab832aee72fedf47a149535b121ac900b6bb4d3fe14712afac9a79", size = 213276, upload-time = "2026-07-31T11:29:55.299Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/a8/79c577bc2f874ee22f6f5ccdab97ba9ce6b96806be3fcc3a6d8490f88a21/websockets-17.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:55b12e47dcee83673a40d07686cfb6f9d6dfc285976ade9463f61d2bef3fad22", size = 212593, upload-time = "2026-07-31T11:29:56.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/99/e1cfaf419bb3b2fcfd6792a846f1d936293132b0b9a56530ced016c83c7b/websockets-17.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c118a6b0e25bfc9a6802075d748fa6321714ffbdf3c88d29d9a0e3c7386c75", size = 210280, upload-time = "2026-07-31T11:29:57.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/ef/cc994494bf7d97e41833f6ff55c24f535e4d527a10370b9631737e9c2f00/websockets-17.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:734d20364dc2cfe03674883cafcf580b6e431c5ce42b476312b9285310230cf9", size = 210538, upload-time = "2026-07-31T11:29:59.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/32/fbf2d132f63ba3e67f675bccf333469786a24e0418969ce1d8e6ff9e6f02/websockets-17.0.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9493314a99e599163c854fb5900ad7f7ea38c5cb9d9103aa30b3c6b8181c01fa", size = 219925, upload-time = "2026-07-31T11:30:00.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/50/64eee3d25a47fe744a9490e0627cc373dca096755db740f91c28bd61cd35/websockets-17.0.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:18ded646ce98cdd3c0235825b3252f1df55765ba49b616bb10282f758667b4d0", size = 220206, upload-time = "2026-07-31T11:30:01.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/56/10ed4bc4dd75f204e3c62bd4898e44a8742a27773c80b188cfa7888aad2d/websockets-17.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1bec5d6a19f5fbe87e4940739cfc65e7bb53d8b353e1029b8037a1653b321bc", size = 221445, upload-time = "2026-07-31T11:30:02.788Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/be/bb14328614c068ab09569962fbf218fc00413ce3febc6d2684c764b6f37e/websockets-17.0.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:872273e629ca7e3d35f16a2dc6ede84e1d5c831e616b8277de6e4f83114e7c58", size = 222887, upload-time = "2026-07-31T11:30:03.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/1b/4cb0eec2fee310007104687493175af190019f705940c864f9c523fe9f6f/websockets-17.0.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1df81d174c1561292de9e40b141cafc04f69077272f6c352afe1d743e20810df", size = 222072, upload-time = "2026-07-31T11:30:05.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/9c/14e6391de777ddb39c439c450deb551406d445e25a5877d6fa25c49d4544/websockets-17.0.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:759adeb5b0c5775b563254ec63b5b79089fc0045b479143a0b1b8c0ebaae1253", size = 220826, upload-time = "2026-07-31T11:30:06.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/57/96e94e384442247bbed5d3ab67381c7257355c2d66b62c3ad33a17f5d385/websockets-17.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1d99db29b5444e3982f1ce2ba8a833508ad44b2f1fbd0bd99e81d825c0b461", size = 218107, upload-time = "2026-07-31T11:30:07.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/8c/9c9dedd14c3919435df9b35cdee7111268c751252b87652f3a6a4f56e760/websockets-17.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:02ed63bf26dda9fa27df730a41f6664586c4ee05972c8fb667ce1725b3fd13d3", size = 220889, upload-time = "2026-07-31T11:30:09.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/4d/ca73c2ac82c00f50c529784bacb323e42da4816333211bc1543d90c9cf11/websockets-17.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:eab6de8a98b9a7772cf686d00b4de439fc7efb8ab05ae106ef227291d06f87c5", size = 219486, upload-time = "2026-07-31T11:30:10.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/da/fb37ac09dcd7c69dd73bac979ed393df35f78a3c232e293d1ff3bd586d24/websockets-17.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2a855b6dfe21c4d3420be265ae031829ba8ba0be0ea350d9f7c3ef30ae63ebe2", size = 220258, upload-time = "2026-07-31T11:30:11.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/04/9693f191d968a93f37326a17301a101d49580889c688f466699f89ecdee1/websockets-17.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7002d5f9e1c3ddd991cdfdbfee18cc8c8b196b2445022892badacd6cb338bbbc", size = 221358, upload-time = "2026-07-31T11:30:12.858Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/29/ad0d85c01db5dcf22898d51648bd2c25af0dd0a4a41c550b11acddeeeba7/websockets-17.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c395bda8e7d8f51a02e80261fb57127979e5c472675d9a96b2860619ad47da48", size = 218921, upload-time = "2026-07-31T11:30:14.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/3b/bf8e855e495dcca63f2b8aa019cf2ada3160e1fa66d833c7417f3b1f7f38/websockets-17.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aadc298969ad229d8e3029fc5cc751fdad286696230f9cf014e90ff9cd8e6ea0", size = 219871, upload-time = "2026-07-31T11:30:15.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/db/c7abd6639a93a40279cd1ddc57e09e1c4f8381c4cfccdb775aa5aac9770a/websockets-17.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f11a398d8170b7ac5000baf7f258dcda579ef3ea744e0cc6a165e0dfbc0d3198", size = 220154, upload-time = "2026-07-31T11:30:16.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/2a/25a9f8f2e5a6ef34e911d2f55d9f756bdeb92b4c28cfb77b8430bbc73cb1/websockets-17.0.1-cp313-cp313-win32.whl", hash = "sha256:846a4a8b0833e3cad57523d9e3bd50ec8ea05ab9d06c582f82a1340ba096af5f", size = 213038, upload-time = "2026-07-31T11:30:18.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/2f/ea1380f72bb11b64fc5bc7ae0d42de5bbf3e6dc13b965706b2a1d4e17cdf/websockets-17.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:409d93efcaa14f7a99592c5baaef5ec6ca94fba0f5aec1a86f693977c69c9c1c", size = 213348, upload-time = "2026-07-31T11:30:19.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/c7/b956ed9151c3c74530ebc62d716fbfdbde7507a6acc6423a64f9ecfb6b8a/websockets-17.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:90246fa9e6cb192a778ce6ce024057ec54317a894db7899c922dcdc1f4cbf6a5", size = 213282, upload-time = "2026-07-31T11:30:21.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/dc/cadab608924ac605647031472fb1f8792d7d4ea07565ba1899ec42028e0d/websockets-17.0.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:53b90c00bc6201ab6695c7ff51a04d0e425514c37515e9eeecd2c1b978ac6c0e", size = 212640, upload-time = "2026-07-31T11:30:22.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/7a/b034d13ca181211bbd58bb50835cb196a7784cd505b5a2079d4d03374f9f/websockets-17.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f33a649bfcb8312524173cc4bbafa7dbb236e18eee9aa31a1d324ca0ddda28c", size = 210332, upload-time = "2026-07-31T11:30:23.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/4d/943ede39b53744768edf1ed84a3f9401527388228a3d6c1249c02c3d6bd7/websockets-17.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cddc675ec31bca65473321f9a9794e488b43b3b8de5d02c8ef4810c5d5792163", size = 210546, upload-time = "2026-07-31T11:30:24.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/e3/88dc159d2ae66743c669443246243f28d873b0c5e58271b8cc1ca0440334/websockets-17.0.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b3ff0ad440ad52dda64138f16895f66403f40192365e39b1010e889f289746b0", size = 219928, upload-time = "2026-07-31T11:30:26.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/f2/ff27eaefa15851a5cf7f004ab827a022bf2d6632cb520f89cb100db7e84b/websockets-17.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:72d7f2a5aeb4e82daa4ee18f125b4277f427033359be5c745ad709608446cc2c", size = 220279, upload-time = "2026-07-31T11:30:27.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/2e/a5166149f363d2449c1cb2dde6486a245521979509d53b87a09f3e79662b/websockets-17.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6fd88365da261c53d3e943fb37e0d0721b9cde119f6b2e3fc84369b6ab234d63", size = 221525, upload-time = "2026-07-31T11:30:28.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/b5/f46931269b3ff3bde65d27c65ddb22f9bb8ce92ac2c6c4df0910128f6219/websockets-17.0.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ab9f962a5b64a5c3c845d556b7dc4e6fb683f7b67179f8205e814bb2e0213ffe", size = 222897, upload-time = "2026-07-31T11:30:30.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/f4/deccf3439f35df953ec35e13fe07986821c5f1ab5785d69614283bdb9034/websockets-17.0.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c07f145d0b9e90cbd96035f31fb79199aef4da1872854e36ebeb258e3d57594", size = 222129, upload-time = "2026-07-31T11:30:31.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/a5/e1b57a59da92ade37fd021567a17b518ea8267b28e5530075844cdb525fe/websockets-17.0.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9f7747d3daa41a11f25f7cca5dc988fc51da97b311bed4c9d843860f79779283", size = 220875, upload-time = "2026-07-31T11:30:32.805Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/30/e7d0889c790a854156de424575fd67af79ddbaed9ff3157ae863dfd1c1dc/websockets-17.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2abb1ba0a5133b7d2ef3c1c9f4b0c1e8a101012dce0b594ab2b2888d9a64820e", size = 218160, upload-time = "2026-07-31T11:30:34.512Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/2e/43db785d6ed9ae7594fae7b62bbc9cb4dfee2b015e06a1005f1e5ce283b6/websockets-17.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f3fd9a1f87f8f0f3f8e9f9bd0195f7516562d13f5b178db8c5784d1f60b60bed", size = 220951, upload-time = "2026-07-31T11:30:35.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/f5/4ac3cab3d5e8a830657a822f64a8910e3803229c6783e59c3fd9a3487427/websockets-17.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2bc14b481e05e331811108daa1aeb41a5e237a5564ef2f02ec5a356a0f102f78", size = 219460, upload-time = "2026-07-31T11:30:37.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/0e/c3a4020673ffc17c82cf1a467835038a196a555d3b4f2a50f0f063cf8ccc/websockets-17.0.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57d2ee9b24b404ce75f3814f92073c0ed88106c950148d2427fe8d25ca254d1f", size = 220248, upload-time = "2026-07-31T11:30:38.527Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/87/e47a6a278cc1dfade38444c893ce18322943c25d4b780a74450d9d164be1/websockets-17.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1b363bfd72a52c0658a3154a4cff219f15a474b35a235057d38853bf151acce7", size = 221421, upload-time = "2026-07-31T11:30:39.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/8f/473d5fc4e3836e375b0233c6ef26777e6e5e3f7bfc84ccd524eae4090ed5/websockets-17.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:10b1587c599fa0f2c89154587c80e0fda98ade6c9fa8c0260a2823fb1800b685", size = 218975, upload-time = "2026-07-31T11:30:41.192Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/b9/819ec2dcdf69031d7e9cab11247f3a6ff9bbc8c7c53ada1dbcb9055b227b/websockets-17.0.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d7d72843691f50b91127c50688df10cb72ec6f4c4b1d7e2c11ab33b16acf8e51", size = 219925, upload-time = "2026-07-31T11:30:42.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/b9/6c0da301f6118502e079cf92f4e864adf28e56b3f8c0f6085076ced7b876/websockets-17.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90973a3a00f23afdfd1c9b06fb84289bf0220f247ef8a62501a1967c7af54f7b", size = 220218, upload-time = "2026-07-31T11:30:44.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/f9/cba32dc9dd856565263d6272f594255bd0e2781deb8cd982c026a54760ad/websockets-17.0.1-cp314-cp314-win32.whl", hash = "sha256:599b03beb77633bffc095334338fad79cafc2b01fbd58953838130a9ae967d7b", size = 212626, upload-time = "2026-07-31T11:30:45.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/95/91cdd8c192287d7ea741f37cf7d64fdc1a14410f06f73805e428a1a590af/websockets-17.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:81ce19c6046ace11da7001781be7317bb1dc389f399af4b2ed962190f76f9add", size = 212969, upload-time = "2026-07-31T11:30:46.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/fd/8c98a1e431960661c5769ab1a4dd66494e87ab02d791cc79e51e0d9a289f/websockets-17.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:efe0ae052a8d023b87198921e8a7ce1dc7768816bcd2fbc20df171ac73a04891", size = 212850, upload-time = "2026-07-31T11:30:48.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/c1/142f5186ee7dc3beee0426b998a79e223e067b7689afcaa95890d64aa800/websockets-17.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ab56439c9f74c52770690c7b2f616b3bf775cb3920453ee355ac765c032d8bbf", size = 212967, upload-time = "2026-07-31T11:30:49.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/de/4b03ed316c9dee180365286c298219809ff247be39beeaaf9958b21167ab/websockets-17.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:20a92f78ac8250984ed459faa9ca48c285adbfc0038ddc3fdac6046990a9c9ed", size = 210504, upload-time = "2026-07-31T11:30:50.987Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/d8/ad2b3e8f867e1e8cac3077e2f33ffb60b71bd763d6cfc71bd916f113c3bf/websockets-17.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6a434e59962a4fb9016bea327e1d14d6cd67670ecfb8942b4f4a0c24036634ce", size = 210702, upload-time = "2026-07-31T11:30:52.261Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/18/7a77a82ce9d6f831c07b176da3942f7e71acd0f115f3ecdb1d00a040eb01/websockets-17.0.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2503c7e2a5049a12d5dac917a46d5d52591283a766165b8176bb167560421b38", size = 220290, upload-time = "2026-07-31T11:30:53.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/af/43c3e3c3ea7ba4693c2181743f3221957df28bededadcbd9fc8a0661bde0/websockets-17.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:28012a54510fe8301bb893ef143cec30a2780a2d3bc20b7bbdf4379d7a63945d", size = 220573, upload-time = "2026-07-31T11:30:54.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/bd/ed48eca15725743ee7e2dc172e15c61de29e85ec98dace7b14257f366836/websockets-17.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22bd00f8bae2bccdb5dbe41e20f58ba44ca9fff0b4b561aaf39099c35da762ed", size = 221747, upload-time = "2026-07-31T11:30:56.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/50/838deb7937a8225c4925dd4a977eafea473fabf444178a99de0bc7e92bb0/websockets-17.0.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e98ec9ec61cce5bc4b8b218322ad090b0994eb060bb04da704c62ef0a3d864e6", size = 223891, upload-time = "2026-07-31T11:30:58.127Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/e9/657fb70c6eb6bcd01adfa5d2b06496e9911e1c1a8813d353b8c00f7591cd/websockets-17.0.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e387adb0c692c6b5571bdeafc8ac9d1901ea30f10309134780b16ecd35e6605", size = 222317, upload-time = "2026-07-31T11:30:59.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/4c/82cb722afa5428fed981331210c4c07600570db01bb1620579f655b5adaf/websockets-17.0.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd1470d2c53fe53269bf5619da7725d30dd9b9693f1689f7a85eab8dea734442", size = 221047, upload-time = "2026-07-31T11:31:00.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/84/bd6d67d6bc65f0de0cb50de55dab42f256a9876a351c4736522eb168fda0/websockets-17.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:884af729b8ab50486acd94d9768c2b60914bf39b579ebba0a5cb73bfdfd61fd2", size = 218626, upload-time = "2026-07-31T11:31:02.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/cb/6a372c8553976f0d8f97f5115826ed47e34b3be6b8bf0d0249af249a7416/websockets-17.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a60fa1a25cca1bcc2bf87b8d6be37a741f0a3239fb5e9cfb7a37173b68ffcf87", size = 221299, upload-time = "2026-07-31T11:31:03.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/31/f966e8472337974f74d788b3ef6c6f3b8b9a5f201efd16a843c91d269fa5/websockets-17.0.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:e8208f2729cba030ff872a92064c97584eeb9502f53d32a05a0f05d5a17ca6c6", size = 219789, upload-time = "2026-07-31T11:31:05.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/34/404e83a6cc7b0efcac810b7041bffd72ff76900e6fd0aa45a26c92fb2ffe/websockets-17.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:54cdcaa56f5d3eafd57058f0fa4a3de93a310b43a3c4699f06efc4c0bd054a5a", size = 220678, upload-time = "2026-07-31T11:31:06.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/70/8946188c2a68d67251859b589a3634918cf7867bf0b891347a5ecaa43d30/websockets-17.0.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4d41c0a1d47a478bc432b3b9068097bee1ce0c5b19327ea6f75c2ab34ab1f2fb", size = 221697, upload-time = "2026-07-31T11:31:08.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/16/ee73fc2083a2938ac6209f4ec804960496835b20a0068dbcfe8424957c04/websockets-17.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f991247276797d0c61ab7770bc9791eadc16f683b4d83517f624932adc1a8bab", size = 219390, upload-time = "2026-07-31T11:31:09.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/6a/d5f88033c69932af6cdaa72da62516ade47c257e3bf69f4c0ba5f40e12a2/websockets-17.0.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:733e3cc7171fa1b899edbe725ef9382d0e960657dc1fd933f3281ae910c01dab", size = 220161, upload-time = "2026-07-31T11:31:10.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/2e/6183dd2c0370287ecf4afe0bb33aca364208e5e7b0e1a286adcaecc0c78b/websockets-17.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:810cb3fb5fa6e447216f4e82d9a85cb8aed0929ae3538153ddfe8a6e3121a58d", size = 220591, upload-time = "2026-07-31T11:31:12.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/93/70f6516d85b9744f7eac224c4b1b9ef4e84133f80b53be02080cb1c3e663/websockets-17.0.1-cp314-cp314t-win32.whl", hash = "sha256:17ac37716c0244e82c9e384c41653c090b1864c6610224ca3857e7f7b58fce10", size = 212755, upload-time = "2026-07-31T11:31:13.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/2c/9d9c1da5a7ea9af307b386d25f64d1dead4729644198d2b92e36db5dfd41/websockets-17.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bb31f42ea095ea826463c770829aa188a86c9a5c976b1467cbbf583c811de833", size = 213094, upload-time = "2026-07-31T11:31:15.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/24/a585e7573e128070605d003b5544729bcd58d9756c7e99d550818ca4b916/websockets-17.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dbfae8e75b342e31fc6fd1a8bbb393b7cbb91d6cfd581650300a94381e7b7e2b", size = 213009, upload-time = "2026-07-31T11:31:16.776Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/ce/3929538b2b9918f5eee623fbf3346893973191f6df93f19bbda097bd7bb7/websockets-17.0.1-py3-none-any.whl", hash = "sha256:c6be9cba65c65cc76dfa3d4619e359ff02a4476c74e179b215236c11a0b32345", size = 206718, upload-time = "2026-07-31T11:31:26.037Z" },
|
||||
]
|
||||
Generated
+420
-2
@@ -16,6 +16,7 @@
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-autostart": "^2.5.1",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"mqtt": "^5.15.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.15.0",
|
||||
@@ -1070,6 +1071,15 @@
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-26.2.0.tgz",
|
||||
"integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/offscreencanvas": {
|
||||
"version": "2019.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
|
||||
@@ -1095,6 +1105,15 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/readable-stream": {
|
||||
"version": "4.0.24",
|
||||
"resolved": "https://registry.npmmirror.com/@types/readable-stream/-/readable-stream-4.0.24.tgz",
|
||||
"integrity": "sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stats.js": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmmirror.com/@types/stats.js/-/stats.js-0.17.4.tgz",
|
||||
@@ -1121,6 +1140,15 @@
|
||||
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@use-gesture/core": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/@use-gesture/core/-/core-10.3.1.tgz",
|
||||
@@ -1160,6 +1188,18 @@
|
||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -1202,6 +1242,30 @@
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmmirror.com/bl/-/bl-6.1.6.tgz",
|
||||
"integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/readable-stream": "^4.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/broker-factory": {
|
||||
"version": "3.1.15",
|
||||
"resolved": "https://registry.npmmirror.com/broker-factory/-/broker-factory-3.1.15.tgz",
|
||||
"integrity": "sha512-ko+aWvgNuP49meGrdjUu7rC+Y+Wai3cCPxP3xWwHsHfehFjOh5ZQM2yC4gEB2UddeZ/YXhm0K1eG/L6fxym2Og==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.7",
|
||||
"fast-unique-numbers": "^9.0.27",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-factory": "^7.0.50"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.28.2",
|
||||
"resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz",
|
||||
@@ -1260,6 +1324,12 @@
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/camera-controls": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/camera-controls/-/camera-controls-3.1.2.tgz",
|
||||
@@ -1315,6 +1385,41 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/commist": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/commist/-/commist-3.2.0.tgz",
|
||||
"integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||
"engines": [
|
||||
"node >= 6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.0.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-stream/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
@@ -1388,7 +1493,6 @@
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@@ -1594,6 +1698,37 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-unique-numbers": {
|
||||
"version": "9.0.27",
|
||||
"resolved": "https://registry.npmmirror.com/fast-unique-numbers/-/fast-unique-numbers-9.0.27.tgz",
|
||||
"integrity": "sha512-nDA9ADeINN8SA2u2wCtU+siWFTTDqQR37XvgPIDDmboWQeExz7X0mImxuaN+kJddliIqy2FpVRmnvRZ+j8i1/A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
|
||||
@@ -1675,6 +1810,12 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/help-me": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/help-me/-/help-me-5.0.0.tgz",
|
||||
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/hls.js": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/hls.js/-/hls.js-1.7.0.tgz",
|
||||
@@ -1707,6 +1848,21 @@
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.5.0.tgz",
|
||||
"integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-2.2.2.tgz",
|
||||
@@ -1740,6 +1896,16 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sdsl": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz",
|
||||
"integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/js-sdsl"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -2075,6 +2241,15 @@
|
||||
"integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/motion": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/motion/-/motion-13.1.0.tgz",
|
||||
@@ -2112,11 +2287,59 @@
|
||||
"integrity": "sha512-7DnN7TmbLcYXcG4RVadXIihWlyuM9afoUww8Y5Agg431kGKiuL2/OMyP4mJ5wLz+pvN3t5ySClLOaVXJ+wekRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mqtt": {
|
||||
"version": "5.15.2",
|
||||
"resolved": "https://registry.npmmirror.com/mqtt/-/mqtt-5.15.2.tgz",
|
||||
"integrity": "sha512-VWZU2CSUY3U3oN0PSBRDE5SNsFi4zqqNeQ/uv3pZWqY3CrBXD/dhd0ZLjlsk5YnebGlrapi4lRVNJPUNQ5aZ3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/readable-stream": "^4.0.21",
|
||||
"@types/ws": "^8.18.1",
|
||||
"commist": "^3.2.0",
|
||||
"concat-stream": "^2.0.0",
|
||||
"debug": "^4.4.1",
|
||||
"help-me": "^5.0.0",
|
||||
"lru-cache": "^10.4.3",
|
||||
"minimist": "^1.2.8",
|
||||
"mqtt-packet": "^9.0.2",
|
||||
"number-allocator": "^1.0.14",
|
||||
"readable-stream": "^4.7.0",
|
||||
"rfdc": "^1.4.1",
|
||||
"socks": "^2.8.6",
|
||||
"split2": "^4.2.0",
|
||||
"worker-timers": "^8.0.23",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"bin": {
|
||||
"mqtt": "build/bin/mqtt.js",
|
||||
"mqtt_pub": "build/bin/pub.js",
|
||||
"mqtt_sub": "build/bin/sub.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mqtt-packet": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/mqtt-packet/-/mqtt-packet-9.0.2.tgz",
|
||||
"integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^6.0.8",
|
||||
"debug": "^4.3.4",
|
||||
"process-nextick-args": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mqtt/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
@@ -2144,6 +2367,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/number-allocator": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmmirror.com/number-allocator/-/number-allocator-1.0.14.tgz",
|
||||
"integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.1",
|
||||
"js-sdsl": "4.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -2205,6 +2438,21 @@
|
||||
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/promise-worker-transferable": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
|
||||
@@ -2325,6 +2573,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"process": "^0.11.10",
|
||||
"string_decoder": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
@@ -2340,6 +2604,12 @@
|
||||
"integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.60.3",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.3.tgz",
|
||||
@@ -2384,6 +2654,26 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz",
|
||||
@@ -2427,6 +2717,30 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.9.tgz",
|
||||
"integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ip-address": "^10.1.1",
|
||||
"smart-buffer": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2436,6 +2750,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/stats-gl": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/stats-gl/-/stats-gl-2.4.2.tgz",
|
||||
@@ -2462,6 +2785,15 @@
|
||||
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/suspend-react": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/suspend-react/-/suspend-react-0.1.3.tgz",
|
||||
@@ -2627,6 +2959,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz",
|
||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||
@@ -2667,6 +3011,12 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utility-types": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmmirror.com/utility-types/-/utility-types-3.11.0.tgz",
|
||||
@@ -2785,6 +3135,74 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-factory": {
|
||||
"version": "7.0.50",
|
||||
"resolved": "https://registry.npmmirror.com/worker-factory/-/worker-factory-7.0.50.tgz",
|
||||
"integrity": "sha512-hhwc0G+sFwM4qBuhJIUBn2p1Jf8v/FwmLUANBf/Q+Lt2uI8mfIZQhXaZQACodQD4R7Zp6cn/6702bIvNn2puJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.7",
|
||||
"fast-unique-numbers": "^9.0.27",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-timers": {
|
||||
"version": "8.0.34",
|
||||
"resolved": "https://registry.npmmirror.com/worker-timers/-/worker-timers-8.0.34.tgz",
|
||||
"integrity": "sha512-WXL+Dqsm0G6dnC66rQsvM3tPT2adhbqSirWUCZGglkALOr8ocS0KlpU0iuKbjflJOlu3ydZikMEVrYnHOM9suw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.7",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-timers-broker": "^8.0.18",
|
||||
"worker-timers-worker": "^9.0.15"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-timers-broker": {
|
||||
"version": "8.0.18",
|
||||
"resolved": "https://registry.npmmirror.com/worker-timers-broker/-/worker-timers-broker-8.0.18.tgz",
|
||||
"integrity": "sha512-FrjzDVX1wKfZN0gRbCFqv8VHuTncG4sbI/WGEg4tSSQeIsnwqg4YBYWMAHYLJtUDEYYmiK65UKFrVMVk2irDSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.7",
|
||||
"broker-factory": "^3.1.15",
|
||||
"fast-unique-numbers": "^9.0.27",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-timers-worker": "^9.0.15"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-timers-worker": {
|
||||
"version": "9.0.15",
|
||||
"resolved": "https://registry.npmmirror.com/worker-timers-worker/-/worker-timers-worker-9.0.15.tgz",
|
||||
"integrity": "sha512-KKUe7lZ/Aignr51H6hOUik8LwTnIgojH/1lwhli8A8qIEIyewogZTpNpMW5B6BF7nmwBOkUoTYgf1H3QShcjSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.7",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-factory": "^7.0.50"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.21.3",
|
||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.3.tgz",
|
||||
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-autostart": "^2.5.1",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"mqtt": "^5.15.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.15.0",
|
||||
|
||||
+2
-27
@@ -3,7 +3,6 @@ mod models;
|
||||
mod server;
|
||||
mod storage;
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
@@ -13,32 +12,8 @@ pub fn run() {
|
||||
server::setup_npc();
|
||||
});
|
||||
|
||||
// 在后台线程启动 HTTP 服务器
|
||||
std::thread::spawn(|| {
|
||||
let rt = tokio::runtime::Runtime::new().expect("无法创建 tokio runtime");
|
||||
rt.block_on(async {
|
||||
server::start().await;
|
||||
});
|
||||
});
|
||||
|
||||
// 等待 HTTP 服务器就绪后再打开窗口
|
||||
for i in 0..50 {
|
||||
if let Ok(mut s) = std::net::TcpStream::connect_timeout(
|
||||
&"0.0.0.0:10801".parse().unwrap(),
|
||||
Duration::from_millis(500),
|
||||
) {
|
||||
// 发送一个简单的 HTTP 请求测试
|
||||
let _ = s.write_all(b"GET /api/state HTTP/1.0\r\n\r\n");
|
||||
let mut buf = [0u8; 4];
|
||||
if s.read(&mut buf).is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if i == 0 {
|
||||
eprintln!("[dpm] 等待 HTTP 服务器启动...");
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
// 等待内网穿透就绪(可选,不阻塞)
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
|
||||
+162
-206
@@ -1,241 +1,214 @@
|
||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import Icon from './Icons';
|
||||
import { API_BASE } from '../config';
|
||||
import { getMqttStatus } from '../utils/mqtt';
|
||||
|
||||
/* =========================================================
|
||||
AI 智能助手对话面板(共享)—— 遵循 bd- 浅色科技 UI 规范
|
||||
两种交互模式:
|
||||
· 文字对话:消息流 + 快捷提问 + 输入发送
|
||||
· 语音对话:语音交互按钮(聆听 → 识别 → 以文字进入对话流)
|
||||
AI 智能助手对话面板 —— 文字 / 语音双模式
|
||||
· 文字:后端 /api/ai/chat(阿里云通义千问 + 工具调用)
|
||||
· 语音:本地录音 → 后端 /api/ai/asr(阿里云 paraformer)→ 进入对话流
|
||||
后端不可用时自动回退本地模拟(离线兜底)
|
||||
========================================================= */
|
||||
|
||||
/* ---------- 快捷提问 ---------- */
|
||||
const QUICK_QUESTIONS = [
|
||||
'如何申请入驻园区?',
|
||||
'园区有哪些政策扶持?',
|
||||
'办公场地怎么收费?',
|
||||
'展示入驻企业卡片',
|
||||
'AI 赋能服务有哪些?',
|
||||
'三个分区有什么区别?',
|
||||
'切换到数字孪生页面',
|
||||
];
|
||||
|
||||
/* 语音识别兜底题库(无麦克风权限 / 环境不支持时模拟识别) */
|
||||
const VOICE_SAMPLES = [
|
||||
'如何申请入驻园区?',
|
||||
'园区有哪些政策扶持?',
|
||||
'办公场地怎么收费?',
|
||||
'AI 赋能服务有哪些?',
|
||||
'三个分区有什么区别?',
|
||||
'园区有哪些入驻企业?',
|
||||
'怎么联系创业导师?',
|
||||
/* 离线兜底回复(后端不可用时) */
|
||||
const FALLBACK_REPLIES = [
|
||||
'园区提供「空间 + 孵化 + 融资 + 政策 + 资源 + AI 赋能 + 综合服务」七位一体服务,在园项目 158 家,累计带动就业 2,186 人,累计营收 2.08 亿元。',
|
||||
'入驻流程:提交申请 → 项目评审 → 签订协议 → 安排场地并对接导师服务。在园项目 158 家,累计孵化 208 余家。',
|
||||
'园区政策覆盖场地租金减免、创业补贴、税费优惠与高企认定申报辅导(在园高企 32 家)。',
|
||||
'园区按成长阶段分三个区域:加速区(A1–A12)、国际区(I1–I7)、成长区(G1–G8),共 39 家入驻企业。',
|
||||
];
|
||||
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||
|
||||
/* ---------- 园区知识库(模拟回复) ---------- */
|
||||
const WELCOME = '您好,我是 OPC 园区 AI 智能助手。\n\n我可以为您解答入驻流程、政策扶持、场地服务、AI 赋能、企业名录等问题。您可以在「文字」模式输入问题,或切换到「语音」模式直接说话提问。';
|
||||
|
||||
const RULES = [
|
||||
{
|
||||
match: /入驻|申请|入孵|入园|如何加入|怎样加入/,
|
||||
reply: '入驻流程如下:\n\n1. 提交入驻申请(线上表单或到园区服务中心现场办理)\n2. 项目评审(园区组织专家对项目可行性、团队背景进行评估)\n3. 签订入驻协议,办理入驻手续\n4. 安排办公场地,对接创业导师与孵化服务\n\n目前园区在园项目 158 家,累计孵化企业 208 余家。如需协助,可直接联系园区服务中心。',
|
||||
},
|
||||
{
|
||||
match: /政策|补贴|优惠|扶持|减免|奖励|资助/,
|
||||
reply: '园区政策服务覆盖「七位一体」中的政策维度:\n\n· 场地租金减免:符合条件的大学生创业项目可享受场地优惠\n· 创业补贴:协助对接省、市两级创业补贴与一次性创业资助\n· 税费优惠:对接小微企业所得税减免、增值税优惠等政策\n· 项目申报:指导高企认定(目前在园高新技术企业 32 家)、科技型中小企业备案\n\n更多政策细节可咨询园区政策服务窗口。',
|
||||
},
|
||||
{
|
||||
match: /场地|办公室|工位|租金|收费|面积|多少钱|价格/,
|
||||
reply: '园区总建筑面积 1.2 万㎡,按功能划分为三个区域:\n\n· 加速区(A1–A12):成长型项目办公室,共 11 间独立办公室 + 1 间园区管理办公室\n· 国际区(I1–I7):面向国际合作的办公空间(7 间)\n· 成长区(G1–G8):初创团队成长空间(8 间)\n\n租金按创业项目阶段与政策给予差异化优惠,具体可到园区服务中心咨询,或询问「入驻流程」。',
|
||||
},
|
||||
{
|
||||
match: /AI|模型|算力|tokens|token|工具|智能|赋能|大模型/,
|
||||
reply: '园区 AI 赋能服务是「七位一体」的核心特色:\n\n· 大模型推理:接入 DeepSeek-V3、通义千问、智谱 GLM-4、豆包、讯飞星火等多模型\n· 工具调用:文档生成、数据查询、图像创作、代码执行、语音合成等智能工具\n· 算力与成本:实时提供 token 消耗统计与速率监控,帮助企业控制 AI 使用成本\n\n今日园区 AI 推理任务持续运行,多模型负载均衡调度,服务节点 12/12 在线。',
|
||||
},
|
||||
{
|
||||
match: /加速区|国际区|成长区|分区|区域|区别|布局/,
|
||||
reply: '园区按企业成长阶段划分三个区域:\n\n· 加速区(蓝色):面向已具备产品与市场验证的成长型项目,提供独立办公室(A1–A12),目前在驻 9 家企业\n· 国际区(青色):面向国际合作与跨境业务项目(I1–I7),在驻 7 家\n· 成长区(绿色):面向初创团队的共享成长空间(G1–G8),在驻 8 家\n\n园区管理办公室位于加速区 M 房间,提供一站式服务。',
|
||||
},
|
||||
{
|
||||
match: /企业|公司|名录|名单|入驻企业|有哪些企业|查.*企业/,
|
||||
reply: '园区目前有 39 家入驻企业,分布在三个区域:\n\n· 加速区:云南派音人工智能科技、米勒克尔蓝宝石珠宝、云南宸中低空经济等 9 家\n· 国际区:仰光客厅、中越生物医疗、酷享野农AI农业等 7 家\n· 成长区:Facebook越南跨境电商、昆明云韵体育、云南星瑞航空等 8 家\n\n您也可以直接查看右侧「企业分布」面板,按分区浏览企业名录与房间号。',
|
||||
},
|
||||
{
|
||||
match: /融资|投资|资金|贷款|估值|资本/,
|
||||
reply: '园区融资服务包含:\n\n· 融资对接:定期组织路演与投资机构对接活动\n· 孵化估值:累计孵化企业估值约 5.2 亿元\n· 金融支持:协助对接银行创业贷款、政府创业担保贷款\n· 财务辅导:提供融资方案设计与商业计划书辅导\n\n有融资需求的企业可向园区服务中心提交商业计划书。',
|
||||
},
|
||||
{
|
||||
match: /就业|招聘|岗位|人才|用工/,
|
||||
reply: '园区累计带动就业 2,186 人(其中高校毕业生 1,680 人,实习岗位 506 个)。\n\n· 企业招聘:在驻企业持续发布岗位(今日新增多个岗位)\n· 人才对接:与在昆高校共建实习实训基地\n· 创业带动:园区鼓励以创业带动就业,多个项目已形成稳定用工\n\n具体招聘岗位可关注园区实时动态与入驻企业名录。',
|
||||
},
|
||||
{
|
||||
match: /导师|培训|课程|辅导|孵化服务|孵化器/,
|
||||
reply: '园区配备创业导师 58 位,提供全周期孵化服务:\n\n· 一对一辅导:创业导师定期坐诊,覆盖商业模式、法律财税、市场营销\n· 培训课程:创业训练营、行业专题沙龙、政策解读会\n· 资源对接:技术、供应链、渠道等产业资源链接\n· 公共服务:工商注册、知识产权、法律咨询一站式代办',
|
||||
},
|
||||
{
|
||||
match: /你好|您好|hi|hello|嗨|在吗/,
|
||||
reply: '您好!我是 OPC 园区 AI 智能助手。\n\n可以问我:入驻流程、政策扶持、场地收费、AI 赋能、企业名录、融资服务等问题,也可以切换到「语音」模式直接说话提问。',
|
||||
},
|
||||
{
|
||||
match: /谢谢|感谢|好的|明白|ok/,
|
||||
reply: '不客气,随时为您服务。您也可以点击右侧「企业分布」查看入驻企业,或继续向我提问。',
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_REPLY = '园区提供「空间 + 孵化 + 融资 + 政策 + 资源 + AI 赋能 + 综合服务」七位一体服务,在园项目 158 家,累计带动就业 2,186 人,累计营收 2.08 亿元。\n\n您可以这样问我:\n· 如何申请入驻园区?\n· 园区有哪些政策扶持?\n· AI 赋能服务有哪些?';
|
||||
|
||||
function getReply(input) {
|
||||
const text = String(input || '').trim();
|
||||
for (const rule of RULES) {
|
||||
if (rule.match.test(text)) return rule.reply;
|
||||
}
|
||||
return DEFAULT_REPLY;
|
||||
}
|
||||
/* 本地兜底识别题库(ASR 不可用时) */
|
||||
const VOICE_SAMPLES = QUICK_QUESTIONS;
|
||||
|
||||
/* ---------- 消息 ---------- */
|
||||
let msgId = 0;
|
||||
const nextId = () => ++msgId;
|
||||
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||
|
||||
/** 执行后端返回的工具调用。
|
||||
后端已通过 MQTT 广播(发布成功时所有大屏都会收到,本地跳过避免重复);
|
||||
仅当 MQTT 发布失败 / 后端不可达时本地执行,保证当前屏幕也能响应 */
|
||||
function executeLocalTools(tools, mqttPublished) {
|
||||
if (!Array.isArray(tools) || tools.length === 0) return;
|
||||
if (mqttPublished !== false) return;
|
||||
for (const tool of tools) {
|
||||
const type = tool.type;
|
||||
const params = tool.params || {};
|
||||
if (type === 'navigate' || type === 'control' || type === 'alert' || type === 'show_card') {
|
||||
window.dispatchEvent(new CustomEvent('dpm:mqtt-local', { detail: { action: type, params } }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function AiChatPanel() {
|
||||
const [msgs, setMsgs] = useState(() => [
|
||||
{ id: nextId(), role: 'ai', text: WELCOME },
|
||||
]);
|
||||
const [msgs, setMsgs] = useState(() => [{ id: nextId(), role: 'ai', text: '您好,我是 OPC 园区 AI 智能助手(阿里云通义千问)。\n\n支持文字与语音提问,可以为您解答入驻流程、政策扶持、场地服务、AI 赋能等问题,还可以通过语音或文字让我切换页面、展示企业卡片。' }]);
|
||||
const [input, setInput] = useState('');
|
||||
const [typing, setTyping] = useState(false);
|
||||
const [mode, setMode] = useState('text'); // text | voice
|
||||
const [voiceState, setVoiceState] = useState('idle'); // idle | listening | processing
|
||||
const [mode, setMode] = useState('text');
|
||||
const [voiceState, setVoiceState] = useState('idle'); // idle | recording | transcribing
|
||||
const scrollRef = useRef(null);
|
||||
const timerRef = useRef(null);
|
||||
const recRef = useRef(null);
|
||||
const recorderRef = useRef(null);
|
||||
const chunksRef = useRef([]);
|
||||
const voiceStateRef = useRef(voiceState);
|
||||
|
||||
useEffect(() => { voiceStateRef.current = voiceState; }, [voiceState]);
|
||||
|
||||
// 新消息 / 状态变化自动滚动到底部
|
||||
useEffect(() => {
|
||||
const el = scrollRef.current;
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
}, [msgs, typing, mode, voiceState]);
|
||||
|
||||
// 卸载清理:定时器 + 语音识别
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeout(timerRef.current);
|
||||
try { recRef.current && recRef.current.abort && recRef.current.abort(); } catch { /* ignore */ }
|
||||
if (recorderRef.current && recorderRef.current.state !== 'inactive') {
|
||||
try { recorderRef.current.stop(); } catch { /* ignore */ }
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
/** 生成用户消息并触发 AI 回复 */
|
||||
const send = useCallback((raw, viaVoice = false) => {
|
||||
/** 发送消息 → 后端 LLM */
|
||||
const send = useCallback(async (raw, viaVoice = false) => {
|
||||
const text = String(raw || '').trim();
|
||||
if (!text || typing) return;
|
||||
setInput('');
|
||||
setMsgs((prev) => [...prev, { id: nextId(), role: 'me', text, voice: viaVoice }]);
|
||||
setTyping(true);
|
||||
const reply = getReply(text);
|
||||
timerRef.current = setTimeout(() => {
|
||||
setMsgs((prev) => [...prev, { id: nextId(), role: 'ai', text: reply }]);
|
||||
|
||||
let reply = null;
|
||||
let tools = [];
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/ai/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ messages: [{ role: 'user', content: text }] }),
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
reply = data.reply;
|
||||
tools = data.tools || [];
|
||||
}
|
||||
} catch { /* 后端不可用 */ }
|
||||
|
||||
if (!reply) reply = pick(FALLBACK_REPLIES);
|
||||
setTyping(false);
|
||||
}, 700 + Math.random() * 600);
|
||||
setMsgs((prev) => [...prev, { id: nextId(), role: 'ai', text: reply }]);
|
||||
executeLocalTools(tools);
|
||||
}, [typing]);
|
||||
|
||||
const clear = useCallback(() => {
|
||||
clearTimeout(timerRef.current);
|
||||
setTyping(false);
|
||||
setMsgs([{ id: nextId(), role: 'ai', text: WELCOME }]);
|
||||
setMsgs([{ id: nextId(), role: 'ai', text: '您好,我是 OPC 园区 AI 智能助手。有什么可以帮您?' }]);
|
||||
}, []);
|
||||
|
||||
/* ================= 语音对话 ================= */
|
||||
/* ================= 语音对话(录音 → 后端 ASR) ================= */
|
||||
|
||||
// 供安全兜底定时器读取当前状态
|
||||
const voiceStateRef = useRef(voiceState);
|
||||
useEffect(() => { voiceStateRef.current = voiceState; }, [voiceState]);
|
||||
const pickMimeType = () => {
|
||||
const types = ['audio/mp4', 'audio/webm;codecs=opus', 'audio/ogg;codecs=opus', 'audio/webm'];
|
||||
for (const t of types) {
|
||||
if (window.MediaRecorder && MediaRecorder.isTypeSupported(t)) return t;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/** 结束聆听:把识别出的文字送入对话流 */
|
||||
const finishVoice = useCallback((text) => {
|
||||
setVoiceState('processing');
|
||||
clearTimeout(timerRef.current);
|
||||
const startRecording = useCallback(() => {
|
||||
const mime = pickMimeType();
|
||||
if (!window.MediaRecorder || !navigator.mediaDevices?.getUserMedia) {
|
||||
// 无录音能力:直接模拟
|
||||
setVoiceState('transcribing');
|
||||
timerRef.current = setTimeout(() => {
|
||||
setVoiceState('idle');
|
||||
send(text, true);
|
||||
}, 450);
|
||||
}, [send]);
|
||||
|
||||
/** 兜底:环境不支持 / 无麦克风时模拟识别 */
|
||||
const fallbackVoice = useCallback(() => {
|
||||
if (recRef.current) { try { recRef.current.abort(); } catch { /* ignore */ } recRef.current = null; }
|
||||
finishVoice(pick(VOICE_SAMPLES));
|
||||
}, [finishVoice]);
|
||||
|
||||
/** 点击语音按钮:idle → 开始聆听;listening → 结束 */
|
||||
const toggleVoice = useCallback(() => {
|
||||
if (voiceState === 'processing') return;
|
||||
if (voiceState === 'listening') {
|
||||
// 用户主动结束:尝试取识别结果,无结果则兜底
|
||||
clearTimeout(timerRef.current);
|
||||
if (recRef.current) {
|
||||
try { recRef.current.stop(); } catch { /* ignore */ }
|
||||
send(pick(VOICE_SAMPLES), true);
|
||||
}, 1200);
|
||||
return;
|
||||
}
|
||||
return; // 由 onend/onerror 或兜底定时器完成收尾
|
||||
}
|
||||
// 开始聆听
|
||||
setVoiceState('listening');
|
||||
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
if (SR) {
|
||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then((stream) => {
|
||||
const rec = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined);
|
||||
chunksRef.current = [];
|
||||
rec.ondataavailable = (e) => { if (e.data && e.data.size > 0) chunksRef.current.push(e.data); };
|
||||
rec.onstop = async () => {
|
||||
stream.getTracks().forEach((t) => t.stop());
|
||||
setVoiceState('transcribing');
|
||||
const blob = new Blob(chunksRef.current, { type: mime || 'audio/webm' });
|
||||
try {
|
||||
const rec = new SR();
|
||||
rec.lang = 'zh-CN';
|
||||
rec.interimResults = false;
|
||||
rec.maxAlternatives = 1;
|
||||
let done = false;
|
||||
rec.onresult = (e) => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
clearTimeout(timerRef.current);
|
||||
const t = Array.from(e.results).map((r) => r[0].transcript).join(' ');
|
||||
finishVoice(t || pick(VOICE_SAMPLES));
|
||||
const fmt = (mime || 'webm').split(';')[0].split('/')[1] || 'm4a';
|
||||
const form = new FormData();
|
||||
form.append('file', blob, `recording.${fmt === 'mp4' ? 'm4a' : fmt}`);
|
||||
form.append('format', fmt === 'mp4' ? 'm4a' : fmt);
|
||||
const res = await fetch(`${API_BASE}/api/ai/asr`, { method: 'POST', body: form });
|
||||
const data = await res.json();
|
||||
const text = (data && data.text || '').trim();
|
||||
if (text) {
|
||||
send(text, true);
|
||||
} else {
|
||||
setVoiceState('idle');
|
||||
setMsgs((prev) => [...prev, { id: nextId(), role: 'ai', text: '抱歉,没有听清您的问题,请再试一次,或改用文字输入。' }]);
|
||||
}
|
||||
} catch {
|
||||
// 后端不可用:回退本地模拟识别
|
||||
setVoiceState('transcribing');
|
||||
timerRef.current = setTimeout(() => {
|
||||
setVoiceState('idle');
|
||||
send(pick(VOICE_SAMPLES), true);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
rec.onerror = () => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
clearTimeout(timerRef.current);
|
||||
fallbackVoice();
|
||||
stream.getTracks().forEach((t) => t.stop());
|
||||
setVoiceState('idle');
|
||||
send(pick(VOICE_SAMPLES), true);
|
||||
};
|
||||
rec.onend = () => {
|
||||
recRef.current = null;
|
||||
if (!done) {
|
||||
done = true;
|
||||
clearTimeout(timerRef.current);
|
||||
fallbackVoice();
|
||||
}
|
||||
};
|
||||
recRef.current = rec;
|
||||
recorderRef.current = rec;
|
||||
rec.start();
|
||||
// 安全兜底:4 秒内无结果则模拟(识别静默失败的环境)
|
||||
setVoiceState('recording');
|
||||
})
|
||||
.catch(() => {
|
||||
// 麦克风权限被拒:模拟
|
||||
setVoiceState('transcribing');
|
||||
timerRef.current = setTimeout(() => {
|
||||
if (!done && voiceStateRef.current === 'listening') {
|
||||
done = true;
|
||||
try { rec.abort(); } catch { /* ignore */ }
|
||||
fallbackVoice();
|
||||
}
|
||||
}, 4000);
|
||||
return;
|
||||
} catch { /* 继续走兜底 */ }
|
||||
}
|
||||
// 无 SpeechRecognition:模拟聆听 → 识别
|
||||
timerRef.current = setTimeout(() => {
|
||||
fallbackVoice();
|
||||
}, 1300);
|
||||
}, [voiceState, finishVoice, fallbackVoice]);
|
||||
setVoiceState('idle');
|
||||
send(pick(VOICE_SAMPLES), true);
|
||||
}, 1200);
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
// 切换模式时中止聆听
|
||||
const switchMode = useCallback((m) => {
|
||||
setMode(m);
|
||||
if (voiceState === 'listening') {
|
||||
clearTimeout(timerRef.current);
|
||||
if (recRef.current) { try { recRef.current.abort(); } catch { /* ignore */ } recRef.current = null; }
|
||||
const toggleVoice = useCallback(() => {
|
||||
if (voiceStateRef.current === 'recording') {
|
||||
const rec = recorderRef.current;
|
||||
if (rec && rec.state === 'recording') {
|
||||
rec.stop(); // 触发 onstop → 上传识别
|
||||
} else {
|
||||
setVoiceState('idle');
|
||||
}
|
||||
}, [voiceState]);
|
||||
} else if (voiceStateRef.current === 'idle') {
|
||||
startRecording();
|
||||
}
|
||||
}, [startRecording]);
|
||||
|
||||
const switchMode = useCallback((m) => {
|
||||
setMode(m);
|
||||
if (voiceStateRef.current === 'recording') {
|
||||
try { recorderRef.current && recorderRef.current.stop(); } catch { /* ignore */ }
|
||||
setVoiceState('idle');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const voiceHint = {
|
||||
idle: '点击麦克风,说出你的问题',
|
||||
listening: '正在聆听… 请说出你的问题',
|
||||
processing: '正在识别语音…',
|
||||
recording: '正在聆听… 再次点击结束',
|
||||
transcribing: '正在识别语音…',
|
||||
}[voiceState];
|
||||
|
||||
return (
|
||||
@@ -245,41 +218,29 @@ export default function AiChatPanel() {
|
||||
<span className="bd-chat-logo"><Icon name="robot" size={17} /></span>
|
||||
<div className="bd-chat-titles">
|
||||
<b>AI 智能助手</b>
|
||||
<span>OPC 园区知识库 · 文字 / 语音双模式</span>
|
||||
<span>阿里云通义千问 · 语音识别</span>
|
||||
</div>
|
||||
<div className="bd-chat-modes">
|
||||
<button
|
||||
className={mode === 'text' ? 'active' : ''}
|
||||
onClick={() => switchMode('text')}
|
||||
title="文字对话"
|
||||
>
|
||||
<button className={mode === 'text' ? 'active' : ''} onClick={() => switchMode('text')} title="文字对话">
|
||||
<Icon name="message-circle" size={13} />文字
|
||||
</button>
|
||||
<button
|
||||
className={mode === 'voice' ? 'active' : ''}
|
||||
onClick={() => switchMode('voice')}
|
||||
title="语音对话"
|
||||
>
|
||||
<button className={mode === 'voice' ? 'active' : ''} onClick={() => switchMode('voice')} title="语音对话">
|
||||
<Icon name="microphone" size={13} />语音
|
||||
</button>
|
||||
</div>
|
||||
<span className="bd-chat-model"><i />DeepSeek-V3</span>
|
||||
<span className="bd-chat-model"><i />通义千问</span>
|
||||
<button className="bd-chat-clear" title="清空对话" onClick={clear}>
|
||||
<Icon name="eraser" size={15} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 消息区(文字 / 语音共用) */}
|
||||
{/* 消息区 */}
|
||||
<div className="bd-chat-msgs" ref={scrollRef}>
|
||||
{msgs.map((m) => (
|
||||
<div className={`bd-msg ${m.role}`} key={m.id}>
|
||||
{m.role === 'ai' && (
|
||||
<span className="bd-msg-avatar"><Icon name="robot" size={13} /></span>
|
||||
)}
|
||||
{m.role === 'ai' && <span className="bd-msg-avatar"><Icon name="robot" size={13} /></span>}
|
||||
<div className="bd-msg-bubble">
|
||||
{m.voice && (
|
||||
<span className="bd-msg-voice-tag"><Icon name="microphone" size={10} />语音</span>
|
||||
)}
|
||||
{m.voice && <span className="bd-msg-voice-tag"><Icon name="microphone" size={10} />语音</span>}
|
||||
{m.text}
|
||||
</div>
|
||||
</div>
|
||||
@@ -292,7 +253,7 @@ export default function AiChatPanel() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 文字模式:快捷提问 + 输入区 */}
|
||||
{/* 文字模式 */}
|
||||
{mode === 'text' && (
|
||||
<>
|
||||
<div className="bd-chat-quick">
|
||||
@@ -306,7 +267,7 @@ export default function AiChatPanel() {
|
||||
<div className="bd-chat-input">
|
||||
<input
|
||||
value={input}
|
||||
placeholder="输入你的问题,如:如何申请入驻园区?"
|
||||
placeholder="输入你的问题,如:展示入驻企业卡片"
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
|
||||
@@ -315,39 +276,34 @@ export default function AiChatPanel() {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="bd-chat-send"
|
||||
disabled={!input.trim() || typing}
|
||||
onClick={() => send(input)}
|
||||
title="发送"
|
||||
>
|
||||
<button className="bd-chat-send" disabled={!input.trim() || typing} onClick={() => send(input)} title="发送">
|
||||
<Icon name="send" size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 语音模式:语音交互按钮 */}
|
||||
{/* 语音模式 */}
|
||||
{mode === 'voice' && (
|
||||
<div className="bd-chat-voice">
|
||||
<div className={`bd-chat-voice-hint ${voiceState}`}>
|
||||
{voiceState === 'listening' && <span className="bd-voice-wave"><i /><i /><i /><i /><i /></span>}
|
||||
{voiceState === 'recording' && <span className="bd-voice-wave"><i /><i /><i /><i /><i /></span>}
|
||||
{voiceHint}
|
||||
</div>
|
||||
<button
|
||||
className={`bd-chat-mic ${voiceState}`}
|
||||
onClick={toggleVoice}
|
||||
title={voiceState === 'listening' ? '结束并发送' : '开始语音提问'}
|
||||
title={voiceState === 'recording' ? '结束并发送' : '开始语音提问'}
|
||||
>
|
||||
{voiceState === 'listening' && <span className="bd-mic-ring r1" />}
|
||||
{voiceState === 'listening' && <span className="bd-mic-ring r2" />}
|
||||
{voiceState === 'recording' && <span className="bd-mic-ring r1" />}
|
||||
{voiceState === 'recording' && <span className="bd-mic-ring r2" />}
|
||||
<Icon
|
||||
name={voiceState === 'listening' ? 'microphone-filled' : 'microphone'}
|
||||
name={voiceState === 'recording' ? 'microphone-filled' : 'microphone'}
|
||||
size={30}
|
||||
/>
|
||||
</button>
|
||||
<div className="bd-chat-voice-tip">
|
||||
{voiceState === 'listening' ? '再次点击结束并发送' : '点击开始说话 · 再次点击结束'}
|
||||
{voiceState === 'recording' ? '再次点击结束并识别' : '点击开始说话 · 阿里云语音识别'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import Icon from './Icons';
|
||||
import { API_BASE } from '../config';
|
||||
|
||||
/* =========================================================
|
||||
DpmOverlays —— 后端/AI 指令驱动的全局覆盖层
|
||||
· alert → 右上角通知(自动消失)
|
||||
· show_card → 中央信息卡片(企业分布 / 分区 / 园区总览 / 自定义)
|
||||
遵循 bd- 浅色科技 UI 规范,图标全部使用 appica SVG
|
||||
========================================================= */
|
||||
|
||||
function CardContent({ card }) {
|
||||
const [data, setData] = useState(null);
|
||||
const { card: type, title, content } = card || {};
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
if (type === 'companies') {
|
||||
fetch(`${API_BASE}/api/park/companies`)
|
||||
.then((r) => r.json())
|
||||
.then((d) => alive && setData(d.companies || []))
|
||||
.catch(() => alive && setData([]));
|
||||
} else if (type === 'zones') {
|
||||
fetch(`${API_BASE}/api/park/zones`)
|
||||
.then((r) => r.json())
|
||||
.then((d) => alive && setData(d.zones || []))
|
||||
.catch(() => alive && setData([]));
|
||||
} else if (type === 'overview') {
|
||||
fetch(`${API_BASE}/api/dashboard/snapshot`)
|
||||
.then((r) => r.json())
|
||||
.then((d) => alive && setData(d))
|
||||
.catch(() => alive && setData(null));
|
||||
}
|
||||
return () => { alive = false; };
|
||||
}, [type]);
|
||||
|
||||
if (type === 'companies') {
|
||||
const groups = {};
|
||||
(data || []).forEach((c) => {
|
||||
(groups[c.zone] = groups[c.zone] || []).push(c);
|
||||
});
|
||||
return (
|
||||
<div className="bd-ov-card-body">
|
||||
{Object.keys(groups).map((z) => (
|
||||
<div className="bd-ov-card-group" key={z}>
|
||||
<div className="bd-ov-card-group-title">{z}</div>
|
||||
<div className="bd-ov-card-chips">
|
||||
{groups[z].map((c) => (
|
||||
<span className="bd-ov-card-chip" key={c.room}>
|
||||
<b style={{ color: c.color, borderColor: c.color }}>{c.room}</b>
|
||||
{c.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'zones') {
|
||||
return (
|
||||
<div className="bd-ov-card-body">
|
||||
<div className="bd-ov-card-chips">
|
||||
{(data || []).map((z) => (
|
||||
<span className="bd-ov-card-chip" key={z.name}>
|
||||
<b style={{ color: z.color, borderColor: z.color }}>{z.name}</b>
|
||||
{z.count} 家
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'overview' && data) {
|
||||
const rows = [
|
||||
['在园项目', `${data.projects?.inPark ?? '-'} 家`],
|
||||
['累计孵化', `${data.projects?.cum ?? '-'} 家`],
|
||||
['带动就业', `${(data.jobs?.total ?? 0).toLocaleString()} 人`],
|
||||
['累计营收', `${(data.revenue?.total ?? 0) / 10000} 亿`],
|
||||
['今日营收', `${data.revenue?.today ?? '-'} 万`],
|
||||
['工具调用', `${(data.tools?.today ?? 0).toLocaleString()} 次`],
|
||||
['AI 速率', `${data.token?.rate ?? '-'} t/s`],
|
||||
['设备在线', `${data.park?.devices ?? '-'}%`],
|
||||
];
|
||||
return (
|
||||
<div className="bd-ov-card-grid">
|
||||
{rows.map(([k, v]) => (
|
||||
<div className="bd-ov-card-stat" key={k}>
|
||||
<span>{k}</span>
|
||||
<b>{v}</b>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// custom / 未知类型:标题 + 内容
|
||||
return (
|
||||
<div className="bd-ov-card-body">
|
||||
<div className="bd-ov-card-text">{content || '(无内容)'}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DpmOverlays() {
|
||||
const [alert, setAlert] = useState(null); // {title, content, id}
|
||||
const [card, setCard] = useState(null); // {card, title, content, id}
|
||||
const alertTimer = useRef(null);
|
||||
|
||||
const showAlert = useCallback((params) => {
|
||||
const item = { title: params.title || '提示', content: params.content || '', id: Date.now() + Math.random() };
|
||||
setAlert(item);
|
||||
clearTimeout(alertTimer.current);
|
||||
alertTimer.current = setTimeout(() => setAlert(null), 6000);
|
||||
}, []);
|
||||
|
||||
const showCard = useCallback((params) => {
|
||||
setCard({ card: params.card || 'custom', title: params.title || '', content: params.content || '', id: Date.now() + Math.random() });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onAlert = (e) => showAlert(e.detail || {});
|
||||
const onCard = (e) => showCard(e.detail || {});
|
||||
window.addEventListener('dpm:alert', onAlert);
|
||||
window.addEventListener('dpm:show-card', onCard);
|
||||
return () => {
|
||||
window.removeEventListener('dpm:alert', onAlert);
|
||||
window.removeEventListener('dpm:show-card', onCard);
|
||||
};
|
||||
}, [showAlert, showCard]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{alert && (
|
||||
<div className="bd-overlay bd-overlay-alert" key={alert.id}>
|
||||
<div className="bd-overlay-alert-ico"><Icon name="broadcast" size={15} /></div>
|
||||
<div className="bd-overlay-alert-body">
|
||||
<b>{alert.title}</b>
|
||||
{alert.content && <span>{alert.content}</span>}
|
||||
</div>
|
||||
<button className="bd-overlay-close" onClick={() => setAlert(null)}><Icon name="x" size={13} /></button>
|
||||
</div>
|
||||
)}
|
||||
{card && (
|
||||
<div className="bd-overlay bd-overlay-mask" onClick={() => setCard(null)}>
|
||||
<div className="bd-overlay-card" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="bd-overlay-card-head">
|
||||
<span className="bd-overlay-card-ico"><Icon name="sparkles" size={15} /></span>
|
||||
<b>{card.title || 'AI 助手卡片'}</b>
|
||||
<button className="bd-overlay-close" onClick={() => setCard(null)}><Icon name="x" size={14} /></button>
|
||||
</div>
|
||||
<CardContent card={card} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -20,7 +20,10 @@ export default function ParkOverviewStrip({ d }) {
|
||||
];
|
||||
return (
|
||||
<footer className="bd-strip">
|
||||
<div className="bd-strip-title"><Icon name="map-pin" size={14} /> 园区概览</div>
|
||||
<div className="bd-strip-title">
|
||||
<Icon name="map-pin" size={14} />
|
||||
园区概览
|
||||
</div>
|
||||
<div className="bd-strip-chips">
|
||||
{chips.map((c, i) => (
|
||||
<div className="bd-chip" key={i}>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import PageHeader from './PageHeader';
|
||||
import DpmOverlays from './DpmOverlays';
|
||||
import { usePageNav } from '../utils/pageNav';
|
||||
import { useMqttControl } from '../utils/useMqttControl';
|
||||
|
||||
/* =========================================================
|
||||
ScreenLayout —— 大屏页面统一布局
|
||||
通过 App 嵌套路由注入统一页眉 + 全局左右键循环导航
|
||||
通过 MQTT 接收后端控制(页面切换 / 媒体控制 / 卡片 / 通知)
|
||||
页脚由各页面按需自行渲染(数字孪生页无需页脚)
|
||||
========================================================= */
|
||||
|
||||
export default function ScreenLayout() {
|
||||
// 全局左右方向键循环切换(数据大屏 / 数字孪生 / 媒体轮播)
|
||||
// 全局左右方向键循环切换(数据大屏 / 数字孪生 / AI 助手 / 媒体轮播)
|
||||
usePageNav();
|
||||
// 后端 MQTT 控制通道(切页 / 媒体控制 / 卡片 / 通知)
|
||||
useMqttControl();
|
||||
|
||||
return (
|
||||
<div className="bd-layout">
|
||||
@@ -18,6 +23,8 @@ export default function ScreenLayout() {
|
||||
<main className="bd-layout-main">
|
||||
<Outlet />
|
||||
</main>
|
||||
{/* 全局覆盖层(通知 / 卡片) */}
|
||||
<DpmOverlays />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/* =========================================================
|
||||
运行配置 —— 后端地址 / MQTT Broker
|
||||
优先级:window.__DPM_*__ 全局变量 > Vite 环境变量(.env.local)> 内置默认
|
||||
部署时在项目根目录 .env.local 配置(已 gitignore):
|
||||
VITE_API_BASE=http://192.168.x.x:18003
|
||||
VITE_MQTT_URL=ws://192.168.x.x:8083/mqtt
|
||||
VITE_MQTT_USERNAME=...
|
||||
VITE_MQTT_PASSWORD=...
|
||||
========================================================= */
|
||||
|
||||
const env = import.meta.env || {};
|
||||
|
||||
export const API_BASE = window.__DPM_API__ || env.VITE_API_BASE || 'http://localhost:10085';
|
||||
export const MQTT_URL = window.__DPM_MQTT__ || env.VITE_MQTT_URL || 'ws://192.168.1.3:8083/mqtt';
|
||||
export const MQTT_USERNAME = window.__DPM_MQTT_USER__ || env.VITE_MQTT_USERNAME || '';
|
||||
export const MQTT_PASSWORD = window.__DPM_MQTT_PASS__ || env.VITE_MQTT_PASSWORD || '';
|
||||
|
||||
export const MQTT_TOPIC_COMMAND = 'opc/display/command';
|
||||
export const MQTT_TOPIC_TICK = 'opc/dashboard/tick';
|
||||
+4
-119
@@ -2,10 +2,11 @@ import { useState, useEffect, useRef } from 'react';
|
||||
import '../styles/datascreen.css';
|
||||
import Icon from '../components/Icons';
|
||||
import ParkOverviewStrip from '../components/ParkOverviewStrip';
|
||||
import { useParkSim } from '../utils/parkData';
|
||||
|
||||
/* =========================================================
|
||||
昆明市大学生创业园 · OPC 智能园区数字运营中心
|
||||
—— 数据大屏(深色科技风 · 实时模拟数据)
|
||||
—— 数据大屏(浅色科技风 · 数据来自 FastAPI 后端)
|
||||
---------------------------------------------------------
|
||||
基础数据来源:
|
||||
· 《昆明市创业园宣传册》:在园项目 150+ / 累计孵化 200+ /
|
||||
@@ -13,7 +14,7 @@ import ParkOverviewStrip from '../components/ParkOverviewStrip';
|
||||
高企 32 家 / 重点研发项目 18 项 / 大赛奖项 47 项
|
||||
· 《昆明市大学生创业园入驻企业信息表》:39 家本期入驻企业
|
||||
(区域、行业、名称)
|
||||
大屏实时数据为演示模拟(每 2.2s 跳动一次)
|
||||
大屏实时数据由后端 GET /api/dashboard/snapshot 提供(每 2.2s 刷新)
|
||||
========================================================= */
|
||||
|
||||
/* ---------- 真实基础数据(入驻企业信息表) ---------- */
|
||||
@@ -100,129 +101,13 @@ const TOOL_RANK = [
|
||||
{ name: '语音合成', value: 10 },
|
||||
];
|
||||
|
||||
const TOOL_NAMES = TOOL_RANK.map((t) => t.name);
|
||||
const MODEL_NAMES = MODELS.map((m) => m.name);
|
||||
const COMPANY_NAMES = PROJECTS.map((p) => p[0]);
|
||||
|
||||
/* ---------- 工具函数 ---------- */
|
||||
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||
const fmtInt = (n) => Math.round(n).toLocaleString('zh-CN');
|
||||
const fmtWan = (n) => (n >= 10000 ? (n / 10000).toFixed(2) + '亿' : n >= 100 ? n.toFixed(1) + '万' : n.toFixed(1));
|
||||
const nowTime = () => new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||
|
||||
function makeSeries(base, growth, noise, n = 30) {
|
||||
const arr = [];
|
||||
let v = base;
|
||||
for (let i = 0; i < n; i++) {
|
||||
v = v * (1 + growth) + (Math.random() - 0.5) * noise;
|
||||
arr.push(Math.max(1, Math.round(v * 100) / 100));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/* 实时动态事件模板 */
|
||||
function genEvent() {
|
||||
const r = Math.random;
|
||||
const pool = [
|
||||
{ icon: 'bolt', text: `AI 推理任务完成 · 消耗 ${Math.round(800 + r() * 9000).toLocaleString()} tokens(${pick(MODEL_NAMES)})` },
|
||||
{ icon: 'wrench', text: `工具「${pick(TOOL_NAMES)}」被调用 ${Math.round(10 + r() * 90)} 次` },
|
||||
{ icon: 'building', text: `「${pick(COMPANY_NAMES)}」提交入驻申请 · 进入评审流程` },
|
||||
{ icon: 'users', text: `「${pick(COMPANY_NAMES)}」新增招聘岗位 ${Math.round(1 + r() * 5)} 个` },
|
||||
{ icon: 'coin', text: `园区企业完成一笔 ¥${(0.5 + r() * 9).toFixed(1)}万 交易` },
|
||||
{ icon: 'broadcast', text: `创业沙龙「AI 一人公司实战课」报名开启` },
|
||||
{ icon: 'robot', text: `「${pick(MODEL_NAMES)}」模型完成一次微调任务` },
|
||||
{ icon: 'flask', text: `在孵项目完成实验验证 · 进入加速评审` },
|
||||
];
|
||||
const e = pick(pool);
|
||||
return { id: Date.now() + Math.random(), icon: e.icon, text: e.text, time: nowTime() };
|
||||
}
|
||||
|
||||
function initFeed() {
|
||||
return [
|
||||
{ id: 1, icon: 'bolt', text: '云南派音AI 完成音频向量嵌入任务 · 消耗 12,480 tokens(DeepSeek-V3)', time: '09:02:14' },
|
||||
{ id: 2, icon: 'building', text: '「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间', time: '09:05:41' },
|
||||
{ id: 3, icon: 'wrench', text: '工具「文档生成」被调用 36 次', time: '09:11:03' },
|
||||
{ id: 4, icon: 'coin', text: '园区企业完成一笔 ¥3.6万 交易', time: '09:17:26' },
|
||||
{ id: 5, icon: 'users', text: '「昆明舒护安养老服务」新增招聘岗位 2 个', time: '09:23:58' },
|
||||
];
|
||||
}
|
||||
|
||||
/* ---------- 模拟数据引擎(每 2.2s 跳动) ---------- */
|
||||
function initSnapshot() {
|
||||
return {
|
||||
t: 0,
|
||||
token: { today: 128.64, total: 12840, rate: 84.6, series: makeSeries(82, 0.012, 9) },
|
||||
tools: { today: 3568, total: 365204, success: 98.7 },
|
||||
projects: { inPark: 158, cum: 208, todayNew: 2 },
|
||||
jobs: { total: 2186, todayNew: 3 },
|
||||
revenue: { today: 38.6, total: 20800, growth: 8.2, series: makeSeries(30, 0.006, 4) },
|
||||
park: { devices: 98.6, energy: 386, people: 127, desk: 76, meeting: 3, nodes: 12 },
|
||||
feed: initFeed(),
|
||||
};
|
||||
}
|
||||
|
||||
function nextSnapshot(s) {
|
||||
const r = Math.random;
|
||||
const tDeltaWan = +(0.26 + r() * 0.34).toFixed(2); // 万 tokens
|
||||
|
||||
const token = {
|
||||
today: +(s.token.today + tDeltaWan).toFixed(2),
|
||||
total: +(s.token.total + tDeltaWan).toFixed(2),
|
||||
rate: +(76 + r() * 20).toFixed(1),
|
||||
};
|
||||
const toolDelta = Math.round(13 + r() * 22);
|
||||
const tools = {
|
||||
today: s.tools.today + toolDelta,
|
||||
total: s.tools.total + toolDelta,
|
||||
success: +(98.1 + r() * 1.2).toFixed(1),
|
||||
};
|
||||
const revDelta = +(0.6 + r() * 1.7).toFixed(1);
|
||||
const revenue = {
|
||||
...s.revenue,
|
||||
today: +(s.revenue.today + revDelta).toFixed(1),
|
||||
total: +(s.revenue.total + revDelta).toFixed(1),
|
||||
growth: +(7.2 + r() * 2.2).toFixed(1),
|
||||
};
|
||||
const park = {
|
||||
devices: +(97.6 + r() * 1.6).toFixed(1),
|
||||
energy: Math.round(320 + r() * 130),
|
||||
people: Math.round(80 + r() * 95),
|
||||
desk: Math.round(62 + r() * 24),
|
||||
meeting: Math.round(2 + r() * 4),
|
||||
nodes: 12,
|
||||
};
|
||||
|
||||
const projects = { ...s.projects };
|
||||
if (r() < 0.055) projects.todayNew += 1;
|
||||
if (r() < 0.035) { projects.inPark += 1; projects.cum += 1; }
|
||||
const jobs = { ...s.jobs };
|
||||
if (r() < 0.08) jobs.todayNew += 1;
|
||||
if (r() < 0.05) jobs.total += 1;
|
||||
|
||||
// 30 日曲线:今日点持续爬升,每 12 个 tick 滚动一天
|
||||
let tSeries = s.token.series.slice();
|
||||
tSeries[tSeries.length - 1] = +(tSeries[tSeries.length - 1] + tDeltaWan).toFixed(2);
|
||||
if (s.t % 12 === 11) tSeries = [...tSeries.slice(1), +(72 + r() * 30).toFixed(2)];
|
||||
token.series = tSeries;
|
||||
|
||||
let rSeries = s.revenue.series.slice();
|
||||
rSeries[rSeries.length - 1] = +(rSeries[rSeries.length - 1] + revDelta).toFixed(1);
|
||||
if (s.t % 12 === 11) rSeries = [...rSeries.slice(1), +(26 + r() * 9).toFixed(1)];
|
||||
revenue.series = rSeries;
|
||||
|
||||
const feed = s.t % 3 === 2 ? [genEvent(), ...s.feed].slice(0, 7) : s.feed;
|
||||
|
||||
return { ...s, t: s.t + 1, token, tools, revenue, park, projects, jobs, feed };
|
||||
}
|
||||
|
||||
function useParkSim() {
|
||||
const [snap, setSnap] = useState(initSnapshot);
|
||||
useEffect(() => {
|
||||
const iv = setInterval(() => setSnap((p) => nextSnapshot(p)), 2200);
|
||||
return () => clearInterval(iv);
|
||||
}, []);
|
||||
return snap;
|
||||
}
|
||||
/* 数据源:后端 /api/dashboard/snapshot(离线时本地模拟兜底),见 utils/parkData.js */
|
||||
|
||||
/* ---------- 通用 Hooks ---------- */
|
||||
|
||||
|
||||
@@ -292,6 +292,47 @@ export default function MediaScreen() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// MQTT 控制通道(后端 opc/display/command → window 事件)
|
||||
useEffect(() => {
|
||||
const handler = (e) => {
|
||||
const msg = e.detail || {};
|
||||
switch (msg.action) {
|
||||
case 'play':
|
||||
setPaused(false);
|
||||
break;
|
||||
case 'pause':
|
||||
setPaused(true);
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
if (videoRef.current) videoRef.current.pause();
|
||||
break;
|
||||
case 'next':
|
||||
setPaused(false);
|
||||
skip(1);
|
||||
break;
|
||||
case 'prev':
|
||||
setPaused(false);
|
||||
skip(-1);
|
||||
break;
|
||||
case 'set_mode':
|
||||
if (msg.params && msg.params.mode) setPlayMode(msg.params.mode);
|
||||
break;
|
||||
case 'minimize':
|
||||
getCurrentWindow().minimize().catch(() => {});
|
||||
break;
|
||||
case 'playlist_changed':
|
||||
reloadPlaylist();
|
||||
break;
|
||||
case 'settings_changed':
|
||||
applySettings(msg.params || msg);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
window.addEventListener('dpm:media-control', handler);
|
||||
return () => window.removeEventListener('dpm:media-control', handler);
|
||||
}, [skip, reloadPlaylist, applySettings]);
|
||||
|
||||
const enableSound = useCallback(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
@@ -2137,6 +2137,195 @@
|
||||
.bd-msg { max-width: 94%; }
|
||||
}
|
||||
|
||||
/* ============ 全局覆盖层(后端/AI 指令:通知 + 卡片) ============ */
|
||||
.bd-overlay {
|
||||
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
.bd-overlay-alert {
|
||||
position: fixed;
|
||||
top: 74px;
|
||||
right: 18px;
|
||||
z-index: 3000;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
max-width: 360px;
|
||||
padding: 12px 14px;
|
||||
background: rgba(255,255,255,0.92);
|
||||
border: 1px solid rgba(47,107,255,0.22);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 30px rgba(47,107,255,0.16);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
animation: bdOverlayIn 0.3s ease;
|
||||
}
|
||||
.bd-overlay-alert-ico {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan));
|
||||
}
|
||||
.bd-overlay-alert-body {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
.bd-overlay-alert-body b {
|
||||
font-size: 13px;
|
||||
color: var(--bd-ink);
|
||||
}
|
||||
.bd-overlay-alert-body span {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.5;
|
||||
color: var(--bd-sub);
|
||||
}
|
||||
.bd-overlay-close {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: rgba(120,160,220,0.12);
|
||||
color: var(--bd-sub);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.bd-overlay-close:hover {
|
||||
color: var(--bd-red);
|
||||
background: rgba(240,68,56,0.1);
|
||||
}
|
||||
@keyframes bdOverlayIn {
|
||||
from { opacity: 0; transform: translateY(-8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.bd-overlay-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(29,44,68,0.28);
|
||||
backdrop-filter: blur(3px);
|
||||
-webkit-backdrop-filter: blur(3px);
|
||||
animation: bdOverlayIn 0.25s ease;
|
||||
}
|
||||
.bd-overlay-card {
|
||||
width: min(680px, 86vw);
|
||||
max-height: 78vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: rgba(255,255,255,0.94);
|
||||
border: 1px solid rgba(120,160,220,0.25);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 24px 60px rgba(29,44,68,0.22);
|
||||
}
|
||||
.bd-overlay-card-head {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 18px;
|
||||
background: linear-gradient(180deg, rgba(76,194,255,0.08), rgba(255,255,255,0));
|
||||
border-bottom: 1px solid rgba(120,160,220,0.14);
|
||||
}
|
||||
.bd-overlay-card-ico {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan));
|
||||
}
|
||||
.bd-overlay-card-head b {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 15px;
|
||||
color: var(--bd-ink);
|
||||
}
|
||||
.bd-overlay-card-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 16px 18px;
|
||||
}
|
||||
.bd-overlay-card-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.bd-overlay-card-group-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: var(--bd-sub);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.bd-overlay-card-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.bd-overlay-card-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 6px 11px;
|
||||
font-size: 11.5px;
|
||||
color: #33415c;
|
||||
background: rgba(255,255,255,0.85);
|
||||
border: 1px solid rgba(120,160,220,0.18);
|
||||
border-radius: 9px;
|
||||
}
|
||||
.bd-overlay-card-chip b {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 10.5px;
|
||||
font-weight: 700;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
.bd-overlay-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
.bd-overlay-card-stat {
|
||||
padding: 10px 12px;
|
||||
background: rgba(255,255,255,0.85);
|
||||
border: 1px solid rgba(120,160,220,0.16);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.bd-overlay-card-stat span {
|
||||
font-size: 10.5px;
|
||||
color: var(--bd-sub);
|
||||
}
|
||||
.bd-overlay-card-stat b {
|
||||
font-size: 16px;
|
||||
color: var(--bd-blue);
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
.bd-overlay-card-text {
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: #33415c;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* 页眉左右切换按钮(← 左页 · 右页 →,可点击) */
|
||||
.bd-nav-keys {
|
||||
display: inline-flex;
|
||||
|
||||
+3
-12
@@ -1,15 +1,6 @@
|
||||
// API 基地址
|
||||
// 同源请求(hostname=localhost/127.0.0.1):`''` 相对路径,开发模式或 macOS Tauri
|
||||
// Windows Tauri(hostname 含 "tauri"):后端在本机,固定 http://localhost:10801
|
||||
// 局域网访问(hostname 为 LAN IP):后端在同一台机器的 10801 端口
|
||||
const hostname = window.location.hostname;
|
||||
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1';
|
||||
const isTauri = hostname.includes('tauri');
|
||||
const BASE = isLocal
|
||||
? ''
|
||||
: isTauri
|
||||
? 'http://localhost:10801'
|
||||
: `http://${hostname}:10801`;
|
||||
// API 基地址 —— 独立 FastAPI 后端(配置见 src/config.js,可用 window.__DPM_API__ 覆盖)
|
||||
import { API_BASE as BASE } from '../config';
|
||||
|
||||
export const API_BASE = BASE;
|
||||
|
||||
async function request(url, options = {}) {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import mqtt from 'mqtt';
|
||||
import { MQTT_URL, MQTT_TOPIC_COMMAND, MQTT_TOPIC_TICK, MQTT_USERNAME, MQTT_PASSWORD } from '../config';
|
||||
|
||||
/* =========================================================
|
||||
MQTT 客户端(mqtt.js)—— 后端控制通道
|
||||
订阅 opc/display/command(页面/媒体/卡片控制)与 opc/dashboard/tick(数据快照)
|
||||
断线自动重连;未连接时静默降级(页面仍可本地操作)
|
||||
========================================================= */
|
||||
|
||||
let client = null;
|
||||
let status = 'connecting'; // connecting | connected | offline
|
||||
const listeners = new Set();
|
||||
|
||||
function notifyStatus() {
|
||||
window.dispatchEvent(new CustomEvent('dpm:mqtt-status', { detail: { status } }));
|
||||
}
|
||||
|
||||
export function connectMqtt() {
|
||||
if (client) return;
|
||||
try {
|
||||
client = mqtt.connect(MQTT_URL, {
|
||||
reconnectPeriod: 3000,
|
||||
connectTimeout: 8000,
|
||||
clientId: `dpm-screen-${Math.random().toString(16).slice(2, 8)}`,
|
||||
clean: true,
|
||||
username: MQTT_USERNAME || undefined,
|
||||
password: MQTT_PASSWORD || undefined,
|
||||
});
|
||||
client.on('connect', () => {
|
||||
status = 'connected';
|
||||
notifyStatus();
|
||||
client.subscribe([MQTT_TOPIC_COMMAND, MQTT_TOPIC_TICK], { qos: 1 });
|
||||
});
|
||||
client.on('reconnect', () => {
|
||||
status = 'connecting';
|
||||
notifyStatus();
|
||||
});
|
||||
client.on('close', () => {
|
||||
status = 'offline';
|
||||
notifyStatus();
|
||||
});
|
||||
client.on('error', () => {
|
||||
status = 'offline';
|
||||
notifyStatus();
|
||||
});
|
||||
client.on('message', (topic, payload) => {
|
||||
let data = null;
|
||||
try {
|
||||
data = JSON.parse(payload.toString());
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
listeners.forEach((fn) => fn(topic, data));
|
||||
});
|
||||
} catch (e) {
|
||||
status = 'offline';
|
||||
notifyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
export function getMqttStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/** 订阅 MQTT 消息,返回取消订阅函数 */
|
||||
export function onMqttMessage(fn) {
|
||||
listeners.add(fn);
|
||||
return () => listeners.delete(fn);
|
||||
}
|
||||
|
||||
/** React Hook:MQTT 连接状态 */
|
||||
export function useMqttStatus() {
|
||||
const [s, setS] = useState(status);
|
||||
useEffect(() => {
|
||||
connectMqtt();
|
||||
const on = (e) => setS(e.detail.status);
|
||||
window.addEventListener('dpm:mqtt-status', on);
|
||||
return () => window.removeEventListener('dpm:mqtt-status', on);
|
||||
}, []);
|
||||
return s;
|
||||
}
|
||||
+19
-4
@@ -1,8 +1,10 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { API_BASE } from '../config';
|
||||
|
||||
/* =========================================================
|
||||
园区模拟数据引擎(共享)—— 供数据大屏 / 媒体播放页复用
|
||||
每 2.2s 跳动一次,保持页眉与园区概览页脚数据一致
|
||||
园区数据引擎(共享)—— 供数据大屏 / 媒体播放页复用
|
||||
数据来自 FastAPI 后端 GET /api/dashboard/snapshot(每 2.2s 刷新)
|
||||
后端不可用时回退本地模拟引擎,保证离线可用
|
||||
========================================================= */
|
||||
|
||||
export const fmtInt = (n) => Math.round(n).toLocaleString('zh-CN');
|
||||
@@ -125,8 +127,21 @@ function nextSnapshot(s) {
|
||||
export function useParkSim() {
|
||||
const [snap, setSnap] = useState(initSnapshot);
|
||||
useEffect(() => {
|
||||
const iv = setInterval(() => setSnap((p) => nextSnapshot(p)), 2200);
|
||||
return () => clearInterval(iv);
|
||||
let cancelled = false;
|
||||
const load = async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/dashboard/snapshot`, { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = await res.json();
|
||||
if (!cancelled && data && data.token) setSnap(data);
|
||||
} catch {
|
||||
// 后端不可用:本地模拟引擎继续推进(离线兜底)
|
||||
if (!cancelled) setSnap((p) => nextSnapshot(p));
|
||||
}
|
||||
};
|
||||
load();
|
||||
const iv = setInterval(load, 2200);
|
||||
return () => { cancelled = true; clearInterval(iv); };
|
||||
}, []);
|
||||
return snap;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { onMqttMessage, connectMqtt } from './mqtt';
|
||||
import { PAGE_ORDER } from './pageNav';
|
||||
|
||||
/* =========================================================
|
||||
MQTT 控制 —— 后端通过 opc/display/command 控制大屏
|
||||
· navigate / navigate_rel:页面切换(与方向键同一通道)
|
||||
· play / pause / next / prev / set_mode:媒体控制
|
||||
· alert:全局通知
|
||||
· show_card:展示信息卡片(AI 工具调用 / 管理端指令)
|
||||
========================================================= */
|
||||
|
||||
const PAGE_ALIAS = {
|
||||
home: '/', '数据大屏': '/', '首页': '/',
|
||||
twin: '/twin', '数字孪生': '/twin', '3d': '/twin',
|
||||
ai: '/ai', 'ai助手': '/ai',
|
||||
screen: '/screen', '媒体轮播': '/screen', media: '/screen',
|
||||
};
|
||||
|
||||
function resolvePage(page) {
|
||||
if (!page) return null;
|
||||
if (PAGE_ALIAS[String(page).toLowerCase()] !== undefined) return PAGE_ALIAS[String(page).toLowerCase()];
|
||||
if (PAGE_ORDER.some((p) => p.path === page)) return page;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function useMqttControl() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
connectMqtt();
|
||||
const off = onMqttMessage((topic, cmd) => {
|
||||
if (topic !== 'opc/display/command' || !cmd || !cmd.action) return;
|
||||
|
||||
switch (cmd.action) {
|
||||
case 'navigate': {
|
||||
const path = resolvePage(cmd.params?.page);
|
||||
if (path) navigate(path);
|
||||
break;
|
||||
}
|
||||
case 'navigate_rel': {
|
||||
const delta = Number(cmd.params?.delta ?? 1) || 1;
|
||||
const idx = PAGE_ORDER.findIndex((p) => p.path === location.pathname);
|
||||
if (idx >= 0) {
|
||||
const n = PAGE_ORDER.length;
|
||||
navigate(PAGE_ORDER[(idx + delta + n) % n].path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'alert':
|
||||
window.dispatchEvent(new CustomEvent('dpm:alert', { detail: cmd.params || {} }));
|
||||
break;
|
||||
case 'show_card':
|
||||
window.dispatchEvent(new CustomEvent('dpm:show-card', { detail: cmd.params || {} }));
|
||||
break;
|
||||
case 'play':
|
||||
case 'pause':
|
||||
case 'next':
|
||||
case 'prev':
|
||||
case 'set_mode':
|
||||
case 'minimize':
|
||||
case 'settings_changed':
|
||||
case 'playlist_changed':
|
||||
window.dispatchEvent(new CustomEvent('dpm:media-control', { detail: cmd }));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// 离线兜底:AI 对话返回的工具调用在 MQTT 未连接时本地执行
|
||||
const onLocal = (e) => {
|
||||
const { action, params } = e.detail || {};
|
||||
if (action === 'navigate') {
|
||||
const path = resolvePage(params?.page);
|
||||
if (path) navigate(path);
|
||||
} else if (action === 'control') {
|
||||
window.dispatchEvent(new CustomEvent('dpm:media-control', { detail: { action: params?.action } }));
|
||||
} else if (action === 'alert') {
|
||||
window.dispatchEvent(new CustomEvent('dpm:alert', { detail: params || {} }));
|
||||
} else if (action === 'show_card') {
|
||||
window.dispatchEvent(new CustomEvent('dpm:show-card', { detail: params || {} }));
|
||||
}
|
||||
};
|
||||
window.addEventListener('dpm:mqtt-local', onLocal);
|
||||
return () => {
|
||||
off();
|
||||
window.removeEventListener('dpm:mqtt-local', onLocal);
|
||||
};
|
||||
}, [navigate, location.pathname]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -157,7 +157,7 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
|
||||
"@babel/runtime@^7.17.8", "@babel/runtime@^7.26.0", "@babel/runtime@^7.29.2":
|
||||
"@babel/runtime@^7.17.8", "@babel/runtime@^7.26.0", "@babel/runtime@^7.29.2", "@babel/runtime@^7.29.7":
|
||||
version "7.29.7"
|
||||
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz"
|
||||
integrity sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==
|
||||
@@ -488,6 +488,13 @@
|
||||
resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
|
||||
"@types/node@*", "@types/node@^20.19.0 || >=22.12.0":
|
||||
version "26.2.0"
|
||||
resolved "https://registry.npmmirror.com/@types/node/-/node-26.2.0.tgz"
|
||||
integrity sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==
|
||||
dependencies:
|
||||
undici-types "~8.3.0"
|
||||
|
||||
"@types/offscreencanvas@^2019.6.4":
|
||||
version "2019.7.3"
|
||||
resolved "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz"
|
||||
@@ -505,6 +512,13 @@
|
||||
dependencies:
|
||||
csstype "^3.2.2"
|
||||
|
||||
"@types/readable-stream@^4.0.0", "@types/readable-stream@^4.0.21":
|
||||
version "4.0.24"
|
||||
resolved "https://registry.npmmirror.com/@types/readable-stream/-/readable-stream-4.0.24.tgz"
|
||||
integrity sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/stats.js@*":
|
||||
version "0.17.4"
|
||||
resolved "https://registry.npmmirror.com/@types/stats.js/-/stats.js-0.17.4.tgz"
|
||||
@@ -527,6 +541,13 @@
|
||||
resolved "https://registry.npmmirror.com/@types/webxr/-/webxr-0.5.24.tgz"
|
||||
integrity sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==
|
||||
|
||||
"@types/ws@^8.18.1":
|
||||
version "8.18.1"
|
||||
resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz"
|
||||
integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@use-gesture/core@10.3.1":
|
||||
version "10.3.1"
|
||||
resolved "https://registry.npmmirror.com/@use-gesture/core/-/core-10.3.1.tgz"
|
||||
@@ -551,6 +572,13 @@
|
||||
"@types/babel__core" "^7.20.5"
|
||||
react-refresh "^0.17.0"
|
||||
|
||||
abort-controller@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz"
|
||||
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
|
||||
dependencies:
|
||||
event-target-shim "^5.0.0"
|
||||
|
||||
base64-js@^1.3.1, base64-js@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz"
|
||||
@@ -568,6 +596,26 @@ bidi-js@^1.0.2:
|
||||
dependencies:
|
||||
require-from-string "^2.0.2"
|
||||
|
||||
bl@^6.0.8:
|
||||
version "6.1.6"
|
||||
resolved "https://registry.npmmirror.com/bl/-/bl-6.1.6.tgz"
|
||||
integrity sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==
|
||||
dependencies:
|
||||
"@types/readable-stream" "^4.0.0"
|
||||
buffer "^6.0.3"
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^4.2.0"
|
||||
|
||||
broker-factory@^3.1.15:
|
||||
version "3.1.15"
|
||||
resolved "https://registry.npmmirror.com/broker-factory/-/broker-factory-3.1.15.tgz"
|
||||
integrity sha512-ko+aWvgNuP49meGrdjUu7rC+Y+Wai3cCPxP3xWwHsHfehFjOh5ZQM2yC4gEB2UddeZ/YXhm0K1eG/L6fxym2Og==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.29.7"
|
||||
fast-unique-numbers "^9.0.27"
|
||||
tslib "^2.8.1"
|
||||
worker-factory "^7.0.50"
|
||||
|
||||
browserslist@^4.24.0, "browserslist@>= 4.21.0":
|
||||
version "4.28.2"
|
||||
resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz"
|
||||
@@ -579,6 +627,11 @@ browserslist@^4.24.0, "browserslist@>= 4.21.0":
|
||||
node-releases "^2.0.36"
|
||||
update-browserslist-db "^1.2.3"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
buffer@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz"
|
||||
@@ -609,6 +662,21 @@ clsx@^2.1.1:
|
||||
resolved "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
|
||||
commist@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmmirror.com/commist/-/commist-3.2.0.tgz"
|
||||
integrity sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==
|
||||
|
||||
concat-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz"
|
||||
integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.0.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
convert-source-map@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz"
|
||||
@@ -645,7 +713,7 @@ date-fns@^4.0.0, date-fns@^4.1.0, date-fns@^4.4.0:
|
||||
resolved "https://registry.npmmirror.com/date-fns/-/date-fns-4.4.0.tgz"
|
||||
integrity sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==
|
||||
|
||||
debug@^4.1.0, debug@^4.3.1:
|
||||
debug@^4.1.0, debug@^4.3.1, debug@^4.3.4, debug@^4.4.1:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz"
|
||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||
@@ -774,6 +842,24 @@ escalade@^3.2.0:
|
||||
resolved "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz"
|
||||
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
|
||||
|
||||
event-target-shim@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz"
|
||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
||||
|
||||
events@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz"
|
||||
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
||||
|
||||
fast-unique-numbers@^9.0.27:
|
||||
version "9.0.27"
|
||||
resolved "https://registry.npmmirror.com/fast-unique-numbers/-/fast-unique-numbers-9.0.27.tgz"
|
||||
integrity sha512-nDA9ADeINN8SA2u2wCtU+siWFTTDqQR37XvgPIDDmboWQeExz7X0mImxuaN+kJddliIqy2FpVRmnvRZ+j8i1/A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.29.2"
|
||||
tslib "^2.8.1"
|
||||
|
||||
fdir@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz"
|
||||
@@ -818,6 +904,11 @@ graceful-fs@^4.2.4:
|
||||
resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
||||
help-me@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmmirror.com/help-me/-/help-me-5.0.0.tgz"
|
||||
integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==
|
||||
|
||||
hls.js@^1.5.17:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.npmmirror.com/hls.js/-/hls.js-1.7.0.tgz"
|
||||
@@ -833,6 +924,16 @@ immediate@~3.0.5:
|
||||
resolved "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz"
|
||||
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
||||
|
||||
inherits@^2.0.3, inherits@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ip-address@^10.1.1:
|
||||
version "10.5.0"
|
||||
resolved "https://registry.npmmirror.com/ip-address/-/ip-address-10.5.0.tgz"
|
||||
integrity sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==
|
||||
|
||||
is-promise@^2.1.0:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.npmmirror.com/is-promise/-/is-promise-2.2.2.tgz"
|
||||
@@ -855,6 +956,11 @@ jiti@^2.7.0, jiti@>=1.21.0:
|
||||
resolved "https://registry.npmmirror.com/jiti/-/jiti-2.7.0.tgz"
|
||||
integrity sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==
|
||||
|
||||
js-sdsl@4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz"
|
||||
integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==
|
||||
|
||||
js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz"
|
||||
@@ -901,6 +1007,11 @@ lightningcss@^1.21.0, lightningcss@1.32.0:
|
||||
lightningcss-win32-arm64-msvc "1.32.0"
|
||||
lightningcss-win32-x64-msvc "1.32.0"
|
||||
|
||||
lru-cache@^10.4.3:
|
||||
version "10.4.3"
|
||||
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz"
|
||||
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
|
||||
|
||||
lru-cache@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz"
|
||||
@@ -930,6 +1041,11 @@ meshoptimizer@~1.1.1:
|
||||
resolved "https://registry.npmmirror.com/meshoptimizer/-/meshoptimizer-1.1.1.tgz"
|
||||
integrity sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==
|
||||
|
||||
minimist@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
motion-dom@^13.0.0:
|
||||
version "13.0.0"
|
||||
resolved "https://registry.npmmirror.com/motion-dom/-/motion-dom-13.0.0.tgz"
|
||||
@@ -950,6 +1066,37 @@ motion@^13.0.0:
|
||||
framer-motion "^13.1.0"
|
||||
tslib "^2.4.0"
|
||||
|
||||
mqtt-packet@^9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.npmmirror.com/mqtt-packet/-/mqtt-packet-9.0.2.tgz"
|
||||
integrity sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==
|
||||
dependencies:
|
||||
bl "^6.0.8"
|
||||
debug "^4.3.4"
|
||||
process-nextick-args "^2.0.1"
|
||||
|
||||
mqtt@^5.15.2:
|
||||
version "5.15.2"
|
||||
resolved "https://registry.npmmirror.com/mqtt/-/mqtt-5.15.2.tgz"
|
||||
integrity sha512-VWZU2CSUY3U3oN0PSBRDE5SNsFi4zqqNeQ/uv3pZWqY3CrBXD/dhd0ZLjlsk5YnebGlrapi4lRVNJPUNQ5aZ3w==
|
||||
dependencies:
|
||||
"@types/readable-stream" "^4.0.21"
|
||||
"@types/ws" "^8.18.1"
|
||||
commist "^3.2.0"
|
||||
concat-stream "^2.0.0"
|
||||
debug "^4.4.1"
|
||||
help-me "^5.0.0"
|
||||
lru-cache "^10.4.3"
|
||||
minimist "^1.2.8"
|
||||
mqtt-packet "^9.0.2"
|
||||
number-allocator "^1.0.14"
|
||||
readable-stream "^4.7.0"
|
||||
rfdc "^1.4.1"
|
||||
socks "^2.8.6"
|
||||
split2 "^4.2.0"
|
||||
worker-timers "^8.0.23"
|
||||
ws "^8.18.3"
|
||||
|
||||
ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
|
||||
@@ -965,6 +1112,14 @@ node-releases@^2.0.36:
|
||||
resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.44.tgz"
|
||||
integrity sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==
|
||||
|
||||
number-allocator@^1.0.14:
|
||||
version "1.0.14"
|
||||
resolved "https://registry.npmmirror.com/number-allocator/-/number-allocator-1.0.14.tgz"
|
||||
integrity sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==
|
||||
dependencies:
|
||||
debug "^4.3.1"
|
||||
js-sdsl "4.3.0"
|
||||
|
||||
path-key@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz"
|
||||
@@ -994,6 +1149,16 @@ potpack@^1.0.1:
|
||||
resolved "https://registry.npmmirror.com/potpack/-/potpack-1.0.2.tgz"
|
||||
integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==
|
||||
|
||||
process-nextick-args@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
process@^0.11.10:
|
||||
version "0.11.10"
|
||||
resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz"
|
||||
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
||||
|
||||
promise-worker-transferable@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmmirror.com/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz"
|
||||
@@ -1047,6 +1212,26 @@ react-use-measure@^2.1.7:
|
||||
resolved "https://registry.npmmirror.com/react/-/react-19.2.6.tgz"
|
||||
integrity sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==
|
||||
|
||||
readable-stream@^3.0.2:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz"
|
||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@^4.2.0, readable-stream@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-4.7.0.tgz"
|
||||
integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==
|
||||
dependencies:
|
||||
abort-controller "^3.0.0"
|
||||
buffer "^6.0.3"
|
||||
events "^3.3.0"
|
||||
process "^0.11.10"
|
||||
string_decoder "^1.3.0"
|
||||
|
||||
require-from-string@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz"
|
||||
@@ -1057,6 +1242,11 @@ reselect@^5.2.0:
|
||||
resolved "https://registry.npmmirror.com/reselect/-/reselect-5.2.0.tgz"
|
||||
integrity sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==
|
||||
|
||||
rfdc@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz"
|
||||
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
|
||||
|
||||
rollup@^4.43.0:
|
||||
version "4.60.3"
|
||||
resolved "https://registry.npmmirror.com/rollup/-/rollup-4.60.3.tgz"
|
||||
@@ -1091,6 +1281,11 @@ rollup@^4.43.0:
|
||||
"@rollup/rollup-win32-x64-msvc" "4.60.3"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
scheduler@^0.27.0:
|
||||
version "0.27.0"
|
||||
resolved "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz"
|
||||
@@ -1118,11 +1313,29 @@ shebang-regex@^3.0.0:
|
||||
resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
smart-buffer@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz"
|
||||
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
|
||||
|
||||
socks@^2.8.6:
|
||||
version "2.8.9"
|
||||
resolved "https://registry.npmmirror.com/socks/-/socks-2.8.9.tgz"
|
||||
integrity sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==
|
||||
dependencies:
|
||||
ip-address "^10.1.1"
|
||||
smart-buffer "^4.2.0"
|
||||
|
||||
source-map-js@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
|
||||
split2@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz"
|
||||
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
|
||||
|
||||
stats-gl@^2.2.8:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.npmmirror.com/stats-gl/-/stats-gl-2.4.2.tgz"
|
||||
@@ -1136,6 +1349,13 @@ stats.js@^0.17.0:
|
||||
resolved "https://registry.npmmirror.com/stats.js/-/stats.js-0.17.0.tgz"
|
||||
integrity sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==
|
||||
|
||||
string_decoder@^1.1.1, string_decoder@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
suspend-react@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.npmmirror.com/suspend-react/-/suspend-react-0.1.3.tgz"
|
||||
@@ -1211,7 +1431,7 @@ troika-worker-utils@^0.52.0:
|
||||
resolved "https://registry.npmmirror.com/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz"
|
||||
integrity sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==
|
||||
|
||||
tslib@^2.4.0:
|
||||
tslib@^2.4.0, tslib@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
@@ -1223,6 +1443,16 @@ tunnel-rat@^0.1.2:
|
||||
dependencies:
|
||||
zustand "^4.3.2"
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz"
|
||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||
|
||||
undici-types@~8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz"
|
||||
integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==
|
||||
|
||||
update-browserslist-db@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz"
|
||||
@@ -1236,6 +1466,11 @@ use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0, use-sync-externa
|
||||
resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz"
|
||||
integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
utility-types@^3.11.0:
|
||||
version "3.11.0"
|
||||
resolved "https://registry.npmmirror.com/utility-types/-/utility-types-3.11.0.tgz"
|
||||
@@ -1277,6 +1512,50 @@ which@^2.0.1:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
worker-factory@^7.0.50:
|
||||
version "7.0.50"
|
||||
resolved "https://registry.npmmirror.com/worker-factory/-/worker-factory-7.0.50.tgz"
|
||||
integrity sha512-hhwc0G+sFwM4qBuhJIUBn2p1Jf8v/FwmLUANBf/Q+Lt2uI8mfIZQhXaZQACodQD4R7Zp6cn/6702bIvNn2puJQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.29.7"
|
||||
fast-unique-numbers "^9.0.27"
|
||||
tslib "^2.8.1"
|
||||
|
||||
worker-timers-broker@^8.0.18:
|
||||
version "8.0.18"
|
||||
resolved "https://registry.npmmirror.com/worker-timers-broker/-/worker-timers-broker-8.0.18.tgz"
|
||||
integrity sha512-FrjzDVX1wKfZN0gRbCFqv8VHuTncG4sbI/WGEg4tSSQeIsnwqg4YBYWMAHYLJtUDEYYmiK65UKFrVMVk2irDSg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.29.7"
|
||||
broker-factory "^3.1.15"
|
||||
fast-unique-numbers "^9.0.27"
|
||||
tslib "^2.8.1"
|
||||
worker-timers-worker "^9.0.15"
|
||||
|
||||
worker-timers-worker@^9.0.15:
|
||||
version "9.0.15"
|
||||
resolved "https://registry.npmmirror.com/worker-timers-worker/-/worker-timers-worker-9.0.15.tgz"
|
||||
integrity sha512-KKUe7lZ/Aignr51H6hOUik8LwTnIgojH/1lwhli8A8qIEIyewogZTpNpMW5B6BF7nmwBOkUoTYgf1H3QShcjSA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.29.7"
|
||||
tslib "^2.8.1"
|
||||
worker-factory "^7.0.50"
|
||||
|
||||
worker-timers@^8.0.23:
|
||||
version "8.0.34"
|
||||
resolved "https://registry.npmmirror.com/worker-timers/-/worker-timers-8.0.34.tgz"
|
||||
integrity sha512-WXL+Dqsm0G6dnC66rQsvM3tPT2adhbqSirWUCZGglkALOr8ocS0KlpU0iuKbjflJOlu3ydZikMEVrYnHOM9suw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.29.7"
|
||||
tslib "^2.8.1"
|
||||
worker-timers-broker "^8.0.18"
|
||||
worker-timers-worker "^9.0.15"
|
||||
|
||||
ws@^8.18.3:
|
||||
version "8.21.3"
|
||||
resolved "https://registry.npmmirror.com/ws/-/ws-8.21.3.tgz"
|
||||
integrity sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==
|
||||
|
||||
yallist@^3.0.2:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz"
|
||||
|
||||
Reference in New Issue
Block a user