@tencent-rtc/trtc-agent-skills 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/CODEBUDDY.md +1 -1
- package/README.md +15 -70
- package/README.zh.md +15 -66
- package/bin/cli.js +9 -7
- package/package.json +3 -2
- package/skills/trtc/SKILL.md +56 -5
- package/skills/trtc/tests/test_reporting_v2_docs_query.py +175 -0
- package/skills/trtc/tools/reporting_v2.py +243 -5
- package/skills/trtc-ai-oral-coach/README.ja.md +131 -0
- package/skills/trtc-ai-oral-coach/README.md +132 -0
- package/skills/trtc-ai-oral-coach/README.zh-CN.md +131 -0
- package/skills/trtc-ai-oral-coach/SKILL.md +395 -0
- package/skills/trtc-ai-oral-coach/auto_adapters/integration_templates/custom-learning-kb-sample.md +88 -0
- package/skills/trtc-ai-oral-coach/auto_adapters/integration_templates/generic-integration.md +104 -0
- package/skills/trtc-ai-oral-coach/auto_adapters/manifest.yaml +27 -0
- package/skills/trtc-ai-oral-coach/auto_adapters/python/README.md +49 -0
- package/skills/trtc-ai-oral-coach/auto_adapters/python/coach_client.py.tpl +185 -0
- package/skills/trtc-ai-oral-coach/auto_adapters/web/README.md +46 -0
- package/skills/trtc-ai-oral-coach/auto_adapters/web/oral-coach-client.js.tpl +210 -0
- package/skills/trtc-ai-oral-coach/capabilities/ability-report/manifest.yaml +52 -0
- package/skills/trtc-ai-oral-coach/capabilities/ability-report/src/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/ability-report/src/adapters/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/ability-report/src/adapters/default.py +177 -0
- package/skills/trtc-ai-oral-coach/capabilities/ability-report/src/handler.py +29 -0
- package/skills/trtc-ai-oral-coach/capabilities/ability-report/src/router.py +23 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/.env.example +59 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/manifest.yaml +46 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/requirements.txt +6 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/TLSSigAPIv2.py +275 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/_capability_loader.py +227 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/agent.py +197 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/config.py +149 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/core/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/core/report_llm.py +129 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/health.py +36 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/server.py +205 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/trtc_client.py +86 -0
- package/skills/trtc-ai-oral-coach/capabilities/conversation-core/src/usersig.py +33 -0
- package/skills/trtc-ai-oral-coach/capabilities/custom-learning-kb/manifest.yaml +65 -0
- package/skills/trtc-ai-oral-coach/capabilities/custom-learning-kb/src/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/custom-learning-kb/src/adapters/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/custom-learning-kb/src/adapters/clients.py +126 -0
- package/skills/trtc-ai-oral-coach/capabilities/custom-learning-kb/src/ports.py +46 -0
- package/skills/trtc-ai-oral-coach/capabilities/custom-learning-kb/src/router.py +27 -0
- package/skills/trtc-ai-oral-coach/capabilities/quick-correct/manifest.yaml +48 -0
- package/skills/trtc-ai-oral-coach/capabilities/quick-correct/src/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/quick-correct/src/adapters/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/quick-correct/src/adapters/default.py +106 -0
- package/skills/trtc-ai-oral-coach/capabilities/quick-correct/src/handler.py +33 -0
- package/skills/trtc-ai-oral-coach/capabilities/quick-correct/src/router.py +23 -0
- package/skills/trtc-ai-oral-coach/capabilities/reply-suggestion/manifest.yaml +49 -0
- package/skills/trtc-ai-oral-coach/capabilities/reply-suggestion/src/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/reply-suggestion/src/adapters/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/reply-suggestion/src/adapters/default.py +86 -0
- package/skills/trtc-ai-oral-coach/capabilities/reply-suggestion/src/handler.py +29 -0
- package/skills/trtc-ai-oral-coach/capabilities/reply-suggestion/src/router.py +23 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/data/practice-scenarios.json +500 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/manifest.yaml +54 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/src/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/src/adapters/__init__.py +2 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/src/adapters/default.py +113 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/src/compose.py +143 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/src/defaults.py +140 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/src/handler.py +29 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/src/router.py +22 -0
- package/skills/trtc-ai-oral-coach/capabilities/scenario-roleplay/src/scenario_source.py +179 -0
- package/skills/trtc-ai-oral-coach/references/design-specs.md +36 -0
- package/skills/trtc-ai-oral-coach/references/evaluator-port.md +75 -0
- package/skills/trtc-ai-oral-coach/scenarios/speaking-coach/recipe.yaml +86 -0
- package/skills/trtc-ai-oral-coach/scenarios/speaking-coach/ui/avatars/friend.png +0 -0
- package/skills/trtc-ai-oral-coach/scenarios/speaking-coach/ui/avatars/listener.png +0 -0
- package/skills/trtc-ai-oral-coach/scenarios/speaking-coach/ui/avatars/local.png +0 -0
- package/skills/trtc-ai-oral-coach/scenarios/speaking-coach/ui/coach.html +1986 -0
- package/skills/trtc-ai-oral-coach/scenarios/speaking-coach/ui/i18n.js +323 -0
- package/skills/trtc-ai-oral-coach/scenarios/speaking-coach/ui/practice-scenarios.json +500 -0
- package/skills/trtc-ai-oral-coach/scenarios/speaking-coach/ui/tokens.css +100 -0
- package/skills/trtc-ai-oral-coach/scripts/add-capability.py +256 -0
- package/skills/trtc-ai-oral-coach/scripts/verify-credentials.py +96 -0
- package/skills/trtc-ai-oral-coach/start.sh +59 -0
- package/skills/trtc-ai-oral-coach/triggers.yaml +32 -0
- package/skills/trtc-ai-service/README.ja.md +27 -34
- package/skills/trtc-ai-service/README.md +27 -34
- package/skills/trtc-ai-service/README.zh-CN.md +27 -34
- package/skills/trtc-ai-service/SKILL.md +189 -121
- package/skills/trtc-chat/.docs-query.yaml +1 -0
- package/skills/trtc-chat/SKILL.md +1 -1
- package/skills/trtc-chat/docs/SKILL.md +2 -2
- package/skills/trtc-chat/references/05-path-d-script.md +213 -125
- package/skills/trtc-chat/references/08-state-config.md +3 -1
- package/skills/trtc-chat/references/13-reporting.md +47 -9
- package/skills/trtc-chat/tests/test_chat_bundle_contract.py +37 -0
- package/hooks/__pycache__/cursor-adapter.cpython-313.pyc +0 -0
- package/skills/trtc/hooks/__pycache__/report_prompt.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/query_classifier.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/reporting.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/search.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/session.cpython-313.pyc +0 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_onboarding_contract.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_topic_flow_contract.cpython-313-pytest-9.0.2.pyc +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Python Adapter — Oral Coach Client
|
|
2
|
+
|
|
3
|
+
> Auto-generated by ai-oral-coach auto_adapters/python.
|
|
4
|
+
> This adapter generates `coach_client.py` — a headless Python client for backend aggregation.
|
|
5
|
+
|
|
6
|
+
## What's generated
|
|
7
|
+
|
|
8
|
+
| File | Contents |
|
|
9
|
+
|:---|:---|
|
|
10
|
+
| `coach_client.py` | `CoachClient` class — full API wrapper (config / agent control / coach capabilities / KB) |
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from coach_client import CoachClient
|
|
16
|
+
|
|
17
|
+
# Initialize
|
|
18
|
+
coach = CoachClient(base_url="http://localhost:8000")
|
|
19
|
+
|
|
20
|
+
# Health check
|
|
21
|
+
coach.health() # → { status: "ok", tencent_cloud: ok, trtc: ok, llm: ok }
|
|
22
|
+
|
|
23
|
+
# Get TRTC credentials (your frontend uses these to enter room)
|
|
24
|
+
config = coach.get_config("student_001")
|
|
25
|
+
# → { sdkAppId, roomId, userId, userSig, agentUserId, ... }
|
|
26
|
+
|
|
27
|
+
# Start agent (after frontend enters room)
|
|
28
|
+
result = coach.start_agent(
|
|
29
|
+
room_id=config["roomId"],
|
|
30
|
+
scenario="travel",
|
|
31
|
+
level="intermediate",
|
|
32
|
+
style="friend",
|
|
33
|
+
agent_config=config.get("agentConfig"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Coach capabilities (call from your backend as needed)
|
|
37
|
+
correction = coach.correct("I go to park yesterday")
|
|
38
|
+
suggestions = coach.suggest("What did you do last weekend?")
|
|
39
|
+
report = coach.generate_report(transcript=[...], duration_sec=120)
|
|
40
|
+
|
|
41
|
+
# Graceful end
|
|
42
|
+
coach.farewell(lang="en")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Dependencies
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install requests
|
|
49
|
+
```
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# coach_client.py
|
|
2
|
+
# Auto-generated by ai-oral-coach auto_adapters/python
|
|
3
|
+
# Python client wrapper — full oral coach API coverage for backend aggregation.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# from coach_client import CoachClient
|
|
7
|
+
# coach = CoachClient(base_url="http://localhost:8000")
|
|
8
|
+
# health = coach.health()
|
|
9
|
+
# config = coach.get_config("student_001")
|
|
10
|
+
# result = coach.start_agent(room_id="...", scenario="travel", level="intermediate", style="friend", agent_config=config["agent_config"])
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
import requests
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
DEFAULT_BASE_URL = "${SKELETON_BASE_URL}"
|
|
21
|
+
API_PREFIX = "${API_PREFIX}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CoachClient:
|
|
25
|
+
"""Headless client for the AI Oral Coach backend.
|
|
26
|
+
|
|
27
|
+
All coach capabilities (correct / suggest / report / scene) are exposed as
|
|
28
|
+
methods. TRTC room entry is handled by your own TRTC SDK — this client
|
|
29
|
+
provides the credentials and control-plane API.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, base_url: str = DEFAULT_BASE_URL, api_key: Optional[str] = None):
|
|
33
|
+
self.base_url = base_url.rstrip("/")
|
|
34
|
+
self.api_key = api_key
|
|
35
|
+
self.session = requests.Session()
|
|
36
|
+
if api_key:
|
|
37
|
+
self.session.headers["Authorization"] = f"Bearer {api_key}"
|
|
38
|
+
self.session.headers["Content-Type"] = "application/json"
|
|
39
|
+
self.task_id: Optional[str] = None
|
|
40
|
+
|
|
41
|
+
# ─── HTTP helpers ──────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
def _post(self, path: str, body: dict = None) -> dict:
|
|
44
|
+
url = f"{self.base_url}{API_PREFIX}{path}"
|
|
45
|
+
resp = self.session.post(url, json=body or {}, timeout=(5, 30))
|
|
46
|
+
resp.raise_for_status()
|
|
47
|
+
return resp.json()
|
|
48
|
+
|
|
49
|
+
def _get(self, path: str) -> dict:
|
|
50
|
+
url = f"{self.base_url}{API_PREFIX}{path}"
|
|
51
|
+
resp = self.session.get(url, timeout=5)
|
|
52
|
+
resp.raise_for_status()
|
|
53
|
+
return resp.json()
|
|
54
|
+
|
|
55
|
+
# ─── Core ──────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
def health(self) -> dict:
|
|
58
|
+
"""Health check — returns { status, tencent_cloud, trtc, llm }."""
|
|
59
|
+
return self._get("/health")
|
|
60
|
+
|
|
61
|
+
def get_config(self, user_id: str) -> dict:
|
|
62
|
+
"""Get UserSig + room credentials. Call before entering TRTC room."""
|
|
63
|
+
return self._post("/config", {"userid": user_id})
|
|
64
|
+
|
|
65
|
+
# ─── Agent Control ─────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
def start_agent(
|
|
68
|
+
self,
|
|
69
|
+
room_id: str,
|
|
70
|
+
scenario: str = "travel",
|
|
71
|
+
level: str = "intermediate",
|
|
72
|
+
style: str = "friend",
|
|
73
|
+
agent_config: Optional[dict] = None,
|
|
74
|
+
) -> dict:
|
|
75
|
+
"""Start the voice coach agent (requires room already joined via TRTC SDK)."""
|
|
76
|
+
body: Dict[str, Any] = {
|
|
77
|
+
"RoomId": room_id,
|
|
78
|
+
"Scenario": scenario,
|
|
79
|
+
"Level": level,
|
|
80
|
+
"Style": style,
|
|
81
|
+
}
|
|
82
|
+
if agent_config:
|
|
83
|
+
body["AgentConfig"] = agent_config
|
|
84
|
+
data = self._post("/agent/start", body)
|
|
85
|
+
self.task_id = data.get("TaskId") or data.get("task_id")
|
|
86
|
+
return data
|
|
87
|
+
|
|
88
|
+
def stop_agent(self) -> dict:
|
|
89
|
+
"""Stop the current agent session."""
|
|
90
|
+
return self._post("/agent/stop", {"TaskId": self.task_id})
|
|
91
|
+
|
|
92
|
+
def farewell(self, lang: str = "en", text: str = None) -> dict:
|
|
93
|
+
"""Graceful farewell — triggers closing remarks + auto-stop."""
|
|
94
|
+
body: Dict[str, Any] = {"TaskId": self.task_id, "Lang": lang}
|
|
95
|
+
if text:
|
|
96
|
+
body["FarewellText"] = text
|
|
97
|
+
return self._post("/agent/farewell", body)
|
|
98
|
+
|
|
99
|
+
def invoke(self, text: str = "") -> dict:
|
|
100
|
+
"""Push-to-talk: manually trigger AI reply."""
|
|
101
|
+
return self._post("/agent/invoke", {"TaskId": self.task_id, "Text": text})
|
|
102
|
+
|
|
103
|
+
# ─── Coach Capabilities ────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
def generate_scene(
|
|
106
|
+
self,
|
|
107
|
+
field: str,
|
|
108
|
+
scenario: str = "travel",
|
|
109
|
+
level: str = "intermediate",
|
|
110
|
+
style: str = "friend",
|
|
111
|
+
language: str = "en",
|
|
112
|
+
context: dict = None,
|
|
113
|
+
) -> dict:
|
|
114
|
+
"""Generate a random scene element (aiRole / myRole / scene)."""
|
|
115
|
+
body = {
|
|
116
|
+
"Field": field,
|
|
117
|
+
"Scenario": scenario,
|
|
118
|
+
"Level": level,
|
|
119
|
+
"Style": style,
|
|
120
|
+
"Language": language,
|
|
121
|
+
}
|
|
122
|
+
if context:
|
|
123
|
+
body["Context"] = context
|
|
124
|
+
return self._post("/scene/generate", body)
|
|
125
|
+
|
|
126
|
+
def correct(
|
|
127
|
+
self,
|
|
128
|
+
sentence: str,
|
|
129
|
+
scenario: str = "travel",
|
|
130
|
+
level: str = "intermediate",
|
|
131
|
+
ui_language: str = "en",
|
|
132
|
+
) -> dict:
|
|
133
|
+
"""Correct a single spoken sentence — returns corrected, explanation, grammar tips."""
|
|
134
|
+
return self._post("/correct", {
|
|
135
|
+
"UserSentence": sentence,
|
|
136
|
+
"Scenario": scenario,
|
|
137
|
+
"Level": level,
|
|
138
|
+
"UILanguage": ui_language,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
def suggest(
|
|
142
|
+
self,
|
|
143
|
+
last_message: str,
|
|
144
|
+
scenario: str = "travel",
|
|
145
|
+
level: str = "intermediate",
|
|
146
|
+
style: str = "friend",
|
|
147
|
+
) -> dict:
|
|
148
|
+
"""Get 3 reply suggestions based on AI's last message."""
|
|
149
|
+
return self._post("/suggest", {
|
|
150
|
+
"AiLastMessage": last_message,
|
|
151
|
+
"Scenario": scenario,
|
|
152
|
+
"Level": level,
|
|
153
|
+
"Style": style,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
def generate_report(
|
|
157
|
+
self,
|
|
158
|
+
transcript: List[dict],
|
|
159
|
+
duration_sec: int,
|
|
160
|
+
scenario: str = "travel",
|
|
161
|
+
level: str = "intermediate",
|
|
162
|
+
style: str = "friend",
|
|
163
|
+
language: str = "en",
|
|
164
|
+
) -> dict:
|
|
165
|
+
"""Generate a 4-dimension analysis report.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
transcript: [{speaker, text, timestamp}, ...]
|
|
169
|
+
duration_sec: Session duration in seconds
|
|
170
|
+
language: en | zh
|
|
171
|
+
"""
|
|
172
|
+
return self._post("/report", {
|
|
173
|
+
"Scenario": scenario,
|
|
174
|
+
"Level": level,
|
|
175
|
+
"Style": style,
|
|
176
|
+
"DurationSec": duration_sec,
|
|
177
|
+
"Transcript": transcript,
|
|
178
|
+
"Language": language,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
# ─── Knowledge Base (optional) ─────────────────────────────────
|
|
182
|
+
|
|
183
|
+
def retrieve_kb(self, query: str, top_k: int = 3) -> dict:
|
|
184
|
+
"""Retrieve KB segments (only works if custom-learning-kb is configured)."""
|
|
185
|
+
return self._post("/kb/retrieve", {"query": query, "top_k": top_k})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Web Adapter — Oral Coach Headless Client
|
|
2
|
+
|
|
3
|
+
> Auto-generated by ai-oral-coach auto_adapters/web.
|
|
4
|
+
> This adapter generates `oral-coach-client.js` — a **headless** client module.
|
|
5
|
+
> It handles HTTP API calls and TRTC custom message routing. No UI components.
|
|
6
|
+
|
|
7
|
+
## What's generated
|
|
8
|
+
|
|
9
|
+
| File | Contents |
|
|
10
|
+
|:---|:---|
|
|
11
|
+
| `oral-coach-client.js` | `OralCoachClient` class — full API wrapper (config / agent control / coach capabilities / KB) |
|
|
12
|
+
|
|
13
|
+
## What you need to provide
|
|
14
|
+
|
|
15
|
+
- **TRTC Web SDK** (`trtc-sdk-v5` or `trtc-js-sdk`): room entry, audio publish, custom message handling
|
|
16
|
+
- **Your UI layer**: render subtitles, AI status, report, suggestions — the client provides data via callbacks
|
|
17
|
+
|
|
18
|
+
## Integration flow
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
1. coach = new OralCoachClient({ userId: '...' })
|
|
22
|
+
2. config = await coach.getConfig() # GET /api/v1/config → UserSig, roomId
|
|
23
|
+
3. Your code: use TRTC SDK to enter room with config
|
|
24
|
+
4. coach.start({ roomId, scenario, level, style, agentConfig }) # POST /api/v1/agent/start
|
|
25
|
+
5. Practice loop:
|
|
26
|
+
- coach.correct(sentence) # POST /api/v1/correct
|
|
27
|
+
- coach.suggest(lastAIMessage) # POST /api/v1/suggest
|
|
28
|
+
- coach.invoke(text) # POST /api/v1/agent/invoke (push-to-talk)
|
|
29
|
+
6. coach.generateReport({ transcript, durationSec }) # POST /api/v1/report
|
|
30
|
+
7. coach.farewell() # POST /api/v1/agent/farewell
|
|
31
|
+
|
|
32
|
+
TRTC hooks:
|
|
33
|
+
onCustomMessage(cmd, data) → coach.handleTRTCMessage(cmd, data)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Callback hooks (override in your UI)
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
coach.onSubtitle = (text) => { /* render subtitle in your UI */ };
|
|
40
|
+
coach.onAIStatus = (status) => { /* show listening/speaking/thinking */ };
|
|
41
|
+
coach.onCorrectReady = (result) => { /* display correction card */ };
|
|
42
|
+
coach.onSuggestReady = (hints) => { /* display suggestion chips */ };
|
|
43
|
+
coach.onReportReady = (report) => { /* render 4-dimension report */ };
|
|
44
|
+
coach.onSceneReady = (scene) => { /* update setup screen */ };
|
|
45
|
+
coach.onError = (err) => { /* handle errors gracefully */ };
|
|
46
|
+
```
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// oral-coach-client.js
|
|
2
|
+
// Auto-generated by ai-oral-coach auto_adapters/web
|
|
3
|
+
// Headless client — your UI, our APIs. No UI components emitted.
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// import { OralCoachClient } from './oral-coach-client.js';
|
|
7
|
+
// const coach = new OralCoachClient({ baseUrl: '${SKELETON_BASE_URL}', userId: 'student_001' });
|
|
8
|
+
// await coach.init();
|
|
9
|
+
// const { config } = await coach.getConfig();
|
|
10
|
+
// // ... use TRTC SDK to enter room with config ...
|
|
11
|
+
// await coach.start({ scenario: 'travel', level: 'intermediate', style: 'friend', roomId: config.roomId, agentConfig: config.agentConfig });
|
|
12
|
+
// // ... coach callbacks fire: onSubtitle / onAIStatus ...
|
|
13
|
+
|
|
14
|
+
const DEFAULT_BASE_URL = '${SKELETON_BASE_URL}';
|
|
15
|
+
const API_PREFIX = '${API_PREFIX}';
|
|
16
|
+
|
|
17
|
+
class OralCoachClient {
|
|
18
|
+
/**
|
|
19
|
+
* @param {Object} opts
|
|
20
|
+
* @param {string} [opts.baseUrl] Coach backend URL
|
|
21
|
+
* @param {string} opts.userId Unique user identifier
|
|
22
|
+
* @param {Object} [opts.trtc] TRTC SDK instance (if you manage it yourself)
|
|
23
|
+
*/
|
|
24
|
+
constructor(opts = {}) {
|
|
25
|
+
this.baseUrl = (opts.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
26
|
+
this.userId = opts.userId || `user_${Date.now()}`;
|
|
27
|
+
this.taskId = null;
|
|
28
|
+
this.sessionId = null;
|
|
29
|
+
this._config = null;
|
|
30
|
+
|
|
31
|
+
// Callback hooks — override these in your UI layer
|
|
32
|
+
this.onSubtitle = (text) => {}; // CMD 10000: AI speech text
|
|
33
|
+
this.onAIStatus = (status) => {}; // CMD 10001: listening | speaking | thinking
|
|
34
|
+
this.onError = (err) => { console.error('[OralCoach]', err); };
|
|
35
|
+
this.onReportReady = (report) => {}; // After report generated
|
|
36
|
+
this.onCorrectReady = (result) => {}; // After quick-correct
|
|
37
|
+
this.onSuggestReady = (hints) => {}; // After reply-suggestion
|
|
38
|
+
this.onSceneReady = (scene) => {}; // After scene/generate
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── HTTP helpers ──────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
async _post(path, body = {}) {
|
|
44
|
+
const url = `${this.baseUrl}${API_PREFIX}${path}`;
|
|
45
|
+
const resp = await fetch(url, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify(body),
|
|
49
|
+
});
|
|
50
|
+
if (!resp.ok) {
|
|
51
|
+
const err = await resp.text().catch(() => resp.statusText);
|
|
52
|
+
throw new Error(`POST ${path}: ${resp.status} ${err}`);
|
|
53
|
+
}
|
|
54
|
+
return resp.json();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _get(path) {
|
|
58
|
+
const url = `${this.baseUrl}${API_PREFIX}${path}`;
|
|
59
|
+
const resp = await fetch(url);
|
|
60
|
+
if (!resp.ok) throw new Error(`GET ${path}: ${resp.status}`);
|
|
61
|
+
return resp.json();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Core: Config & Health ──────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/** Get UserSig + room credentials. Call once before entering room. */
|
|
67
|
+
async getConfig() {
|
|
68
|
+
const data = await this._post('/config', { userid: this.userId });
|
|
69
|
+
this._config = data;
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Health check — returns { status, tencent_cloud, trtc, llm } */
|
|
74
|
+
async health() {
|
|
75
|
+
return this._get('/health');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Agent Control ──────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Start the voice coach agent (requires room already joined via TRTC SDK).
|
|
82
|
+
* @param {Object} params
|
|
83
|
+
* @param {string} params.roomId TRTC room ID
|
|
84
|
+
* @param {string} params.scenario travel | work | study | free
|
|
85
|
+
* @param {string} params.level beginner | intermediate | advanced
|
|
86
|
+
* @param {string} params.style friend | listener
|
|
87
|
+
* @param {Object} params.agentConfig { UserId, UserSig, TargetUserId }
|
|
88
|
+
*/
|
|
89
|
+
async start({ roomId, scenario, level, style, agentConfig }) {
|
|
90
|
+
const data = await this._post('/agent/start', {
|
|
91
|
+
RoomId: roomId,
|
|
92
|
+
Scenario: scenario,
|
|
93
|
+
Level: level,
|
|
94
|
+
Style: style,
|
|
95
|
+
AgentConfig: agentConfig,
|
|
96
|
+
});
|
|
97
|
+
this.taskId = data.TaskId || data.task_id;
|
|
98
|
+
this.sessionId = data.SessionId || data.session_id;
|
|
99
|
+
return data;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Stop the current agent session. */
|
|
103
|
+
async stop() {
|
|
104
|
+
return this._post('/agent/stop', { TaskId: this.taskId });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Graceful farewell — triggers closing remarks + auto-stop. */
|
|
108
|
+
async farewell(lang = 'en') {
|
|
109
|
+
return this._post('/agent/farewell', { TaskId: this.taskId, Lang: lang });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Push-to-talk: manually trigger AI reply (no voice input needed). */
|
|
113
|
+
async invoke(text) {
|
|
114
|
+
return this._post('/agent/invoke', { TaskId: this.taskId, Text: text || '' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Coach Capabilities ─────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Generate a random scene element (aiRole / myRole / scene description).
|
|
121
|
+
* @param {string} field aiRole | myRole | scene
|
|
122
|
+
* @param {Object} context { Scenario, Level, Style, Language }
|
|
123
|
+
*/
|
|
124
|
+
async generateScene(field, context = {}) {
|
|
125
|
+
const data = await this._post('/scene/generate', { Field: field, ...context });
|
|
126
|
+
this.onSceneReady(data);
|
|
127
|
+
return data;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Correct a single spoken sentence.
|
|
132
|
+
* @param {string} sentence The user's spoken sentence
|
|
133
|
+
* @param {Object} context { Scenario, Level, UILanguage? }
|
|
134
|
+
*/
|
|
135
|
+
async correct(sentence, context = {}) {
|
|
136
|
+
const data = await this._post('/correct', {
|
|
137
|
+
UserSentence: sentence,
|
|
138
|
+
Scenario: context.Scenario || 'travel',
|
|
139
|
+
Level: context.Level || 'intermediate',
|
|
140
|
+
UILanguage: context.UILanguage || 'en',
|
|
141
|
+
});
|
|
142
|
+
this.onCorrectReady(data);
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get 3 reply suggestions based on AI's last message.
|
|
148
|
+
* @param {string} lastMessage AI's most recent utterance
|
|
149
|
+
* @param {Object} context { Scenario, Level, Style }
|
|
150
|
+
*/
|
|
151
|
+
async suggest(lastMessage, context = {}) {
|
|
152
|
+
const data = await this._post('/suggest', {
|
|
153
|
+
AiLastMessage: lastMessage,
|
|
154
|
+
Scenario: context.Scenario || 'travel',
|
|
155
|
+
Level: context.Level || 'intermediate',
|
|
156
|
+
Style: context.Style || 'friend',
|
|
157
|
+
});
|
|
158
|
+
this.onSuggestReady(data);
|
|
159
|
+
return data;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generate a 4-dimension analysis report.
|
|
164
|
+
* @param {Object} params
|
|
165
|
+
* @param {Array} params.transcript [{speaker, text, timestamp}]
|
|
166
|
+
* @param {number} params.durationSec Session duration in seconds
|
|
167
|
+
* @param {string} params.level beginner | intermediate | advanced
|
|
168
|
+
* @param {string} params.style friend | listener
|
|
169
|
+
* @param {string} params.scenario travel | work | study | free
|
|
170
|
+
* @param {string} params.language en | zh
|
|
171
|
+
*/
|
|
172
|
+
async generateReport({ transcript, durationSec, level, style, scenario, language = 'en' }) {
|
|
173
|
+
const data = await this._post('/report', {
|
|
174
|
+
Scenario: scenario || 'travel',
|
|
175
|
+
Level: level || 'intermediate',
|
|
176
|
+
Style: style || 'friend',
|
|
177
|
+
DurationSec: durationSec,
|
|
178
|
+
Transcript: transcript,
|
|
179
|
+
Language: language,
|
|
180
|
+
});
|
|
181
|
+
this.onReportReady(data);
|
|
182
|
+
return data;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── Knowledge Base (optional) ──────────────────────────────
|
|
186
|
+
|
|
187
|
+
/** Retrieve KB segments (only works if custom-learning-kb is configured). */
|
|
188
|
+
async retrieveKb(query, topK = 3) {
|
|
189
|
+
return this._post('/kb/retrieve', { query, top_k: topK });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── TRTC Custom Message Handler ────────────────────────────
|
|
193
|
+
// Call this from your TRTC SDK onCustomMessage callback.
|
|
194
|
+
|
|
195
|
+
handleTRTCMessage(cmd, data) {
|
|
196
|
+
switch (cmd) {
|
|
197
|
+
case 10000: // Subtitle
|
|
198
|
+
this.onSubtitle(typeof data === 'string' ? data : data?.text || '');
|
|
199
|
+
break;
|
|
200
|
+
case 10001: // AI Status
|
|
201
|
+
this.onAIStatus(typeof data === 'string' ? data : data?.status || '');
|
|
202
|
+
break;
|
|
203
|
+
default:
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { OralCoachClient };
|
|
210
|
+
export default OralCoachClient;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ability-report —— 4 维能力分析报告
|
|
2
|
+
name: ability-report
|
|
3
|
+
version: "1.0.0"
|
|
4
|
+
type: capability
|
|
5
|
+
description: "练习结束时,把整段 transcript 生成 4 维能力分析(流利度/词汇/语法/完整度)+ 高光句 + 替代表达 + 下一步建议。双语并行。"
|
|
6
|
+
|
|
7
|
+
dependencies:
|
|
8
|
+
- { name: conversation-core, version: ">=1.0.0,<2.0.0" }
|
|
9
|
+
|
|
10
|
+
extensions:
|
|
11
|
+
- inject_at: server.router_extension
|
|
12
|
+
inline_code: |
|
|
13
|
+
from ._capability_loader import try_load_capability as _try
|
|
14
|
+
_m = _try("ability-report", "src/router.py")
|
|
15
|
+
if _m is not None and hasattr(_m, "router"):
|
|
16
|
+
app.include_router(_m.router, prefix="/api/v1/report", tags=["ability-report"])
|
|
17
|
+
|
|
18
|
+
config:
|
|
19
|
+
max_transcript_turns: { default: 200 } # 上限,防滥用
|
|
20
|
+
languages: { default: ["en", "zh"] } # 双语并行生成
|
|
21
|
+
adapter: { default: "default", env: "AR_ADAPTER" }
|
|
22
|
+
|
|
23
|
+
endpoints:
|
|
24
|
+
- method: POST
|
|
25
|
+
path: /api/v1/report
|
|
26
|
+
description: "提交 transcript → 返回 4 维能力报告({Reports:{en,zh}})"
|
|
27
|
+
|
|
28
|
+
business_contract:
|
|
29
|
+
port_class: "src.ports.evaluator.Evaluator"
|
|
30
|
+
default_adapter: "src.adapters.default.LLMEvaluator"
|
|
31
|
+
customization_sop: "references/evaluator-port.md#1-evaluator-port"
|
|
32
|
+
external_apis:
|
|
33
|
+
- name: report.generate
|
|
34
|
+
direction: inbound
|
|
35
|
+
method: POST
|
|
36
|
+
path: /api/v1/report
|
|
37
|
+
request_schema: { scenario: string, level: string, style: string, durationSec: int, transcript: "array", language: string }
|
|
38
|
+
# 自由文本字段为目标语言;引用学习者原话保留英文
|
|
39
|
+
response_schema:
|
|
40
|
+
Reports:
|
|
41
|
+
en: "{ summary, abilityAnalysis{fluency,vocabulary,grammar,completeness}, mistakes[], betterExpressions[], nextSuggestions[] }"
|
|
42
|
+
zh: "{ 同 en 结构 }"
|
|
43
|
+
|
|
44
|
+
integration:
|
|
45
|
+
auto_adapters:
|
|
46
|
+
- { tech_stack: ["react","vue","angular"], adapter: frontend-spa }
|
|
47
|
+
- { tech_stack: ["express","next"], adapter: node-backend }
|
|
48
|
+
- { tech_stack: ["flask","fastapi"], adapter: python-backend }
|
|
49
|
+
|
|
50
|
+
acceptance:
|
|
51
|
+
- "提交 transcript 返回 4 维能力分析,无数字评分"
|
|
52
|
+
- "JSON 解析失败时走兜底骨架(至少 summary 不为空)"
|