feat: Refactor DpmOverlays to use ShowCard for rendering cards and add ToolStatusToast for operation status notifications

- Moved card rendering logic from DpmOverlays to a new ShowCard component for better reusability.
- Introduced ToolStatusToast to display real-time operation statuses in the top right corner.
- Updated PageHeader to conditionally render credits based on the current path.
- Modified PromptPanel to change tool names and update prompt titles.
- Enhanced ScreenLayout to include ToolStatusToast.
- Updated styles for new components and adjusted existing styles for consistency.
- Implemented statusBus utility for dispatching tool status events.
- Updated useMqttControl to integrate tool status notifications during navigation and card display actions.
This commit is contained in:
Pine
2026-08-19 12:28:35 +08:00
parent 81a888f5ca
commit 107727ea5e
29 changed files with 2076 additions and 247 deletions
+9 -2
View File
@@ -5,8 +5,13 @@
工具:navigate(切页) / control(媒体控制) / alert(通知) / show_card(展示卡片)
"""
import json
import logging
from .mqtt import hub
log = logging.getLogger("dpm.ai_tools")
# ---------------- 工具执行 ----------------
def execute_tool(tool):
@@ -35,6 +40,8 @@ def run_tools(tools):
all_ok = True
for tool in tools or []:
ok = bool(execute_tool(tool))
log.info("ai_tools: 执行工具 %s params=%s -> %s", tool.get("type"),
json.dumps(tool.get("params") or {}, ensure_ascii=False), ok)
results.append({"type": tool.get("type"), "ok": ok})
all_ok = all_ok and ok
return results, all_ok
@@ -82,12 +89,12 @@ RULES = [
},
{
"match": ["导师", "培训", "课程", "辅导", "孵化服务", "孵化器"],
"reply": "园区组建创业指导专家团队 50 人(省级绩效材料口径),提供全周期孵化服务:\n\n· 一对一辅导:创业导师定期坐诊,覆盖商业模式、法律财税、市场营销\n· 培训课程:创业训练营、行业专题沙龙、政策解读会\n· 资源对接:技术、供应链、渠道等产业资源链接\n· 公共服务:工商注册、知识产权、法律咨询一站式代办",
"reply": "园区组建创业指导专家团队 50 人,提供全周期孵化服务:\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 在线。",
"reply": "园区 AI 赋能服务是「七位一体」的核心特色:\n\n· 大模型推理:接入 DeepSeek、通义千问、智谱 GLM-4、豆包、讯飞星火等多模型\n· 工具调用:文档生成、数据查询、图像创作、代码执行、语音合成等智能工具\n· 算力与成本:实时提供 token 消耗统计与速率监控\n\n今日园区 AI 推理任务持续运行,服务节点 12/39 在线。",
"tools": [],
},
{
+5 -3
View File
@@ -75,6 +75,7 @@ class Settings:
S2S_HOST = _env("DPM_S2S_HOST", "0.0.0.0")
S2S_PORT = int(_env("DPM_S2S_PORT", "8765"))
S2S_NUM_PIPELINES = int(_env("DPM_S2S_NUM_PIPELINES", "1"))
VISION_LLM_MODEL = _env("DPM_VISION_LLM_MODEL", "qwen3-vl-flash") # 对话开场画面识别(多模态)
S2S_STT_MODEL = _env("DPM_S2S_STT_MODEL", "qwen3-asr-flash-realtime")
S2S_LLM_MODEL = _env("DPM_S2S_LLM_MODEL", "qwen-plus")
S2S_TTS_MODEL = _env("DPM_S2S_TTS_MODEL", "qwen3-tts-flash-realtime")
@@ -83,9 +84,10 @@ class Settings:
S2S_BUILD_RETRY_DELAY = float(_env("DPM_S2S_BUILD_RETRY_DELAY", "10"))
S2S_LOG_LEVEL = _env("DPM_S2S_LOG_LEVEL", "INFO").upper()
# 对外可访问的 s2s 实时语音 WS 地址(供 /api/config 下发;DPM_VOICE_WS 可显式覆盖)
S2S_WS_URL = _env(
"DPM_VOICE_WS",
f"ws://{_env('MQTT_PUBLIC_HOST', _env('MQTT_BROKER_HOST', 'localhost'))}:8765/v1/realtime",
VOICE_WS = _env("DPM_VOICE_WS", "") # 显式指定时优先使用(s2s 独立进程场景)
S2S_WS_URL = (
VOICE_WS
or f"ws://{_env('MQTT_PUBLIC_HOST', _env('MQTT_BROKER_HOST', 'localhost'))}:{S2S_PORT}/v1/realtime"
)
# 语音助手基础提示词(.env 设 DPM_S2S_INSTRUCTIONS 可覆盖;知识库内容由 rag 自动拼接)
S2S_INSTRUCTIONS = _env(
+59 -7
View File
@@ -16,6 +16,13 @@ from .config import settings
log = logging.getLogger("dpm.llm")
def _trunc(s, n=400):
"""日志用截断:超长内容只留前 n 字并标注总长。"""
s = str(s)
return s if len(s) <= n else s[:n] + f"...(共{len(s)}字)"
# macOS 系统 Python 无系统 CA,使用 certifi 提供的根证书
_SSL_CTX = ssl.create_default_context(cafile=certifi.where())
@@ -25,8 +32,14 @@ SYSTEM_PROMPT = (
"回答园区相关问题时,必须严格依据【园区知识库】中的标准资料口径回答,"
"不得使用资料外的数字;涉及运营数据时请区分口径(官方统计/省级绩效累计/AI平台指标),"
"并在需要时调用 get_park_overview 工具获取实时准确数据。"
"当用户要求「切换页面 / 控制播放 / 展示卡片 / 弹出通知」时,必须调用对应工具;"
"当可能需要「切换页面 / 控制播放 / 展示卡片 / 弹出通知」时,必须调用对应工具;"
"其余园区相关问题用简洁、专业的中文回答,可适当使用列表。"
"你必须基于知识库或者工具调用结果回答问题,不能编造数据或内容。"
"如果有不确定的内容,请明确告知用户「我不确定,请咨询园区工作人员」。"
"如果用户询问园区的企业,没有指定的情况下,必须介绍派音人工智能"
"企业信息(企业名称、负责人、入驻分区、简介)一律以【园区知识库·检索命中】中企业名录原文为准;"
"严禁自行编造或补充企业备案号、成立时间、注册资本、投资方、获奖、政府项目等知识库未记载的细节;"
"若用户问到的企业细节在知识库中没有记载,请如实说明「该信息在园区资料中未记载」,不要编造。"
)
# 暴露给大模型的工具定义(执行时经 MQTT 广播到前端)
@@ -102,6 +115,7 @@ _TOOL_MAP = {
def _chat_once(messages, with_tools=True):
log.info("llm: 调用模型 %s(%d 条消息, 带工具=%s)", settings.LLM_MODEL, len(messages), with_tools)
payload = {
"model": settings.LLM_MODEL,
"messages": messages,
@@ -148,16 +162,37 @@ def run_chat(messages):
log.warning("未配置 DASHSCOPE_API_KEY,使用本地规则引擎")
return _fallback(messages)
# 注入园区知识库摘要(标准资料口径),确保回答准确
# 注入园区知识库(标准资料口径):按用户 query 检索命中段落动态注入,
# 替代原 1200 字截断全量注入(避免尾部知识丢失)。无命中时回退 brief()。
system = SYSTEM_PROMPT
kb = ""
retrieved_count = 0
try:
from .rag import brief
kb = brief()
from .rag import brief, retrieve
query = ""
for m in reversed(messages or []):
if m.get("role") in ("user", "me"):
query = (m.get("content") or "").strip()
break
hits = retrieve(query, top_k=4) if query else []
if hits:
retrieved_count = len(hits)
kb = "\n\n".join(f"[{i + 1}] {c}" for i, c in enumerate(hits))
log.info("llm: 知识库检索命中 %d 段(query=%s)", len(hits), _trunc(query, 120))
else:
kb = brief()
if kb:
system += f"\n\n【园区知识库】\n{kb}"
system += f"\n\n【园区知识库 · 检索命中】\n{kb}"
except Exception as e: # noqa: BLE001
log.warning("llm: 知识库注入失败(仅用基础提示词): %s", e)
log.info("llm: ── 对话开始(%d 条输入消息)──", len(messages or []))
log.info("llm: 系统提示词=%s", _trunc(system, 700))
if kb:
log.info("llm: 注入知识库内容=%s", _trunc(kb, 500))
for m in (messages or [])[-5:]:
log.info("llm: 输入[%s] %s", m.get("role"), _trunc(m.get("content", ""), 300))
msgs = [{"role": "system", "content": system}] + [
{"role": m.get("role") == "me" and "user" or m.get("role", "user"), "content": m.get("content", "")}
for m in (messages or [])
@@ -171,6 +206,10 @@ def run_chat(messages):
executed = []
if tool_calls:
log.info("llm: 模型请求调用 %d 个工具:", len(tool_calls))
for tc in tool_calls:
fn = tc.get("function", {})
log.info("llm: 工具 %s args=%s", fn.get("name", ""), _trunc(fn.get("arguments", ""), 300))
for tc in tool_calls:
fn = tc.get("function", {})
name = fn.get("name", "")
@@ -187,7 +226,10 @@ def run_chat(messages):
"content": json.dumps({"ok": True}, ensure_ascii=False),
})
# 工具执行(MQTT 广播),并汇总发布结果
_, all_ok = _exec_all(executed)
results, all_ok = _exec_all(executed)
log.info("llm: 工具执行结果 %s", results)
for t in executed:
log.info("llm: 执行 %s %s", t["type"], json.dumps(t["params"], ensure_ascii=False))
# 二次调用:携带工具结果生成最终回复
try:
data2 = _chat_once(msgs, with_tools=False)
@@ -200,7 +242,17 @@ def run_chat(messages):
if not reply:
reply = "已完成操作。您还可以让我切换页面、控制播放或展示园区卡片。"
return {"reply": reply, "tools": executed, "model": settings.LLM_MODEL, "mqtt_published": all_ok}
log.info("llm: 最终回复=%s", _trunc(reply, 600))
log.info("llm: ── 对话结束 ──")
return {
"reply": reply,
"tools": executed,
"model": settings.LLM_MODEL,
"mqtt_published": all_ok,
"retrieved": retrieved_count > 0,
"retrieved_count": retrieved_count,
"tool_names": [t.get("type") for t in executed if t.get("type")],
}
except urllib.error.HTTPError as e:
log.warning("DashScope HTTP %s: %s", e.code, e.read()[:300])
except Exception as e: # noqa: BLE001
+19
View File
@@ -101,6 +101,25 @@ app.add_api_route("/admin/login", admin_login, methods=["POST"], include_in_sche
app.add_api_route("/admin/logout", admin_logout, methods=["GET"], include_in_schema=False)
# ---------- 客户端安装包下载(必须在 SPA 回退之前注册) ----------
_DOWNLOAD_ROOT = Path(__file__).resolve().parent.parent # backend/
@app.get("/download", include_in_schema=False)
@app.get("/11", include_in_schema=False)
async def download_installer():
"""下载 Windows 客户端安装包(backend/ 下 *.setup.exe,如 云超服..._x64-setup.exe)。"""
for f in sorted(_DOWNLOAD_ROOT.glob("*setup.exe")):
if f.is_file():
return FileResponse(
path=str(f),
filename=f.name,
media_type="application/octet-stream",
)
return JSONResponse({"detail": "安装包不存在"}, status_code=404)
# ---------- 前端静态托管 + SPA 回退(浏览器直接访问 :8000 即可) ----------
@app.get("/{path:path}", include_in_schema=False)
+396 -51
View File
@@ -1,96 +1,430 @@
# -*- coding: utf-8 -*-
"""园区知识库 —— 轻量本地 RAG(embedding + numpy 余弦检索)
"""园区知识库 —— 轻量本地 RAG(embedding + numpy 余弦检索,双路索引)
- 知识源:backend/knowledge/park.md(按标题/段落分块)
- 向量化:dashscope text-embedding-v3(云端 API,索引构建一次后缓存)
- 检索:numpy 余弦 top-k(资料量小,暴力检索足够)
升级为「细粒度分块 + LLM 生成问答对 + 双路向量索引(原句 + 问题)+ 余弦检索返回原文」:
- 知识源:backend/knowledge/park.md(按 H2 节 → 原子事实两级分块)
- 向量化:dashscope text-embedding-v4(云端 API,索引构建一次后缓存,构建时 L2 归一化)
- 问答:复用 llm._chat_once 对每块生成 3-5 个口语化问句,缓存到 kb_qa.json(无 key 自动单路降级)
- 检索:query 同时比对原句向量与问题向量,按块去重,返回 top_k 个原文块
- 提供:
- retrieve(query, top_k) → 相关段落(供对话动态注入)
- brief() → 固定知识摘要(供前端拼进 instructions,静态知识一次注入)
- retrieve(query, top_k) → 检索命中的原文块(对话动态注入)
- full_kb() → 整库原文(小库全量注入,供语音 instructions)
- brief() → 前 N 字摘要(兼容 /api/kb/brief 与检索无命中回退)
"""
import hashlib
import json
import logging
import os
import re
from pathlib import Path
import numpy as np
log = logging.getLogger("dpm.rag")
KB_FILE = Path(__file__).resolve().parent.parent / "knowledge" / "park.md"
INDEX_FILE = Path(__file__).resolve().parent.parent / "knowledge" / "kb_index.json"
EMBED_MODEL = "text-embedding-v3"
_index = None # 缓存 {chunks, embeddings}
def _trunc(s, n=300):
"""日志用截断:超长内容只留前 n 字并标注总长。"""
s = str(s)
return s if len(s) <= n else s[:n] + f"...(共{len(s)}字)"
KB_DIR = Path(__file__).resolve().parent.parent / "knowledge"
KB_GLOB = "*.md" # 知识库 = knowledge/ 下全部 markdown(park.md + opc.md 等)
META_FILE = KB_DIR / "kb_meta.json" # 元数据(JSON,可读):model/hash/chunks/questions/embed_texts/embed_owner
VECTOR_FILE = KB_DIR / "kb_vectors.npy" # 向量矩阵(二进制 numpy float32,快载小体积)
LEGACY_INDEX_FILE = KB_DIR / "kb_index.json" # 旧版单文件索引(迁移后删除)
QA_FILE = KB_DIR / "kb_qa.json"
EMBED_MODEL = "text-embedding-v4"
EMBED_BATCH = 10 # DashScope embedding 单批上限
QA_BATCH = 5 # 每批几块生成问答
CHUNK_MAX = 240 # 单块超过此长度按句拆
_index = None # 缓存 {chunks, questions, embed_texts, embed_owner, embeddings(归一化 np.ndarray)}
def _kb_text() -> str:
"""拼接 knowledge/ 下全部 markdown 内容(按文件名排序,稳定顺序)。"""
parts = []
for f in sorted(KB_DIR.glob(KB_GLOB)):
try:
parts.append(f.read_text("utf-8"))
except OSError as e: # noqa: BLE001
log.warning("rag: 跳过知识文件 %s: %s", f.name, e)
return "\n\n".join(parts)
def _data_hash() -> str:
"""全部知识文件内容签名,用于索引/问答缓存失效判断。"""
return hashlib.sha1(_kb_text().encode("utf-8")).hexdigest()
def _split_long(text: str, limit: int = CHUNK_MAX) -> list[str]:
"""超长块按句号/分号/顿号等边界拆成多块,保留语义完整。"""
if len(text) <= limit:
return [text]
parts: list[str] = []
cur = ""
for seg in re.split(r"(?<=[。!?;、])", text):
if len(cur) + len(seg) > limit and cur:
parts.append(cur.strip())
cur = ""
cur += seg
if cur.strip():
parts.append(cur.strip())
return [p for p in parts if len(p) > 8]
def _chunk_md(text: str) -> list[str]:
"""按标题/段落分块"""
"""两级切分:H2 节标题作上下文前缀 + 节内原子事实。
企业条目(加粗公司名行 `**...|公司名**` + 负责人/简介列表)合并为单个块,
保证「企业名称 + 企业介绍」作为一个整体可被按名召回。
"""
chunks: list[str] = []
cur: list[str] = []
section = ""
para: list[str] = []
company: list[str] | None = None # 正在累积的单个企业块
def flush_para():
nonlocal para
if para:
t = "\n".join(para).strip()
if len(t) > 8:
chunks.extend(_split_long(t))
para = []
def flush_company():
nonlocal company
if company:
t = "\n".join(company).strip()
if len(t) > 8:
chunks.append(t) # 企业块不拆,保证名称+介绍同块
company = None
def add_fact(content: str):
full = f"{section}\n{content}" if section else content
if len(full) > 8:
chunks.extend(_split_long(full))
for line in text.splitlines():
if line.startswith("#"):
if cur:
chunks.append("\n".join(cur).strip())
cur = [line]
else:
cur.append(line)
if cur:
chunks.append("\n".join(cur).strip())
return [c for c in chunks if len(c) > 10]
s = line.rstrip()
if s.startswith("# "): # H1:不产块
flush_para(); flush_company()
continue
if s.startswith("## "): # H2 小节:切换上下文前缀
flush_para(); flush_company()
section = s[3:].strip()
continue
if s.startswith("###"): # H3 子节:上下文(如分区),flush 企业
flush_para(); flush_company()
para.append(s.strip())
continue
stripped = s.strip()
if not stripped:
continue
# 企业条目:加粗公司名行作为单个企业块的起点
if stripped.startswith("**") and "|" in stripped:
flush_para(); flush_company()
company = [stripped]
continue
if company is not None:
# 企业块的跟随行(负责人/简介等列表项)
if stripped.startswith("- ") or stripped.startswith("问:") or re.match(r"^\d+[\.、]\s", stripped):
company.append(stripped)
else:
flush_company()
para.append(stripped)
continue
if stripped.startswith("- "): # 列表项 = 原子事实
flush_para(); add_fact(stripped)
elif re.match(r"^\d+[\.、]\s", stripped): # 编号步
flush_para(); add_fact(stripped)
elif stripped.startswith("问:"): # 单条问答
flush_para(); add_fact(stripped)
else: # 普通段落:累积后整段成块(超长再拆)
para.append(stripped)
flush_para()
flush_company()
return chunks
def _company_name(chunk: str) -> str:
"""从企业块提取企业名称:`**加速1|云南派音人工智能科技有限公司**` → `云南派音人工智能科技有限公司`。"""
m = re.match(r"^\*\*[^|\n]*|([^*]+)\*\*", chunk) or re.match(r"^\*\*([^*]+)\*\*", chunk)
return m.group(1).strip() if m else ""
def _embed(texts: list[str]) -> list[list[float]]:
"""dashscope text-embedding-v3 批量向量化"""
"""dashscope text-embedding-v4 批量向量化(每批 ≤10 条,按 text_index 排序拼接)。"""
import dashscope
api_key = os.environ.get("DASHSCOPE_API_KEY") or ""
resp = dashscope.TextEmbedding.call(
model=EMBED_MODEL,
input=texts,
api_key=api_key,
)
if resp.status_code != 200:
raise RuntimeError(f"embedding API {resp.status_code}: {resp.code} {resp.message}")
out = [e["embedding"] for e in resp.output["embeddings"]]
out: list[list[float]] = []
for i in range(0, len(texts), EMBED_BATCH):
batch = texts[i:i + EMBED_BATCH]
resp = dashscope.TextEmbedding.call(
model=EMBED_MODEL,
input=batch,
api_key=api_key,
)
if resp.status_code != 200:
raise RuntimeError(
f"embedding API {resp.status_code}: {resp.code} {resp.message}"
)
embs = sorted(
resp.output["embeddings"],
key=lambda e: e.get("text_index", e.get("index", 0)),
)
out.extend(e["embedding"] for e in embs)
log.info("rag: embedding 完成 %d 条(%s,每批 %d)", len(texts), EMBED_MODEL, EMBED_BATCH)
return out
def _normalize(vectors: list[list[float]]) -> np.ndarray:
"""转为 float32 np.ndarray 并做 L2 行归一化(点积即余弦相似度)。"""
arr = np.asarray(vectors, dtype=np.float32)
norms = np.linalg.norm(arr, axis=1, keepdims=True)
norms[norms == 0] = 1.0
return arr / norms
# ── LLM 问答生成 ────────────────────────────────────────────────
def _parse_json_array(text: str) -> list:
"""解析模型返回的 JSON 数组:剥 markdown 围栏 → json.loads → 截取首尾方括号兜底。"""
text = (text or "").strip()
if text.startswith("```"):
lines = [l for l in text.splitlines() if not l.strip().startswith("```")]
text = "\n".join(lines).strip()
for candidate in (text,):
try:
return json.loads(candidate)
except Exception:
pass
i = text.find("[")
j = text.rfind("]")
if i != -1 and j > i:
try:
return json.loads(text[i:j + 1])
except Exception:
pass
return []
def _qa_for_batch(chunks: list[str]) -> list[list[str]]:
"""对一批分块,用 LLM 生成每块的 3-5 个口语化问句(输出 JSON 数组,与块一一对应)。"""
from .llm import _chat_once
numbered = "\n\n".join(f"[{i}]\n{c}" for i, c in enumerate(chunks))
def build_prompt():
return (
"下面是园区知识库的若干分块,每块标有 [编号]。"
"请为每一块生成 3-5 个用户可能会问的自然口语化中文问句,"
"覆盖该块全部信息点与常见同义问法。"
f"严格只输出一个 JSON 数组,数组长度必须为 {len(chunks)},"
"第 i 个元素是第 i 块的问题字符串数组。不要输出任何其他文字或解释。\n\n"
+ numbered
)
for attempt in range(2):
try:
data = _chat_once([{"role": "user", "content": build_prompt()}], with_tools=False)
content = data["choices"][0]["message"].get("content") or ""
arr = _parse_json_array(content)
if isinstance(arr, list) and arr:
out: list[list[str]] = []
for i in range(len(chunks)):
qs = arr[i] if i < len(arr) and isinstance(arr[i], list) else []
out.append([str(q).strip() for q in qs if str(q).strip()][:5])
return out
except Exception as e: # noqa: BLE001
log.warning("rag: QA 批生成失败(尝试 %d/%d): %s", attempt + 1, 2, e)
return [[] for _ in chunks]
def _first_line(chunk: str) -> str:
"""取块首行(去标题),用作无问句时的兜底检索问题。"""
for ln in chunk.splitlines():
ln = ln.strip()
if ln and not ln.startswith("#"):
return ln
return chunk[:60]
def _ensure_questions(chunks: list[str], questions) -> list[list[str]]:
"""确保每块都有至少一个检索问题:未生成问句的块,用块首句兜底(保证全量覆盖)。"""
qs: list[list[str]] = [list(q or []) for q in (questions or [])]
if len(qs) < len(chunks):
qs.extend([] for _ in range(len(chunks) - len(qs)))
for i, c in enumerate(chunks):
if not qs[i]:
qs[i] = [_first_line(c)]
return qs
def _load_questions(chunks: list[str]) -> list[list[str]]:
"""加载/生成问答缓存;返回与 chunks 对齐的问题数组(保证每块 ≥1 个问题)。无 key 时单路回退。"""
from .config import settings
if not settings.DASHSCOPE_API_KEY:
log.warning("rag: 无 DASHSCOPE_API_KEY,跳过问答生成(单路检索)")
return _ensure_questions(chunks, [[] for _ in chunks])
h = _data_hash()
if QA_FILE.exists():
try:
data = json.loads(QA_FILE.read_text("utf-8"))
if data.get("data_hash") == h and data.get("chunks") == chunks:
log.info("rag: 问答缓存已加载(%d 块)", len(chunks))
return _ensure_questions(chunks, data["questions"])
except Exception: # noqa: BLE001
log.warning("rag: 问答缓存损坏,重建")
questions: list[list[str]] = []
total = len(chunks)
for i in range(0, total, QA_BATCH):
questions.extend(_qa_for_batch(chunks[i:i + QA_BATCH]))
log.info("rag: 问答生成进度 %d/%d", min(i + QA_BATCH, total), total)
questions = _ensure_questions(chunks, questions)
QA_FILE.write_text(
json.dumps({"data_hash": h, "chunks": chunks, "questions": questions}, ensure_ascii=False),
"utf-8",
)
log.info("rag: 问答缓存已生成(%d 块)-> %s", len(chunks), QA_FILE)
return questions
# ── 双路向量索引 ────────────────────────────────────────────────
def _migrate_legacy():
"""把旧版单文件 kb_index.json(向量内嵌在 JSON)就地转为 kb_vectors.npy + kb_meta.json,再删除旧文件。"""
if not LEGACY_INDEX_FILE.exists() or META_FILE.exists():
return
try:
d = json.loads(LEGACY_INDEX_FILE.read_text("utf-8"))
np.save(VECTOR_FILE, np.asarray(d["embeddings"], dtype=np.float32))
META_FILE.write_text(
json.dumps({
"model": d.get("model"),
"data_hash": d.get("data_hash"),
"chunks": d["chunks"],
"questions": d.get("questions", []),
"embed_texts": d["embed_texts"],
"embed_owner": d["embed_owner"],
}, ensure_ascii=False),
"utf-8",
)
LEGACY_INDEX_FILE.unlink()
log.info("rag: 已迁移旧 kb_index.json -> kb_vectors.npy + kb_meta.json(无重建,未调 API)")
except Exception as e: # noqa: BLE001
log.warning("rag: 旧索引迁移失败(将重建): %s", e)
def _load_index():
"""懒加载索引;缺失时构建并缓存"""
"""构建/加载双路向量索引(原句 + 生成问题)。向量存二进制 .npy,元数据存 JSON。"""
global _index
if _index is not None:
return _index
if INDEX_FILE.exists():
_migrate_legacy()
h = _data_hash()
if META_FILE.exists() and VECTOR_FILE.exists():
try:
_index = json.loads(INDEX_FILE.read_text("utf-8"))
log.info("rag: 知识索引已加载(%d 块)", len(_index["chunks"]))
return _index
data = json.loads(META_FILE.read_text("utf-8"))
valid = (
data.get("model") == EMBED_MODEL
and data.get("data_hash") == h
and len(data.get("embed_texts", [])) == len(data.get("embed_owner", []))
)
if valid:
vectors = np.load(VECTOR_FILE) # 已归一化 float32
if len(vectors) == len(data["embed_texts"]):
_index = {
"chunks": data["chunks"],
"questions": data.get("questions", []),
"embed_texts": data["embed_texts"],
"embed_owner": data["embed_owner"],
"embeddings": vectors,
}
log.info("rag: 双路知识索引已加载(%d 块 → %d 向量, %s)",
len(_index["chunks"]), len(_index["embed_texts"]), EMBED_MODEL)
return _index
log.warning("rag: 索引失效(model/hash/长度不符),重建")
except Exception: # noqa: BLE001
log.warning("rag: 索引损坏,重建")
# 构建
chunks = _chunk_md(KB_FILE.read_text("utf-8"))
log.info("rag: 构建知识索引(%d 块,embedding 中…)", len(chunks))
embs = _embed(chunks)
_index = {"chunks": chunks, "embeddings": embs}
INDEX_FILE.write_text(json.dumps(_index, ensure_ascii=False), "utf-8")
log.info("rag: 知识索引构建完成 -> %s", INDEX_FILE)
chunks = _chunk_md(_kb_text())
questions = _load_questions(chunks)
embed_texts: list[str] = []
embed_owner: list[int] = []
for i, c in enumerate(chunks):
embed_texts.append(c)
embed_owner.append(i)
# 企业块:把「企业名称」作为确定性问题加入索引,保证按名称可召回(名称为问、介绍为答)
cname = _company_name(c)
if cname:
embed_texts.append(cname)
embed_owner.append(i)
for q in questions[i]:
embed_texts.append(q)
embed_owner.append(i)
log.info("rag: 构建双路索引(%d 块 → %d 向量,%s embedding 中…)",
len(chunks), len(embed_texts), EMBED_MODEL)
arr = _normalize(_embed(embed_texts))
_index = {
"chunks": chunks,
"questions": questions,
"embed_texts": embed_texts,
"embed_owner": embed_owner,
"embeddings": arr,
}
np.save(VECTOR_FILE, arr)
META_FILE.write_text(
json.dumps({
"model": EMBED_MODEL,
"data_hash": h,
"chunks": chunks,
"questions": questions,
"embed_texts": embed_texts,
"embed_owner": embed_owner,
}, ensure_ascii=False),
"utf-8",
)
log.info("rag: 双路索引构建完成(%d 块, dim=%d)-> %s + %s",
len(chunks), arr.shape[1], VECTOR_FILE, META_FILE)
return _index
def retrieve(query: str, top_k: int = 3) -> list[str]:
"""检索与 query 最相关的知识段落"""
# ── 对外接口 ────────────────────────────────────────────────────
def retrieve(query: str, top_k: int = 3, threshold: float = 0.3) -> list[str]:
"""双路检索:query 同时比对原句与问题向量,按块去重,返回 top_k 个原文块。"""
if not query or not query.strip():
return []
idx = _load_index()
q = _embed([query])[0]
sims = np.dot(np.array(idx["embeddings"]), np.array(q))
top = np.argsort(-sims)[:top_k]
return [idx["chunks"][int(i)] for i in top if float(sims[int(i)]) > 0.3]
q = _normalize([_embed([query])[0]])[0]
sims = np.dot(idx["embeddings"], q)
order = np.argsort(-sims)
best: dict[int, float] = {} # owner -> 该块最高分
for e in order:
owner = idx["embed_owner"][int(e)]
sc = float(sims[int(e)])
if sc < threshold:
break
if owner not in best:
best[owner] = sc
if len(best) >= top_k:
break
ordered = sorted(best.items(), key=lambda kv: kv[1], reverse=True)
hits = [idx["chunks"][o] for o, _ in ordered]
log.info("rag: retrieve() query=%s top_k=%d threshold=%.2f -> 命中 %d 段",
_trunc(query, 120), top_k, threshold, len(hits))
for j, (o, sc) in enumerate(ordered):
log.info("rag: [%d] %.4f %s", j, sc, _trunc(idx["chunks"][o], 300))
return hits
def brief(max_chars: int = 1200) -> str:
"""固定知识摘要(前端拼进 instructions,静态知识一次注入)"""
"""固定知识摘要(前 N 字;兼容 /api/kb/brief 与检索无命中回退)。"""
idx = _load_index()
out = []
total = 0
@@ -99,17 +433,28 @@ def brief(max_chars: int = 1200) -> str:
break
out.append(c)
total += len(c)
return "\n\n".join(out)
text = "\n\n".join(out)
log.info("rag: brief() 知识库摘要 %d 字 -> %s", total, _trunc(text, 300))
return text
def full_kb() -> str:
"""整库原文(小库全量注入,用于语音 instructions,修复尾部知识丢失)。"""
idx = _load_index()
text = "\n\n".join(idx["chunks"])
log.info("rag: full_kb() 全库 %d 字 / %d 块", len(text), len(idx["chunks"]))
return text
def build_instructions() -> str:
"""语音助手的完整基础提示词 = 配置提示词 + 园区知识库摘要(服务端统一组装)"""
"""语音助手的完整基础提示词 = 配置提示词 + 整库知识(服务端统一组装)。"""
from .config import settings
base = settings.S2S_INSTRUCTIONS.strip()
try:
kb = brief()
kb = full_kb()
if kb:
base += f"\n\n【园区知识库】\n{kb}"
except Exception as e: # noqa: BLE001
log.warning("rag: 知识库拼接失败(仅用基础提示词): %s", e)
log.info("rag: build_instructions() 语音系统提示词=%s", _trunc(base, 600))
return base
+49 -7
View File
@@ -111,23 +111,34 @@ async def get_settings():
@router.get("/api/config")
async def runtime_config():
"""运行配置(供前端启动引导覆盖):MQTT 地址/账号、API 基址
async def runtime_config(request: Request):
"""运行配置(供前端启动引导覆盖):MQTT 地址/账号、API 基址、语音地址
—— 打包部署时展播端从后端拉取,避免构建期写死的局域网 IP 失效
"""
ws = settings.MQTT_WS_URL
# 从 ws://host:port/mqtt 中提取 broker 主机
# 后端可达主机:优先取客户端实际访问本服务的 Host(与 api_base 一致),
# 回退到从 MQTT broker 地址提取。
host = "192.168.1.9"
try:
host = ws.split("://", 1)[1].split(":", 1)[0]
except Exception: # noqa: BLE001
pass
try:
rh = (request.headers.get("host") or "").split(":")[0]
if rh:
host = rh
except Exception: # noqa: BLE001
pass
# s2s 与后端同机启动 → voice_url 默认跟随后端主机;显式 DPM_VOICE_WS 优先
voice_url = settings.S2S_WS_URL
if not settings.VOICE_WS and host:
voice_url = f"ws://{host}:{settings.S2S_PORT}/v1/realtime"
return {
"ok": True,
"mqtt_url": ws,
"mqtt_username": settings.MQTT_USERNAME or "",
"mqtt_password": settings.MQTT_PASSWORD or "",
"voice_url": settings.S2S_WS_URL,
"voice_url": voice_url,
"api_base": f"http://{host}:{settings.PORT}",
"broker_host": host,
}
@@ -418,7 +429,7 @@ AI_GROUPED_QUESTIONS = [
]
AI_OPC_TOOLS = [
"DeepSeek-V3", "通义千问", "ChatGPT", "豆包",
"DeepSeek", "通义千问", "ChatGPT", "豆包",
"Midjourney", "Stable Diffusion", "剪映", "Notion AI",
"WPS AI", "GitHub Copilot",
]
@@ -550,6 +561,28 @@ def vision_frame(body: VisionFrameBody):
return predict_base64(img_b64)
class VisionLlmBody(BaseModel):
image: str = "" # JPEG base64
prompt: str = "" # 可选,自定义识别指令
@router.post("/api/vision/llm")
def vision_llm(body: VisionLlmBody):
"""对话开场画面识别:多模态 LLM(qwen3-vl-flash)识别人数/性别,返回 {ok, people, males, females, desc}。"""
img = (body.image or "").strip()
if not img:
return {"ok": False, "error": "missing image"}
try:
from .vision_llm import analyze_scene
result = analyze_scene(img, body.prompt)
log.info("api: /api/vision/llm -> people=%s males=%s females=%s desc=%s",
result.get("people"), result.get("males"), result.get("females"),
(result.get("desc") or "")[:60])
return result
except Exception as e: # noqa: BLE001
log.warning("vision llm 失败: %s", e)
return {"ok": False, "error": str(e)}
# ==================== 智能体工具调用 + 知识库 ====================
class ToolsExecBody(BaseModel):
name: str = ""
@@ -563,6 +596,9 @@ def tools_exec(body: ToolsExecBody):
return {"ok": False, "error": "missing tool name"}
from .tools import exec_tool
result = exec_tool(name, body.args or {})
log.info("api: /api/tools/exec name=%s args=%s result=%s", name,
json.dumps(body.args or {}, ensure_ascii=False)[:300],
str(result)[:400])
return {"ok": True, "result": result}
@router.get("/api/kb/brief")
@@ -570,7 +606,9 @@ def kb_brief(max_chars: int = 1200):
"""园区知识摘要(前端拼进对话 instructions)"""
try:
from .rag import brief
return {"ok": True, "brief": brief(max_chars=max_chars)}
result = brief(max_chars=max_chars)
log.info("api: /api/kb/brief max_chars=%d -> %s", max_chars, str(result)[:300])
return {"ok": True, "brief": result}
except Exception as e: # noqa: BLE001
log.warning("kb brief 失败: %s", e)
return {"ok": False, "error": str(e)}
@@ -583,6 +621,7 @@ def kb_retrieve(q: str = "", top_k: int = 3):
try:
from .rag import retrieve
chunks = retrieve(q, top_k=top_k)
log.info("api: /api/kb/retrieve q=%s top_k=%d -> %d 段", q[:100], top_k, len(chunks))
return {"ok": True, "chunks": chunks}
except Exception as e: # noqa: BLE001
log.warning("kb retrieve 失败: %s", e)
@@ -593,7 +632,10 @@ def s2s_instructions():
"""语音助手基础提示词(服务端统一组装:配置提示词 + 园区知识库)"""
try:
from .rag import build_instructions
return {"ok": True, "instructions": build_instructions()}
instructions = build_instructions()
log.info("api: /api/s2s/instructions -> 系统提示词(%d 字)=%s",
len(instructions), instructions[:600])
return {"ok": True, "instructions": instructions}
except Exception as e: # noqa: BLE001
log.warning("s2s instructions 构建失败: %s", e)
return {"ok": False, "error": str(e)}
+2 -2
View File
@@ -99,10 +99,10 @@ FEED = [
{"id": 7, "icon": "trophy", "text": "\"创赢未来\"2026创业大赛昆明选拔赛暨马兰花创业培训讲师大赛举行(13个项目参赛)", "time": "2026-04-22"},
{"id": 8, "icon": "broadcast", "text": "2026年第二期创业企业(项目)入驻招募公告发布(官网 www.kmyc.gov.cn)", "time": "2026-04-15"},
{"id": 21, "icon": "sparkles", "text": "「PineSound」AI驱动音频管理平台上线 pinesound.cn:智能配乐与音效生成,集成100W+音效库、50W+配乐库,全球版权授权(官网)", "time": "2026"},
{"id": 22, "icon": "medal", "text": "「PineSound」发布企业标准 Q/YNPY 001-2026《数字内容创作 配乐通用分类标准》", "time": "2026"},
{"id": 22, "icon": "medal", "text": "PineSound发布企业标准 Q/YNPY 001-2026《数字内容创作 配乐通用分类标准》", "time": "2026"},
{"id": 9, "icon": "award", "text": "2026年第一期入驻评审结果在昆明市人社局官网公示(23个优质项目正式入驻)", "time": "2026-03-27"},
{"id": 10, "icon": "users", "text": "2026年第一期招募集中评审完成:38个申请,23个优质项目正式入驻", "time": "2026-03-24"},
{"id": 19, "icon": "sparkles", "text": "园内企业「云南派音人工智能科技(PineSound)」专注多模态音频技术研发:自研Pine系列模型覆盖音频识别、向量嵌入、音效生成、配乐创作", "time": "2026-04"},
{"id": 19, "icon": "sparkles", "text": "云南派音人工智能科技专注多模态音频技术研发:自研Pine系列模型覆盖音频识别、向量嵌入、音效生成、配乐创作", "time": "2026-04"},
{"id": 20, "icon": "bolt", "text": "「PineSound」2026年4月注册成立,获北京投资支持并吸纳就业4人,入驻加速区(入驻企业信息表)", "time": "2026-04"},
{"id": 11, "icon": "cpu", "text": "云南首个人工智能OPC创新人才基地落地昆明,填补省内个体AI创业培育空白(公开报道)", "time": "2026-03-23"},
{"id": 12, "icon": "medal", "text": "第九届\"春城创业荟\"创业创新大赛圆满闭幕,获奖项目名单公布", "time": "2025-09-30"},
+8 -1
View File
@@ -12,6 +12,12 @@ from datetime import datetime
log = logging.getLogger("dpm.tools")
def _trunc(s, n=800):
"""日志用截断:超长内容只留前 n 字并标注总长。"""
s = str(s)
return s if len(s) <= n else s[:n] + f"...(共{len(s)}字)"
# ── 工具实现 ────────────────────────────────────────────────────────────
def _get_park_overview(args):
@@ -127,7 +133,8 @@ def exec_tool(name: str, args: dict):
return json.dumps({"error": f"未知工具: {name}"}, ensure_ascii=False)
try:
result = t["fn"](args or {})
log.info("tools: %s args=%s -> %s", name, json.dumps(args, ensure_ascii=False)[:120], str(result)[:120])
log.info("tools: 执行工具 %s args=%s", name, json.dumps(args, ensure_ascii=False))
log.info("tools: 结果 -> %s", _trunc(result, 1000))
return result
except Exception as e: # noqa: BLE001
log.error("tools: %s 执行失败 %s", name, e, exc_info=True)
+83
View File
@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
"""对话开场画面识别 —— 用多模态 LLM(qwen3-vl-flash)识别实时画面中的人数、性别等,
并把结果作为上下文注入对话,让 AI 了解当前在场观众。
与 vision_yolo(YOLO 数人脸/姿态)互补:这里用 LLM 做语义级理解(人数、性别构成、场景描述)。
"""
import json
import logging
import ssl
import urllib.request
import certifi
from .config import settings
log = logging.getLogger("dpm.vision")
_SSL_CTX = ssl.create_default_context(cafile=certifi.where())
_DEFAULT_PROMPT = (
"请识别这张实时画面,只输出一个 JSON 对象(不要输出任何其他文字):"
'{"people": 画面中人数(int), "males": 其中男性人数(int), "females": 其中女性人数(int), '
'"desc": 一句话中文描述画面(含大致人数、性别构成、人物大致状态,如年龄/坐站/是否看屏幕)。'
"若画面无人或不确定,则 people=0、males=0、females=0,desc='画面中暂时没有人'。"
)
def analyze_scene(jpeg_b64: str, prompt: str = "") -> dict:
"""调用多模态 LLM 识别画面中人数/性别,返回 {ok, people, males, females, desc}。"""
payload = {
"model": settings.VISION_LLM_MODEL,
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": prompt or _DEFAULT_PROMPT},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{jpeg_b64}"}},
],
}],
}
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=30, context=_SSL_CTX) as resp:
data = json.loads(resp.read().decode("utf-8"))
content = data["choices"][0]["message"].get("content") or ""
return _parse(content)
def _parse(text: str) -> dict:
"""从模型输出中抽取 JSON 对象并规范化为结果 dict。"""
text = (text or "").strip()
if text.startswith("```"):
lines = [l for l in text.splitlines() if not l.strip().startswith("```")]
text = "\n".join(lines).strip()
obj: dict = {}
i, j = text.find("{"), text.rfind("}")
if i != -1 and j > i:
try:
obj = json.loads(text[i:j + 1])
except Exception: # noqa: BLE001
log.warning("vision llm: 返回 JSON 解析失败: %s", text[:120])
people = _as_int(obj.get("people"))
males = _as_int(obj.get("males"))
females = _as_int(obj.get("females"))
return {
"ok": True,
"people": people,
"males": males,
"females": females,
"desc": str(obj.get("desc") or "").strip(),
}
def _as_int(v):
try:
return int(float(v))
except (TypeError, ValueError):
return 0
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+227
View File
@@ -0,0 +1,227 @@
# OPC(一人公司)知识库
> 口径说明:以下政策与数据基于公开资料整理,截至 2026 年 8 月。涉及具体补贴金额、名额、申报条件时,以云南省及昆明市最新官方文件为准;本库作为语义检索与回答口径的参考。
## 一、什么是 OPC(一人公司)
- 定义:一人公司(One Person Company, OPC)指只有一个股东的公司,分为一人有限责任公司和一人股份有限公司两类。
- 法律沿革:传统公司法理论认为公司属社团法人、应有 2 人以上股东;我国自 2005 年修订《公司法》起正式引入一人有限责任公司。
- 时代定位:在 AI 时代,一人公司成为低成本、高效率、普惠型的创业形态——创业者仅凭一人之力,借助 AI 工具即可构建覆盖产品研发、市场营销、客户服务与财务管理的完整业务闭环。
- 核心理念:「你负责商业判断,AI 负责高效执行」,实现从"出卖单次时间"到"经营自动化系统"的跨越。
- 与自由职业的区别:一人公司具备单一股东、有限责任、极简架构等正规企业属性,同时兼具灵活高效、税负更可控等优势。
- 规模:截至 2025 年 6 月,全国一人有限责任公司已超过 1600 万家。
## 二、新《公司法》对一人公司的调整(2024 年 7 月 1 日施行)
- 总特征:准入放宽与责任强化并存。
(准入放宽)
- 允许设立一人股份有限公司:设立股份有限公司应有一人以上二百人以下发起人,一名股东也可设立股份有限公司。
- 取消一人股东数量限制:删除旧法"一个自然人只能投资设立一个一人有限责任公司,且该一人公司不能投资设立新的一人公司"的规定;一个自然人可设多个一人公司,可形成投资链条。
- 放宽股东类型:删除"一人公司股东只能是自然人或者法人"的限制,个体工商户、合伙企业、个人独资企业等理论上也可作股东设立一人公司。
- 删除专节规定:不再采用"一人有限责任公司"或"一人股份有限公司"的特殊表述,统一为"只有一个股东的公司",纳入普通公司规制。
- 删除年度审计强制要求:不再强制一人公司每年编制财务会计报告并审计。
- 删除登记特别注明:不再要求在登记及营业执照中注明"自然人独资"或"法人独资"。
(责任强化,需重点注意)
- 举证责任倒置:新《公司法》第 23 条第 3 款规定,只有一个股东的公司,股东不能证明公司财产独立于股东自己财产的,应对公司债务承担连带责任。
- 财产混同是主要认定依据:司法实践中,公司账户与股东个人账户资金往来频繁是财产混同的主要认定依据。
- AI 时代"数字资产混同"风险:用个人名义注册付费订阅 AI 大模型、购买云端算力、将训练数据与生成物存于个人账号,一旦发生纠纷,可能被认定公司缺乏独立财产基础,判令股东承担连带责任。
- 不设股东会时须书面决定:股东就利润分配、亏损弥补、修改章程等事项,须以书面形式决定,签名或盖章后置备于公司。
(合规建议)
- 建立健全财务管理制度,账目清晰、独立核算,严格区分公司与个人财产界限。
- 适时出具连续完整的会计年度审计报告,或股东与公司往来专项审计报告,作为财产独立性的有力证据。
- 将合规视野从"财务隔离"拓展到"数据与数字资产隔离"。
## 三、OPC 创业思路与经验
(方法论核心)
- 先完成,再完美:AI 已把门槛拆掉,不要等学会所有技能再开始。
- 最小成本验证模式:用 AI 思考与决策,用外包解决人力,用最小成本验证商业假设。
- 聚焦四力:产品能力(解决什么问题)、流量能力(客户如何找到你)、交付能力(如何高效完成服务)、变现能力(如何设计商业模式)。
(盈利三把钥匙与商业模式)
- 盈利三把钥匙:用 AI 降本增效、用信任创造溢价、用平台借势腾飞。
- 六大主流商业模式:知识付费(课程/咨询/私教)、内容变现(公众号/小红书/抖音)、服务外包(代运营/代写/代设计)、工具产品(模板/插件/SaaS)、社群运营(付费社群/会员制)、混合模式。
(创业路径建议)
- 从盘点个人技能 → 验证市场需求 → 找准差异化定位 → 用极低成本测试 MVP → 搭建自动化内容引流矩阵与私域池 → 打造标准化交付质控系统 → 设计高溢价产品矩阵 → 构建抗风险的多元化收入线。
(案例经验)
- 电商:河南创业者卧床 70 天,用 AI 替代原计划的 10 人团队做跨境电商,开出 12 家网店(后优化至 4 家主力店),单店月销 5-8 万、月净利 2.5-3 万,每天只需工作 2-3 小时;用扣子搭工作流自动生成经营日报。
- 内容与服务:无锡创业者 2025 年转型"企业陪跑",帮企业做小红书内容转化;指出 OPC 两大难题是"如何定价"与"如何建立交易机制(个人即产品、前期缺乏标准化交付物)"。
- 全 AI 员工公司:创业者用开源 AI 代理框架创建"零人类公司",业务含卖教程、AI 技能市场抽佣、企业定制代理,几周收入近 20 万美元,运营成本仅约每月 1500 美元。
- 大厂裸辞:35 岁管理者裸辞环游多国,靠 AI 创立一人公司,兼任大模型工具开发者、企业咨询顾问、自媒体博主,年入近百万。
(清醒认知与挑战)
- AI 没有消灭赚钱的门槛:认知、决策能力甚至运气同样重要;没有商业逻辑,再好的工具也没用。
- 理性看待收入神话:网上"一人公司月入十万"的故事大多不靠谱,成功相当程度依赖运气。
- 一人公司的孤独感与亲力亲为:现金流、回款、定价等都需要自己承担。
## 四、云南当前生效的 OPC 超级个体政策
(省级专项:2026 年 7 月云南省人社厅《关于支持服务人工智能 OPC 创业的若干措施》)
- 创业生态社区:鼓励盘活闲置楼宇、高校实训场地等,整合算力、绿电、数据、市场渠道等要素,打造"低成本空间+普惠算力+全链条生态服务"的 AI OPC 创业生态社区;运营效果好的可按创业孵化载体补助政策认定补助。
- 免费工位与安居支持:支持现有创业园区开设 OPC 专区,提供拎包入驻免费工位;有条件地区为 OPC 创业人才提供免租或低租人才公寓、青年驿站。
- 金融支持:"云岭创业贷"——企业法人最高可申请 500 万元信用贷款、3000 万元抵押担保贷款;个人创业者最高可申请 50 万元信用贷款、1000 万元抵押担保贷款;鼓励推出"算力贷""数智创新专项信贷"等专属产品。
- 创业补贴:符合条件者给予 5000 元一次性创业补贴;电商创业项目每个最高补贴 3 万元。
- 见习补贴:吸纳高校毕业生就业见习的按每人每月 1500 元(留用率达 50% 以上可增至 2000 元)发放见习补贴。
- 培训补贴:AI 相关职业(如数据标注员)纳入培训补贴范围,最高可获 1700 元创业培训补贴和 7280 元技能培训补贴。
- 人才政策:符合条件的 AI OPC 创始人纳入"兴滇英才支持计划"等省级人才计划;设立人工智能专业职称评审;打破学历、年龄、资历限制建立人才自主评价机制。
- 政务服务:深化"个人创业一件事"集成办理,实现 OPC 企业登记一表申请、一网联办、一次办结。
(市级:昆明市大学生创业园)
- 面向对象:在校大学生、毕业不超过 8 年的高校毕业生、归国及外籍留学生等青年创业者。
- 入驻方向:项目须聚焦昆明市"10+N"产业链(人工智能、数字经济、电子信息、新媒体直播、生物医药、先进装备、新材料、新能源、绿色食品、现代物流、文化旅游、花卉等);获市级以上创业大赛奖项、有突出学术成就或高新专利者优先。
- 空间保障:免费提供办公工位或独立区域。
- 孵化服务:专家一对一创业指导、商业模式梳理、沙龙路演。
- 融资支持:联动创投机构设立种子资金,每年组织不少于 15 场融资对接活动。
- 政策对接:协助申请创业扶持资金、税收减免、社保补贴。
- 资源整合:对接高校、科研院所、龙头企业。
- 专项赋能:免费提供"AI 育笋计划"和"AI+OPC"全套培训(涵盖 AI 应用、一人公司落地、成本控制等)。
- 综合服务:工商注册、税务代办、知识产权咨询、法律援助一站式支持。
## 五、OPC 的几种创业模式
(OPC 核心范式)
- OPC 的核心是「1 个创始人 + N 个 AI 数字员工」的 1+N 模式:创始人负责战略决策、创意设计与关键价值判断,AI 承担代码生成、内容创作、数据运营、客户服务等标准化重复工作。
- OPC 将创业从「资本密集型」转向「认知密集型」:无需融资和组建团队,只需对领域有深度认知,再用 AI 将其转化为产品、服务或收入。
- 创业门槛从「资源门槛」降为「认知门槛」:代码可用 Claude Code 写、内容可用 AI 生成、跨语言可实时翻译、客户对接可用 AI Agent 筛选。
(六大已跑通的 OPC 商业模式)
- 专业服务型:以深度专业技能为核心价值,交付定制化解决方案或咨询,按项目或小时收费,利润率高。
- 高端服务型:以行业认知与战略判断力为核心,提供高端顾问、私教、战略规划,高客单(一单可达几十万)。
- 知识产品型:将系统化专业知识做成标准化数字产品(课程、电子书、会员),边际交付成本趋近于零,最被看好。
- 数字产品型:可复用的软件或数字工具(SaaS、应用、插件、模板),订阅制或一次性/API 收费,规模化潜力最大。
- 创意内容型:靠个人影响力或 IP,产出博客、视频、社媒内容,通过广告分成、品牌合作、知识付费变现。
- 平台整合型:做资源连接与撮合,自身不生产产品,靠佣金或撮合费。
(如何选择适合你的模式)
- 先想清三个问题:卖「能力」还是卖「系统」?服务边界清不清楚?你是「执行者」还是「系统设计师」?
- 顾问/咨询型适合专业壁垒深厚、客户关系强的人;AI 可解放线索收集、标准问答、方案初稿。
- 内容/知识型放大个人 IP,内容可标准化复用、多平台分发,边际成本趋零,适合有表达与知识结构化能力的人。
- 产品化服务型(SaaS/数字产品)将方法论打包成模板或工具,从「卖服务」转向「卖产品」,长期复利最可观但技术含量最高。
- 热门赛道:健康/医疗/心理支持、硬件/IoT/机器人、教育/培训/知识服务、金融/法律/政务等专业服务、企业服务/SaaS。
## 六、如何成立一家 OPC(一人公司)
(主体选择)
- 先副业后主体:不建议立即辞职,先以副业验证模式。
- 个体工商户注册快、税负低,适合早期验证、接小单;有限责任公司信用强、利于 B 端,适合长期发展,但务必公私分明避免财产混同。
- 若业务面向 B 端长期合作选有限公司,早期验证可选个体户。
(注册流程)
- 公司名称核准:提交 3-5 个备选名称(行政区划+字号+行业+有限公司),经市场监管局官网或「一网通办/一窗通」平台核准。
- 提交设立登记申请:线上提交公司章程、股东身份证明、注册地址证明等,约 3-5 个工作日领取营业执照。
- 刻制印章:公章、财务章、法人章,到公安备案刻章点办理,费用约 500 元。
- 银行开户:预约开基本户,需带执照、公章、法人身份证,年费约 500-1000 元。
- 税务登记:领取执照后 30 日内办理,核定税种、申请发票。
- 新《公司法》允许单人担任法人且无需监事。
(注册资本与地址)
- 实行认缴制,注册时无需实缴全部资金;但须在章程明确认缴期限,且 5 年内实缴到位。
- 认缴≠不缴:切忌盲目追高注册资本,经营不善时股东需在认缴范围内承担责任。
- 注册地址须为商用地址(写字楼、商铺、共享办公工位、集中登记地/虚拟地址等),住宅原则上不可用。
- 可租用园区或共享工位,或用集中登记地/虚拟地址(部分园区免费或年费约 3000-6000 元)。
(记账报税与合规)
- 公司必须建立账簿,即使零收入也须按月或按季申报(零申报),逾期可能被列入经营异常名录。
- 小规模纳税人季度开票≤30 万免增值税(普票);小微企业年应纳税所得额≤300 万按 5% 缴企业所得税(2027 年底前)。
- 一人公司利润分配后转账需缴 20% 分红个税。
- 代理记账费用约 200-500 元/月,起步阶段建议找代账公司。
- 每年 6 月 30 日前登录「国家企业信用信息公示系统」提交年报。
- 严格区分公司财产与股东个人财产,避免公私账户混同,否则股东可能对公司债务承担连带责任。
## 七、如何选用合适的 AI 工具
(选型原则)
- 按「场景适配」选工具,不要盲目追新:没有哪一款模型在所有任务上全面领先。
- OPC 中 AI 首先承担软件开发角色(多数团队用 AI 编程),其次才是内容创作。
- 优先选生态开放、能接 API/插件、支持工作流编排的平台,方便搭建自动化。
(按场景选模型)
- 写代码、推理、性价比选 DeepSeek(开源、API 定价低)或智谱 GLM(编程强、可私有化)。
- 中文通用与云生态选通义千问(阿里云百炼 MaaS、长上下文)。
- 多模态、免费 C 端选豆包(字节),创意内容可用其视频/图像生成。
- 长文档、研报处理选 Kimi(超长上下文)。
- 政企、资质、央国企场景选文心一言(百度,合规资质全)或讯飞星火(语音、信创)。
- 微信生态、快慢思考选腾讯混元。
(Agent 与工作流搭建平台)
- 扣子(Coze):字节推出的零代码 Agent 平台,拖拽式搭建,适合个人/非技术用户,支持豆包、DeepSeek、Kimi、通义千问、GLM 等模型。
- Dify:企业级、技术向,工作流节点丰富,支持国内外主流模型与私有化部署。
- 用无代码平台可把客服、内容、竞品、线索、数据等搭建为智能体,月成本可低至约 170 元。
## 八、AI Agent(智能体)概念与一人公司应用
(概念)
- AI 智能体是一套能自主理解目标、制定计划、执行操作、自我修正的完整闭环系统,不同于普通聊天机器人。
- 核心公式:AI Agent = 大模型 + 记忆 + 主动规划 + 工具使用。
- 三大特征:自主决策(自己判断下一步)、工具调用(操作浏览器、调用 API、读写文件、发邮件)、持续学习(优化策略)。
- 成熟智能体通常含五个核心模块:大语言模型(大脑)、任务规划、记忆存储、工具调用、环境感知。
(如何搭建一个智能体)
- 角色设定:赋予具体人员设置与知识边界,上传历史文档。
- 知识库挂载(RAG):整理笔记、业务 SOP、白皮书,通过文档切片与向量检索让 AI 具备「记忆」。
- 工作流设计:将重复劳动逻辑化,如自媒体运营分身:聚焦热点、生成文案、生成图表、多平台分发。
- 插件与 API 集成:接入搜索、数据库、API 插件,让智能体具备「手脚」。
- 避坑:防止幻觉(强制优先从知识库取答案)、遵循 MVP 原则不过度设计、定期分析对话日志迭代提示词。
(OPC 的 1+N 组织架构)
- 核心层(1 人)只做四件事:定战略、定标准、审关键输出、管现金流。
- AI 智能体层(N 个数字员工):按岗位拆分为独立 Agent,如产品/研发、内容/营销、流量/投放、销售/客服、交付/项目、财务/行政。
- 外部协作层:把设计、剪辑、翻译等 AI 搞不定的重活外包。
(流水线工作流与运营闭环)
- 标准工作流:需求输入、AI 拆解、AI 生成初稿、创始人审核微调、AI 优化排版、成品入库或上线。
- 运营闭环:AI 批量产内容并多平台分发、AI 客服 7×24 响应并自动生成报价单、标准化 SOP 加 AI 自动交付、AI 记账报税。
- 效果:启动成本可从传统创业的 20 万-100 万压缩到每月 500-5000 元;试错周期从半年缩短到 2-6 周。
## 九、国内主要大模型厂商与选型
(市场格局)
- 国内大模型市场呈「三超多强」格局:第一梯队为豆包(字节)、通义千问(阿里)、DeepSeek;第二梯队为智谱 AI、文心一言(百度)、混元(腾讯);第三梯队为 Kimi、MiniMax 等创新企业。
- 在简单问答、常识领域各大模型差异甚小,差异主要体现在复杂指令、多模态理解生成与多轮对话。
(各厂商定位)
- DeepSeek:推理、代码、数学、性价比强,开源(MIT),API 定价低,适合写代码与高性价比推理。
- 通义千问(阿里):通用、中文、企业云生态强,阿里云百炼 MaaS,长上下文,回答完整度突出。
- 豆包(字节):C 端应用、多模态、Agent,日活高、免费多模态,深度逻辑与代码能力相对弱。
- 智谱 GLM:编程、长程 Agent 任务强,开源、长上下文,政企私有化根基扎实。
- 文心一言(百度):全模态、搜索、中文写作,功能完善度领先、合规资质最全。
- Kimi(月之暗面):超长文档、研报解析领先。
- 讯飞星火(科大讯飞):中文、语音交互、教育医疗,央国企与信创环境应用广。
- 混元(腾讯):社交、多模态、快慢思考,微信生态深度整合。
(选型建议)
- 写代码商用:智谱 GLM 或 DeepSeek;长文档处理:Kimi;多模态/免费 C 端:豆包;企业政企落地:智谱、文心一言或通义千问。
- 各家价格口径不统一(有美元/人民币计、缓存命中与上下文位影响单价),比价务必回官网核对同一口径。
- 首期成效:受理 58 个项目,23 个成功入驻,覆盖人工智能、低空经济、跨境服务等领域。
+546
View File
@@ -0,0 +1,546 @@
# 昆明市大学生创业园 · 全量真实数据汇总
> 整理自 `数据资料/` 目录全部原始材料:
> 附件1.省级创业孵化载体相关情况简介材料.xlsx · 昆明市大学生创业园入驻企业信息表.xlsx · 昆明市大学生创业园运行情况统计表(2026年7月).xls · 昆明市大学生创业园创业企业(项目)入驻管理规程.wps · 昆明市创业园宣传册.pdf · 空间布局.png / 详细的布局.png · 昆明市大学生创业园 定稿(品牌设计稿)
>
> 本文件尽量保留原始表格、文件中的真实数值与原文字,供对外申报、总结汇报、宣传推广等使用。
---
## 目录
1. [园区基本信息](#1-园区基本信息)
2. [运营绩效核心指标(省级申报口径)](#2-运营绩效核心指标省级申报口径)
3. [2026年1—7月运行情况月统计](#3-2026年17月运行情况月统计)
4. [入驻企业完整名录(39家/信息表)](#4-入驻企业完整名录39家信息表)
- [A. 孵化加速区(加速1–10)](#a-孵化加速区)
- [B. 国际创客区(国际1–7)](#b-国际创客区)
- [C. 成长区(成长1–8)](#c-成长区)
- [D. OPC创业空间](#d-opc创业空间)
- [E. 一楼直播间](#e-一楼直播间)
5. [省级申报·入驻实体名单(31家)](#5-省级申报入驻实体名单31家)
6. [企业(项目)入驻管理规程要点](#6-企业项目入驻管理规程要点)
7. [空间布局](#7-空间布局)
8. [宣传册核心内容(园区简介·服务·政策)](#8-宣传册核心内容园区简介服务政策)
9. [入驻条件与入驻流程](#9-入驻条件与入驻流程)
10. [孵化周期(四大孵化区域)](#10-孵化周期四大孵化区域)
11. [报名方式](#11-报名方式)
12. [媒体报道与合作媒体](#12-媒体报道与合作媒体)
13. [园区新闻与活动大事记](#13-园区新闻与活动大事记)
14. [品牌VI设计规范](#14-品牌vi设计规范)
---
## 1. 园区基本信息
| 项目 | 内容 |
|---|---|
| 载体名称 | 昆明市大学生创业园 |
| 载体类别 | 省级创业园(2012年获评) |
| 载体地址 | 云南省昆明市官渡区民航路229号(昆明市人力资源中心二楼) |
| 成立时间 | 2009年9月(2026年4月全面优化后“焕新亮相”) |
| 主管部门 | 昆明市人力资源和社会保障局 |
| 运营机构 | 云南汇浙青创园区投资有限公司(统一社会信用代码 91530112MA6PWKTUX2) |
| 申报单位 | 昆明市人力资源和社会保障局 |
| 申报单位负责人 | 黄斌(联系电话 63362979) |
| 运营负责人 | 李云鹏(联系电话 18087147411) |
| 载体特色 | 云南省首家政府主办、为创业大学生免费提供创业平台的开放式园区 |
| 主导产业 | 数字经济与电子信息产业 |
| 载体占地面积 | 3000 平方米 |
| 孵化场所面积 | 800 平方米 |
| 载体资产总值 | 400 万元 |
| 总投资(开园) | 近 100 万元 |
| 累计投入园区运营资金 | 560 万元 |
| 可容纳团队(实体)户数 | 50 户(统计表中某口径为 49 个) |
| 园区等级/称号 | 国家大学科技园体系、省级创业园等(宣传册口径:国家众创空间、国家级科技企业孵化器等称号口径不一,以正式文件为准) |
**园区简介(300字内口径)**
昆明市大学生创业园位于昆明市民航路301号昆明人力资源中心二楼,2009年9月成立,总投资近100万元,是云南省首家免费为创业大学生提供创业平台的开放式园区,隶属于昆明市人力资源和社会保障局,由昆明市大学生创业园服务中心承担日常管理服务。园区总面积约3000平方米,可容纳50个大学生创业团队入驻,为入驻团队提供免费办公场地及配套设施,并配套政策咨询、创业规划、创业指导、创业培训、导师对接、跟踪服务、资金扶持、法律援助等全维度创业支持服务。园区坚持“高起点、高标准、高水平”办园宗旨,遵循“优质、高效、便捷”服务原则与“规范、完善、提高”管理理念,致力打造企业家培育摇篮与全省示范创业园区。截至2026年5月,园区累计孵化企业181家,其中在孵31家、成功出园149家,累计带动就业1529人、上缴利税426.2万元,38家企业累计获市级创业扶持资金149.25万元。
---
## 2. 运营绩效核心指标(省级申报口径)
> 出处:附件1.2 省级创业孵化载体简介(运营绩效情况)
| 指标 | 认定以来累计 | 本年度 | 当前实有 |
|---|---|---|---|
| 可容纳团队(实体)户数 | — | 50 户 | — |
| 已入驻团队(实体)户数 | 181 户 | 31 户 | 31 户 |
| 其中:应届高校毕业生(含离校2年内)参创实体数 | 114 户 | 6 户 | — |
| 团队(实体)出园数 | 150 户 | 11 户 | — |
| 返乡农民工参加创办实体数 | 0 户 | 0 户 | — |
| 入驻团队(实体)发明专利数 | 171 项 | 13 项 | — |
| 入驻团队(实体)经营收入 | 21911.14 万元 | 161.3 万元 | — |
| 入驻团队(实体)税收缴纳金额 | 426.2 万元 | 0.22 万元 | — |
| 入驻团队(实体)获得创业资金扶持金额 | 38 户 | 149.25 万元 | — |
| 孵化创业实体数 | 181 户 | — | 31 户(在孵) |
| 带动就业人数 | 1529 人 | 106 人 | — |
| 运营团队人数 | — | 3 人 | — |
| 创业指导专家团队人数 | — | 50 人 | — |
| 开展创业指导服务活动次数 | 83 场 | 18 场 | — |
| 年末孵化场所利用率 | 90%(0.9) | — | — |
| 近3年入孵实体孵化成功率 | 95.91% | — | — |
| 近3年入孵实体到期出园率 | 100%(1) | — | — |
---
## 3. 2026年1—7月运行情况月统计
> 出处:昆明市大学生创业园运行情况统计表(2026年7月).xls —《昆明市创业平台运行情况月统计表》及《年度汇总表》
### 3.1 2026年7月当月快照
| 指标 | 数值 |
|---|---|
| 园区建筑面积 | 3000 m² |
| 开园以来累计投入园区运营资金 | 560 万元 |
| 园区可容纳企业(实体)个数 | 49 个 |
| 园区实际入驻企业数 | 38 个 |
| 入驻企业累计带动就业人数 | 213 人 |
| 入驻企业累计生产经营总额 | 252.15 万元 |
| 入驻企业累计上缴税利总额 | 1.03 万元 |
| 开园以来累计成功孵化出园企业(实体)数 | 149 户 |
| 开展活动场次 | 11 场 |
| 培训人数 | 362 人 |
> 备注:报表第3、9栏数据为开园以来累计数,第5栏为报送当月实际入驻企业数,其他栏均为当年累计数。
### 3.2 2026年1—7月逐月变化(年度汇总表)
| 月份 | 园区建筑面积(m²) | 累计运营资金(万元) | 可容纳个数 | 实际入驻企业数 | 带动就业(人) | 累计生产经营总额(万元) | 累计上缴税利(万元) | 累计出园(户) | 活动场次 | 培训人数 |
|---|---|---|---|---|---|---|---|---|---|---|
| 1月 | 700 | 510 | 18 | 15 | 73 | 28.3 | 0.04 | 139 | 0 | 0 |
| 2月 | 700 | 510 | 18 | 15 | 73 | 50.4 | 0.08 | 139 | 0 | 0 |
| 3月 | 700 | 560 | 18 | 5 | 73 | 59.1 | 0.09 | 149 | 0 | 0 |
| 4月 | 3000 | 560 | 49 | 24 | 175 | 69.73 | 0.10 | 149 | 0 | 0 |
| 5月 | 3000 | 560 | 49 | 31 | 179 | 161.3 | 0.22 | 149 | 5 | 149 |
| 6月 | 3000 | 560 | 49 | 31 | 186 | 170.4 | 0.35 | 149 | 7 | 206 |
| 7月 | 3000 | 560 | 49 | 38 | 213 | 252.15 | 1.03 | 149 | 11 | 362 |
> 数据说明:4月起园区迁入民航路229号新场地,建筑面积由700㎡增至3000㎡、可容纳数由18个增至49个,运营资金由510万元增至560万元。截至7月底实际入驻38个,累计带动就业213人。
**填报信息**:单位负责人 李云鹏 / 填表人 李禹贤 / 联系电话 18087174891 / 邮箱 714066050@qq.com / 填表时间 2026年7月31日
---
## 4. 入驻企业完整名录(39家/信息表)
> 出处:昆明市大学生创业园入驻企业信息表.xlsx(共39个项目,序号1–39,按“场地安排”列分区分布)
> 分区口径:加速=孵化加速区、国际=国际创客区、成长=成长区、OPC=OPC创业空间、一楼直播间。
### A. 孵化加速区
(加速1—加速8、加速10,共9家;表中无“加速9”)
**加速1|云南派音人工智能科技有限公司**
- 负责人:王思松,2020年毕业于应急管理大学新闻学专业。
- 简介:OPC一人公司,2026年3月启动,入驻不足一个月即获北京相关投资支持,2026年4月正式注册成立,吸纳就业4人。核心业务主攻人工智能算法及音频模型应用研究,兼备内容创作、运营管理与技术研发综合能力,专注多模态音频技术研发。自研Pine全系列模型家族,覆盖音频识别、向量嵌入、音效生成、配乐创作及数字内容创意中枢,贯通音频理解至声音合成全链路智能。
**加速2|米勒克尔蓝宝石珠宝产业化(项目)**
- 负责人:王昱博,2023年3月毕业于美国加州大学圣克鲁兹分校 Multi-/Interdisciplinary 管理学专业。
- 简介:2026年3月启动,海归留学生发起,覆盖米勒克尔大尺寸蓝宝石新材料与珠宝产业化全链条(晶体生长、材料精密加工、终端珠宝设计)。与产业链上下游深度协同,专注高品质、高附加值蓝宝石珠宝产品,推动蓝宝石新材料与珠宝产业深度融合。
**加速3|中泰研学合作(项目)**
- 负责人:刘泽位,泰国西那瓦大学工商管理博士二年级,昆明市侨联青年委员会副会长,精通英语、熟练泰语。
- 简介:2026年启动运营,海归在读博士发起,围绕西那瓦大学文理学院管理运营,统筹泰国环球工商职业学院中国区事务。与中泰院校、机构深度合作,提供出国留学、中外合作办学、校企合作、产教融合及企业出海一站式服务。
**加速4|云南宸中低空经济有限公司**
- 负责人:王爱玲,云南大学EMBA在读,公司负责人。
- 简介:2026年2月成立,扎根园区的创新型初创企业,专注低空经济全产业链开发运营,依托园区创业扶持体系与营商环境,快速完成项目落地与团队组建,致力于成为云南低空经济领域新兴力量。
**加速5|昆明智海银高文化科技有限公司**
- 负责人:宋芃燃,昆明文理学院审计学专业在读,中共党员,公司合伙人。
- 简介:2026年4月成立,聚焦数字化财税与企业咨询服务,覆盖传统财税、AI智能财税、海外财税、财务人才培训及企业综合咨询。依托AIGC技术打造AI集成服务平台,提供财税咨询、代理记账、法律合规、企业管理、出海服务等一站式解决方案。
**加速6|人工智能机器人大模型训练(项目)**
- 负责人:周凯,2022年毕业于天津大学机械设计及其自动化专业。
- 简介:2026年4月成立,聚焦人工智能机器人大模型训练,围绕大模型研发与落地提供全流程数据支撑与技术服务,开展数据采集、清洗、标注、模型训练及优化迭代,推动机器人大模型在多场景落地应用。
**加速7|云南廷秀文旅康养服务(项目)**
- 负责人:李雨桐,2025年毕业于日本樱美林大学老年社会学硕士。
- 简介:负责人计划5月归国创业。立足云南“旅居云南”趋势与银发经济增长需求,解决民宿、旅居空间、康养点位在服务标准、适老能力、长期居住支持和运营转化方面的短板,建立面向银发人群的文旅旅居服务支持体系。
**加速8|瀚颖AI+教育信息咨询(项目)**
- 负责人:王翰啸,2019年毕业于云南大学滇池学院财务管理专业。
- 简介:昆明瀚颖“AI+数字教育”项目2026年4月成立,由创业培训服务领域人员发起,融合AI与教育教学体系,打造集课程研发、人才培育、企业赋能、师资共建于一体的全链条数字化教育服务生态。
**加速10|云南大学AI+联合创业服务平台(项目)**
- 负责人:顾耘华,云南大学艺术与设计学院艺术设计在读硕士。
- 简介:云南大学在校研究生、毕业生和创业导师共同发起的AI创业平台。以OPC社区AI人才培养与就业服务为核心,覆盖AI短视频、AI漫剧、AI+PPT制作美化等。已帮助多名大学生成功开展OPC创业;联合云南AIGC产业联盟打造“育笋计划”公益品牌,辐射大学生就创业500人+;王升雅《生态秘境》插画在《人与自然》杂志连载六期;王洋《智绘古海——AI复原寒武纪古生物》获国家级创赛金奖;杨沐晨打造西南城市鸟类多样性生态视觉品牌,承办7项生态展览;王润曦协同云南省委宣传部助力会泽白雾村打造乡村文旅振兴名片。
### B. 国际创客区
(国际1—国际7,共7家)
**国际1|仰光客厅(项目)**
- 负责人:缅甸籍留学生,毕业于云南大学经济学院。
- 简介:缅甸留华学生发起的中缅文旅融合项目,以仰光近郊沉浸式文化营地为核心载体,面向中国客群、在缅中资机构及本地青年,集住宿、特色餐饮、文化体验、城市导览于一体,与本地社区、手工艺人合作带动就业与增收,搭建跨文化交流平台。
**国际2|千楣之路(项目)**
- 负责人:缅甸籍留学生,现就读于云南大学工商管理学院。
- 简介:缅甸籍留学生创立,主营缅甸传承两千余年的传统护肤产品檀娜卡(黄香楝树干制成,缅甸已于2025年申报联合国非遗)。打造传统原木套装、便捷粉泥、现代美妆三大产品线,以“文化内容+跨境电商”模式引入中国市场。
**国际3|联翼航空技术低空经济应用国际化拓展(项目)**
- 负责人:杨俊蓝,2023年毕业于云南财经大学电子商务专业,瑞士商学院MSC硕士在读。
- 简介:2026年4月启动,在读硕士发起,立足云南面向东南亚的区位门户优势,开展低空经济业务国际化拓展,在航空通航、人工智能与数字信息、教育培训方向布局,输出无人机应用、低空数据服务、跨境技能培训等内容,推动低空经济产业、人才、服务双向辐射东南亚。
**国际4|酷享野农AI农业(项目)**
- 负责人:晏和波,西南林业大学农业工程与信息技术专业在校研究生。
- 简介:2026年4月启动,西南林业大学在校研究生发起,聚焦智慧农业、科研种植、农旅融合,用AI智能视觉提供农业宣传内容制作服务。与科研机构合作开展高原早熟苹果科研种植,与地方政府共建共享农庄,助力乡村振兴。
**国际5|滇缅国际设计(项目)**
- 负责人:张浩鹏,2022年毕业于云南财经职业学院审计学专业。
- 简介:2026年4月启动,依托一带一路机遇,立足滇缅对外开放节点开展工程设计服务,创新跨国协同设计模式,融入绿色建造技术,覆盖交通、城乡基建等领域,与境外机构合作推动跨境项目落地。
**国际6|昆明舒诺生物科技有限责任公司**
- 负责人:舒岐涛,云南林业职业技术学院环境艺术专业在校生。
- 简介:2026年4月成立,聚焦生物科技与宠物营养领域,围绕精准营养、全生命周期养护、清洁标签开展业务,以科学配方与生物活性技术打造宠物精细化营养产品。
**国际7|达岸教育管理(云南)有限公司**
- 负责人:吕琳,2022年毕业于泰国暹罗大学MBA研究生专业。
- 简介:专注德国双元制教育与德国留学一站式服务(2024年启动),与德国院校合作,提供双元制培养、公立院校申请、德语培训、签证办理、境外陪护等全流程服务,帮助学生实现带薪留学、学历提升与稳定就业。
### C. 成长区
(成长1—成长8,共8家)
**成长1|花仙子园艺肥料(云南)有限公司**
- 负责人:翁淞耀,2023年毕业于云南师范大学社会体育指导与管理专业。
- 简介:2025年4月成立,专注家庭园艺肥研发生产,针对产品异味大、品质不稳、使用不便等痛点推出安全无异味、易操作系列产品,已实现规模化生产。采用线上线下融合渠道,服务家庭养花、阳台种植民生需求。
**成长2|研X——同行者网络(项目)**
- 负责人:陈品祥,2024年昆明理工大学硕士毕业,兼职任教昆明冶金职业大学。
- 简介:面向全国1621万硕博及6000万泛科研人群的一站式学术成长与资源共享平台。首创“1+6+N”生态(1个实名制平台+文献工具导航、代码共享交易、就业创业对接、学术/情感社交婚恋、科教娱乐、研究生严选商城6大模块)。已完成安卓、iOS、Windows三端开发并上架,已有全国300余所高校注册,两位高校教授以个人资金投入种子轮。
**成长3|朵哈·玫瑰特色产业链(项目)**
- 负责人:刘志贤,云南轻纺职业学院艺术设计专业大二在校生。
- 简介:2026年4月启动,云南轻纺学院在校大学生发起,立足昆明安宁八街核心产区,以地标产品重瓣红玫瑰为核心原料,主打玫瑰米酿特色饮品,与省内农业产业化龙头企业合作实现原料优选、标准化生产与品质管控。
**成长4|昆明舒护安养老服务有限责任公司**
- 负责人:严宇佳,2019年毕业于云南艺术学院广播电视编导专业。
- 简介:OPC一人公司,入驻园区孵化仅一个月成功吸纳就业3人。专注正规医疗护理服务,主营医院专属陪护与居家上门护理,覆盖术后康复、失能半失能照料、慢病护理、偏瘫照护、生活监护等全场景,与医疗机构深度合作。
**成长5|南菌优培——食用菌专用生物制剂创新与高原特色野生菌高值化全链开发(项目)**
- 负责人:王荣飞,2026年毕业于中山大学外科学专业。
- 简介:三年以上研发基础与产业化探索,核心团队含分析化学博士、农业硕士、医学硕士等。计划2026年7月完成公司注册并启动。聚焦食用菌专用生物制剂(引入海藻糖-6-磷酸、寡糖等生物源成分)与高原特色野生菌(硬皮地星、硬皮马勃)高值化全链开发。
**成长6|五华区丽裳文化艺术工作室**
- 负责人:王娟,2024年毕业于昆明幼儿师范高等专科学校幼儿教育专业。
- 简介:2026年4月成立,专注传统文化、民俗活动、茶文化及国风品牌活动策划执行与文旅研学。依托云南自贸区优势,推动民俗文创、茶产品、干花制品及高原特色产品出海,搭建“账号引流+内容传播+产品出海”运营体系。
**成长7|云南星瑞航空(项目)**
- 负责人:尹永浩,2019年毕业于滇西科技师范学院亚洲微电影学院数字媒体技术专业。
- 简介:2026年4月启动,专注低空智能应用,以大疆行业级无人机为核心装备,业务涵盖无人机测绘、三维建模、数字孪生、应急救援、公共安全巡检、夜间红外搜救及电力、光伏、油气管道、森林防火等作业,并深耕文物数字化保护。具备硬件销售、数据处理、技术支持、人才培训一体化能力。
**成长8|综合性云南直播私域服务平台**
- 负责人:金满满,2026年7月毕业于周口师范学院学前教育专业。
- 简介:综合性云南直播私域服务平台,深耕云南本土特色资源,打造自主IP形象,以短视频创作+全域直播运营为核心,搭建全国性自主私域流量池;同时搭建大学生创业孵化通道,构建人才迭代体系。
### D. OPC创业空间
(场地安排=OPC,共6家)
**D1|中外青少年研学(项目)**(OPC)
- 负责人:王得荣,2022年武汉大学法学专业毕业,2024—2025年英国剑桥大学短期交流学习。
- 简介:国际游学与教育交流服务项目,已与美国、英国、澳大利亚、新加坡等多国顶尖教育机构达成长期直营战略合作,为青少年、在职教师及家长提供一站式全球化教育交流解决方案。
**D2|星杭商务信息咨询昆明有限责任公司**
- 负责人:杨之华,2024年6月毕业于云南师范大学教育学专业。
- 简介:主要经营商务服务、信息咨询培训、商务接待及服务保障配套。
**D3|蓝智科技(项目)**
- 负责人:徐安厅,2026年毕业于昆明城市学院计算机科学与技术专业。
- 简介:OPC一人公司,2026年5月启动,从业3人。立足西南蓝莓特色种植产业,专注农业物联网技术研发与智慧基地数字化运营,自研蓝智全系列智慧种植监控系统,涵盖田间环境监测、基质养分检测、水肥灌溉、种植生产数据管理等。
**D4|云品出滇·纸享万家(项目)**
- 负责人:王帅,2024年毕业于昆明理工大学电子信息工程专业。
- 简介:2026年3月成立,以新型悬挂式抽纸为核心切入产品,依托AI工具与电商平台,打造国内电商+跨境电商双增长模式的日用纸品品牌。当前已在拼多多平台实现真实销售验证,未来向东南亚跨境电商扩展。
**D5|云迹轻旅——昆明本土一站式轻量化文旅服务工作室(项目)**
- 负责人:王海璇,云南大学工商管理(MBA)毕业,10年云南文旅从业经验。
- 简介:单人OPC轻资产运营,构建“内容+服务+技术”三位一体业务矩阵:文旅自媒体IP孵化、私人小众旅行定制、非遗自然研学小团、文旅商业咨询策划、AI数字文旅技术服务五大板块。面向C端游客与B端本地文旅商家提供服务。
**D6|启元人工智能科技(项目)**
- 负责人:李瑞康,2017年毕业于吉林动画学院广播电视编导专业。
- 简介:OPC一人公司,专注8-12岁儿童人工智能工具的技术推广与科普体验服务,自主研发适配低龄儿童的AI实操工具包,依托共享空间开展少儿AI创意科技体验沙龙(AI绘画、AI音乐创作、AI视频制作)。
> 其他在简介中表述为“OPC一人公司”但按场地安排归入其他分区的项目:云南派音人工智能科技(加速1)、昆明舒护安养老服务(成长4)、云南大学AI+联合创业服务平台(加速10)等。
> 另:云南大学AI+联合创业服务平台完整介绍见【孵化加速区-加速10】。
### E. 一楼直播间
(共1家)
**一楼直播间|昆明屿澈电子商务有限公司**
- 负责人:杨君武,2024年6月毕业于云南交通职业技术学院建设工程管理专业。
- 简介:高校毕业生发起的东南亚跨境电商AI智能直播+短视频出海项目,依托TikTok Shop正规跨境渠道,面向马来西亚、泰国、越南、菲律宾四国,主营日用百货、家居用品、电子配件、文创收纳、便携小家电等。采用AI数字人7×24小时无人直播、AI短视频批量引流、1688一件代发轻资产模式。
### F. 信息表中“场地安排”列为空的其他项目
《入驻企业信息表》中以下项目(共8家)的“场地安排”列未标注分区,照原文列出供全量归档:
**云南上古绝学文化发展有限公司(简称:上古书院)**
- 负责人:赵泳淇,2024年毕业于贵州大学教育技术学专业。
- 简介:专注上古家学文化传承与国学赋能的文化机构,以“修身齐家”为核心理念,业务涵盖家学文化课程体系研发、国学经典文化交流、家训家规个性化定制及文化研学,走“从文化体验到家族传承”路径。
**中越生物医疗(项目)**
- 负责人:武妙英(VŨ DIỆU ANH),越南籍留学生,云南大学工商管理专业在读,精通越语、法语、中文、英语。
- 简介:聚焦越南标准化生物蛋白生产联合实验室建设,计划覆盖区域80%以上缺口市场,依托中越技术协同与资源互补开展高纯度生物活性蛋白合规化跨境生产与出口,满足疫苗、诊断试剂及医疗器械领域需求。已获越南五部门联合认证。
**中越文旅交流(项目)**
- 负责人:阮胡竹珠,云南民族大学广播电视专业在读(2027年毕业)。
- 简介:搭建中越文化旅游交流平台,提供旅游咨询、行程定制、文化体验、语言服务及跨境旅游推广,依托广播电视专业背景与中越双语优势开展中越旅游宣传内容制作、新媒体运营、跨境文化交流活动策划。
**“绿态”环保科技项目**
- 负责人:陈国旗,云南民族大学在读留学生,负责海外工厂对接、中越跨境供应链统筹及中国全渠道分销网络核心运营。
- 简介:紧抓“禁塑令”及绿色环保政策导向,专注天然槟榔叶(mo cau)环保餐具研发、跨境贸易与新媒体全渠道销售。在越南本土自有实体生产工厂,100%越南本土完成从源头集采到杀菌包装,成品经正规清关出口至中国,在昆明园区设“中国市场总分销库与营销/直播工作室”,构建“抖音直播+货架电商双驱闭环”。
**昆明滇油石化有限责任公司**
- 负责人:李政德,预计2027年毕业于云南国土资源职业学院地球物理勘探专业。
- 简介:专注生物基纳米驱油剂的研发、生产与销售,为国内低渗透油藏及老油田二次/三次采油提供绿色高效增产解决方案。目标用户:中石油(华北、长庆、新疆、大庆等)、中石化(胜利、中原等)、中海油(渤海等)下属老油田;延伸至一带一路沿线国家老油田(中亚、中东、非洲)及民营油服企业(如延长石油)。
**Facebook越南市场普洱茶与建水紫陶壶跨境电商(项目)**
- 负责人:罗有艳,2020年毕业于云南农业大学越南语专业。
- 简介:依托Facebook全域流量生态(主页、直播、短视频、广告),聚焦越南垂直市场,主营普洱茶、手工茶具(云南建水紫陶小品壶及公道杯组合)、中式茶礼套装跨境零售。借助“内容种草+直播转化+9610跨境报关+越南专线直邮”闭环模式。
**鬼才明AI创意工作室(项目)**
- 负责人:赵广明,2022年毕业于重庆大学计算机专业。
- 简介:2026年4月启动,聚焦产教融合、创新创业赛事运营与数智化品牌赋能,围绕AI技术商业化落地、新商科专业规划、全品类活动策展及职业技能人才培育开展服务。搭建AI智能交互体系与AI数字人内容输出矩阵,为政校企提供AI场景应用、全域获客、职业技能培训、赛事集训、资格认证、品牌全案、项目申报等全链路解决方案。
**昆明云韵体育有限责任公司 / 男子自行车洲际队(项目)**
- 负责人:舒江贵,2020年7月毕业于麒麟区职业学校运动训练专业。
- 简介:2026年4月发起,拟在云南注册成立UCI男子自行车洲际队,打造立足云南、辐射全国、征战亚洲赛场的职业自行车竞技项目,依托云南高原训练优势,构建“职业赛事+商业赞助+高原训练+体育文旅+青苗人才培养”一体化运营模式。
> 注:申报名单(第5节)将该“男子自行车洲际队项目”归类为 OPC。
---
## 5. 省级申报·入驻实体名单(31家)
> 出处:附件1.3 创业孵化载体入驻实体名单(省级申报口径,31家)
| 序号 | 入驻经营实体或项目团队名单 |
|---|---|
| 1 | 昆明舒护安养老服务有限责任公司 |
| 2 | 星杭商务信息咨询(昆明)有限责任公司 |
| 3 | 达岸教育管理(云南)有限公司 |
| 4 | 五华区丽裳文化艺术工作室 |
| 5 | 米勒克尔蓝宝石(项目) |
| 6 | 中越生物合作——高纯度鲎血蛋白提取的跨境技术创新和商业推广(项目) |
| 7 | 云南宸中低空经济有限公司 |
| 8 | 中泰教育合作(项目) |
| 9 | 人工智能机器人训练(项目) |
| 10 | 欧森纳东盟香港留学(项目) |
| 11 | 仰光客厅中缅文化体验营地(项目) |
| 12 | PineSound派音智能——下一代音效与配乐基座(项目) |
| 13 | 千楣之路——缅甸“千年之木”Thanaka非遗文化传播与跨境贸易(项目) |
| 14 | 昆明朵哈食品有限公司 |
| 15 | 滇缅一带一路国际化建设设计(项目) |
| 16 | 云南定影文化传播有限公司 |
| 17 | 酷享野农AI农业 |
| 18 | 昆明智海银高文化科技有限公司 |
| 19 | 鬼才明AI创意策划工作室(项目) |
| 20 | 昆明就惯你生物科技(项目) |
| 21 | 联翼航空技术低空经济应用国际化拓展(项目) |
| 22 | 中外青少年研学交流(项目) |
| 23 | 昆明瀚颖教育信息咨询有限公司 |
| 24 | 花仙子园艺肥料(云南)有限公司 |
| 25 | 云南星瑞航空科技(项目) |
| 26 | 男子自行车洲际队项目(OPC) |
| 27 | 云南廷秀文旅康养服务项目(OPC) |
| 28 | 中通乐创(项目) |
| 29 | 跨境电商出口运营项目 |
| 30 | 华寮国际教育交流(云南)有限责任公司 |
| 31 | 综合性云南直播私域服务平台 |
> 说明:本名单项目名称部分与《入驻企业信息表》略有出入(如“米勒克尔蓝宝石”、“欧森纳东盟香港留学”、中越生物改名等),系两表不同填报口径/时点所致,均照原文保留。
---
## 6. 企业(项目)入驻管理规程要点
> 出处:昆明市大学生创业园创业企业(项目)入驻管理规程.wps(昆明市人力资源和社会保障局,2026年3月13日)
### 一、实施主体
- 运营主管:昆明市人力资源和社会保障局(主管部门)
- 执行:通过政府购买服务委托第三方服务机构(服务机构),负责入驻全流程工作;主管部门行使监督、考核、验收职权。
### 二、入驻条件
- **对象范围**:在校大学生及毕业8年内高校毕业生、归国留学生、外籍留学生等青年创业企业(项目),申请人须担任法定代表人/项目负责人。符合昆明市产业发展的优质企业(项目)或OPC企业可适当放宽。
- **产业方向**:聚焦昆明市“10+N”产业链——数字经济、电子信息、生物医药、先进装备、新材料、新能源、绿色食品、现代物流、文化旅游、花卉等,具备创新性、可操作性、市场前景良好。
- **其他要求**:明确可行的经营计划、启动资金和风险承担能力,规范的企业管理制度;无知识产权纠纷、违法犯罪记录、经营异常记录;守法经营、诚实守信,遵守园区各项规定。
- **优先入驻**:获得市级及以上创业大赛奖项、突出学术成就、重大发明创造及高新专利技术的企业(项目)。
### 三、入驻流程
1. **提出申请**:提交《入驻申请表》《创业计划书》、身份证(外籍提供护照/签证)、学历证明、营业执照(已注册)、资质专利证书等。
2. **项目初审**:服务机构初审,材料不齐一次性告知补全。
3. **入驻评审**:服务机构从《昆明市就业创业导师库》抽取3名评审专家,路演答辩评价,量化打分制(100分),80分及以上通过。
- 团队资质与能力 **20分**(学历背景、专业匹配度、创业经验、组织管理、人员稳定)
- 项目创新性与技术壁垒 **25分**(核心技术/商业模式创新点、自主知识产权、技术领先性、行业壁垒)
- 产业适配与市场前景 **25分**(与重点产业链匹配度、市场需求、竞品、市场拓展、盈利模式)
- 运营规划与落地能力 **15分**(商业计划完整性、启动资金、经营管理制度、阶段目标、落地方案)
- 社会价值与发展潜力 **15分**(带动就业能力、税收贡献预期、行业潜力、可持续发展)
4. **评审公示**:报主管部门备案后,在市人社局官网公示,无异议准予入驻。
5. **签署协议**:签订《入驻孵化协议》,分配办公场地/工位,正式入驻。
### 四、入驻周期(梯度孵化四大区域)
| 孵化区域 | 面向对象 | 配套 | 孵化周期 |
|---|---|---|---|
| OPC创业空间 | 以AI为支撑、聚焦数字经济/AI应用/软件开发/文创设计/电商直播等轻资产领域 | 拎包入驻的独立办公位或共享工位 | 最长2年 |
| 创业苗圃区 | 创意阶段、未注册企业的大学生创业团队 | 开放式共享工位 | 最长2年(落地成长好可推荐转加速区1年) |
| 孵化加速区 | 已完成工商注册、处于初创期的企业(项目) | 独立办公空间 | 基础2年,评估合格可延1年,累计≤3年 |
| 国际创客区 | 归国留学生、外籍留学生创业企业(项目) | 个性化办公空间+国际化配套服务 | 参照孵化加速区 |
### 五、项目年度考核
- **考核维度**(量化打分制,100分):经营发展能力(20):营收/利润/纳税、经营计划完成度、管理制度完善度;技术创新能力(25):知识产权新增、研发投入、产品/服务迭代、产学研合作;合规与履约情况(20):遵守管理制度、协议履约、场地规范、材料报送、守法经营;创业带动成效(20):新增就业(高校毕业生占比)、实习实训岗位;发展潜力与孵化成效(15):融资进展、资质认证、市场拓展、可持续规划、出孵成熟度。
- **考核等级**:优秀(80分及以上)、合格(60–79分)、不合格(60分以下)。
- **结果处理**:合格准予继续孵化(期满可申请延期);不合格下达《整改通知书》,整改期满仍不合格的可解除孵化协议、勒令限期退出。
### 六、退出管理(四种类型)
1. **成功孵化出园**:满足任一成熟出孵标准——①产权明晰,有自主核心产品并规模化;②经营良好,产品有稳定市占率与盈利;③制度健全、组织完备、团队稳定;④已获行业经营资质、质量稳定;⑤有持续研发能力与规划。
2. **孵化期满退出**:周期届满未申请延期或延期未通过、已延期到期。
3. **主动申请退出**:因经营问题、团队变动等申请退出。
4. **强制勒令退出**:①协议签订后累计30个工作日无人到现场办公,或连续7个工作日空置且未报备累计超3次;②违反规定造成重大经济损失/名誉损失;③不服从管理情节严重;④被停业、吊销执照、破产清算;⑤违法经营;⑥私自转租、变更经营项目;⑦虚报数据、伪造材料、拒不配合。
---
## 7. 空间布局
> 出处:空间布局.png(概念布局)与 详细的布局.png(分区布置图)
**概念布局(空间布局.png)**:顶部左侧为“玻璃顶”(开敞中庭/天光区);上部右侧以“过道”纵贯,两侧各若干“房间”;中上部为“大厅”;中部横向依次为若干“房间”、橙色的“共享工位区”、绿色的“休息厅”;底部由完整“过道”贯通,下接一排9间“房间”。
**分区布置(详细的布局.png)**:
| 区域类别 | 编号/名称 | 数量 |
|---|---|---|
| 成长区 | 成长区01–08 | 8 |
| 国际区 | 国际区01–07 | 7 |
| 加速区 | 加速区01–13 | 13 |
| 共享办公 | 共享工位区 | 1 |
| 会议空间 | 小会议室 / 大会议室 | 2 |
| 功能空间 | 玻璃顶(中庭)、休息厅、过道、楼道 | 若干 |
> 布局总览:上部左侧为“玻璃顶”开敞区,右上依次为成长区与国际化两纵列;中部靠上为共享工位区与休息厅;下方以“过道”为轴,分布加速区、大小会议室及楼道。分区命名与《入驻企业信息表》中的“加速/国际/成长/OPC”场地安排完全对应。
---
## 8. 宣传册核心内容(园区简介·服务·政策)
> 出处:昆明市创业园宣传册.pdf(16页彩页;以下为正文要点,图片部分略)
### 8.1 园区定位与理念
- 云南省首家由政府部门主办、为创业青年大学生提供**免费创业孵化服务**的**开放式园区**。
- 2009年正式成立,昆明市人力资源和社会保障局主办运营,2012年获评省级创业园;位于民航路229号昆明市人力资源中心1–2楼,总面积约3000平方米。
- 以助力青年大学生创业梦想为核心,构建 **“空间+孵化+融资+政策+资源+AI赋能+综合服务”七位一体**全链条创业扶持体系,打造“一站式”创业孵化服务平台。
- 紧扣面向南亚东南亚的国际定位,融合AI前沿技术,打造现代化创业/创新服务平台,成为青年人才来昆留昆创业创新的“首站”与“家园”。
### 8.2 政策服务(七大板块)
1. **空间保障**:免费办公工位或独立办公区域,配备办公桌椅、文件柜、高速网络等基础办公设施。
2. **孵化服务**:专业创业指导专家团队提供创业规划、技术指导、商业模式梳理、市场对接等“一对一”服务;定期举办创业沙龙、创业讲堂、项目路演。
3. **融资支持**:联动省内外创投机构,设立创业种子资金/合作投资基金,提供政策性贷款、商业银行贷款、股权融资等推荐指导。
4. **政策对接**:协助申报创业扶持资金、税收减免、社保补贴等,落实高校毕业生创业扶持政策。
5. **资源整合**:对接驻昆高校、科研院所、龙头企业,提供技术合作、成果转化、人才招聘;纳入昆明创业创新服务体系,优先参与“春城创业荟”等品牌活动。
6. **专项赋能培训**:定期开展AI公益性培训,提供AI应用、数字内容创作、一人公司创业落地、企业AI降本增效等公益培训与实战辅导。
7. **其他服务**:提供工商注册、税务代办、知识产权咨询、法律援助等专业化服务。
---
## 9. 入驻条件与入驻流程
### 入驻条件(宣传册口径,与管理规程一致)
(一)**对象范围**:在校大学生及毕业8年内高校毕业生、归国留学生、外籍留学生等青年创业企业(项目),申请人须担任法定代表人/项目负责人;优质项目或OPC企业可放宽。
(二)**产业方向**:聚焦昆明市“10+N”产业链(数字经济、电子信息、生物医药、先进装备、新材料、新能源、绿色食品、现代物流、文化旅游、花卉等),具备创新性、可操作性、市场前景良好。
(三)**其他要求**:明确可行的经营计划、启动资金和风险承担能力,规范管理制度;无知识产权纠纷、违法犯罪及经营异常记录。
(四)**优先入驻**:获得市级及以上创业大赛奖项、突出学术成就、重大发明创造及高新专利技术的项目。
### 入驻流程(宣传册六步)
咨询报名 → 提出申请 → 项目初审 → 入驻评审(路演答辩,量化打分≥80分通过)→ 评审公示(市人社局官微公示)→ 签署协议、分配场地工位、正式入驻。
---
## 10. 孵化周期(四大孵化区域)
| 区域 | 对象 | 配套 | 周期 |
|---|---|---|---|
| **OPC创业空间** | AI为支撑,聚焦数字经济/AI应用/软件开发/文创设计/电商直播等轻资产领域 | 拎包入驻独立办公位或共享工位 | 最长2年 |
| **创业苗圃区** | 创意阶段、未注册企业的大学生创业团队 | 开放式共享工位 | 最长2年(成长好可转加速区1年) |
| **孵化加速区** | 已工商注册、初创期企业 | 独立办公空间 | 基础2年,评估合格延1年,累计≤3年 |
| **国际创客区** | 归国留学生、外籍留学生 | 个性化办公空间+国际化配套 | 参照孵化加速区 |
---
## 11. 报名方式
- 咨询电话:18087174891、18214573650(宣传册);18087174891(统计表填报人);云南省就业局/园区申报口径见第3节填报信息
- 咨询地址:昆明市民航路229号(昆明市人力资源中心二楼 昆明市大学生创业园)
- 接收邮箱:714066050@qq.com
---
## 12. 媒体报道与合作媒体
**媒体报道矩阵**:整合电视、广播、报纸、新媒体等资源,通过电视专栏、广播专题、报纸专版、网络直播、地铁专列、新媒体推广等方式,全方位宣传报道园区创业创新典型事迹。
**合作媒体机构**(宣传册列示):
- 全国主流:央视网(CCTV.com)、中国网(China.org.cn)、人民网(people.cn)、今日头条、网易、搜狐、抖音
- 省级媒体:云南日报、云南广播电视台(Yunnan Media Group)、云南网、春城晚报
- 市级媒体:昆明广播电视台、昆明日报、掌上春城(KMCC)、无线昆明
- 其他:8099999 新闻热线等
**重要外宣数据口径(宣传册/新闻引用,酌情采用)**:
- 昆明市政府网站:园区运营超过10年,入驻企业超1000家、带动创业者超1万人次(2024-04报道口径)。
- 春城晚报:近3年累计孵化企业超100家,培育国家级高新技术企业5家、省级科技型中小企业12家。
- 昆明日报:2024年以来新增入驻企业32家、其中高新技术企业15家;举办各类活动126场,覆盖青年创业者超2万人次。
- **提示**:以上为媒体报道口径,与省级申报/月报表中的官方累计口径(累计孵化181家、出园149家、带动就业1529人等)存在差异,对外引用时请注明来源。
---
## 13. 园区新闻与活动大事记
| 时间 | 事件 |
|---|---|
| 2009年9月 | 昆明市大学生创业园成立 |
| 2012年 | 获评省级创业园 |
| 2024-04-15 | 昆明市大学生创业园2026年第二期入驻招募公告发布(同时发布第一期评审结果公示) |
| 2024-04-27前后 | 园区经全周期优化后“焕新亮相”获多家媒体报道(央视网5G、中国网、昆明市政府网、昆明日报、8099999等),报道时间多为2024-04-28 |
| 2024-05-08 | “聚力赋能·共创未来”高校创新创业交流暨项目路演活动(主办:园区;协办:滇池学院、昆明城市学院、五华区大学生创业联盟) |
| 2024-05-10 | 昆明城市学院师生赴园区“取经”参观交流 |
| 2024-05-12 | 春城晚报、云南日报整版深度报道《创业有园 未来可期》 |
| 2024-05-13 | “昆明市青年企业家协会走进园区暨创新创业基地揭牌仪式” |
| 2024-05-14 | “云南高校·昆明市大学生创业园”就业创业交流活动暨创新创业基地揭牌仪式(云南经济管理学院、昆明技师学院80余人参加) |
| 2026-03-13 | 《入驻管理规程》印发(昆明市人力资源和社会保障局) |
| 2026-03-20 | 2026年第一期入驻申请受理截止:收到申请38个,25个通过初审、13个提交复审 |
| 2026-03-24 | 第一期集中评审(30个路演参评,评审走“资格复核—现场路演—专家提问—综合评议”,量化打分) |
| 2026-03-25 | 《工作简讯〔2026〕第1期》:第一期最终确定23个优质企业(项目)正式入驻 |
| 2026-04-15 | 发布2026年第二期入驻招募公告 |
| 2026-04-24 | “创赢未来”2026创业大赛昆明选拔赛暨马兰花创业培训讲师大赛落幕(主办:昆明市人社局;承办:昆明市公共就业和人才服务中心;4月20日起4天路演,13个项目晋级省级大赛) |
| 2026-05-06 | 《工作简讯〔2026〕第2期》:此梳理第一期23家入驻企业全覆盖实地摸排,2家长期停滞企业督促整改 |
| 2026-05-20 | “滇水逐梦·雨林创航”2026昆明·西双版纳创业路演PK赛(西双版纳州公共就业和人才服务中心、滇池学院、园区联合主办;园区企业鬼才明AI活动策划工作室、云南定影文化传播有限公司协办) |
| 2026-06-03 | 云南旅游职业学院赴园区开展就业创业交流活动 |
| 近期(2026) | 云南师范大学与园区联合举办就业创业交流活动暨就业创业实践基地揭牌仪式(开启“校地产教融合、跨境双创人才共育”,缅甸、越南、印尼、老挝等国留学生参加) |
---
## 14. 品牌VI设计规范
> 出处:昆明市大学生创业园 定稿(.ai 源文件 + 制版PNG/JPG)
- **Logo**:橙、蓝双色组合标识——橙色“K”与深蓝“M”交织成圆环形/动感造型,英文全称“KUNMING UNIVERSITY STUDENT ENTREPRENEURSHIP PARK”,主推版式居中,品牌色橙蓝相衬。
- **品牌主色(设计稿拾色器取值)**:
| 色彩 | Hex | R | G | B | 说明 |
|---|---|---|---|---|---|
| 主色·橙 | `#FC8318` | 252 | 131 | 24 | 活力橙,主标识/强调 |
| 辅色·深蓝 | `#022B97` | 2 | 43 | 151 | 深邃蓝,主标识/正文 |
- **配套文件**:`昆明市大学生创业园 定稿.ai`(矢量源文件)、`_画板 1.png/.jpg`(主画板成品)、`_画板 1 副本.png`(色彩规范参考)。
---
### 附注:数据口径说明
1. **累计数与当年数**:第2节绩效、第3节月报表中均区分“认定以来累计 / 本年度 / 当前实有”。本月报表中累计出园149户与省级申报累计出园150户相差1户,为两表填报时点不同所致,均照原文保留。
2. **入驻规模**:信息表(39家)面向内部全量项目;省级申报名单(31家)为认定口径,项目名称个别有别。
3. **宣传册数据**(媒体报道/外宣口径)与官方申报累计口径存在差异,引用时务必注明出处(见第12节提示)。
4. 本文件所列数值均直接源自原始文件,未作推算加工。
---
*文档生成日期:2026年 · 依据 `.xlsx / .xls / .wps / .pdf / .png` 全量原始资料整理*
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""知识库入库脚本:分块 → LLM 生成问答 → 双路向量索引 → 落盘 kb_index.json / kb_qa.json。
知识源 = backend/knowledge/ 下全部 *.md(park.md / opc.md / park_data.md …)。
- 幂等:缓存有效时直接加载(不重新构建);--force 强制删除缓存重建。
- 首次运行会调用 DashScope(text-embedding-v4 向量化 + qwen-plus 生成问答),
耗时约数分钟,仅构建一次,之后命中缓存秒开。
用法(在 backend/ 目录下执行):
python scripts/ingest_kb.py # 有缓存则加载,无缓存则构建
python scripts/ingest_kb.py --force # 强制重建(任一知识文件变更后)
"""
import argparse
import sys
from pathlib import Path
BACKEND_DIR = Path(__file__).resolve().parent.parent
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
def main() -> None:
parser = argparse.ArgumentParser(description="知识库入库(分块 + 问答 + 双路向量索引)")
parser.add_argument("--force", action="store_true", help="删除缓存并强制重建")
parser.add_argument("--dry", action="store_true", help="只做分块预览,不调 API、不落盘")
args = parser.parse_args()
from app import rag
if args.dry:
chunks = rag._chunk_md(rag._kb_text())
print(f"[dry] 知识源 {len(list(rag.KB_DIR.glob(rag.KB_GLOB)))} 个文件,全库 "
f"{len(rag._kb_text())} 字 → {len(chunks)} 块(最长 {max(map(len, chunks))} 字)")
return
if args.force:
for p in (rag.META_FILE, rag.VECTOR_FILE, rag.QA_FILE, rag.LEGACY_INDEX_FILE):
if p.exists():
p.unlink()
print(f"[ingest] 已删除缓存 {p.name}")
print("[ingest] 知识源:")
for f in sorted(rag.KB_DIR.glob(rag.KB_GLOB)):
print(f" - {f.name} ({f.stat().st_size} B)")
idx = rag._load_index()
n_chunk = len(idx["chunks"])
n_vec = len(idx["embed_texts"])
dim = idx["embeddings"].shape[1]
n_q = sum(1 for qs in idx["questions"] for _ in qs)
print("[ingest] 入库完成:")
print(f" - 分块数: {n_chunk}")
print(f" - 生成问句数: {n_q}")
print(f" - 向量数: {n_vec}(dim={dim},含原句+问句双路)")
print(f" - 向量矩阵: {rag.VECTOR_FILE}")
print(f" - 元数据: {rag.META_FILE}")
print(f" - 问答缓存: {rag.QA_FILE}")
print("[ingest] 成功。")
if __name__ == "__main__":
main()
+77 -4
View File
@@ -1,8 +1,10 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import Icon from './Icons';
import ShowCard from './ShowCard';
import { getApiBase as API_BASE } from '../config';
import { Sfx } from '../utils/sounds';
import { getMqttStatus } from '../utils/mqtt';
import { aiStatus } from '../utils/statusBus';
import { renderMarkdown } from '../voice/markdown.jsx';
/* =========================================================
@@ -41,19 +43,49 @@ const nextId = () => ++msgId;
/** 执行后端返回的工具调用。
后端已通过 MQTT 广播(发布成功时所有大屏都会收到,本地跳过避免重复);
仅当 MQTT 发布失败 / 后端不可达时本地执行,保证当前屏幕也能响应 */
仅当 MQTT 发布失败 / 后端不可达时本地执行,保证当前屏幕也能响应。
show_card 除外:卡片已内联渲染进对话气泡,不再走全局弹窗。 */
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') {
if (type === 'navigate' || type === 'control' || type === 'alert') {
if (type === 'navigate') aiStatus('正在切换页面', { icon: 'page' });
else if (type === 'control') aiStatus('正在控制媒体播放', { icon: 'tool' });
// alert:通知本身即展示,不再加状态提示
window.dispatchEvent(new CustomEvent('dpm:mqtt-local', { detail: { action: type, params } }));
}
}
}
/** 由 /api/ai/chat 响应生成「状态芯片」:知识库检索 + 工具/技能调用。 */
function buildChips(data) {
const chips = [];
if (data && data.retrieved) chips.push({ icon: 'database', text: '知识库检索' });
const labels = {
navigate: '切换页面', control: '媒体控制', show_card: '展示卡片', alert: '发送通知',
};
const icons = { navigate: 'compass', control: 'player-play', show_card: 'sparkles', alert: 'broadcast' };
(data?.tool_names || []).forEach((name) => {
chips.push({ icon: icons[name] || 'wrench', text: `调用·${labels[name] || name}` });
});
return chips;
}
/** 由 /api/ai/chat 响应提取「内联卡片」(show_card 工具 → 可视化卡片数据)。 */
function buildCards(data) {
const cards = [];
(data?.tools || []).forEach((t) => {
if (t && t.type === 'show_card') {
const p = t.params || {};
cards.push({ card: p.card || 'custom', title: p.title || '', content: p.content || '' });
}
});
return cards;
}
export default function AiChatPanel() {
const [msgs, setMsgs] = useState(() => [{ id: nextId(), role: 'ai', text: WELCOME_TEXT }]);
const [input, setInput] = useState('');
@@ -111,8 +143,15 @@ export default function AiChatPanel() {
setMsgs((prev) => [...prev, { id: nextId(), role: 'me', text, voice: viaVoice }]);
setTyping(true);
// 右上角状态提示:检索 → 查询 → 语义检索(进行中)
let s1, s2;
aiStatus('正在检索知识库');
s1 = setTimeout(() => aiStatus('正在查询相关信息'), 350);
s2 = setTimeout(() => aiStatus('正在构建语义检索'), 800);
let reply = null;
let tools = [];
let data = null; // 提升到函数作用域,供 chips/cards 使用
try {
// 多轮:将历史对话(去掉系统首条问候)作为 messages 上下文
const history = msgsRef.current
@@ -125,16 +164,34 @@ export default function AiChatPanel() {
body: JSON.stringify({ messages: history }),
});
if (res.ok) {
const data = await res.json();
data = await res.json();
reply = data.reply;
tools = data.tools || [];
}
} catch { /* 后端不可用 */ }
if (!reply) reply = pick(FALLBACK_REPLIES);
clearTimeout(s1); clearTimeout(s2);
setTyping(false);
setMsgs((prev) => [...prev, { id: nextId(), role: 'ai', text: reply }]);
setMsgs((prev) => [...prev, {
id: nextId(), role: 'ai', text: reply,
chips: buildChips(data), cards: buildCards(data),
}]);
Sfx.notify();
// 右上角状态:工具 / 检索 / 完成(通知 alert 除外——通知本身即展示)
const statusTools = tools.filter((t) => t.type !== 'alert');
if (statusTools.length) {
const lbl = statusTools
.map((t) => ({ navigate: '切换页面', control: '媒体控制', show_card: '展示卡片' }[t.type] || t.type))
.filter(Boolean).join('、');
aiStatus(`正在调用工具:${lbl}`, { icon: 'tool' });
setTimeout(() => aiStatus('操作完成', { state: 'done', icon: 'check' }), 700);
} else if (data?.retrieved) {
aiStatus('已完成知识检索', { state: 'done', icon: 'check' });
} else {
aiStatus('已回复', { state: 'done', icon: 'check' });
}
executeLocalTools(tools);
resetIdleTimer(); // 有提问 → 重置 1 分钟空闲计时
}, [typing, resetIdleTimer]);
@@ -336,6 +393,22 @@ export default function AiChatPanel() {
<div className="bd-msg-bubble">
{m.voice && <span className="bd-msg-voice-tag"><Icon name="microphone" size={10} />语音</span>}
{m.role === 'ai' ? renderMarkdown(m.text) : m.text}
{m.chips && m.chips.length > 0 && (
<div className="bd-msg-chips">
{m.chips.map((c, i) => (
<span className="bd-msg-chip" key={i}><Icon name={c.icon} size={11} />{c.text}</span>
))}
</div>
)}
{m.cards && m.cards.length > 0 && m.cards.map((card, i) => (
<div className="bd-msg-card" key={i}>
<div className="bd-msg-card-head">
<span className="bd-msg-card-ico"><Icon name="sparkles" size={12} /></span>
<b>{card.title || '信息卡片'}</b>
</div>
<ShowCard card={card} />
</div>
))}
</div>
</div>
))}
+44 -101
View File
@@ -1,110 +1,39 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import Icon from './Icons';
import { getApiBase as API_BASE } from '../config';
import ShowCard from './ShowCard';
import { Sfx } from '../utils/sounds';
/* =========================================================
DpmOverlays —— 后端/AI 指令驱动的全局覆盖层
· alert → 右上角通知(自动消失)
· show_card → 中央信息卡片(企业分布 / 分区 / 园区总览 / 自定义)
卡片渲染复用 ShowCard(也可内联进对话气泡)
遵循 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 / 未知类型:标题 + 内容
/* 关闭按钮图标:内联 SVG,避免依赖图标库中不存在的 "x" 名称导致空白按钮 */
function CloseIcon({ size = 14 }) {
return (
<div className="bd-ov-card-body">
<div className="bd-ov-card-text">{content || '(无内容)'}</div>
</div>
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M6 6l12 12M18 6L6 18" />
</svg>
);
}
/* AI 对话页(/ai):卡片已内联进对话气泡,不再以全局弹窗弹出;其余页面保留弹窗 */
const isAiChatPage = () =>
typeof window !== 'undefined' && window.location.pathname.startsWith('/ai');
export default function DpmOverlays() {
const [alert, setAlert] = useState(null); // {title, content, id}
const [card, setCard] = useState(null); // {card, title, content, id}
@@ -137,24 +66,38 @@ export default function DpmOverlays() {
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 className="bd-overlay bd-overlay-alert" key={alert.id} role="alert">
<div className="bd-alert-ico"><Icon name="broadcast" size={16} /></div>
<div className="bd-alert-main">
<div className="bd-alert-title">{alert.title}</div>
{alert.content && <div className="bd-alert-text">{alert.content}</div>}
</div>
<button className="bd-overlay-close" onClick={() => setAlert(null)}><Icon name="x" size={13} /></button>
<button
className="bd-overlay-close"
aria-label="关闭通知"
title="关闭"
onClick={() => setAlert(null)}
>
<CloseIcon size={14} />
</button>
</div>
)}
{card && (
{card && !isAiChatPage() && (
<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>
<button
className="bd-overlay-close"
aria-label="关闭卡片"
title="关闭"
onClick={() => setCard(null)}
>
<CloseIcon size={15} />
</button>
</div>
<CardContent card={card} />
<ShowCard card={card} />
</div>
</div>
)}
+8 -6
View File
@@ -148,13 +148,15 @@ export default function PageHeader() {
<h1 className="bd-header-title">昆明市大学生创业园 <span className="bd-header-title-accent">· OPC 智能园区数字运营中心</span></h1>
<div className="bd-header-sub">云南省首家政府主办大学生创业孵化园区 · <span className="bd-header-content—highlights">空间+孵化+融资+政策+资源+AI赋能+综合服务</span></div>
</div>
<span className="bd-credit">
<Icon name="copyright" size={13} />
<span className="bd-credit-text">
<span className="bd-credit-name">云南派音人工智能科技有限公司</span>
<span className="bd-credit-dev">开发</span>
{location.pathname !== '/screen' && (
<span className="bd-credit">
<Icon name="copyright" size={13} />
<span className="bd-credit-text">
<span className="bd-credit-name">云南派音人工智能科技有限公司</span>
<span className="bd-credit-dev">开发</span>
</span>
</span>
</span>
)}
</div>
<div className="bd-header-right">
{/* 媒体轮播页:页眉内嵌播放控制 */}
+3 -3
View File
@@ -33,7 +33,7 @@ export default function PromptPanel({ onSend }) {
{ title: 'OPC 概念', questions: ['什么是 OPC?', 'OPC 常用的人工智能工具有哪些?'] },
{ title: '园区企业介绍', questions: ['介绍一下园区入驻企业', '介绍一下云南派音人工智能科技'] },
]);
setTools(['DeepSeek-V3', '通义千问', '豆包', '剪映']);
setTools(['DeepSeek', '通义千问', '豆包', '剪映']);
}
}
})();
@@ -51,8 +51,8 @@ export default function PromptPanel({ onSend }) {
<aside className="bd-prompt-side">
<div className="bd-prompt-head">
<Icon name="bulb" size={15} />
<b>您可以这样问</b>
<span>预设问题 · 点击即向 AI 提问</span>
<b>提问</b>
<span>点击即向 AI 提问</span>
</div>
<div className="bd-prompt-groups">
+2
View File
@@ -1,6 +1,7 @@
import { Outlet } from 'react-router-dom';
import PageHeader from './PageHeader';
import DpmOverlays from './DpmOverlays';
import ToolStatusToast from './ToolStatusToast';
import GlobalVision from './GlobalVision';
import { usePageNav } from '../utils/pageNav';
import { useMqttControl } from '../utils/useMqttControl';
@@ -27,6 +28,7 @@ export default function ScreenLayout() {
{/* 全局覆盖层(通知 / 卡片 / 手势识别徽标) */}
<GlobalVision />
<DpmOverlays />
<ToolStatusToast />
</div>
);
}
+104
View File
@@ -0,0 +1,104 @@
import { useState, useEffect } from 'react';
import { getApiBase as API_BASE } from '../config';
/* =========================================================
ShowCard —— 信息卡片(企业分布 / 分区介绍 / 园区总览 / 自定义)
复用 bd-ov-card-* 样式,既可用于 DpmOverlays 全局弹窗,
也可内联渲染进对话气泡(AiChatPanel / VoiceAssistant)。
card = { card: 'companies'|'zones'|'overview'|'custom', title?, content? }
========================================================= */
export default function ShowCard({ card }) {
const [data, setData] = useState(null);
const { card: type, 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>
);
}
+50
View File
@@ -0,0 +1,50 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import Icon from './Icons';
/* =========================================================
ToolStatusToast —— 右上角「操作进行中」状态提示
监听 dpm:tool-status 事件,展示 AI 检索/查询/调用工具等实时状态。
state: working(转圈) | done(绿色对勾) | error(红色)
========================================================= */
const ICON_MAP = {
search: 'database', // 检索知识库
info: 'activity', // 查询/语义检索
tool: 'wrench', // 调用工具
page: 'compass', // 切换页面
check: 'check',
error: 'info',
};
export default function ToolStatusToast() {
const [toast, setToast] = useState(null);
const timerRef = useRef(null);
const idRef = useRef(0);
const show = useCallback((d = {}) => {
const id = ++idRef.current;
const state = d.state || 'working';
setToast({ id, text: d.text || '', state, icon: ICON_MAP[d.icon] || 'activity' });
clearTimeout(timerRef.current);
const dur = d.duration != null ? d.duration : (state === 'working' ? 4200 : 2400);
timerRef.current = setTimeout(() => setToast((t) => (t && t.id === id ? null : t)), dur);
}, []);
useEffect(() => {
const on = (e) => show(e.detail || {});
window.addEventListener('dpm:tool-status', on);
return () => {
window.removeEventListener('dpm:tool-status', on);
clearTimeout(timerRef.current);
};
}, [show]);
if (!toast) return null;
return (
<div className="ts-toast" key={toast.id} role="status">
<span className={`ts-ico ${toast.state}`}><Icon name={toast.icon} size={14} /></span>
<span className="ts-text">{toast.text}</span>
{toast.state === 'working' && <span className="ts-spinner" />}
</div>
);
}
+1 -1
View File
@@ -25,7 +25,7 @@ import { useParkSim } from '../utils/parkData';
/* ---------- 模型占比(TOKEN 构成演示分布) ---------- */
const MODELS = [
{ name: 'DeepSeek-V3', value: 38, color: '#4c8dff' },
{ name: 'DeepSeek', value: 38, color: '#4c8dff' },
{ name: '通义千问', value: 21, color: '#22d3ee' },
{ name: '智谱 GLM-4', value: 16, color: '#34d399' },
{ name: '豆包', value: 12, color: '#fbbf24' },
+7
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import Icon from '../components/Icons';
import * as api from '../utils/api';
import { getApiBase } from '../config';
import { Sfx } from '../utils/sounds';
@@ -557,6 +558,12 @@ export default function MediaScreen() {
)}
{/* 播放控制已上移页眉(PageHeader 内渲染,媒体区保持纯净全屏) */}
{/* 左下角:版权/开发 玻璃质感芯片(顶部已移除) */}
<div className="ms-credit-chip">
<Icon name="copyright" size={12} />
<span>云南派音人工智能科技有限公司 · 开发</span>
</div>
</main>
{/* 页脚:园区概览(与数据大屏一致) */}
+44 -29
View File
@@ -9,6 +9,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { S2sWsRealtimeClient } from '../voice/s2s-ws-client.js';
import useVisionDetection, { VISION_DWELL_MS } from '../voice/useVisionDetection.js';
import { getApiBase as API_BASE, getVoiceWsUrl } from '../config';
import { aiStatus } from '../utils/statusBus';
import '../styles/datascreen.css';
import './voice-original.css';
import './voice-overrides.css';
@@ -115,8 +116,11 @@ export default function VoiceAssistant() {
const [messages, setMessages] = useState([]);
const [muted, setMuted] = useState(false);
const [errorMsg, setErrorMsg] = useState('');
// 全局人物识别开关(MQTT vision_set 控制)
// 摄像头常开:左下角视频预览保持显示(识别到人也不会自动问候,问候由 greetEnabled 控制)
const [visionEnabled, setVisionEnabled] = useState(true);
// 主动问候开关:默认关闭「识别到人 → 主动问候」,但摄像头/视频仍常开。
// 需要恢复自动问候时改回 true,或用 MQTT 下发开启。
const [greetEnabled, setGreetEnabled] = useState(false);
const clientRef = useRef(null);
const audioCtxRef = useRef(null);
@@ -133,6 +137,7 @@ export default function VoiceAssistant() {
const live = LIVE_STATES.has(state);
// ── 自动问候生命周期状态(须在 start 之前定义:start 依赖数组引用 sendGreeting)──
const GREETING_TEXT = '您好呀,需要我的帮助吗?';
const GREETING_WAIT_MS = 25000; // 问候播完后等待用户回答的超时
@@ -224,6 +229,14 @@ export default function VoiceAssistant() {
try { args = JSON.parse(argsStr || '{}'); } catch { /* 忽略非法参数 */ }
console.warn(`[tools] LLM 调用工具: ${name}`, args);
pushMessage('assistant', `🔧 调用工具:${name}`, false);
// 右上角状态提示
const toolLabel = {
control_display: args?.action === 'switch_page' ? '切换页面' : '控制大屏',
get_park_overview: '查询园区数据',
query_companies: '查询企业名录',
get_time: '查询时间',
}[name] || name;
aiStatus(`正在调用工具:${toolLabel}`, { icon: name === 'control_display' && args?.action === 'switch_page' ? 'page' : 'tool' });
let output;
try {
const res = await fetch(`${API_BASE()}/api/tools/exec`, {
@@ -301,10 +314,23 @@ export default function VoiceAssistant() {
audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
});
streamRef.current = stream;
if (!audioCtxRef.current) audioCtxRef.current = new AudioContext();
if (audioCtxRef.current.state === 'suspended') await audioCtxRef.current.resume();
// 复用一个仍在运行的上下文;若已关闭(此前被误关)或未创建,则重建。
// 注意:client.close() 不再关闭共享上下文,故会话结束(含手势重启)后仍可复用。
let _ctx = audioCtxRef.current;
if (!_ctx || _ctx.state === 'closed') {
_ctx = new AudioContext();
audioCtxRef.current = _ctx;
}
if (_ctx.state === 'suspended') {
try { await _ctx.resume(); } catch { /* ignore */ }
// 无用户激活(手势)时 resume 可能异步生效,稍等再试一次
if (_ctx.state === 'suspended') {
await new Promise((r) => setTimeout(r, 80));
try { await _ctx.resume(); } catch { /* ignore */ }
}
}
// 基础提示词:从后端拉取(配置 + 园区知识库已由服务端组装);失败用极简兜底
// 基础提示词:从后端拉取(配置 + 园区知识库已由服务端组装);失败用极简兜底。
let instructions = FALLBACK_INSTRUCTIONS;
try {
const insRes = await fetch(`${API_BASE()}/api/s2s/instructions`, { cache: 'no-store' });
@@ -386,12 +412,20 @@ export default function VoiceAssistant() {
if (c) { try { await c.close(); } catch { /* noop */ } }
streamRef.current?.getTracks().forEach((t) => t.stop());
streamRef.current = null;
// 结束会话后丢弃 AudioContext 引用:下次启动一律新建,避免复用可能被
// Chromium 自动挂起/关闭的旧上下文(否则麦克风不处理 → 一直「聆听」)。
audioCtxRef.current = null;
setMuted(false);
setErrorMsg('');
dismissAllBubbles();
setState('idle');
}, [dismissAllBubbles]);
// 离开语音对话页时强制停止会话与麦克风,避免残留触发/占用
useEffect(() => {
return () => { void stop(); };
}, [stop]);
const toggleMute = useCallback(() => {
const c = clientRef.current;
if (!c) return;
@@ -446,37 +480,18 @@ export default function VoiceAssistant() {
});
// 手势 → 对话控制:举手 toggle —— 无对话则开始,正在对话则结束
const onGesture = useCallback((g) => {
if (g !== 'raise') return;
if (clientRef.current) {
void stop();
} else {
void start();
}
}, [start, stop]);
// 【已按需求关闭】视觉手势识别误判会触发右上角弹窗/误开对话,故这里不再响应手势。
const onGesture = useCallback(() => {
// 视觉手势已关闭:识别到人脸/手势不再自动开始/结束对话
}, []);
useEffect(() => {
gestureRef.current = onGesture;
}, [onGesture]);
const onVisionTrigger = useCallback(() => {
// 仅 idle 允许自动问候:对话中/连接中/其他状态一律不打扰
if (voiceStateRef.current !== 'idle') {
console.warn('[vision] 自动问候跳过:当前状态', voiceStateRef.current);
return;
}
pendingGreetingRef.current = true;
if (clientRef.current) {
sendGreeting('');
} else {
void start(); // start 连接成功后检查 pendingGreetingRef 再发问候
}
fetch(`${API_BASE()}/api/vision/event`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ event: 'triggered', faces: vision.faces, dwell_ms: VISION_DWELL_MS }),
}).catch(() => { /* 上报失败不影响本地问候 */ });
}, [vision.faces, start, sendGreeting]);
// 主动问候已彻底关闭:识别到人(含停留 10 秒)不再自动问候/开始对话,也不弹任何提示
}, []);
useEffect(() => {
visionTriggerRef.current = onVisionTrigger;
+224 -28
View File
@@ -2149,71 +2149,267 @@
.bd-msg { max-width: 94%; }
}
/* ---- AI 消息内联:状态芯片 + 可视化卡片(随气泡潜入对话) ---- */
.bd-msg.ai { max-width: 92%; }
.bd-msg-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed rgba(120,160,220,0.28);
}
.bd-msg-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 9px;
font-size: 10.5px;
font-weight: 600;
border-radius: 20px;
color: var(--bd-blue-deep);
background: rgba(47,107,255,0.08);
border: 1px solid rgba(47,107,255,0.18);
}
.bd-msg-chip .appica-ico { color: var(--bd-blue); }
.bd-msg-card {
margin-top: 10px;
padding: 12px 14px;
border-radius: 10px;
background: #f7fafd;
border: 1px solid var(--bd-line);
}
.bd-msg-card-head {
display: flex;
align-items: center;
gap: 7px;
margin-bottom: 10px;
}
.bd-msg-card-head b {
font-size: 13px;
font-weight: 700;
color: var(--bd-ink);
}
.bd-msg-card-ico {
width: 22px;
height: 22px;
flex-shrink: 0;
border-radius: 7px;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan));
}
/* ---- 右上角:工具 / 操作进行中状态提示 ---- */
.ts-toast {
position: fixed;
top: 84px;
right: 22px;
z-index: 3100;
display: flex;
align-items: center;
gap: 10px;
max-width: 320px;
padding: 11px 14px;
background: #fff;
border: 1px solid var(--bd-line);
border-left: 3px solid var(--bd-blue);
border-radius: 12px;
box-shadow: 0 12px 30px rgba(29, 44, 68, 0.14);
animation: bdOverlayIn 0.25s ease;
}
.ts-ico {
width: 28px;
height: 28px;
flex-shrink: 0;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan));
}
.ts-ico.done { background: linear-gradient(140deg, var(--bd-green), #4ade80); }
.ts-ico.error { background: linear-gradient(140deg, var(--bd-red), #f87171); }
.ts-text {
font-size: 12.5px;
line-height: 1.4;
color: var(--bd-ink);
word-break: break-word;
}
.ts-spinner {
width: 14px;
height: 14px;
flex-shrink: 0;
margin-left: auto;
border: 2px solid rgba(47, 107, 255, 0.2);
border-top-color: var(--bd-blue);
border-radius: 50%;
animation: tsSpin 0.8s linear infinite;
}
@keyframes tsSpin { to { transform: rotate(360deg); } }
.bd-msg-card .bd-ov-card-body { padding: 0; max-height: 40vh; }
/* ---- 信息卡片内部布局(ShowCard:企业 / 分区 / 总览 / 自定义;弹窗与内联共用) ---- */
.bd-ov-card-body {
max-height: 46vh;
overflow-y: auto;
padding: 4px 2px;
display: flex;
flex-direction: column;
gap: 14px;
}
.bd-ov-card-group-title {
display: flex;
align-items: center;
gap: 7px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.05em;
color: var(--bd-sub);
margin-bottom: 9px;
}
.bd-ov-card-group-title::before {
content: "";
width: 4px;
height: 13px;
border-radius: 2px;
background: linear-gradient(180deg, var(--bd-blue), var(--bd-cyan));
}
.bd-ov-card-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.bd-ov-card-chip {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 4px 10px;
font-size: 12px;
line-height: 1.4;
color: var(--bd-ink);
background: #fff;
border: 1px solid var(--bd-line);
border-radius: 8px;
}
.bd-ov-card-chip b {
font-weight: 700;
font-size: 10px;
padding: 1px 6px;
border-radius: 6px;
border: 1px solid;
white-space: nowrap;
}
.bd-ov-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 8px;
}
.bd-msg-card .bd-ov-card-grid { grid-template-columns: repeat(2, 1fr); }
.bd-ov-card-stat {
display: flex;
flex-direction: column;
gap: 3px;
padding: 10px 12px;
background: #fff;
border: 1px solid var(--bd-line);
border-radius: 8px;
}
.bd-ov-card-stat span {
font-size: 10.5px;
color: var(--bd-sub);
}
.bd-ov-card-stat b {
font-size: 15px;
font-weight: 700;
color: var(--bd-ink);
}
.bd-ov-card-text {
font-size: 12.5px;
line-height: 1.7;
color: var(--bd-ink);
white-space: pre-wrap;
word-break: break-word;
}
/* ============ 全局覆盖层(后端/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;
top: 84px;
right: 22px;
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);
gap: 12px;
min-width: 280px;
max-width: 380px;
padding: 14px 14px 14px 12px;
background: #fff;
border: 1px solid var(--bd-line);
border-left: 3px solid var(--bd-blue);
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;
box-shadow: 0 12px 32px rgba(29, 44, 68, 0.14);
animation: bdOverlayIn 0.28s ease;
}
.bd-overlay-alert-ico {
width: 28px;
height: 28px;
.bd-alert-ico {
width: 32px;
height: 32px;
flex-shrink: 0;
border-radius: 8px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan));
background: linear-gradient(140deg, var(--bd-blue) 0%, var(--bd-cyan) 100%);
box-shadow: 0 4px 10px rgba(47, 107, 255, 0.28);
}
.bd-overlay-alert-body {
.bd-alert-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
gap: 4px;
padding-top: 1px;
}
.bd-overlay-alert-body b {
font-size: 13px;
.bd-alert-title {
font-size: 14px;
font-weight: 600;
color: var(--bd-ink);
line-height: 1.35;
}
.bd-overlay-alert-body span {
font-size: 11.5px;
line-height: 1.5;
.bd-alert-text {
font-size: 12.5px;
line-height: 1.6;
color: var(--bd-sub);
white-space: pre-wrap;
word-break: break-word;
}
.bd-overlay-close {
width: 22px;
height: 22px;
width: 26px;
height: 26px;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 6px;
background: rgba(120,160,220,0.12);
border-radius: 8px;
background: transparent;
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);
background: rgba(240, 68, 56, 0.12);
}
.bd-overlay-close:active {
transform: scale(0.92);
}
@keyframes bdOverlayIn {
from { opacity: 0; transform: translateY(-8px); }
+23
View File
@@ -67,6 +67,29 @@
-webkit-backdrop-filter: blur(14px);
}
/* 左下角:版权/开发 玻璃质感芯片 */
.ms-credit-chip {
position: absolute;
left: 16px;
bottom: 14px;
z-index: 8;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
font-size: 11px;
font-weight: 500;
color: rgba(255, 255, 255, 0.92);
border-radius: 20px;
background: rgba(29, 44, 68, 0.32);
border: 1px solid rgba(255, 255, 255, 0.22);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
backdrop-filter: blur(10px) saturate(140%);
-webkit-backdrop-filter: blur(10px) saturate(140%);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.ms-credit-chip .appica-ico { color: rgba(255, 255, 255, 0.85); }
/* 媒体播放器(填满整个媒体区,等比裁切铺满) */
.ms-media {
position: absolute;
+12
View File
@@ -0,0 +1,12 @@
/* =========================================================
工具 / 操作进行中状态提示总线
统一 dispatch `dpm:tool-status` 事件,由 ToolStatusToast 监听显示在右上角。
用法:
aiStatus('正在检索知识库') // 进行中(带转圈)
aiStatus('正在调用工具:切换页面', { icon: 'tool' })
aiStatus('操作完成', { state: 'done', icon: 'check' }) // 完成态
aiStatus('出错了', { state: 'error', icon: 'error' })
========================================================= */
export function aiStatus(text, opts = {}) {
window.dispatchEvent(new CustomEvent('dpm:tool-status', { detail: { text, ...opts } }));
}
+9 -2
View File
@@ -4,6 +4,7 @@ import { onMqttMessage, connectMqtt, publishMqtt, getClientId, useMqttStatus } f
import { MQTT_TOPIC_HEARTBEAT } from '../config';
import { Sfx } from './sounds';
import { PAGE_ORDER } from './pageNav';
import { aiStatus } from './statusBus';
/* =========================================================
MQTT 控制 —— 后端通过 opc/display/command 控制大屏
@@ -72,7 +73,7 @@ export function useMqttControl() {
switch (cmd.action) {
case 'navigate': {
const path = resolvePage(cmd.params?.page);
if (path) { Sfx.page(); navigate(path); }
if (path) { Sfx.page(); aiStatus('正在切换页面', { icon: 'page' }); navigate(path); }
break;
}
case 'navigate_rel': {
@@ -80,14 +81,17 @@ export function useMqttControl() {
const idx = PAGE_ORDER.findIndex((p) => p.path === location.pathname);
if (idx >= 0) {
const n = PAGE_ORDER.length;
aiStatus('正在切换页面', { icon: 'page' });
navigate(PAGE_ORDER[(idx + delta + n) % n].path);
}
break;
}
case 'alert':
// 通知本身即展示,无需再弹「正在发送通知」状态提示
window.dispatchEvent(new CustomEvent('dpm:alert', { detail: cmd.params || {} }));
break;
case 'show_card':
aiStatus('正在展示信息卡片', { icon: 'tool' });
window.dispatchEvent(new CustomEvent('dpm:show-card', { detail: cmd.params || {} }));
break;
case 'play':
@@ -155,12 +159,15 @@ export function useMqttControl() {
const { action, params } = e.detail || {};
if (action === 'navigate') {
const path = resolvePage(params?.page);
if (path) navigate(path);
if (path) { aiStatus('正在切换页面', { icon: 'page' }); navigate(path); }
} else if (action === 'control') {
aiStatus('正在控制媒体播放', { icon: 'tool' });
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') {
aiStatus('正在展示信息卡片', { icon: 'tool' });
window.dispatchEvent(new CustomEvent('dpm:show-card', { detail: params || {} }));
}
};