2787 lines
124 KiB
Python
2787 lines
124 KiB
Python
from __future__ import annotations
|
||
|
||
import json
|
||
import re
|
||
from typing import Any
|
||
|
||
from fastapi import Depends, HTTPException, Query
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class OneLinerProfileRequest(BaseModel):
|
||
project_id: str = ""
|
||
assistant_id: str = ""
|
||
display_name: str = "OneLiner"
|
||
long_term_goal: str = ""
|
||
notes: str = ""
|
||
default_platform: str = ""
|
||
config: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
class OneLinerSessionCreateRequest(BaseModel):
|
||
project_id: str = ""
|
||
title: str = ""
|
||
preferred_platform: str = ""
|
||
initial_message: str = ""
|
||
|
||
|
||
class OneLinerMessageRequest(BaseModel):
|
||
content: str
|
||
project_id: str = ""
|
||
platform: str = ""
|
||
target_account_id: str = ""
|
||
remember_preference: bool = False
|
||
|
||
|
||
class PlatformAgentProfileRequest(BaseModel):
|
||
project_id: str = ""
|
||
assistant_id: str = ""
|
||
name: str = ""
|
||
mission: str = ""
|
||
notes: str = ""
|
||
status: str = "active"
|
||
config: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
class AgentMemoryUpsertRequest(BaseModel):
|
||
project_id: str = ""
|
||
subject_type: str = "project"
|
||
subject_id: str = ""
|
||
memory_key: str
|
||
title: str = ""
|
||
summary: str
|
||
details: dict[str, Any] = Field(default_factory=dict)
|
||
confidence: float = Field(default=0.7, ge=0.0, le=1.0)
|
||
|
||
|
||
class AgentSkillUpsertRequest(BaseModel):
|
||
project_id: str = ""
|
||
skill_key: str
|
||
name: str
|
||
status: str = "draft"
|
||
method: dict[str, Any] = Field(default_factory=dict)
|
||
test_spec: dict[str, Any] = Field(default_factory=dict)
|
||
last_result: dict[str, Any] = Field(default_factory=dict)
|
||
success_count: int = Field(default=0, ge=0)
|
||
failure_count: int = Field(default=0, ge=0)
|
||
last_score: float = 0.0
|
||
|
||
|
||
class AdminIncidentReviewRequest(BaseModel):
|
||
status: str = "reviewed"
|
||
review_notes: str = ""
|
||
|
||
|
||
class PlatformAgentSelfCheckRequest(BaseModel):
|
||
project_id: str = ""
|
||
sample_limit: int = Field(default=3, ge=1, le=12)
|
||
remember_summary: bool = True
|
||
|
||
|
||
class PlatformSkillReviewRequest(BaseModel):
|
||
project_id: str = ""
|
||
accepted: bool = True
|
||
score: float = Field(default=0.8, ge=0.0, le=1.0)
|
||
status: str = ""
|
||
summary: str = ""
|
||
review_notes: str = ""
|
||
|
||
|
||
class OneLinerActionExecuteRequest(BaseModel):
|
||
action_key: str
|
||
project_id: str = ""
|
||
platform: str = ""
|
||
session_id: str = ""
|
||
payload: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
INTENT_ACTIONS: dict[str, list[dict[str, Any]]] = {
|
||
"create_project": [{"key": "goto-intake", "label": "去我的项目", "kind": "navigate"}],
|
||
"create_assistant": [{"key": "open-create-assistant", "label": "创建 Agent", "kind": "ui_action"}],
|
||
"import_homepage": [{"key": "open-import-homepage", "label": "导入主页", "kind": "ui_action"}],
|
||
"track_account": [{"key": "open-track-selected-account", "label": "跟踪当前账号", "kind": "ui_action"}],
|
||
"analyze_account": [{"key": "analyze-selected-account", "label": "分析当前账号", "kind": "ui_action"}],
|
||
"analyze_top_videos": [{"key": "analyze-top-videos", "label": "分析高分作品", "kind": "ui_action"}],
|
||
"generate_copy": [{"key": "open-generate-copy", "label": "生成文案", "kind": "ui_action"}],
|
||
"ai_video": [{"key": "open-ai-video", "label": "做 AI 视频", "kind": "ui_action"}],
|
||
"real_cut": [{"key": "open-real-cut", "label": "做实拍剪辑", "kind": "ui_action"}],
|
||
"review": [{"key": "goto-review", "label": "去发布与复盘", "kind": "navigate"}],
|
||
"live_recorder": [{"key": "open-live-recorder", "label": "打开录制控制", "kind": "ui_action"}],
|
||
"storage_status": [{"key": "goto-production", "label": "查看生产与存储", "kind": "navigate"}],
|
||
"ops_admin": [{"key": "goto-automation", "label": "去自动流程", "kind": "navigate"}],
|
||
}
|
||
|
||
INTENT_LABELS = {
|
||
"create_project": "创建项目",
|
||
"create_assistant": "创建 Agent",
|
||
"import_homepage": "导入主页",
|
||
"track_account": "跟踪账号",
|
||
"analyze_account": "分析账号",
|
||
"analyze_top_videos": "分析高分作品",
|
||
"generate_copy": "生成文案",
|
||
"ai_video": "生成 AI 视频",
|
||
"real_cut": "实拍剪辑",
|
||
"review": "发布复盘",
|
||
"live_recorder": "直播录制",
|
||
"storage_status": "查看存储",
|
||
"ops_admin": "运维巡检",
|
||
"custom": "自定义任务",
|
||
}
|
||
|
||
|
||
def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||
def now() -> str:
|
||
return legacy.utc_now()
|
||
|
||
def make_id(prefix: str) -> str:
|
||
return legacy.make_id(prefix)
|
||
|
||
def _parse_json(raw: str | None, fallback: Any) -> Any:
|
||
cleaned = str(raw or "").strip()
|
||
if not cleaned:
|
||
return fallback
|
||
try:
|
||
return json.loads(cleaned)
|
||
except json.JSONDecodeError:
|
||
return fallback
|
||
|
||
def _dump(value: Any) -> str:
|
||
return json.dumps(value or {}, ensure_ascii=False)
|
||
|
||
def ensure_schema() -> None:
|
||
schema = """
|
||
CREATE TABLE IF NOT EXISTS oneliner_profiles (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
assistant_id TEXT NOT NULL DEFAULT '',
|
||
display_name TEXT NOT NULL DEFAULT 'OneLiner',
|
||
long_term_goal TEXT NOT NULL DEFAULT '',
|
||
notes TEXT NOT NULL DEFAULT '',
|
||
default_platform TEXT NOT NULL DEFAULT '',
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(assistant_id) REFERENCES assistants(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS oneliner_sessions (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
profile_id TEXT,
|
||
title TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
preferred_platform TEXT NOT NULL DEFAULT '',
|
||
last_platform TEXT NOT NULL DEFAULT '',
|
||
last_intent_key TEXT NOT NULL DEFAULT '',
|
||
last_message_at TEXT NOT NULL,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(profile_id) REFERENCES oneliner_profiles(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS oneliner_messages (
|
||
id TEXT PRIMARY KEY,
|
||
session_id TEXT NOT NULL,
|
||
user_id TEXT NOT NULL,
|
||
role TEXT NOT NULL,
|
||
content TEXT NOT NULL DEFAULT '',
|
||
plan_json TEXT NOT NULL DEFAULT '{}',
|
||
result_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
FOREIGN KEY(session_id) REFERENCES oneliner_sessions(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS platform_agent_profiles (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
platform TEXT NOT NULL,
|
||
assistant_id TEXT NOT NULL DEFAULT '',
|
||
name TEXT NOT NULL DEFAULT '',
|
||
mission TEXT NOT NULL DEFAULT '',
|
||
notes TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'active',
|
||
config_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id, platform),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(assistant_id) REFERENCES assistants(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_memories (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
agent_scope TEXT NOT NULL,
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
subject_type TEXT NOT NULL DEFAULT 'project',
|
||
subject_id TEXT NOT NULL DEFAULT '',
|
||
memory_key TEXT NOT NULL,
|
||
title TEXT NOT NULL DEFAULT '',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
confidence REAL NOT NULL DEFAULT 0.7,
|
||
last_validated_at TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id, agent_scope, platform, subject_type, subject_id, memory_key),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS agent_skills (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL DEFAULT '',
|
||
agent_scope TEXT NOT NULL,
|
||
platform TEXT NOT NULL DEFAULT '',
|
||
parent_skill_id TEXT NOT NULL DEFAULT '',
|
||
skill_key TEXT NOT NULL,
|
||
name TEXT NOT NULL,
|
||
status TEXT NOT NULL DEFAULT 'draft',
|
||
method_json TEXT NOT NULL DEFAULT '{}',
|
||
test_spec_json TEXT NOT NULL DEFAULT '{}',
|
||
last_result_json TEXT NOT NULL DEFAULT '{}',
|
||
success_count INTEGER NOT NULL DEFAULT 0,
|
||
failure_count INTEGER NOT NULL DEFAULT 0,
|
||
last_score REAL NOT NULL DEFAULT 0,
|
||
last_validated_at TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(user_id, project_id, agent_scope, platform, skill_key),
|
||
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS admin_ops_incidents (
|
||
id TEXT PRIMARY KEY,
|
||
tenant_user_id TEXT NOT NULL DEFAULT '',
|
||
tenant_project_id TEXT NOT NULL DEFAULT '',
|
||
source_type TEXT NOT NULL,
|
||
source_id TEXT NOT NULL DEFAULT '',
|
||
severity TEXT NOT NULL DEFAULT 'warn',
|
||
title TEXT NOT NULL,
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
payload_json TEXT NOT NULL DEFAULT '{}',
|
||
status TEXT NOT NULL DEFAULT 'open',
|
||
assigned_to TEXT NOT NULL DEFAULT '',
|
||
reviewed_by TEXT NOT NULL DEFAULT '',
|
||
review_notes TEXT NOT NULL DEFAULT '',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL,
|
||
UNIQUE(source_type, source_id, title),
|
||
FOREIGN KEY(tenant_user_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
||
FOREIGN KEY(tenant_project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(assigned_to) REFERENCES accounts(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(reviewed_by) REFERENCES accounts(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS admin_ops_audit_logs (
|
||
id TEXT PRIMARY KEY,
|
||
actor_user_id TEXT NOT NULL DEFAULT '',
|
||
incident_id TEXT NOT NULL DEFAULT '',
|
||
action_key TEXT NOT NULL DEFAULT '',
|
||
status TEXT NOT NULL DEFAULT 'recorded',
|
||
summary TEXT NOT NULL DEFAULT '',
|
||
details_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
FOREIGN KEY(actor_user_id) REFERENCES accounts(id) ON DELETE SET NULL,
|
||
FOREIGN KEY(incident_id) REFERENCES admin_ops_incidents(id) ON DELETE CASCADE
|
||
);
|
||
"""
|
||
with legacy.db.session() as conn:
|
||
conn.executescript(schema)
|
||
|
||
ensure_schema()
|
||
|
||
@app.on_event("startup")
|
||
def _startup_oneliner_schema() -> None:
|
||
ensure_schema()
|
||
|
||
def _resolve_project(account: dict[str, Any], project_id: str | None) -> dict[str, Any]:
|
||
return legacy.resolve_target_project(account["id"], project_id or None, username=account["username"])
|
||
|
||
def _resolve_assistant(account: dict[str, Any], assistant_id: str | None, project_id: str = "") -> dict[str, Any] | None:
|
||
return legacy.resolve_target_assistant(account["id"], assistant_id or None, project_id)
|
||
|
||
def _safe_platform(platform_value: str | None, fallback: str = "douyin") -> str:
|
||
return legacy.ensure_domestic_platform(platform_value or fallback, allow_blank=not fallback) or fallback
|
||
|
||
def _route_supported(path: str) -> bool:
|
||
return any(getattr(route, "path", "") == path for route in app.routes)
|
||
|
||
def _platform_route_checks(platform: str) -> list[dict[str, Any]]:
|
||
checks = [
|
||
("accounts", f"/v2/{platform}/accounts"),
|
||
("workspace", f"/v2/{platform}/accounts/{{account_id}}/workspace"),
|
||
("videos", f"/v2/{platform}/accounts/{{account_id}}/videos"),
|
||
("analyze_account", f"/v2/{platform}/accounts/{{account_id}}/analysis"),
|
||
("analyze_top_videos", f"/v2/{platform}/accounts/{{account_id}}/videos/analyze-top"),
|
||
("similar_searches", f"/v2/{platform}/similar-searches"),
|
||
("benchmark_links", f"/v2/{platform}/accounts/{{account_id}}/benchmark-links"),
|
||
]
|
||
return [
|
||
{
|
||
"key": key,
|
||
"path": path,
|
||
"ok": _route_supported(path),
|
||
}
|
||
for key, path in checks
|
||
]
|
||
|
||
def _fetch_profile_row(account: dict[str, Any], project_id: str = "") -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"SELECT * FROM oneliner_profiles WHERE user_id = ? AND project_id = ?",
|
||
(account["id"], project_id),
|
||
)
|
||
|
||
def _profile_payload(row: dict[str, Any], *, account: dict[str, Any] | None = None) -> dict[str, Any]:
|
||
assistant = None
|
||
if row.get("assistant_id"):
|
||
assistant_row = legacy.db.fetch_one("SELECT * FROM assistants WHERE id = ?", (row["assistant_id"],))
|
||
if assistant_row and (not account or assistant_row.get("user_id") == account["id"]):
|
||
assistant = legacy.assistant_payload(assistant_row)
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row["user_id"],
|
||
"project_id": row.get("project_id", ""),
|
||
"assistant_id": row.get("assistant_id", ""),
|
||
"display_name": row.get("display_name", "OneLiner"),
|
||
"long_term_goal": row.get("long_term_goal", ""),
|
||
"notes": row.get("notes", ""),
|
||
"default_platform": row.get("default_platform", ""),
|
||
"config": _parse_json(row.get("config_json"), {}),
|
||
"assistant": assistant,
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _ensure_oneliner_profile(account: dict[str, Any], project_id: str = "") -> dict[str, Any]:
|
||
row = _fetch_profile_row(account, project_id)
|
||
if row:
|
||
return row
|
||
assistant_id = ""
|
||
if project_id:
|
||
assistant_row = legacy.db.fetch_one(
|
||
"SELECT * FROM assistants WHERE user_id = ? AND (project_id = ? OR project_id = '') ORDER BY created_at ASC LIMIT 1",
|
||
(account["id"], project_id),
|
||
)
|
||
else:
|
||
assistant_row = legacy.db.fetch_one(
|
||
"SELECT * FROM assistants WHERE user_id = ? ORDER BY created_at ASC LIMIT 1",
|
||
(account["id"],),
|
||
)
|
||
if assistant_row:
|
||
assistant_id = assistant_row["id"]
|
||
profile_id = make_id("oneliner")
|
||
created_at = now()
|
||
default_platform = "douyin"
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_profiles (
|
||
id, user_id, project_id, assistant_id, display_name, long_term_goal, notes,
|
||
default_platform, config_json, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
profile_id,
|
||
account["id"],
|
||
project_id,
|
||
assistant_id,
|
||
"OneLiner",
|
||
"",
|
||
"",
|
||
default_platform,
|
||
_dump({"chat_only_for_unreleased_ui": True}),
|
||
created_at,
|
||
created_at,
|
||
),
|
||
)
|
||
return legacy.db.fetch_one("SELECT * FROM oneliner_profiles WHERE id = ?", (profile_id,))
|
||
|
||
def _list_platform_profiles(account: dict[str, Any], project_id: str = "") -> list[dict[str, Any]]:
|
||
rows = legacy.db.fetch_all(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? ORDER BY platform ASC",
|
||
(account["id"], project_id),
|
||
)
|
||
mapping = {row["platform"]: row for row in rows}
|
||
results: list[dict[str, Any]] = []
|
||
for platform in sorted(legacy.DOMESTIC_PLATFORMS):
|
||
row = mapping.get(platform)
|
||
payload = _platform_agent_payload(account, row, platform=platform, project_id=project_id)
|
||
results.append(payload)
|
||
return results
|
||
|
||
def _platform_agent_payload(
|
||
account: dict[str, Any],
|
||
row: dict[str, Any] | None,
|
||
*,
|
||
platform: str,
|
||
project_id: str = "",
|
||
) -> dict[str, Any]:
|
||
assistant = None
|
||
if row and row.get("assistant_id"):
|
||
assistant_row = legacy.db.fetch_one("SELECT * FROM assistants WHERE id = ?", (row["assistant_id"],))
|
||
if assistant_row and assistant_row.get("user_id") == account["id"]:
|
||
assistant = legacy.assistant_payload(assistant_row)
|
||
memory_count = legacy.db.fetch_one(
|
||
"""
|
||
SELECT COUNT(*) AS count FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)["count"]
|
||
skill_count = legacy.db.fetch_one(
|
||
"""
|
||
SELECT COUNT(*) AS count FROM agent_skills
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)["count"]
|
||
recent_memory_row = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
ORDER BY updated_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)
|
||
recent_skill_row = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_skills
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
ORDER BY
|
||
CASE WHEN status = 'validated' THEN 0 WHEN status = 'draft' THEN 1 ELSE 2 END,
|
||
updated_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)
|
||
readiness_items = [
|
||
bool(row and row.get("status") == "active"),
|
||
bool(assistant),
|
||
bool(memory_count),
|
||
bool(skill_count),
|
||
]
|
||
readiness_score = int(sum(1 for item in readiness_items if item) * 25)
|
||
if readiness_score >= 100:
|
||
readiness_label = "就绪"
|
||
elif readiness_score >= 50:
|
||
readiness_label = "可用"
|
||
else:
|
||
readiness_label = "待补全"
|
||
return {
|
||
"id": row["id"] if row else "",
|
||
"user_id": account["id"],
|
||
"project_id": project_id,
|
||
"platform": platform,
|
||
"platform_label": legacy.platform_label(platform),
|
||
"assistant_id": row.get("assistant_id", "") if row else "",
|
||
"name": row.get("name", f"{legacy.platform_label(platform)} Agent") if row else f"{legacy.platform_label(platform)} Agent",
|
||
"mission": row.get("mission", "") if row else "",
|
||
"notes": row.get("notes", "") if row else "",
|
||
"status": row.get("status", "draft") if row else "draft",
|
||
"config": _parse_json((row or {}).get("config_json"), {}),
|
||
"memory_count": memory_count,
|
||
"skill_count": skill_count,
|
||
"recent_memory": _memory_payload(recent_memory_row) if recent_memory_row else None,
|
||
"recent_skill": _skill_payload(recent_skill_row) if recent_skill_row else None,
|
||
"readiness_score": readiness_score,
|
||
"readiness_label": readiness_label,
|
||
"assistant": assistant,
|
||
"created_at": (row or {}).get("created_at", ""),
|
||
"updated_at": (row or {}).get("updated_at", ""),
|
||
}
|
||
|
||
def _memory_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row["user_id"],
|
||
"project_id": row.get("project_id", ""),
|
||
"agent_scope": row.get("agent_scope", ""),
|
||
"platform": row.get("platform", ""),
|
||
"platform_label": legacy.platform_label(row.get("platform", "")) if row.get("platform") else "",
|
||
"subject_type": row.get("subject_type", ""),
|
||
"subject_id": row.get("subject_id", ""),
|
||
"memory_key": row.get("memory_key", ""),
|
||
"title": row.get("title", ""),
|
||
"summary": row.get("summary", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"confidence": float(row.get("confidence") or 0),
|
||
"last_validated_at": row.get("last_validated_at", ""),
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _skill_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row["user_id"],
|
||
"project_id": row.get("project_id", ""),
|
||
"agent_scope": row.get("agent_scope", ""),
|
||
"platform": row.get("platform", ""),
|
||
"platform_label": legacy.platform_label(row.get("platform", "")) if row.get("platform") else "",
|
||
"parent_skill_id": row.get("parent_skill_id", ""),
|
||
"skill_key": row.get("skill_key", ""),
|
||
"name": row.get("name", ""),
|
||
"status": row.get("status", "draft"),
|
||
"method": _parse_json(row.get("method_json"), {}),
|
||
"test_spec": _parse_json(row.get("test_spec_json"), {}),
|
||
"last_result": _parse_json(row.get("last_result_json"), {}),
|
||
"success_count": int(row.get("success_count") or 0),
|
||
"failure_count": int(row.get("failure_count") or 0),
|
||
"last_score": float(row.get("last_score") or 0),
|
||
"last_validated_at": row.get("last_validated_at", ""),
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _session_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"user_id": row["user_id"],
|
||
"project_id": row.get("project_id", ""),
|
||
"profile_id": row.get("profile_id", ""),
|
||
"title": row.get("title", ""),
|
||
"status": row.get("status", "active"),
|
||
"preferred_platform": row.get("preferred_platform", ""),
|
||
"last_platform": row.get("last_platform", ""),
|
||
"last_intent_key": row.get("last_intent_key", ""),
|
||
"last_message_at": row.get("last_message_at", ""),
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _message_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"session_id": row["session_id"],
|
||
"user_id": row["user_id"],
|
||
"role": row.get("role", "user"),
|
||
"content": row.get("content", ""),
|
||
"plan": _parse_json(row.get("plan_json"), {}),
|
||
"result": _parse_json(row.get("result_json"), {}),
|
||
"created_at": row["created_at"],
|
||
}
|
||
|
||
def _incident_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"tenant_user_id": row.get("tenant_user_id", ""),
|
||
"tenant_project_id": row.get("tenant_project_id", ""),
|
||
"source_type": row.get("source_type", ""),
|
||
"source_id": row.get("source_id", ""),
|
||
"severity": row.get("severity", "warn"),
|
||
"title": row.get("title", ""),
|
||
"summary": row.get("summary", ""),
|
||
"payload": _parse_json(row.get("payload_json"), {}),
|
||
"status": row.get("status", "open"),
|
||
"assigned_to": row.get("assigned_to", ""),
|
||
"reviewed_by": row.get("reviewed_by", ""),
|
||
"review_notes": row.get("review_notes", ""),
|
||
"created_at": row["created_at"],
|
||
"updated_at": row["updated_at"],
|
||
}
|
||
|
||
def _admin_audit_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"id": row["id"],
|
||
"actor_user_id": row.get("actor_user_id", ""),
|
||
"incident_id": row.get("incident_id", ""),
|
||
"action_key": row.get("action_key", ""),
|
||
"status": row.get("status", "recorded"),
|
||
"summary": row.get("summary", ""),
|
||
"details": _parse_json(row.get("details_json"), {}),
|
||
"created_at": row.get("created_at", ""),
|
||
}
|
||
|
||
def _log_admin_audit_event(
|
||
*,
|
||
actor_user_id: str,
|
||
incident_id: str = "",
|
||
action_key: str,
|
||
status: str,
|
||
summary: str,
|
||
details: dict[str, Any] | None = None,
|
||
) -> dict[str, Any]:
|
||
audit_id = make_id("ops_audit")
|
||
timestamp = now()
|
||
sql = """
|
||
INSERT INTO admin_ops_audit_logs (
|
||
id, actor_user_id, incident_id, action_key, status, summary, details_json, created_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
"""
|
||
params = (
|
||
audit_id,
|
||
actor_user_id,
|
||
incident_id,
|
||
action_key,
|
||
status,
|
||
summary,
|
||
_dump(details or {}),
|
||
timestamp,
|
||
)
|
||
if incident_id:
|
||
legacy.db.execute(sql, params)
|
||
else:
|
||
with legacy.db.session() as conn:
|
||
conn.execute("PRAGMA foreign_keys=OFF")
|
||
conn.execute(sql, params)
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_audit_logs WHERE id = ?", (audit_id,))
|
||
return _admin_audit_payload(row)
|
||
|
||
def _platform_source_samples(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
limit: int = 3,
|
||
) -> list[dict[str, Any]]:
|
||
safe_limit = max(1, min(int(limit or 3), 12))
|
||
rows = legacy.db.fetch_all(
|
||
f"""
|
||
SELECT * FROM content_sources
|
||
WHERE user_id = ? AND project_id = ? AND platform = ?
|
||
ORDER BY updated_at DESC
|
||
LIMIT {safe_limit}
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)
|
||
return [legacy.content_source_payload(row) for row in rows]
|
||
|
||
def _resolve_execution_assistant(account: dict[str, Any], *, project_id: str, platform: str = "") -> dict[str, Any] | None:
|
||
normalized_platform = _safe_platform(platform or "", fallback="")
|
||
if normalized_platform:
|
||
profile_row = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project_id, normalized_platform),
|
||
)
|
||
if profile_row and profile_row.get("assistant_id"):
|
||
assistant = _resolve_assistant(account, profile_row.get("assistant_id"), project_id)
|
||
if assistant:
|
||
return assistant
|
||
profile_row = _fetch_profile_row(account, project_id) or _ensure_oneliner_profile(account, project_id)
|
||
if profile_row.get("assistant_id"):
|
||
assistant = _resolve_assistant(account, profile_row.get("assistant_id"), project_id)
|
||
if assistant:
|
||
return assistant
|
||
return _resolve_assistant(account, None, project_id)
|
||
|
||
def _latest_project_job(account: dict[str, Any], *, project_id: str) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM jobs
|
||
WHERE user_id = ? AND project_id = ? AND status IN ('completed', 'done', 'succeeded')
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id),
|
||
)
|
||
|
||
def _last_user_message_text(session_id: str, account_id: str) -> str:
|
||
row = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM oneliner_messages
|
||
WHERE session_id = ? AND user_id = ? AND role = 'user'
|
||
ORDER BY created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(session_id, account_id),
|
||
)
|
||
return str((row or {}).get("content") or "").strip()
|
||
|
||
def _extract_first_url(text: str) -> str:
|
||
cleaned = str(text or "").strip()
|
||
if not cleaned:
|
||
return ""
|
||
match = re.search(r"https?://[^\s<>'\"]+", cleaned)
|
||
if not match:
|
||
return ""
|
||
return match.group(0).rstrip(",。;;,.)]》】!?!?")
|
||
|
||
def _find_creator_source_by_url(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
source_url: str,
|
||
) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'creator_account' AND source_url = ?
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id, platform, source_url),
|
||
)
|
||
|
||
def _latest_platform_account(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
) -> dict[str, Any] | None:
|
||
return legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'creator_account'
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 1
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)
|
||
|
||
def _load_owned_job(account: dict[str, Any], job_id: str) -> dict[str, Any] | None:
|
||
normalized_job_id = str(job_id or "").strip()
|
||
if not normalized_job_id:
|
||
return None
|
||
return legacy.db.fetch_one(
|
||
"SELECT * FROM jobs WHERE id = ? AND user_id = ?",
|
||
(normalized_job_id, account["id"]),
|
||
)
|
||
|
||
def _latest_derivable_job(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
exclude_line_types: set[str] | None = None,
|
||
) -> dict[str, Any] | None:
|
||
excluded = {item.strip() for item in (exclude_line_types or set()) if str(item or "").strip()}
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM jobs
|
||
WHERE user_id = ? AND project_id = ? AND status IN ('completed', 'done', 'succeeded')
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
LIMIT 24
|
||
""",
|
||
(account["id"], project_id),
|
||
)
|
||
for row in rows:
|
||
if str(row.get("line_type") or "").strip() in excluded:
|
||
continue
|
||
return row
|
||
return rows[0] if rows else None
|
||
|
||
def _job_performance_score(job_row: dict[str, Any] | None) -> float:
|
||
if not job_row:
|
||
return 0.0
|
||
result_map = _parse_json(job_row.get("result_json") or "{}", {})
|
||
artifacts_map = _parse_json(job_row.get("artifacts_json") or "{}", {})
|
||
candidates = [
|
||
result_map.get("performance_score"),
|
||
(result_map.get("analysis") or {}).get("performance_score"),
|
||
(result_map.get("scores") or {}).get("performance_score"),
|
||
artifacts_map.get("performance_score"),
|
||
(artifacts_map.get("scores") or {}).get("performance_score"),
|
||
]
|
||
for value in candidates:
|
||
try:
|
||
return float(value)
|
||
except (TypeError, ValueError):
|
||
continue
|
||
return 0.0
|
||
|
||
def _linked_platform_videos(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
account_row: dict[str, Any],
|
||
limit: int = 8,
|
||
) -> list[dict[str, Any]]:
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'video_link'
|
||
ORDER BY updated_at DESC, created_at DESC
|
||
""",
|
||
(account["id"], project_id, platform),
|
||
)
|
||
account_source_url = str(account_row.get("source_url") or "").strip()
|
||
items: list[dict[str, Any]] = []
|
||
for row in rows:
|
||
payload = legacy.content_source_payload(row)
|
||
metadata = payload.get("metadata") or {}
|
||
if metadata.get("origin_content_source_id") != account_row["id"] and metadata.get("source_account_url") != account_source_url:
|
||
continue
|
||
latest_job = legacy.db.fetch_one(
|
||
"SELECT * FROM jobs WHERE content_source_id = ? ORDER BY updated_at DESC, created_at DESC LIMIT 1",
|
||
(row["id"],),
|
||
)
|
||
result_map = _parse_json((latest_job or {}).get("result_json") or "{}", {})
|
||
published_at = (
|
||
metadata.get("published_at")
|
||
or metadata.get("publish_time")
|
||
or metadata.get("created_at")
|
||
or payload.get("updated_at")
|
||
or payload.get("created_at")
|
||
or ""
|
||
)
|
||
items.append(
|
||
{
|
||
"id": payload["id"],
|
||
"title": payload.get("title") or payload.get("handle") or payload.get("source_url") or payload["id"],
|
||
"source_url": payload.get("source_url", ""),
|
||
"published_at": published_at,
|
||
"score": {
|
||
"performance_score": _job_performance_score(latest_job),
|
||
},
|
||
"latest_job_id": (latest_job or {}).get("id", ""),
|
||
"latest_job_status": (latest_job or {}).get("status", ""),
|
||
"summary": str(result_map.get("summary") or result_map.get("headline_summary") or "")[:240],
|
||
}
|
||
)
|
||
items.sort(key=lambda item: (float((item.get("score") or {}).get("performance_score") or 0), item.get("published_at") or ""), reverse=True)
|
||
return items[: max(1, min(int(limit or 8), 16))]
|
||
|
||
def _fallback_platform_videos(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
requested_account_id: str = "",
|
||
limit: int = 8,
|
||
) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]:
|
||
safe_limit = max(1, min(int(limit or 8), 16))
|
||
if platform == "douyin":
|
||
target_account = None
|
||
if requested_account_id:
|
||
target_account = legacy.db.fetch_one(
|
||
"SELECT * FROM douyin_accounts WHERE id = ? AND user_id = ?",
|
||
(requested_account_id, account["id"]),
|
||
)
|
||
if not target_account:
|
||
target_account = legacy.db.fetch_one(
|
||
"SELECT * FROM douyin_accounts WHERE user_id = ? ORDER BY updated_at DESC, created_at DESC LIMIT 1",
|
||
(account["id"],),
|
||
)
|
||
if not target_account:
|
||
return None, []
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM douyin_videos
|
||
WHERE account_id = ?
|
||
ORDER BY COALESCE(published_at, updated_at) DESC, updated_at DESC
|
||
LIMIT ?
|
||
""",
|
||
(target_account["id"], safe_limit),
|
||
)
|
||
items: list[dict[str, Any]] = []
|
||
for row in rows:
|
||
stats = _parse_json(row.get("stats_json") or "{}", {})
|
||
play_count = float(stats.get("play_count") or stats.get("play") or 0)
|
||
like_count = float(stats.get("digg_count") or stats.get("like_count") or 0)
|
||
comment_count = float(stats.get("comment_count") or 0)
|
||
share_count = float(stats.get("share_count") or 0)
|
||
score = min(
|
||
100.0,
|
||
play_count / 10000 * 55
|
||
+ like_count / 1000 * 25
|
||
+ comment_count / 100 * 10
|
||
+ share_count / 100 * 10,
|
||
)
|
||
items.append(
|
||
{
|
||
"id": row["id"],
|
||
"title": row.get("title") or row.get("description") or row.get("share_url") or row["id"],
|
||
"source_url": row.get("share_url", ""),
|
||
"published_at": row.get("published_at") or "",
|
||
"score": {"performance_score": round(score, 2)},
|
||
"latest_job_id": "",
|
||
"latest_job_status": "",
|
||
"summary": "",
|
||
}
|
||
)
|
||
account_payload = {
|
||
"id": target_account["id"],
|
||
"title": target_account.get("nickname") or target_account.get("douyin_id") or "抖音账号",
|
||
"handle": target_account.get("douyin_id") or "",
|
||
"source_url": target_account.get("canonical_profile_url") or target_account.get("profile_url") or "",
|
||
"platform": "douyin",
|
||
}
|
||
items.sort(key=lambda item: (float((item.get("score") or {}).get("performance_score") or 0), item.get("published_at") or ""), reverse=True)
|
||
return account_payload, items[:safe_limit]
|
||
|
||
source_account = _latest_platform_account(account, project_id=project_id, platform=platform)
|
||
if not source_account:
|
||
return None, []
|
||
source_payload = legacy.content_source_payload(source_account)
|
||
metadata = source_payload.get("metadata") or {}
|
||
summary_videos = ((metadata.get("video_summary") or {}).get("videos") or [])[:safe_limit]
|
||
items = []
|
||
for item in summary_videos:
|
||
score = float(item.get("performance_score") or item.get("score") or 0)
|
||
items.append(
|
||
{
|
||
"id": str(item.get("id") or item.get("aweme_id") or item.get("video_id") or make_id(f"{platform}_video")),
|
||
"title": str(item.get("title") or item.get("description") or item.get("share_url") or "平台作品"),
|
||
"source_url": str(item.get("share_url") or item.get("url") or ""),
|
||
"published_at": str(item.get("published_at") or item.get("publish_time") or ""),
|
||
"score": {"performance_score": score},
|
||
"latest_job_id": "",
|
||
"latest_job_status": "",
|
||
"summary": "",
|
||
}
|
||
)
|
||
items.sort(key=lambda item: (float((item.get("score") or {}).get("performance_score") or 0), item.get("published_at") or ""), reverse=True)
|
||
return source_payload, items[:safe_limit]
|
||
|
||
def _assistant_brief_from_job(job_row: dict[str, Any] | None) -> str:
|
||
if not job_row:
|
||
return ""
|
||
result_map = _parse_json(job_row.get("result_json") or "{}", {})
|
||
artifacts_map = _parse_json(job_row.get("artifacts_json") or "{}", {})
|
||
candidates = [
|
||
result_map.get("summary"),
|
||
result_map.get("headline_summary"),
|
||
artifacts_map.get("summary"),
|
||
artifacts_map.get("objective"),
|
||
artifacts_map.get("brief"),
|
||
job_row.get("title"),
|
||
]
|
||
for value in candidates:
|
||
cleaned = str(value or "").strip()
|
||
if cleaned:
|
||
return cleaned[:480]
|
||
return ""
|
||
|
||
def _load_owned_session(session_id: str, account: dict[str, Any]) -> dict[str, Any]:
|
||
row = legacy.db.fetch_one(
|
||
"SELECT * FROM oneliner_sessions WHERE id = ? AND user_id = ?",
|
||
(session_id, account["id"]),
|
||
)
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="OneLiner session not found")
|
||
return row
|
||
|
||
def _deterministic_intent(message: str, platform_hint: str, account: dict[str, Any]) -> dict[str, Any]:
|
||
text = message.strip()
|
||
lowered = text.lower()
|
||
platform = normalize_platform_from_text(text) or _safe_platform(platform_hint or "", fallback="")
|
||
intent_key = "custom"
|
||
confidence = 0.45
|
||
summary = "先理解目标,再把任务路由到合适的平台 Agent 或固定能力。"
|
||
if any(keyword in text for keyword in ("新建项目", "创建项目", "建项目")):
|
||
intent_key = "create_project"
|
||
confidence = 0.96
|
||
summary = "这是一个新项目启动诉求,优先进入项目创建流。"
|
||
elif any(keyword in lowered for keyword in ("create agent",)) or any(keyword in text for keyword in ("创建agent", "创建 Agent", "新建agent", "新建 Agent")):
|
||
intent_key = "create_assistant"
|
||
confidence = 0.96
|
||
summary = "这是定义新 Agent 的需求,适合直接进入 Agent 创建流。"
|
||
elif any(keyword in text for keyword in ("导入主页", "主页链接", "账号主页", "主页账号")):
|
||
intent_key = "import_homepage"
|
||
confidence = 0.9
|
||
summary = "这是账号主页导入诉求,适合进入内容源接入。"
|
||
elif any(keyword in text for keyword in ("跟踪", "日报", "更新提醒", "持续跟")):
|
||
intent_key = "track_account"
|
||
confidence = 0.9
|
||
summary = "这是持续跟踪类任务,适合交给平台 Agent 和跟踪摘要链。"
|
||
elif any(keyword in text for keyword in ("高分", "爆款", "优质作品", "高表现")):
|
||
intent_key = "analyze_top_videos"
|
||
confidence = 0.88
|
||
summary = "这是高表现内容拆解诉求,优先分析高分作品。"
|
||
elif any(keyword in text for keyword in ("对标", "分析账号", "调研", "拆账号")):
|
||
intent_key = "analyze_account"
|
||
confidence = 0.86
|
||
summary = "这是账号层面的调研任务,优先交给对应平台 Agent。"
|
||
elif any(keyword in text for keyword in ("文案", "脚本", "口播", "改写")):
|
||
intent_key = "generate_copy"
|
||
confidence = 0.88
|
||
summary = "这是文案/脚本生成任务,适合走 Agent 生成链。"
|
||
elif any(keyword in text for keyword in ("AI视频", "AI 视频", "生成视频")):
|
||
intent_key = "ai_video"
|
||
confidence = 0.9
|
||
summary = "这是 AI 视频生产任务,适合走 AI 视频链。"
|
||
elif any(keyword in text for keyword in ("实拍", "剪辑", "混剪")):
|
||
intent_key = "real_cut"
|
||
confidence = 0.9
|
||
summary = "这是实拍剪辑任务,适合走 cutvideo 链。"
|
||
elif any(keyword in text for keyword in ("直播", "录制", "开录")):
|
||
intent_key = "live_recorder"
|
||
confidence = 0.9
|
||
summary = "这是直播录制任务,适合走 NAS 录制能力。"
|
||
elif any(keyword in text for keyword in ("复盘", "发布总结", "回看数据")):
|
||
intent_key = "review"
|
||
confidence = 0.84
|
||
summary = "这是发布复盘任务,适合进入复盘工作台。"
|
||
elif any(keyword in text for keyword in ("空间", "缓存", "存储", "NAS")):
|
||
intent_key = "storage_status"
|
||
confidence = 0.82
|
||
summary = "这是存储状态问题,适合查看租户存储面板。"
|
||
elif account.get("role") == "super_admin" and any(keyword in text for keyword in ("报错", "日志", "故障", "运维", "修复")):
|
||
intent_key = "ops_admin"
|
||
confidence = 0.84
|
||
summary = "这是平台级运维诉求,只能交给管理员运维/审计能力。"
|
||
return {
|
||
"intent_key": intent_key,
|
||
"platform": platform or "",
|
||
"confidence": confidence,
|
||
"summary": summary,
|
||
"reasoning_mode": "deterministic-first",
|
||
}
|
||
|
||
def normalize_platform_from_text(text: str) -> str:
|
||
for key, value in legacy.PLATFORM_ALIASES.items():
|
||
if key and key in text.lower():
|
||
if value in legacy.DOMESTIC_PLATFORMS:
|
||
return value
|
||
for key, value in legacy.PLATFORM_ALIASES.items():
|
||
if key and key in text:
|
||
if value in legacy.DOMESTIC_PLATFORMS:
|
||
return value
|
||
return ""
|
||
|
||
async def _model_refine_intent(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
message: str,
|
||
platform_hint: str,
|
||
) -> dict[str, Any]:
|
||
profile = legacy.model_profile_for_account(account["id"], None)
|
||
if not profile:
|
||
raise HTTPException(status_code=503, detail="No model profile available")
|
||
system_prompt = (
|
||
"你是 StoryForge 的 OneLiner 总控主Agent,只负责把用户目标分类成安全的系统动作。"
|
||
"必须输出 JSON,不要输出 Markdown。"
|
||
)
|
||
user_prompt = (
|
||
f"用户角色:{account.get('role','user')}\n"
|
||
f"项目:{project_id or '默认项目'}\n"
|
||
f"平台提示:{platform_hint or '未指定'}\n"
|
||
f"用户原话:{message}\n\n"
|
||
"请输出 JSON:"
|
||
"{"
|
||
'"intent_key":"","platform":"","confidence":0.0,'
|
||
'"summary":"","needs_oneliner_only":false,'
|
||
'"remember_preference":false'
|
||
"}\n"
|
||
"intent_key 只能取:create_project, create_assistant, import_homepage, track_account, analyze_account, analyze_top_videos, generate_copy, ai_video, real_cut, review, live_recorder, storage_status, ops_admin, custom。"
|
||
"如果前端 UI 还没有明确产品化,needs_oneliner_only 返回 true。"
|
||
)
|
||
raw = await legacy.call_model(profile, system_prompt, user_prompt, temperature=0.1)
|
||
parsed = legacy.parse_json_object(raw)
|
||
if not parsed:
|
||
raise HTTPException(status_code=502, detail="OneLiner planner returned empty result")
|
||
return {
|
||
"intent_key": str(parsed.get("intent_key") or "custom").strip() or "custom",
|
||
"platform": normalize_platform_from_text(str(parsed.get("platform") or "")) or _safe_platform(platform_hint or "", fallback=""),
|
||
"confidence": float(parsed.get("confidence") or 0),
|
||
"summary": str(parsed.get("summary") or "").strip() or "已按模型判断用户目标。",
|
||
"needs_oneliner_only": bool(parsed.get("needs_oneliner_only")),
|
||
"remember_preference": bool(parsed.get("remember_preference")),
|
||
"reasoning_mode": "model-refine",
|
||
}
|
||
|
||
async def _plan_oneliner_request(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
message: str,
|
||
platform_hint: str,
|
||
) -> dict[str, Any]:
|
||
plan = _deterministic_intent(message, platform_hint, account)
|
||
if plan["confidence"] < 0.82:
|
||
try:
|
||
refined = await _model_refine_intent(account, project_id=project_id, message=message, platform_hint=platform_hint)
|
||
if refined.get("confidence", 0) >= plan.get("confidence", 0):
|
||
plan = {**plan, **refined}
|
||
except Exception:
|
||
pass
|
||
intent_key = plan.get("intent_key") or "custom"
|
||
actions = INTENT_ACTIONS.get(intent_key, [])
|
||
if intent_key == "ops_admin" and account.get("role") != "super_admin":
|
||
actions = []
|
||
plan["summary"] = "这是平台级运维诉求,但当前账号没有管理员权限。"
|
||
plan["needs_oneliner_only"] = True
|
||
ui_supported = bool(actions)
|
||
if intent_key == "custom":
|
||
plan["needs_oneliner_only"] = True
|
||
plan["ui_supported"] = ui_supported
|
||
plan["delivery_mode"] = "ui" if ui_supported and not plan.get("needs_oneliner_only") else "oneliner"
|
||
plan["suggested_actions"] = actions
|
||
plan["intent_label"] = INTENT_LABELS.get(intent_key, intent_key)
|
||
plan["platform_label"] = legacy.platform_label(plan.get("platform")) if plan.get("platform") else "待判断"
|
||
plan["economicity"] = {
|
||
"policy": "deterministic-first",
|
||
"explanation": "先走固定流程,再走平台 Agent,最后才升级到 OneLiner 深度调度。",
|
||
}
|
||
return plan
|
||
|
||
def _remember_message_preference(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
plan: dict[str, Any],
|
||
message: str,
|
||
) -> dict[str, Any] | None:
|
||
cues = ("记住", "以后", "长期", "默认", "一直", "优先")
|
||
if not plan.get("remember_preference") and not any(cue in message for cue in cues):
|
||
return None
|
||
agent_scope = "oneliner"
|
||
platform = plan.get("platform") or ""
|
||
subject_type = "project" if project_id else "account"
|
||
subject_id = project_id or account["id"]
|
||
memory_key = f"preference::{plan.get('intent_key') or 'custom'}"
|
||
existing = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = ? AND platform = ?
|
||
AND subject_type = ? AND subject_id = ? AND memory_key = ?
|
||
""",
|
||
(account["id"], project_id, agent_scope, platform, subject_type, subject_id, memory_key),
|
||
)
|
||
timestamp = now()
|
||
details = {
|
||
"captured_from": "oneliner_chat",
|
||
"intent_key": plan.get("intent_key", "custom"),
|
||
"source_message": message,
|
||
"platform": platform,
|
||
}
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_memories
|
||
SET title = ?, summary = ?, details_json = ?, confidence = ?, last_validated_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
f"{INTENT_LABELS.get(plan.get('intent_key') or 'custom', '偏好')}偏好",
|
||
message.strip()[:280],
|
||
_dump(details),
|
||
0.82,
|
||
timestamp,
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
stored = legacy.db.fetch_one("SELECT * FROM agent_memories WHERE id = ?", (existing["id"],))
|
||
else:
|
||
memory_id = make_id("mem")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_memories (
|
||
id, user_id, project_id, agent_scope, platform, subject_type, subject_id,
|
||
memory_key, title, summary, details_json, confidence, last_validated_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
memory_id,
|
||
account["id"],
|
||
project_id,
|
||
agent_scope,
|
||
platform,
|
||
subject_type,
|
||
subject_id,
|
||
memory_key,
|
||
f"{INTENT_LABELS.get(plan.get('intent_key') or 'custom', '偏好')}偏好",
|
||
message.strip()[:280],
|
||
_dump(details),
|
||
0.82,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
stored = legacy.db.fetch_one("SELECT * FROM agent_memories WHERE id = ?", (memory_id,))
|
||
return _memory_payload(stored) if stored else None
|
||
|
||
def _remember_platform_observation(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
platform: str,
|
||
memory_key: str,
|
||
title: str,
|
||
summary: str,
|
||
details: dict[str, Any],
|
||
confidence: float = 0.82,
|
||
) -> dict[str, Any]:
|
||
request = AgentMemoryUpsertRequest(
|
||
project_id=project_id,
|
||
subject_type="project",
|
||
subject_id=project_id,
|
||
memory_key=memory_key,
|
||
title=title,
|
||
summary=summary,
|
||
details=details,
|
||
confidence=confidence,
|
||
)
|
||
return _upsert_memory(account, agent_scope="platform", platform=platform, request=request)
|
||
|
||
def _session_context_summary(account: dict[str, Any], project_id: str, platform: str) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
assistant = None
|
||
profile_row = _fetch_profile_row(account, project["id"])
|
||
if profile_row and profile_row.get("assistant_id"):
|
||
assistant = _resolve_assistant(account, profile_row.get("assistant_id"), project["id"])
|
||
platform_profile = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project["id"], platform),
|
||
) if platform else None
|
||
oneliner_memory_rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'oneliner'
|
||
ORDER BY updated_at DESC
|
||
LIMIT 3
|
||
""",
|
||
(account["id"], project["id"]),
|
||
)
|
||
platform_memory_rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
ORDER BY updated_at DESC
|
||
LIMIT 3
|
||
""",
|
||
(account["id"], project["id"], platform),
|
||
) if platform else []
|
||
platform_skill_rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_skills
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
ORDER BY
|
||
CASE WHEN status = 'validated' THEN 0 WHEN status = 'draft' THEN 1 ELSE 2 END,
|
||
updated_at DESC
|
||
LIMIT 3
|
||
""",
|
||
(account["id"], project["id"], platform),
|
||
) if platform else []
|
||
return {
|
||
"project": legacy.project_payload(project),
|
||
"oneliner_profile": _profile_payload(profile_row, account=account) if profile_row else None,
|
||
"platform_agent": _platform_agent_payload(account, platform_profile, platform=platform, project_id=project["id"]) if platform else None,
|
||
"assistant": legacy.assistant_payload(assistant) if assistant else None,
|
||
"oneliner_memories": [_memory_payload(row) for row in oneliner_memory_rows],
|
||
"platform_memories": [_memory_payload(row) for row in platform_memory_rows],
|
||
"platform_skills": [_skill_payload(row) for row in platform_skill_rows],
|
||
}
|
||
|
||
async def _generate_oneliner_reply(
|
||
account: dict[str, Any],
|
||
*,
|
||
project_id: str,
|
||
message: str,
|
||
plan: dict[str, Any],
|
||
) -> dict[str, Any]:
|
||
context = _session_context_summary(account, project_id or "", plan.get("platform") or "")
|
||
platform_agent = context.get("platform_agent") or {}
|
||
primary_action = (plan.get("suggested_actions") or [{}])[0] if plan.get("suggested_actions") else None
|
||
evidence = []
|
||
if platform_agent.get("recent_memory"):
|
||
evidence.append(
|
||
{
|
||
"kind": "memory",
|
||
"title": platform_agent["recent_memory"].get("title") or platform_agent["recent_memory"].get("memory_key") or "最近记忆",
|
||
"summary": platform_agent["recent_memory"].get("summary", ""),
|
||
}
|
||
)
|
||
if platform_agent.get("recent_skill"):
|
||
evidence.append(
|
||
{
|
||
"kind": "skill",
|
||
"title": platform_agent["recent_skill"].get("name") or platform_agent["recent_skill"].get("skill_key") or "最近技能",
|
||
"summary": platform_agent["recent_skill"].get("test_spec", {}).get("summary")
|
||
or platform_agent["recent_skill"].get("method", {}).get("summary")
|
||
or "",
|
||
"score": platform_agent["recent_skill"].get("last_score", 0),
|
||
}
|
||
)
|
||
blocked_reason = ""
|
||
if plan.get("intent_key") == "ops_admin" and account.get("role") != "super_admin":
|
||
blocked_reason = "当前账号不是平台最高权限用户,所以不会开放运维 Agent。"
|
||
elif plan.get("delivery_mode") == "oneliner":
|
||
blocked_reason = "当前更适合由 OneLiner 对话承接,等前端产品化后再下沉到固定 UI。"
|
||
next_steps = []
|
||
if primary_action:
|
||
next_steps.append(f"优先执行「{primary_action.get('label', primary_action.get('key', '下一步'))}」。")
|
||
if platform_agent.get("assistant", {}).get("name"):
|
||
next_steps.append(f"默认调度 {platform_agent['assistant']['name']} 作为执行 Agent。")
|
||
if evidence:
|
||
next_steps.append("我会优先参考该平台 Agent 最近沉淀的方法与技能。")
|
||
summary_lines = [
|
||
f"我理解你的目标是:{plan.get('intent_label', '自定义任务')}。",
|
||
f"建议优先处理的平台:{plan.get('platform_label', '待判断')}。",
|
||
plan.get("summary", ""),
|
||
]
|
||
if plan.get("delivery_mode") == "oneliner":
|
||
summary_lines.append("这项能力当前更适合先由 OneLiner 对话承接,而不是要求你先理解前端功能树。")
|
||
if plan.get("intent_key") == "ops_admin" and account.get("role") != "super_admin":
|
||
summary_lines.append("当前账号不是平台最高权限用户,所以我不会放出运维 Agent 入口。")
|
||
if context.get("platform_agent"):
|
||
summary_lines.append(f"当前 {context['platform_agent']['platform_label']} Agent 已绑定:{context['platform_agent'].get('assistant', {}).get('name') or '未绑定执行 Agent'}。")
|
||
if platform_agent.get("recent_memory"):
|
||
summary_lines.append(f"最近有效经验:{platform_agent['recent_memory'].get('title') or '一条平台记忆'}。")
|
||
if platform_agent.get("recent_skill"):
|
||
summary_lines.append(f"最近有效技能:{platform_agent['recent_skill'].get('name') or '一条技能'}。")
|
||
secondary_actions: list[dict[str, Any]] = []
|
||
if plan.get("platform"):
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "运行平台自检",
|
||
"kind": "api_action",
|
||
"executor_key": "platform-self-check",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
secondary_actions.append(
|
||
{
|
||
"key": "open-platform-agent-detail",
|
||
"label": f"查看{plan.get('platform_label', '平台')} Agent",
|
||
"kind": "ui_action",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
first_url = _extract_first_url(message)
|
||
if plan.get("intent_key") in {"import_homepage", "custom"} and first_url:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接导入主页",
|
||
"kind": "api_action",
|
||
"executor_key": "import-homepage",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_url": first_url,
|
||
},
|
||
}
|
||
)
|
||
if plan.get("intent_key") in {"storage_status", "custom"}:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "查看当前存储状态",
|
||
"kind": "api_action",
|
||
"executor_key": "storage-status",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
if plan.get("intent_key") == "live_recorder":
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "查看录制状态",
|
||
"kind": "api_action",
|
||
"executor_key": "live-recorder-status",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
if first_url:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接保存录制源",
|
||
"kind": "api_action",
|
||
"executor_key": "save-live-recorder-source",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_url": first_url,
|
||
"auto_start": True,
|
||
},
|
||
}
|
||
)
|
||
latest_platform_account = None
|
||
if plan.get("platform"):
|
||
latest_platform_account = _latest_platform_account(
|
||
account,
|
||
project_id=project_id or "",
|
||
platform=plan.get("platform", ""),
|
||
)
|
||
if plan.get("platform") and latest_platform_account and plan.get("intent_key") in {"analyze_top_videos", "analyze_account", "custom"}:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接分析高分作品",
|
||
"kind": "api_action",
|
||
"executor_key": "analyze-top-videos",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"target_account_id": latest_platform_account["id"],
|
||
},
|
||
}
|
||
)
|
||
if context.get("assistant"):
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接生成一版文案",
|
||
"kind": "api_action",
|
||
"executor_key": "generate-copy",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
latest_job = _latest_project_job(account, project_id=project_id or "")
|
||
if latest_job:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "生成复盘草稿",
|
||
"kind": "api_action",
|
||
"executor_key": "review-draft",
|
||
"platform": plan.get("platform", ""),
|
||
}
|
||
)
|
||
derivable_job = _latest_derivable_job(account, project_id=project_id or "", exclude_line_types={"ai_video", "real_cut"})
|
||
if derivable_job and plan.get("intent_key") in {"ai_video", "real_cut", "review", "custom"}:
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接创建 AI 视频",
|
||
"kind": "api_action",
|
||
"executor_key": "create-ai-video",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_job_id": derivable_job["id"],
|
||
},
|
||
}
|
||
)
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "直接创建实拍剪辑",
|
||
"kind": "api_action",
|
||
"executor_key": "create-real-cut",
|
||
"platform": plan.get("platform", ""),
|
||
"payload": {
|
||
"source_job_id": derivable_job["id"],
|
||
},
|
||
}
|
||
)
|
||
if account.get("role") == "super_admin":
|
||
secondary_actions.append(
|
||
{
|
||
"key": "run-oneliner-action",
|
||
"label": "重新扫描故障",
|
||
"kind": "api_action",
|
||
"executor_key": "scan-admin-ops",
|
||
"platform": "",
|
||
}
|
||
)
|
||
return {
|
||
"summary_text": "\n".join([line for line in summary_lines if line.strip()]),
|
||
"context": context,
|
||
"execution_card": {
|
||
"intent_key": plan.get("intent_key", "custom"),
|
||
"intent_label": plan.get("intent_label", "自定义任务"),
|
||
"delivery_mode": plan.get("delivery_mode", "oneliner"),
|
||
"platform": plan.get("platform", ""),
|
||
"platform_label": plan.get("platform_label", "待判断"),
|
||
"platform_agent_name": platform_agent.get("name") or "",
|
||
"assistant_name": platform_agent.get("assistant", {}).get("name") or context.get("assistant", {}).get("name") or "",
|
||
"readiness_label": platform_agent.get("readiness_label") or "",
|
||
"readiness_score": platform_agent.get("readiness_score") or 0,
|
||
"primary_action": primary_action or {},
|
||
"blocked_reason": blocked_reason,
|
||
"evidence": evidence,
|
||
"next_steps": next_steps,
|
||
"secondary_actions": secondary_actions,
|
||
},
|
||
"safe_boundary": {
|
||
"core_code_locked": True,
|
||
"tenant_isolation": True,
|
||
"ops_admin_only": True,
|
||
},
|
||
}
|
||
|
||
def _insert_message(session_id: str, account_id: str, role: str, content: str, plan: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]:
|
||
message_id = make_id("oline_msg")
|
||
created_at = now()
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_messages (id, session_id, user_id, role, content, plan_json, result_json, created_at)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(message_id, session_id, account_id, role, content, _dump(plan), _dump(result), created_at),
|
||
)
|
||
return legacy.db.fetch_one("SELECT * FROM oneliner_messages WHERE id = ?", (message_id,))
|
||
|
||
def _upsert_platform_profile(account: dict[str, Any], platform: str, request: PlatformAgentProfileRequest) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
assistant = _resolve_assistant(account, request.assistant_id or None, project["id"])
|
||
if not assistant:
|
||
fallback_profile = _fetch_profile_row(account, project["id"]) or _ensure_oneliner_profile(account, project["id"])
|
||
if fallback_profile.get("assistant_id"):
|
||
assistant = _resolve_assistant(account, fallback_profile.get("assistant_id"), project["id"])
|
||
if not assistant:
|
||
assistant = _resolve_assistant(account, None, project["id"])
|
||
existing = legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project["id"], platform),
|
||
)
|
||
timestamp = now()
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE platform_agent_profiles
|
||
SET assistant_id = ?, name = ?, mission = ?, notes = ?, status = ?, config_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
(assistant or {}).get("id", ""),
|
||
request.name.strip() or existing.get("name") or f"{legacy.platform_label(platform)} Agent",
|
||
request.mission.strip(),
|
||
request.notes.strip(),
|
||
request.status.strip() or "active",
|
||
_dump(request.config),
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM platform_agent_profiles WHERE id = ?", (existing["id"],))
|
||
else:
|
||
profile_id = make_id("plat_agent")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO platform_agent_profiles (
|
||
id, user_id, project_id, platform, assistant_id, name, mission, notes, status, config_json, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
profile_id,
|
||
account["id"],
|
||
project["id"],
|
||
platform,
|
||
(assistant or {}).get("id", ""),
|
||
request.name.strip() or f"{legacy.platform_label(platform)} Agent",
|
||
request.mission.strip(),
|
||
request.notes.strip(),
|
||
request.status.strip() or "active",
|
||
_dump(request.config),
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM platform_agent_profiles WHERE id = ?", (profile_id,))
|
||
return _platform_agent_payload(account, row, platform=platform, project_id=project["id"])
|
||
|
||
def _upsert_memory(
|
||
account: dict[str, Any],
|
||
*,
|
||
agent_scope: str,
|
||
platform: str,
|
||
request: AgentMemoryUpsertRequest,
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
subject_type = request.subject_type.strip() or "project"
|
||
subject_id = request.subject_id.strip() or (project["id"] if subject_type == "project" else account["id"])
|
||
existing = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = ? AND platform = ?
|
||
AND subject_type = ? AND subject_id = ? AND memory_key = ?
|
||
""",
|
||
(account["id"], project["id"], agent_scope, platform, subject_type, subject_id, request.memory_key.strip()),
|
||
)
|
||
timestamp = now()
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_memories
|
||
SET title = ?, summary = ?, details_json = ?, confidence = ?, last_validated_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
request.title.strip(),
|
||
request.summary.strip(),
|
||
_dump(request.details),
|
||
request.confidence,
|
||
timestamp,
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_memories WHERE id = ?", (existing["id"],))
|
||
else:
|
||
memory_id = make_id("mem")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_memories (
|
||
id, user_id, project_id, agent_scope, platform, subject_type, subject_id,
|
||
memory_key, title, summary, details_json, confidence, last_validated_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
memory_id,
|
||
account["id"],
|
||
project["id"],
|
||
agent_scope,
|
||
platform,
|
||
subject_type,
|
||
subject_id,
|
||
request.memory_key.strip(),
|
||
request.title.strip(),
|
||
request.summary.strip(),
|
||
_dump(request.details),
|
||
request.confidence,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_memories WHERE id = ?", (memory_id,))
|
||
return _memory_payload(row)
|
||
|
||
def _upsert_skill(
|
||
account: dict[str, Any],
|
||
*,
|
||
agent_scope: str,
|
||
platform: str,
|
||
request: AgentSkillUpsertRequest,
|
||
skill_id: str | None = None,
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
existing = None
|
||
if skill_id:
|
||
existing = legacy.db.fetch_one(
|
||
"SELECT * FROM agent_skills WHERE id = ? AND user_id = ?",
|
||
(skill_id, account["id"]),
|
||
)
|
||
if not existing:
|
||
raise HTTPException(status_code=404, detail="Agent skill not found")
|
||
else:
|
||
existing = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_skills
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = ? AND platform = ? AND skill_key = ?
|
||
""",
|
||
(account["id"], project["id"], agent_scope, platform, request.skill_key.strip()),
|
||
)
|
||
timestamp = now()
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_skills
|
||
SET name = ?, status = ?, method_json = ?, test_spec_json = ?, last_result_json = ?,
|
||
success_count = ?, failure_count = ?, last_score = ?, last_validated_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
request.name.strip(),
|
||
request.status.strip() or "draft",
|
||
_dump(request.method),
|
||
_dump(request.test_spec),
|
||
_dump(request.last_result),
|
||
request.success_count,
|
||
request.failure_count,
|
||
request.last_score,
|
||
timestamp,
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_skills WHERE id = ?", (existing["id"],))
|
||
else:
|
||
new_id = make_id("skill")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO agent_skills (
|
||
id, user_id, project_id, agent_scope, platform, parent_skill_id, skill_key, name, status,
|
||
method_json, test_spec_json, last_result_json, success_count, failure_count, last_score,
|
||
last_validated_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
new_id,
|
||
account["id"],
|
||
project["id"],
|
||
agent_scope,
|
||
platform,
|
||
request.skill_key.strip(),
|
||
request.name.strip(),
|
||
request.status.strip() or "draft",
|
||
_dump(request.method),
|
||
_dump(request.test_spec),
|
||
_dump(request.last_result),
|
||
request.success_count,
|
||
request.failure_count,
|
||
request.last_score,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM agent_skills WHERE id = ?", (new_id,))
|
||
return _skill_payload(row)
|
||
|
||
def _create_or_update_incident(
|
||
*,
|
||
tenant_user_id: str,
|
||
tenant_project_id: str,
|
||
source_type: str,
|
||
source_id: str,
|
||
severity: str,
|
||
title: str,
|
||
summary: str,
|
||
payload: dict[str, Any],
|
||
) -> dict[str, Any]:
|
||
existing = legacy.db.fetch_one(
|
||
"SELECT * FROM admin_ops_incidents WHERE source_type = ? AND source_id = ? AND title = ?",
|
||
(source_type, source_id, title),
|
||
)
|
||
timestamp = now()
|
||
if existing:
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE admin_ops_incidents
|
||
SET tenant_user_id = ?, tenant_project_id = ?, severity = ?, summary = ?, payload_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
tenant_user_id,
|
||
tenant_project_id,
|
||
severity,
|
||
summary,
|
||
_dump(payload),
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (existing["id"],))
|
||
else:
|
||
incident_id = make_id("incident")
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO admin_ops_incidents (
|
||
id, tenant_user_id, tenant_project_id, source_type, source_id, severity, title,
|
||
summary, payload_json, status, assigned_to, reviewed_by, review_notes, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', '', '', '', ?, ?)
|
||
""",
|
||
(
|
||
incident_id,
|
||
tenant_user_id,
|
||
tenant_project_id,
|
||
source_type,
|
||
source_id,
|
||
severity,
|
||
title,
|
||
summary,
|
||
_dump(payload),
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (incident_id,))
|
||
return _incident_payload(row)
|
||
|
||
def _scan_admin_incidents(admin: dict[str, Any]) -> dict[str, Any]:
|
||
_ = admin
|
||
created: list[dict[str, Any]] = []
|
||
failed_jobs = legacy.db.fetch_all(
|
||
"SELECT * FROM jobs WHERE status = 'failed' ORDER BY updated_at DESC LIMIT 20"
|
||
)
|
||
for job in failed_jobs:
|
||
created.append(
|
||
_create_or_update_incident(
|
||
tenant_user_id=job.get("user_id", "") or "",
|
||
tenant_project_id=job.get("project_id", "") or "",
|
||
source_type="job",
|
||
source_id=job["id"],
|
||
severity="error",
|
||
title=f"失败任务:{job.get('title') or job['id']}",
|
||
summary=job.get("error", "")[:280] or "任务失败,需要检查执行链。",
|
||
payload=legacy.job_payload(job),
|
||
)
|
||
)
|
||
try:
|
||
integration_health = legacy.integrations_health(admin)
|
||
except Exception as exc:
|
||
integration_health = {"collector": {"reachable": False, "error": str(exc)}}
|
||
for key, payload in integration_health.items():
|
||
reachable = bool(payload.get("reachable", False))
|
||
if reachable and key != "cutvideo":
|
||
continue
|
||
if key == "cutvideo" and payload.get("supports_uploads", True):
|
||
continue
|
||
created.append(
|
||
_create_or_update_incident(
|
||
tenant_user_id="",
|
||
tenant_project_id="",
|
||
source_type="integration",
|
||
source_id=key,
|
||
severity="warn" if key in {"cutvideo", "live_recorder"} else "error",
|
||
title=f"集成异常:{key}",
|
||
summary=str(payload.get("error") or payload.get("upload_error") or "集成健康检查未通过")[:280],
|
||
payload=payload,
|
||
)
|
||
)
|
||
return {
|
||
"created_or_updated": created,
|
||
"count": len(created),
|
||
"audit": _log_admin_audit_event(
|
||
actor_user_id=admin["id"],
|
||
action_key="scan",
|
||
status="completed",
|
||
summary=f"本轮扫描归集 {len(created)} 条事件。",
|
||
details={"count": len(created)},
|
||
),
|
||
}
|
||
|
||
def _admin_ops_overview_payload(admin: dict[str, Any]) -> dict[str, Any]:
|
||
incidents = [
|
||
_incident_payload(row)
|
||
for row in legacy.db.fetch_all(
|
||
"SELECT * FROM admin_ops_incidents ORDER BY updated_at DESC LIMIT 50"
|
||
)
|
||
]
|
||
open_incidents = [item for item in incidents if item.get("status") in {"open", "watching", ""}]
|
||
severity_counts = {
|
||
"error": len([item for item in incidents if item.get("severity") == "error"]),
|
||
"warn": len([item for item in incidents if item.get("severity") == "warn"]),
|
||
"info": len([item for item in incidents if item.get("severity") == "info"]),
|
||
}
|
||
failed_jobs = [
|
||
legacy.job_payload(row)
|
||
for row in legacy.db.fetch_all(
|
||
"SELECT * FROM jobs WHERE status = 'failed' ORDER BY updated_at DESC LIMIT 12"
|
||
)
|
||
]
|
||
pending_accounts = [
|
||
legacy.normalize_account(row)
|
||
for row in legacy.db.fetch_all("SELECT * FROM accounts WHERE approval_status = 'pending' ORDER BY created_at ASC LIMIT 20")
|
||
]
|
||
recent_audits = [
|
||
_admin_audit_payload(row)
|
||
for row in legacy.db.fetch_all(
|
||
"SELECT * FROM admin_ops_audit_logs ORDER BY created_at DESC LIMIT 20"
|
||
)
|
||
]
|
||
return {
|
||
"incidents": incidents,
|
||
"incident_count": len(incidents),
|
||
"open_incident_count": len(open_incidents),
|
||
"severity_counts": severity_counts,
|
||
"failed_jobs": failed_jobs,
|
||
"failed_job_count": len(failed_jobs),
|
||
"pending_accounts": pending_accounts,
|
||
"pending_account_count": len(pending_accounts),
|
||
"recent_audits": recent_audits,
|
||
"audit_count": len(recent_audits),
|
||
"integration_health": legacy.integrations_health(admin),
|
||
}
|
||
|
||
def _platform_self_check(
|
||
account: dict[str, Any],
|
||
*,
|
||
platform: str,
|
||
project_id: str,
|
||
sample_limit: int = 3,
|
||
remember_summary: bool = True,
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
profile = _platform_agent_payload(
|
||
account,
|
||
legacy.db.fetch_one(
|
||
"SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?",
|
||
(account["id"], project["id"], normalized_platform),
|
||
),
|
||
platform=normalized_platform,
|
||
project_id=project["id"],
|
||
)
|
||
route_checks = _platform_route_checks(normalized_platform)
|
||
route_ok_count = len([item for item in route_checks if item["ok"]])
|
||
route_ratio = (route_ok_count / len(route_checks)) if route_checks else 0
|
||
source_samples = _platform_source_samples(account, project_id=project["id"], platform=normalized_platform, limit=sample_limit)
|
||
signal_checks = [
|
||
("配置激活", bool(profile.get("status") == "active")),
|
||
("已绑定执行 Agent", bool(profile.get("assistant_id"))),
|
||
("已有平台记忆", bool(profile.get("memory_count"))),
|
||
("已有平台技能", bool(profile.get("skill_count"))),
|
||
("已有平台账号源", bool(source_samples)),
|
||
]
|
||
signal_score = sum(1 for _, ok in signal_checks if ok) * 12
|
||
route_score = int(route_ratio * 40)
|
||
score = min(100, signal_score + route_score)
|
||
if score >= 85:
|
||
verdict = "validated"
|
||
label = "稳定"
|
||
elif score >= 60:
|
||
verdict = "usable"
|
||
label = "可用"
|
||
else:
|
||
verdict = "needs_work"
|
||
label = "待加强"
|
||
suggestions = []
|
||
if not profile.get("assistant_id"):
|
||
suggestions.append("先给平台 Agent 绑定一个执行 Agent。")
|
||
if not profile.get("memory_count"):
|
||
suggestions.append("补一条平台记忆,沉淀最近有效经验。")
|
||
if not profile.get("skill_count"):
|
||
suggestions.append("补一条可验收的平台技能。")
|
||
if not source_samples:
|
||
suggestions.append("先导入至少一个该平台账号源,避免空跑。")
|
||
if route_ratio < 1:
|
||
suggestions.append("补齐当前平台 workbench 路由,避免调度时出现断点。")
|
||
payload = {
|
||
"platform": normalized_platform,
|
||
"platform_label": legacy.platform_label(normalized_platform),
|
||
"project_id": project["id"],
|
||
"score": score,
|
||
"readiness_label": label,
|
||
"verdict": verdict,
|
||
"route_checks": route_checks,
|
||
"signals": [{"label": name, "ok": ok} for name, ok in signal_checks],
|
||
"source_count": len(source_samples),
|
||
"source_samples": source_samples,
|
||
"checked_at": now(),
|
||
"suggestions": suggestions,
|
||
"profile": profile,
|
||
}
|
||
if remember_summary:
|
||
_remember_platform_observation(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
memory_key=f"self_check::{normalized_platform}",
|
||
title=f"{legacy.platform_label(normalized_platform)} Agent 自检",
|
||
summary=f"平台自检得分 {score},当前判定为{label}。",
|
||
details=payload,
|
||
confidence=0.88 if score >= 85 else 0.72,
|
||
)
|
||
return payload
|
||
|
||
def _review_platform_skill(
|
||
account: dict[str, Any],
|
||
*,
|
||
platform: str,
|
||
skill_id: str,
|
||
request: PlatformSkillReviewRequest,
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
current = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM agent_skills
|
||
WHERE id = ? AND user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
""",
|
||
(skill_id, account["id"], project["id"], normalized_platform),
|
||
)
|
||
if not current:
|
||
raise HTTPException(status_code=404, detail="Platform skill not found")
|
||
accepted = bool(request.accepted)
|
||
next_status = (request.status or "").strip() or ("validated" if accepted else "needs_revision")
|
||
timestamp = now()
|
||
next_success = int(current.get("success_count") or 0) + (1 if accepted else 0)
|
||
next_failure = int(current.get("failure_count") or 0) + (0 if accepted else 1)
|
||
result_payload = {
|
||
**_parse_json(current.get("last_result_json"), {}),
|
||
"accepted": accepted,
|
||
"review_notes": request.review_notes.strip(),
|
||
"summary": request.summary.strip(),
|
||
"reviewed_at": timestamp,
|
||
"reviewed_by": account["id"],
|
||
}
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE agent_skills
|
||
SET status = ?, last_result_json = ?, success_count = ?, failure_count = ?, last_score = ?, last_validated_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
next_status,
|
||
_dump(result_payload),
|
||
next_success,
|
||
next_failure,
|
||
request.score,
|
||
timestamp,
|
||
timestamp,
|
||
skill_id,
|
||
),
|
||
)
|
||
updated = legacy.db.fetch_one("SELECT * FROM agent_skills WHERE id = ?", (skill_id,))
|
||
feedback_summary = (request.summary or request.review_notes or "").strip()
|
||
feedback_memory = None
|
||
if feedback_summary:
|
||
feedback_memory = _remember_platform_observation(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
memory_key=f"skill_feedback::{current.get('skill_key')}",
|
||
title=f"{current.get('name') or current.get('skill_key') or '技能'}·{'已验证' if accepted else '待优化'}",
|
||
summary=feedback_summary[:280],
|
||
details={
|
||
"skill_id": skill_id,
|
||
"skill_key": current.get("skill_key", ""),
|
||
"accepted": accepted,
|
||
"score": request.score,
|
||
"review_notes": request.review_notes.strip(),
|
||
"status": next_status,
|
||
},
|
||
confidence=0.9 if accepted else 0.66,
|
||
)
|
||
payload = _skill_payload(updated)
|
||
if feedback_memory:
|
||
payload["feedback_memory"] = feedback_memory
|
||
return payload
|
||
|
||
async def _execute_oneliner_action(
|
||
account: dict[str, Any],
|
||
request: OneLinerActionExecuteRequest,
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
normalized_platform = normalize_platform_from_text(request.platform) or _safe_platform(request.platform or "", fallback="")
|
||
action_key = (request.action_key or "").strip()
|
||
if not action_key:
|
||
raise HTTPException(status_code=400, detail="Action key is required")
|
||
latest_user_message = _last_user_message_text(request.session_id, account["id"]) if request.session_id else ""
|
||
requested_payload = request.payload or {}
|
||
|
||
async def _run_platform_self_check() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for self-check")
|
||
payload = _platform_self_check(
|
||
account,
|
||
platform=normalized_platform,
|
||
project_id=project["id"],
|
||
sample_limit=int((request.payload or {}).get("sample_limit") or 3),
|
||
remember_summary=True,
|
||
)
|
||
return {
|
||
"title": f"{payload['platform_label']} Agent 自检",
|
||
"summary": f"平台自检得分 {payload['score']},当前状态:{payload['readiness_label']}。",
|
||
"payload": payload,
|
||
}
|
||
|
||
async def _run_storage_status() -> dict[str, Any]:
|
||
payload = legacy.storage_status(project_id=project["id"], account=account)
|
||
tenant_usage = payload.get("tenant_usage", {})
|
||
return {
|
||
"title": "当前存储状态",
|
||
"summary": (
|
||
f"项目 jobs 占用 {tenant_usage.get('project_jobs', {}).get('human_size', '0B')},"
|
||
f"downloads 占用 {tenant_usage.get('project_downloads', {}).get('human_size', '0B')}。"
|
||
),
|
||
"payload": payload,
|
||
}
|
||
|
||
async def _run_live_recorder_status() -> dict[str, Any]:
|
||
payload = legacy.live_recorder_status(project_id=project["id"], account=account)
|
||
return {
|
||
"title": "直播录制状态",
|
||
"summary": f"当前共 {len(payload.get('items', []))} 条录制源,最近文件 {len(payload.get('files', []))} 个。",
|
||
"payload": payload,
|
||
}
|
||
|
||
async def _run_ops_scan() -> dict[str, Any]:
|
||
admin = legacy.require_super_admin(account)
|
||
payload = _scan_admin_incidents(admin)
|
||
return {
|
||
"title": "运维 Agent 故障扫描",
|
||
"summary": f"本轮共归集 {payload.get('count', 0)} 条事件。",
|
||
"payload": payload,
|
||
}
|
||
|
||
async def _run_generate_copy() -> dict[str, Any]:
|
||
assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform)
|
||
if not assistant:
|
||
raise HTTPException(status_code=404, detail="No execution assistant available")
|
||
brief = str((request.payload or {}).get("brief") or latest_user_message or "").strip()
|
||
if not brief:
|
||
brief = f"请基于当前项目目标,输出一版适合{legacy.platform_label(normalized_platform or 'douyin')}发布的短视频文案。"
|
||
payload = await legacy.generate_copy(
|
||
assistant["id"],
|
||
legacy.GenerateCopyRequest(
|
||
brief=brief,
|
||
platform=normalized_platform or "douyin",
|
||
audience=str((request.payload or {}).get("audience") or "创业者"),
|
||
extra_requirements=str((request.payload or {}).get("extra_requirements") or ""),
|
||
knowledge_base_ids=list((request.payload or {}).get("knowledge_base_ids") or []),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已生成文案",
|
||
"summary": f"已用 {assistant.get('name') or '默认 Agent'} 生成一版可发布文案。",
|
||
"payload": payload,
|
||
}
|
||
|
||
async def _run_review_draft() -> dict[str, Any]:
|
||
latest_job = _latest_project_job(account, project_id=project["id"])
|
||
if not latest_job:
|
||
raise HTTPException(status_code=404, detail="No completed job available for review draft")
|
||
existing = legacy.db.fetch_one(
|
||
"SELECT * FROM publish_reviews WHERE user_id = ? AND source_job_id = ? ORDER BY created_at DESC LIMIT 1",
|
||
(account["id"], latest_job["id"]),
|
||
)
|
||
if existing:
|
||
payload = legacy.review_payload(existing)
|
||
return {
|
||
"title": "OneLiner 找到已有复盘",
|
||
"summary": f"任务「{latest_job.get('title') or latest_job['id']}」已经有复盘记录。",
|
||
"payload": payload,
|
||
}
|
||
assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform)
|
||
result = latest_job.get("result_json") or "{}"
|
||
try:
|
||
result_map = json.loads(result)
|
||
except json.JSONDecodeError:
|
||
result_map = {}
|
||
payload = legacy.create_review(
|
||
legacy.ReviewCreateRequest(
|
||
project_id=project["id"],
|
||
source_job_id=latest_job["id"],
|
||
assistant_id=(assistant or {}).get("id", ""),
|
||
title=f"{latest_job.get('title') or '任务'} 复盘草稿",
|
||
platform=normalized_platform or "douyin",
|
||
content_type="video",
|
||
verdict="待补充",
|
||
highlights=str(result_map.get("summary") or result_map.get("headline_summary") or "")[:400],
|
||
next_actions="补充发布结果、完善指标、确认下一步动作。",
|
||
notes=str((request.payload or {}).get("notes") or "由 OneLiner 自动生成复盘草稿。"),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已生成复盘草稿",
|
||
"summary": f"已基于最近完成任务「{latest_job.get('title') or latest_job['id']}」生成复盘草稿。",
|
||
"payload": payload,
|
||
}
|
||
|
||
async def _run_import_homepage() -> dict[str, Any]:
|
||
source_url = str(
|
||
requested_payload.get("source_url")
|
||
or requested_payload.get("sourceUrl")
|
||
or _extract_first_url(latest_user_message)
|
||
or ""
|
||
).strip()
|
||
if not source_url:
|
||
raise HTTPException(status_code=400, detail="No homepage URL available for import")
|
||
inferred_platform = normalize_platform_from_text(
|
||
requested_payload.get("platform")
|
||
or requested_payload.get("platform_label")
|
||
or normalized_platform
|
||
or legacy.infer_platform_from_url(source_url)
|
||
) or _safe_platform(legacy.infer_platform_from_url(source_url), fallback="douyin")
|
||
assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=inferred_platform)
|
||
existing_source = _find_creator_source_by_url(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=inferred_platform,
|
||
source_url=source_url,
|
||
)
|
||
sync_job = await legacy.create_content_source_sync_job(
|
||
legacy.ContentSourceSyncRequest(
|
||
project_id=project["id"],
|
||
knowledge_base_id=str(requested_payload.get("knowledge_base_id") or requested_payload.get("knowledgeBaseId") or ""),
|
||
assistant_id=(assistant or {}).get("id", ""),
|
||
content_source_id=(existing_source or {}).get("id", ""),
|
||
platform=inferred_platform,
|
||
handle=str(requested_payload.get("handle") or ""),
|
||
source_url=source_url,
|
||
title=str(requested_payload.get("title") or requested_payload.get("name") or ""),
|
||
analysis_model_profile_id=str(requested_payload.get("analysis_model_profile_id") or requested_payload.get("analysisModelProfileId") or ""),
|
||
language=str(requested_payload.get("language") or "auto"),
|
||
max_items=max(1, min(int(requested_payload.get("max_items") or requested_payload.get("maxItems") or 5), 20)),
|
||
skip_existing=bool(requested_payload.get("skip_existing", requested_payload.get("skipExisting", True))),
|
||
auto_trigger_analysis=bool(requested_payload.get("auto_trigger_analysis", requested_payload.get("autoTriggerAnalysis", True))),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已导入主页",
|
||
"summary": f"已把主页接入当前项目,并触发 {legacy.platform_label(inferred_platform)} 内容源同步。",
|
||
"payload": {
|
||
"job": sync_job,
|
||
"platform": inferred_platform,
|
||
"source_url": source_url,
|
||
"existing_source_id": (existing_source or {}).get("id", ""),
|
||
},
|
||
}
|
||
|
||
async def _run_analyze_top_videos() -> dict[str, Any]:
|
||
if not normalized_platform:
|
||
raise HTTPException(status_code=400, detail="Platform is required for top video analysis")
|
||
target_account = None
|
||
requested_account_id = str(requested_payload.get("target_account_id") or requested_payload.get("targetAccountId") or "").strip()
|
||
if requested_account_id and normalized_platform != "douyin":
|
||
target_account = legacy.db.fetch_one(
|
||
"""
|
||
SELECT * FROM content_sources
|
||
WHERE id = ? AND user_id = ? AND project_id = ? AND platform = ? AND source_kind = 'creator_account'
|
||
""",
|
||
(requested_account_id, account["id"], project["id"], normalized_platform),
|
||
)
|
||
if not target_account:
|
||
target_account = _latest_platform_account(account, project_id=project["id"], platform=normalized_platform)
|
||
videos = _linked_platform_videos(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
account_row=target_account or {},
|
||
limit=max(2, min(int(requested_payload.get("top_video_count") or requested_payload.get("topVideoCount") or 6), 12)),
|
||
) if target_account else []
|
||
account_payload = legacy.content_source_payload(target_account) if target_account else None
|
||
if not videos:
|
||
account_payload, videos = _fallback_platform_videos(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
requested_account_id=requested_account_id,
|
||
limit=max(2, min(int(requested_payload.get("top_video_count") or requested_payload.get("topVideoCount") or 6), 12)),
|
||
)
|
||
if not account_payload:
|
||
raise HTTPException(status_code=404, detail="No platform account available for top video analysis")
|
||
min_score = float(requested_payload.get("min_score") or requested_payload.get("minScore") or 0)
|
||
ranked = [item for item in videos if float((item.get("score") or {}).get("performance_score") or 0) >= min_score]
|
||
if not ranked:
|
||
raise HTTPException(status_code=404, detail="No candidate videos available for analysis")
|
||
profile = legacy.model_profile_for_account(
|
||
account["id"],
|
||
str(requested_payload.get("model_profile_id") or requested_payload.get("modelProfileId") or ""),
|
||
)
|
||
items: list[dict[str, Any]] = []
|
||
parse_json_object = getattr(legacy, "parse_json_object", None)
|
||
for video in ranked:
|
||
prompt = (
|
||
f"请拆解这条{legacy.platform_label(normalized_platform)}作品为什么值得关注,"
|
||
"输出 JSON,字段包括 summary、borrow_points、risks、next_actions。"
|
||
f"\n\n输入:\n{json.dumps(video, ensure_ascii=False, indent=2)}"
|
||
)
|
||
output = await legacy.call_model(
|
||
profile,
|
||
"你是平台内容拆解助手。尽量输出 JSON,字段包括 summary、borrow_points、risks、next_actions。",
|
||
prompt,
|
||
temperature=float(requested_payload.get("temperature") or 0.25),
|
||
)
|
||
parsed = parse_json_object(output) if callable(parse_json_object) else _parse_json(output, {})
|
||
summary_text = str(parsed.get("summary") or parsed.get("headline_summary") or output).strip()[:240]
|
||
items.append(
|
||
{
|
||
"id": make_id(f"{normalized_platform}_va"),
|
||
"video_id": video["id"],
|
||
"video_title": video["title"],
|
||
"status": "ok",
|
||
"summary_text": summary_text,
|
||
"parsed_json": parsed,
|
||
"performance_score": (video.get("score") or {}).get("performance_score", 0),
|
||
"latest_job_id": video.get("latest_job_id", ""),
|
||
"created_at": now(),
|
||
}
|
||
)
|
||
memory = _remember_platform_observation(
|
||
account,
|
||
project_id=project["id"],
|
||
platform=normalized_platform,
|
||
memory_key=f"top_videos::{target_account['id']}",
|
||
title=f"{legacy.platform_label(normalized_platform)} 高分作品拆解",
|
||
summary=f"已拆解 {len(items)} 条高分作品,继续固化有效结构与风险判断。",
|
||
details={
|
||
"account_id": account_payload["id"],
|
||
"items": items,
|
||
},
|
||
confidence=0.84,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已分析高分作品",
|
||
"summary": f"已为 {legacy.platform_label(normalized_platform)} 账号拆解 {len(items)} 条高分作品。",
|
||
"payload": {
|
||
"platform": normalized_platform,
|
||
"account": account_payload,
|
||
"analyzed_count": len(items),
|
||
"items": items,
|
||
"memory": memory,
|
||
},
|
||
}
|
||
|
||
async def _run_create_ai_video() -> dict[str, Any]:
|
||
source_job = _load_owned_job(account, str(requested_payload.get("source_job_id") or requested_payload.get("sourceJobId") or "")) or _latest_derivable_job(
|
||
account,
|
||
project_id=project["id"],
|
||
exclude_line_types={"ai_video"},
|
||
)
|
||
if not source_job:
|
||
raise HTTPException(status_code=404, detail="No completed source job available for AI video")
|
||
assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform)
|
||
brief = str(
|
||
requested_payload.get("brief")
|
||
or requested_payload.get("video_brief")
|
||
or requested_payload.get("videoBrief")
|
||
or latest_user_message
|
||
or _assistant_brief_from_job(source_job)
|
||
or ""
|
||
).strip()
|
||
if not brief:
|
||
brief = f"请基于任务「{source_job.get('title') or source_job['id']}」输出一版适合短视频平台的 AI 视频。"
|
||
job = await legacy.create_ai_video_job(
|
||
legacy.AiVideoJobRequest(
|
||
project_id=project["id"],
|
||
assistant_id=(assistant or {}).get("id", ""),
|
||
knowledge_base_id=str(requested_payload.get("knowledge_base_id") or requested_payload.get("knowledgeBaseId") or source_job.get("knowledge_base_id") or ""),
|
||
source_job_id=source_job["id"],
|
||
title=str(requested_payload.get("title") or f"{source_job.get('title') or '任务'} · AI 视频"),
|
||
brief=brief,
|
||
style=str(requested_payload.get("style") or "realistic"),
|
||
shots=max(1, min(int(requested_payload.get("shots") or 4), 12)),
|
||
duration=max(3, min(int(requested_payload.get("duration") or 5), 12)),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已创建 AI 视频任务",
|
||
"summary": f"已基于「{source_job.get('title') or source_job['id']}」创建 AI 视频任务。",
|
||
"payload": {
|
||
"job": job,
|
||
"source_job": legacy.job_payload(source_job),
|
||
"brief": brief,
|
||
},
|
||
}
|
||
|
||
async def _run_create_real_cut() -> dict[str, Any]:
|
||
source_job = _load_owned_job(account, str(requested_payload.get("source_job_id") or requested_payload.get("sourceJobId") or "")) or _latest_derivable_job(
|
||
account,
|
||
project_id=project["id"],
|
||
exclude_line_types={"ai_video", "real_cut"},
|
||
)
|
||
if not source_job:
|
||
raise HTTPException(status_code=404, detail="No completed source job available for real cut")
|
||
job = await legacy.create_real_cut_job(
|
||
legacy.RealCutJobRequest(
|
||
project_id=project["id"],
|
||
title=str(requested_payload.get("title") or f"{source_job.get('title') or '任务'} · 实拍剪辑"),
|
||
source_job_id=source_job["id"],
|
||
objective=str(
|
||
requested_payload.get("objective")
|
||
or "保留高信息密度片段,输出适合短视频平台的粗剪结果"
|
||
),
|
||
target_duration_sec=max(10, min(int(requested_payload.get("target_duration_sec") or requested_payload.get("targetDurationSec") or 60), 300)),
|
||
target_aspect_ratio=str(requested_payload.get("target_aspect_ratio") or requested_payload.get("targetAspectRatio") or "9:16"),
|
||
review_enabled=bool(requested_payload.get("review_enabled", requested_payload.get("reviewEnabled", False))),
|
||
),
|
||
account,
|
||
)
|
||
return {
|
||
"title": "OneLiner 已创建实拍剪辑任务",
|
||
"summary": f"已基于「{source_job.get('title') or source_job['id']}」创建实拍剪辑任务。",
|
||
"payload": {
|
||
"job": job,
|
||
"source_job": legacy.job_payload(source_job),
|
||
},
|
||
}
|
||
|
||
async def _run_save_live_recorder_source() -> dict[str, Any]:
|
||
source_url = str(
|
||
requested_payload.get("source_url")
|
||
or requested_payload.get("sourceUrl")
|
||
or _extract_first_url(latest_user_message)
|
||
or ""
|
||
).strip()
|
||
if not source_url:
|
||
raise HTTPException(status_code=400, detail="No live recorder source URL available")
|
||
recorder_platform = normalize_platform_from_text(
|
||
requested_payload.get("platform")
|
||
or requested_payload.get("platform_label")
|
||
or normalized_platform
|
||
or legacy.infer_platform_from_url(source_url)
|
||
) or _safe_platform(legacy.infer_platform_from_url(source_url), fallback="kuaishou")
|
||
assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=recorder_platform)
|
||
saved = legacy.create_live_recorder_source(
|
||
legacy.LiveRecorderSourceCreateRequest(
|
||
project_id=project["id"],
|
||
assistant_id=(assistant or {}).get("id", ""),
|
||
platform=recorder_platform,
|
||
source_url=source_url,
|
||
title=str(requested_payload.get("title") or ""),
|
||
quality=str(requested_payload.get("quality") or "原画"),
|
||
enabled=bool(requested_payload.get("enabled", True)),
|
||
),
|
||
account,
|
||
)
|
||
started = None
|
||
if bool(requested_payload.get("auto_start", requested_payload.get("autoStart", True))):
|
||
try:
|
||
started = legacy.live_recorder_start(account)
|
||
except Exception as exc:
|
||
started = {"ok": False, "message": str(exc)}
|
||
return {
|
||
"title": "OneLiner 已保存录制源",
|
||
"summary": f"已把直播源保存到当前租户的 NAS 录制配置里。",
|
||
"payload": {
|
||
"saved": saved,
|
||
"started": started,
|
||
"platform": recorder_platform,
|
||
"source_url": source_url,
|
||
},
|
||
}
|
||
|
||
executors = {
|
||
"platform-self-check": _run_platform_self_check,
|
||
"storage-status": _run_storage_status,
|
||
"live-recorder-status": _run_live_recorder_status,
|
||
"scan-admin-ops": _run_ops_scan,
|
||
"generate-copy": _run_generate_copy,
|
||
"review-draft": _run_review_draft,
|
||
"import-homepage": _run_import_homepage,
|
||
"analyze-top-videos": _run_analyze_top_videos,
|
||
"create-ai-video": _run_create_ai_video,
|
||
"create-real-cut": _run_create_real_cut,
|
||
"save-live-recorder-source": _run_save_live_recorder_source,
|
||
}
|
||
executor = executors.get(action_key)
|
||
if not executor:
|
||
raise HTTPException(status_code=400, detail=f"Unsupported OneLiner action: {action_key}")
|
||
result = await executor()
|
||
if request.session_id:
|
||
session = _load_owned_session(request.session_id, account)
|
||
_insert_message(
|
||
session["id"],
|
||
account["id"],
|
||
"assistant",
|
||
result["summary"],
|
||
{
|
||
"intent_key": "custom",
|
||
"delivery_mode": "oneliner",
|
||
"platform": normalized_platform,
|
||
"suggested_actions": [],
|
||
},
|
||
{
|
||
"summary_text": result["summary"],
|
||
"execution_result": result,
|
||
},
|
||
)
|
||
return {
|
||
"action_key": action_key,
|
||
"project_id": project["id"],
|
||
"platform": normalized_platform,
|
||
"executed_at": now(),
|
||
**result,
|
||
}
|
||
|
||
@app.get("/v2/oneliner/profile")
|
||
def get_oneliner_profile(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
row = _ensure_oneliner_profile(account, project["id"])
|
||
return _profile_payload(row, account=account)
|
||
|
||
@app.put("/v2/oneliner/profile")
|
||
def put_oneliner_profile(
|
||
request: OneLinerProfileRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
assistant = _resolve_assistant(account, request.assistant_id or None, project["id"])
|
||
existing = _ensure_oneliner_profile(account, project["id"])
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE oneliner_profiles
|
||
SET assistant_id = ?, display_name = ?, long_term_goal = ?, notes = ?, default_platform = ?, config_json = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
(assistant or {}).get("id", ""),
|
||
request.display_name.strip() or "OneLiner",
|
||
request.long_term_goal.strip(),
|
||
request.notes.strip(),
|
||
_safe_platform(request.default_platform or existing.get("default_platform") or "douyin"),
|
||
_dump(request.config),
|
||
timestamp,
|
||
existing["id"],
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM oneliner_profiles WHERE id = ?", (existing["id"],))
|
||
return _profile_payload(row, account=account)
|
||
|
||
@app.get("/v2/oneliner/sessions")
|
||
def list_oneliner_sessions(
|
||
project_id: str | None = Query(default=None),
|
||
limit: int = Query(default=20, ge=1, le=100),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM oneliner_sessions
|
||
WHERE user_id = ? AND project_id = ?
|
||
ORDER BY updated_at DESC
|
||
LIMIT ?
|
||
""",
|
||
(account["id"], project["id"], limit),
|
||
)
|
||
items = [_session_payload(row) for row in rows]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/oneliner/sessions")
|
||
async def create_oneliner_session(
|
||
request: OneLinerSessionCreateRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, request.project_id or None)
|
||
profile = _ensure_oneliner_profile(account, project["id"])
|
||
session_id = make_id("oline")
|
||
timestamp = now()
|
||
preferred_platform = _safe_platform(request.preferred_platform or profile.get("default_platform") or "douyin")
|
||
title = request.title.strip() or "新的 OneLiner 会话"
|
||
legacy.db.execute(
|
||
"""
|
||
INSERT INTO oneliner_sessions (
|
||
id, user_id, project_id, profile_id, title, status, preferred_platform,
|
||
last_platform, last_intent_key, last_message_at, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, 'active', ?, '', '', ?, ?, ?)
|
||
""",
|
||
(
|
||
session_id,
|
||
account["id"],
|
||
project["id"],
|
||
profile["id"],
|
||
title,
|
||
preferred_platform,
|
||
timestamp,
|
||
timestamp,
|
||
timestamp,
|
||
),
|
||
)
|
||
session_row = legacy.db.fetch_one("SELECT * FROM oneliner_sessions WHERE id = ?", (session_id,))
|
||
if request.initial_message.strip():
|
||
await post_oneliner_message(
|
||
session_id,
|
||
OneLinerMessageRequest(
|
||
content=request.initial_message,
|
||
project_id=project["id"],
|
||
platform=preferred_platform,
|
||
),
|
||
account,
|
||
)
|
||
session_row = legacy.db.fetch_one("SELECT * FROM oneliner_sessions WHERE id = ?", (session_id,))
|
||
return _session_payload(session_row)
|
||
|
||
@app.get("/v2/oneliner/sessions/{session_id}/messages")
|
||
def list_oneliner_messages(
|
||
session_id: str,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
session = _load_owned_session(session_id, account)
|
||
rows = legacy.db.fetch_all(
|
||
"SELECT * FROM oneliner_messages WHERE session_id = ? ORDER BY created_at ASC",
|
||
(session["id"],),
|
||
)
|
||
items = [_message_payload(row) for row in rows]
|
||
return {"session": _session_payload(session), "items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/oneliner/sessions/{session_id}/messages")
|
||
async def post_oneliner_message(
|
||
session_id: str,
|
||
request: OneLinerMessageRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
session = _load_owned_session(session_id, account)
|
||
project = _resolve_project(account, request.project_id or session.get("project_id") or None)
|
||
plan = await _plan_oneliner_request(
|
||
account,
|
||
project_id=project["id"],
|
||
message=request.content,
|
||
platform_hint=request.platform or session.get("preferred_platform") or "",
|
||
)
|
||
user_message = _insert_message(session["id"], account["id"], "user", request.content.strip(), {}, {})
|
||
remembered = _remember_message_preference(account, project_id=project["id"], plan=plan, message=request.content)
|
||
result = await _generate_oneliner_reply(account, project_id=project["id"], message=request.content, plan=plan)
|
||
if remembered:
|
||
result["remembered_memory"] = remembered
|
||
assistant_message = _insert_message(session["id"], account["id"], "assistant", result["summary_text"], plan, result)
|
||
session_title = session.get("title") or request.content.strip()[:28] or "新的 OneLiner 会话"
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE oneliner_sessions
|
||
SET title = ?, last_platform = ?, last_intent_key = ?, last_message_at = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
session_title,
|
||
plan.get("platform", ""),
|
||
plan.get("intent_key", ""),
|
||
timestamp,
|
||
timestamp,
|
||
session["id"],
|
||
),
|
||
)
|
||
updated_session = legacy.db.fetch_one("SELECT * FROM oneliner_sessions WHERE id = ?", (session["id"],))
|
||
return {
|
||
"session": _session_payload(updated_session),
|
||
"user_message": _message_payload(user_message),
|
||
"assistant_message": _message_payload(assistant_message),
|
||
"plan": plan,
|
||
"result": result,
|
||
}
|
||
|
||
@app.post("/v2/oneliner/actions/execute")
|
||
async def execute_oneliner_action(
|
||
request: OneLinerActionExecuteRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
return await _execute_oneliner_action(account, request)
|
||
|
||
@app.get("/v2/platform-agents")
|
||
def list_platform_agents(
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
items = _list_platform_profiles(account, project["id"])
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.put("/v2/platform-agents/{platform}/profile")
|
||
def update_platform_agent(
|
||
platform: str,
|
||
request: PlatformAgentProfileRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _upsert_platform_profile(account, normalized_platform, request)
|
||
|
||
@app.get("/v2/platform-agents/{platform}/memories")
|
||
def list_platform_memories(
|
||
platform: str,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_memories
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
ORDER BY updated_at DESC
|
||
""",
|
||
(account["id"], project["id"], normalized_platform),
|
||
)
|
||
items = [_memory_payload(row) for row in rows]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/platform-agents/{platform}/memories")
|
||
def upsert_platform_memory(
|
||
platform: str,
|
||
request: AgentMemoryUpsertRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _upsert_memory(account, agent_scope="platform", platform=normalized_platform, request=request)
|
||
|
||
@app.get("/v2/platform-agents/{platform}/skills")
|
||
def list_platform_skills(
|
||
platform: str,
|
||
project_id: str | None = Query(default=None),
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
project = _resolve_project(account, project_id or None)
|
||
normalized_platform = _safe_platform(platform)
|
||
rows = legacy.db.fetch_all(
|
||
"""
|
||
SELECT * FROM agent_skills
|
||
WHERE user_id = ? AND project_id = ? AND agent_scope = 'platform' AND platform = ?
|
||
ORDER BY updated_at DESC
|
||
""",
|
||
(account["id"], project["id"], normalized_platform),
|
||
)
|
||
items = [_skill_payload(row) for row in rows]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
@app.post("/v2/platform-agents/{platform}/skills")
|
||
def create_platform_skill(
|
||
platform: str,
|
||
request: AgentSkillUpsertRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _upsert_skill(account, agent_scope="platform", platform=normalized_platform, request=request)
|
||
|
||
@app.patch("/v2/platform-agents/{platform}/skills/{skill_id}")
|
||
def update_platform_skill(
|
||
platform: str,
|
||
skill_id: str,
|
||
request: AgentSkillUpsertRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _upsert_skill(account, agent_scope="platform", platform=normalized_platform, request=request, skill_id=skill_id)
|
||
|
||
@app.post("/v2/platform-agents/{platform}/self-check")
|
||
def run_platform_agent_self_check(
|
||
platform: str,
|
||
request: PlatformAgentSelfCheckRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _platform_self_check(
|
||
account,
|
||
platform=normalized_platform,
|
||
project_id=request.project_id,
|
||
sample_limit=request.sample_limit,
|
||
remember_summary=request.remember_summary,
|
||
)
|
||
|
||
@app.post("/v2/platform-agents/{platform}/skills/{skill_id}/review")
|
||
def review_platform_skill(
|
||
platform: str,
|
||
skill_id: str,
|
||
request: PlatformSkillReviewRequest,
|
||
account: dict[str, Any] = Depends(legacy.require_approved),
|
||
) -> dict[str, Any]:
|
||
normalized_platform = _safe_platform(platform)
|
||
return _review_platform_skill(account, platform=normalized_platform, skill_id=skill_id, request=request)
|
||
|
||
@app.get("/v2/admin/ops/overview")
|
||
def admin_ops_overview(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||
return _admin_ops_overview_payload(admin)
|
||
|
||
@app.post("/v2/admin/ops/incidents/scan")
|
||
def admin_ops_scan(admin: dict[str, Any] = Depends(legacy.require_super_admin)) -> dict[str, Any]:
|
||
return _scan_admin_incidents(admin)
|
||
|
||
@app.patch("/v2/admin/ops/incidents/{incident_id}")
|
||
def review_admin_incident(
|
||
incident_id: str,
|
||
request: AdminIncidentReviewRequest,
|
||
admin: dict[str, Any] = Depends(legacy.require_super_admin),
|
||
) -> dict[str, Any]:
|
||
current = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (incident_id,))
|
||
if not current:
|
||
raise HTTPException(status_code=404, detail="Incident not found")
|
||
timestamp = now()
|
||
legacy.db.execute(
|
||
"""
|
||
UPDATE admin_ops_incidents
|
||
SET status = ?, reviewed_by = ?, review_notes = ?, updated_at = ?
|
||
WHERE id = ?
|
||
""",
|
||
(
|
||
request.status.strip() or "reviewed",
|
||
admin["id"],
|
||
request.review_notes.strip(),
|
||
timestamp,
|
||
incident_id,
|
||
),
|
||
)
|
||
row = legacy.db.fetch_one("SELECT * FROM admin_ops_incidents WHERE id = ?", (incident_id,))
|
||
payload = _incident_payload(row)
|
||
payload["audit"] = _log_admin_audit_event(
|
||
actor_user_id=admin["id"],
|
||
incident_id=incident_id,
|
||
action_key="review",
|
||
status=payload.get("status", "reviewed"),
|
||
summary=f"事件「{payload.get('title', incident_id)}」已更新为 {payload.get('status', 'reviewed')}。",
|
||
details={
|
||
"review_notes": request.review_notes.strip(),
|
||
"severity": payload.get("severity", ""),
|
||
"source_type": payload.get("source_type", ""),
|
||
},
|
||
)
|
||
return payload
|