Skip to content

Commit 652cdab

Browse files
authored
Merge branch 'Mai-with-u:dev' into dev
2 parents 2eab150 + 30c853a commit 652cdab

7 files changed

Lines changed: 304 additions & 69 deletions

File tree

dashboard/src/routes/resource/jargon/index.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Check, MessageCircle, Plus, Search, Trash2, X } from 'lucide-react'
2-
import { useEffect, useState } from 'react'
2+
import { useEffect, useRef, useState } from 'react'
33

44
import { Button } from '@/components/ui/button'
55
import { Input } from '@/components/ui/input'
@@ -46,6 +46,7 @@ export function JargonManagementPage() {
4646
const [page, setPage] = useState(1)
4747
const [pageSize, setPageSize] = useState(20)
4848
const [search, setSearch] = useState('')
49+
const [debouncedSearch, setDebouncedSearch] = useState('')
4950
const [scopeFilter, setScopeFilter] = useState<'all' | 'global' | 'local'>('all')
5051
const [filterChatId, setFilterChatId] = useState<string>('all')
5152
const [filterIsJargon, setFilterIsJargon] = useState<string>('all')
@@ -68,30 +69,41 @@ export function JargonManagementPage() {
6869
})
6970
const [chatList, setChatList] = useState<JargonChatInfo[]>([])
7071
const [formChatList, setFormChatList] = useState<JargonChatInfo[]>([])
72+
const jargonListRequestSeqRef = useRef(0)
7173
const { toast } = useToast()
7274

7375
// 加载黑话列表
7476
const loadJargons = async () => {
77+
const requestSeq = jargonListRequestSeqRef.current + 1
78+
jargonListRequestSeqRef.current = requestSeq
7579
try {
7680
setLoading(true)
7781
const response = await getJargonList({
7882
page,
7983
page_size: pageSize,
80-
search: search || undefined,
84+
search: debouncedSearch || undefined,
8185
session_id: scopeFilter !== 'global' && filterChatId !== 'all' ? filterChatId : undefined,
8286
is_jargon: filterIsJargon === 'all' ? undefined : filterIsJargon === 'true' ? true : filterIsJargon === 'false' ? false : undefined,
8387
is_global: scopeFilter === 'all' ? undefined : scopeFilter === 'global',
8488
})
89+
if (requestSeq !== jargonListRequestSeqRef.current) {
90+
return
91+
}
8592
setJargons(response.data)
8693
setTotal(response.total)
8794
} catch (error) {
95+
if (requestSeq !== jargonListRequestSeqRef.current) {
96+
return
97+
}
8898
toast({
8999
title: '加载失败',
90100
description: error instanceof Error ? error.message : '无法加载黑话列表',
91101
variant: 'destructive',
92102
})
93103
} finally {
94-
setLoading(false)
104+
if (requestSeq === jargonListRequestSeqRef.current) {
105+
setLoading(false)
106+
}
95107
}
96108
}
97109

@@ -125,12 +137,27 @@ export function JargonManagementPage() {
125137
}
126138
}
127139

140+
useEffect(() => {
141+
const timerId = window.setTimeout(() => {
142+
const normalizedSearch = search.trim()
143+
setDebouncedSearch((current) => (current === normalizedSearch ? current : normalizedSearch))
144+
setPage((current) => (current === 1 ? current : 1))
145+
setSelectedIds((current) => (current.size === 0 ? current : new Set<number>()))
146+
}, 300)
147+
148+
return () => window.clearTimeout(timerId)
149+
}, [search])
150+
128151
useEffect(() => {
129152
loadJargons()
153+
// eslint-disable-next-line react-hooks/exhaustive-deps
154+
}, [page, pageSize, debouncedSearch, scopeFilter, filterChatId, filterIsJargon])
155+
156+
useEffect(() => {
130157
loadStats()
131158
loadChatList()
132159
// eslint-disable-next-line react-hooks/exhaustive-deps
133-
}, [page, pageSize, search, scopeFilter, filterChatId, filterIsJargon])
160+
}, [])
134161

