-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmd_search.py
More file actions
124 lines (101 loc) · 3.66 KB
/
Copy pathcmd_search.py
File metadata and controls
124 lines (101 loc) · 3.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""Команда search: поиск по тексту сообщений и частей."""
import argparse
import json
from db import SessionError, resolve_session_id
from i18n import _
from utils import build_help_epilog, format_ts
_SEARCH_EXAMPLES = [
('"text"', "help.search.e0"),
('"text" --session ses_xxx', "help.search.e1"),
('"text" --json', "help.search.e2"),
]
def register(subparsers) -> None:
p = subparsers.add_parser(
"search",
help=_("help.cmd.search"),
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=build_help_epilog("search", _SEARCH_EXAMPLES),
)
p.add_argument("query", help="Search text")
p.add_argument("--session", type=str, help="Limit to session")
p.add_argument("--limit", type=int, default=30, help="Max results")
p.add_argument("--json", action="store_true", help="JSON output")
def _search_in_part_data(data, query_lower) -> tuple[bool, str]:
"""Ищет query в part.data (JSON), проверяя текстовые поля."""
if not data:
return False, ""
try:
parsed = json.loads(data) if isinstance(data, str) else data
except (json.JSONDecodeError, TypeError):
return False, ""
text = parsed.get("text", "")
if isinstance(text, str) and query_lower in text.lower():
return True, text[:200]
state = parsed.get("state", {})
if isinstance(state, dict):
t_input = state.get("input", {})
t_output = state.get("output", "")
if isinstance(t_input, dict):
for v in t_input.values():
if isinstance(v, str) and query_lower in v.lower():
return True, f"[tool] {str(v)[:200]}"
if isinstance(t_output, str) and query_lower in t_output.lower():
return True, f"[output] {t_output[:200]}"
return False, ""
def run(args, db) -> int:
query_lower = args.query.lower()
conditions = []
params = []
if args.session:
try:
full_id = resolve_session_id(db, args.session)
except SessionError as e:
print(e.message)
return 1
conditions.append("p.session_id = ?")
params.append(full_id)
where = " AND ".join(conditions) if conditions else "1=1"
rows = db.execute(
f"""
SELECT p.id, p.session_id, p.message_id, p.data,
m.time_created,
json_extract(m.data, '$.role') as role
FROM part p
JOIN message m ON m.id = p.message_id
WHERE {where}
AND p.data IS NOT NULL
ORDER BY p.time_created DESC
LIMIT ?
""",
(*params, args.limit * 5),
).fetchall()
results = []
for r in rows:
found, snippet = _search_in_part_data(r["data"], query_lower)
if found:
results.append(
{
"session_id": r["session_id"][:24],
"message_id": r["message_id"][:12],
"role": r["role"],
"time": format_ts(r["time_created"]) if r["time_created"] else "—",
"snippet": snippet,
}
)
if len(results) >= args.limit:
break
if args.json:
from formatters import print_json
print_json(results)
return 0
if not results:
print(_("search.none", query=args.query))
return 0
print(_("search.header", query=args.query))
print(f" {'─' * 60}")
for r in results:
print(f" [{r['time']}] {r['role']} {r['session_id']} msg:{r['message_id']}")
print(f" {r['snippet']}")
print()
print(_("search.found", n=len(results)))
return 0