135162
// 查看详情
136163
const handleViewDetail = async (jargon: Jargon) => {
@@ -332,7 +359,7 @@ export function JargonManagementPage() {
332359
<Search className="absolute left-2.5 top-2 h-4 w-4 text-muted-foreground" />
333360
<Input
334361
id="search"
335-
placeholder="搜索内容、含义..."
362+
placeholder="搜索黑话内容..."
336363
value={search}
337364
onChange={(e) => setSearch(e.target.value)}
338365
className="h-8 pl-9"

pytests/webui/test_jargon_routes.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,20 +162,25 @@ def test_list_jargons_with_pagination(client: TestClient, sample_jargons):
162162

163163

164164
def test_list_jargons_with_search(client: TestClient, sample_jargons):
165-
"""测试 GET /jargon/list?search=xxx 搜索功能"""
165+
"""测试 GET /jargon/list?search=xxx 只按黑话内容搜索。"""
166166
response = client.get("/api/webui/jargon/list?search=yyds")
167167
assert response.status_code == 200
168168

169169
data = response.json()
170170
assert data["total"] == 1
171171
assert data["data"][0]["content"] == "yyds"
172172

173-
# 测试搜索 meaning
173+
# meaning 不参与搜索
174174
response = client.get("/api/webui/jargon/list?search=你好")
175175
assert response.status_code == 200
176176
data = response.json()
177-
assert data["total"] == 1
178-
assert data["data"][0]["content"] == "hello"
177+
assert data["total"] == 0
178+
179+
# raw_content 不参与搜索
180+
response = client.get("/api/webui/jargon/list?search=永远")
181+
assert response.status_code == 200
182+
data = response.json()
183+
assert data["total"] == 0
179184

180185

181186
def test_list_jargons_with_session_id_filter(client: TestClient, sample_jargons, sample_chat_session: ChatSession):
@@ -193,6 +198,36 @@ def test_list_jargons_with_session_id_filter(client: TestClient, sample_jargons,
193198
assert data["total"] == 0
194199

195200

201+
def test_list_jargons_with_session_id_filter_matches_exact_json_key(client: TestClient, session: Session):
202+
"""session_id 筛选应按 session_id_dict 的 JSON key 精确匹配。"""
203+
session.add(
204+
Jargon(
205+
id=201,
206+
content="exact",
207+
meaning="exact match",
208+
session_id_dict=json.dumps({"stream_1": 1}),
209+
count=2,
210+
)
211+
)
212+
session.add(
213+
Jargon(
214+
id=202,
215+
content="prefix",
216+
meaning="prefix only",
217+
session_id_dict=json.dumps({"stream_10": 1}),
218+
count=1,
219+
)
220+
)
221+
session.commit()
222+
223+
response = client.get("/api/webui/jargon/list?session_id=stream_1")
224+
assert response.status_code == 200
225+
226+
data = response.json()
227+
assert data["total"] == 1
228+
assert data["data"][0]["content"] == "exact"
229+
230+
196231
def test_list_jargons_with_is_jargon_filter(client: TestClient, sample_jargons):
197232
"""测试按 is_jargon 筛选"""
198233
response = client.get("/api/webui/jargon/list?is_jargon=true")
@@ -651,7 +686,7 @@ def test_update_jargon_partial_fields(client: TestClient, sample_jargons):
651686

652687
def test_list_jargons_multiple_filters(client: TestClient, sample_jargons, sample_chat_session: ChatSession):
653688
"""测试组合多个过滤条件"""
654-
response = client.get(f"/api/webui/jargon/list?search=永远&session_id={sample_chat_session.session_id}&is_jargon=true")
689+
response = client.get(f"/api/webui/jargon/list?search=yyds&session_id={sample_chat_session.session_id}&is_jargon=true")
655690
assert response.status_code == 200
656691

657692
data = response.json()

src/config/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
5959
LEGACY_ENV_PATH: Path = (PROJECT_ROOT / ".env").resolve().absolute()
6060
A_MEMORIX_LEGACY_CONFIG_PATH: Path = (CONFIG_DIR / "a_memorix.toml").resolve().absolute()
61-
MMC_VERSION: str = "1.0.0-pre.24"
61+
MMC_VERSION: str = "1.0.0-rc.1"
6262
CONFIG_VERSION: str = "8.12.17"
63-
MODEL_CONFIG_VERSION: str = "1.17.1"
63+
MODEL_CONFIG_VERSION: str = "1.17.2"
6464

6565
logger = get_logger("config")
6666

src/config/model_configs.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,19 @@ class TaskConfig(ConfigBase):
431431
"""任务硬超时(秒),到点未返回则取消请求并尝试切换下一个模型;防止上游代理静默排队导致主循环饥饿"""
432432

433433

434+
def _default_timing_gate_task_config() -> TaskConfig:
435+
"""创建 Timing Gate 任务默认配置。"""
436+
437+
return TaskConfig(
438+
model_list=[],
439+
max_tokens=1024,
440+
temperature=0.3,
441+
slow_threshold=12.0,
442+
selection_strategy="random",
443+
hard_timeout=120.0,
444+
)
445+
446+
434447
class ModelTaskConfig(ConfigBase):
435448
"""模型配置类"""
436449

@@ -452,6 +465,16 @@ class ModelTaskConfig(ConfigBase):
452465
)
453466
"""规划模型配置"""
454467

468+
timing_gate: TaskConfig = Field(
469+
default_factory=_default_timing_gate_task_config,
470+
json_schema_extra={
471+
"x-widget": "custom",
472+
"x-icon": "timer",
473+
"advanced": True,
474+
},
475+
)
476+
"""Timing Gate 节奏控制模型配置;留空时自动继用 planner 模型"""
477+
455478
memory: TaskConfig = Field(
456479
default_factory=TaskConfig,
457480
json_schema_extra={

src/llm_models/utils_model.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
)
6161
DATA_URI_RETRY_MARGIN_BYTES = 128 * 1024
6262
MIN_COMPRESSED_IMAGE_TARGET_SIZE_BYTES = 512 * 1024
63+
EMPTY_TASK_FALLBACKS = {
64+
"learner": "utils",
65+
"mid_memory": "planner",
66+
"timing_gate": "planner",
67+
}
6368

6469

6570
class RequestType(Enum):
@@ -113,12 +118,7 @@ def _get_task_config_or_raise(self) -> TaskConfig:
113118
if not isinstance(task_config, TaskConfig):
114119
raise ValueError(f"未找到名为 '{self.task_name}' 的任务配置")
115120
if not any(str(model_name).strip() for model_name in task_config.model_list):
116-
fallback_task_name = ""
117-
if self.task_name == "learner":
118-
fallback_task_name = "utils"
119-
elif self.task_name == "mid_memory":
120-
fallback_task_name = "planner"
121-
121+
fallback_task_name = EMPTY_TASK_FALLBACKS.get(self.task_name, "")
122122
if fallback_task_name:
123123
fallback_task_config = getattr(model_task_config, fallback_task_name, None)
124124
if isinstance(fallback_task_config, TaskConfig):

src/maisaka/chat_loop_service.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
"planner": "maisaka_planner",
5252
"timing_gate": "maisaka_timing_gate",
5353
}
54+
MODEL_TASK_NAME_BY_REQUEST_KIND = {
55+
"timing_gate": "timing_gate",
56+
}
5457
PROMPT_PREVIEW_CATEGORY_BY_REQUEST_KIND = {
5558
"planner": "planner",
5659
"timing_gate": "timing_gate",
@@ -524,15 +527,22 @@ def _resolve_prompt_preview_category(request_kind: str) -> str:
524527
return "planner"
525528
return PROMPT_PREVIEW_CATEGORY_BY_REQUEST_KIND.get(normalized_request_kind, normalized_request_kind)
526529

530+
def _resolve_model_task_name(self, request_kind: str) -> str:
531+
"""根据请求类型解析模型任务配置名。"""
532+
533+
normalized_request_kind = str(request_kind or "").strip().lower()
534+
return MODEL_TASK_NAME_BY_REQUEST_KIND.get(normalized_request_kind, self._model_task_name)
535+
527536
def _get_llm_chat_client(self, request_kind: str) -> LLMServiceClient:
528537
"""获取当前请求类型对应的 LLM 客户端。"""
529538

530539
request_type = self._resolve_llm_request_type(request_kind)
531-
client_key = f"{self._model_task_name}:{request_type}"
540+
model_task_name = self._resolve_model_task_name(request_kind)
541+
client_key = f"{model_task_name}:{request_type}"
532542
llm_client = self._llm_chat_clients.get(client_key)
533543
if llm_client is None:
534544
llm_client = LLMServiceClient(
535-
task_name=self._model_task_name,
545+
task_name=model_task_name,
536546
request_type=request_type,
537547
session_id=self._session_id,
538548
)

0 commit comments

Comments
 (0)