ima-python-sdk 0.1.0__py3-none-any.whl
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.
- ima_python_sdk-0.1.0.dist-info/METADATA +93 -0
- ima_python_sdk-0.1.0.dist-info/RECORD +15 -0
- ima_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- ima_python_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- ima_python_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- ima_python_sdk-0.1.0.dist-info/top_level.txt +1 -0
- ima_sdk/__init__.py +90 -0
- ima_sdk/cli.py +447 -0
- ima_sdk/client.py +268 -0
- ima_sdk/cos_uploader.py +158 -0
- ima_sdk/file_checker.py +226 -0
- ima_sdk/knowledge_base.py +591 -0
- ima_sdk/logger.py +89 -0
- ima_sdk/notes.py +343 -0
- ima_sdk/types.py +424 -0
ima_sdk/notes.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""笔记管理器。
|
|
2
|
+
|
|
3
|
+
封装 IMA 笔记相关的所有 API 操作,包括搜索、列表、创建、追加和读取。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
from .client import ImaClient
|
|
11
|
+
from . import logger
|
|
12
|
+
from .types import (
|
|
13
|
+
ContentFormat,
|
|
14
|
+
DocBasic,
|
|
15
|
+
NoteBookInfo,
|
|
16
|
+
NoteFolderItem,
|
|
17
|
+
SearchedDoc,
|
|
18
|
+
SearchType,
|
|
19
|
+
SortType,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# 笔记 API 基础路径
|
|
23
|
+
_BASE = "openapi/note/v1"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _ensure_utf8(text: str) -> str:
|
|
27
|
+
"""确保文本为合法 UTF-8 编码。
|
|
28
|
+
|
|
29
|
+
对无法编码的字符进行替换处理。
|
|
30
|
+
"""
|
|
31
|
+
return text.encode("utf-8", errors="replace").decode("utf-8")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NotesManager:
|
|
35
|
+
"""笔记管理器。
|
|
36
|
+
|
|
37
|
+
用法::
|
|
38
|
+
|
|
39
|
+
from ima_sdk import ImaClient, NotesManager
|
|
40
|
+
|
|
41
|
+
client = ImaClient()
|
|
42
|
+
notes = NotesManager(client)
|
|
43
|
+
|
|
44
|
+
# 搜索笔记
|
|
45
|
+
results = notes.search("会议记录")
|
|
46
|
+
|
|
47
|
+
# 创建笔记
|
|
48
|
+
doc_id = notes.create("# 会议纪要\\n\\n今日要点...")
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, client: ImaClient):
|
|
52
|
+
self._client = client
|
|
53
|
+
|
|
54
|
+
# ── 搜索笔记 ──────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
def search(
|
|
57
|
+
self,
|
|
58
|
+
query: str,
|
|
59
|
+
*,
|
|
60
|
+
search_type: int = SearchType.TITLE,
|
|
61
|
+
sort_type: int = SortType.MODIFY_TIME,
|
|
62
|
+
start: int = 0,
|
|
63
|
+
end: int = 20,
|
|
64
|
+
) -> Tuple[List[SearchedDoc], int, bool]:
|
|
65
|
+
"""搜索笔记。
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
query: 搜索关键词
|
|
69
|
+
search_type: 检索方式,0=标题(默认),1=正文
|
|
70
|
+
sort_type: 排序方式,0=更新时间, 1=创建时间, 2=标题, 3=大小
|
|
71
|
+
start: 翻页起始位置
|
|
72
|
+
end: 翻页结束位置
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
(搜索结果列表, 总命中数, 是否最后一页) 的元组
|
|
76
|
+
"""
|
|
77
|
+
# 构建 query_info
|
|
78
|
+
logger.log_step("搜索笔记", f"query={query!r} search_type={search_type} sort_type={sort_type}")
|
|
79
|
+
query_info = {}
|
|
80
|
+
if search_type == SearchType.TITLE:
|
|
81
|
+
query_info["title"] = query
|
|
82
|
+
else:
|
|
83
|
+
query_info["content"] = query
|
|
84
|
+
|
|
85
|
+
data = self._client.post(
|
|
86
|
+
f"{_BASE}/search_note_book",
|
|
87
|
+
{
|
|
88
|
+
"search_type": search_type,
|
|
89
|
+
"sort_type": sort_type,
|
|
90
|
+
"query_info": query_info,
|
|
91
|
+
"start": start,
|
|
92
|
+
"end": end,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
docs = [SearchedDoc.from_dict(d) for d in data.get("docs", [])]
|
|
96
|
+
total = data.get("total_hit_num", 0)
|
|
97
|
+
is_end = data.get("is_end", True)
|
|
98
|
+
logger.log(f" 命中 {total} 条,本页 {len(docs)} 条,is_end={is_end}")
|
|
99
|
+
return docs, total, is_end
|
|
100
|
+
|
|
101
|
+
def search_all(
|
|
102
|
+
self,
|
|
103
|
+
query: str,
|
|
104
|
+
*,
|
|
105
|
+
search_type: int = SearchType.TITLE,
|
|
106
|
+
sort_type: int = SortType.MODIFY_TIME,
|
|
107
|
+
page_size: int = 20,
|
|
108
|
+
) -> List[SearchedDoc]:
|
|
109
|
+
"""搜索笔记并自动翻页获取全部结果。
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
query: 搜索关键词
|
|
113
|
+
search_type: 检索方式
|
|
114
|
+
sort_type: 排序方式
|
|
115
|
+
page_size: 每页数量
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
所有搜索结果列表
|
|
119
|
+
"""
|
|
120
|
+
logger.log_step("搜索全部笔记", f"query={query!r} page_size={page_size}")
|
|
121
|
+
all_docs: List[SearchedDoc] = []
|
|
122
|
+
start = 0
|
|
123
|
+
while True:
|
|
124
|
+
docs, _, is_end = self.search(
|
|
125
|
+
query,
|
|
126
|
+
search_type=search_type,
|
|
127
|
+
sort_type=sort_type,
|
|
128
|
+
start=start,
|
|
129
|
+
end=start + page_size,
|
|
130
|
+
)
|
|
131
|
+
all_docs.extend(docs)
|
|
132
|
+
if is_end or not docs:
|
|
133
|
+
break
|
|
134
|
+
start += page_size
|
|
135
|
+
logger.log(f" 共获取 {len(all_docs)} 条笔记")
|
|
136
|
+
return all_docs
|
|
137
|
+
|
|
138
|
+
# ── 列出笔记本 ────────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
def list_folders(
|
|
141
|
+
self, *, limit: int = 50
|
|
142
|
+
) -> List[NoteFolderItem]:
|
|
143
|
+
"""列出所有笔记本(单页)。
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
limit: 数量限制
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
笔记本列表
|
|
150
|
+
"""
|
|
151
|
+
logger.log_step("列出笔记本", f"limit={limit}")
|
|
152
|
+
data = self._client.post(
|
|
153
|
+
f"{_BASE}/list_note_folder_by_cursor",
|
|
154
|
+
{"cursor": "0", "limit": limit},
|
|
155
|
+
)
|
|
156
|
+
result = [
|
|
157
|
+
NoteFolderItem.from_dict(f) for f in data.get("note_book_folders", [])
|
|
158
|
+
]
|
|
159
|
+
logger.log(f" 获取 {len(result)} 个笔记本")
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
def list_all_folders(self, *, limit: int = 50) -> List[NoteFolderItem]:
|
|
163
|
+
"""列出所有笔记本(自动翻页)。
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
limit: 每页数量
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
所有笔记本列表
|
|
170
|
+
"""
|
|
171
|
+
logger.log_step("列出全部笔记本", f"limit={limit}")
|
|
172
|
+
all_folders: List[NoteFolderItem] = []
|
|
173
|
+
cursor = "0"
|
|
174
|
+
while True:
|
|
175
|
+
data = self._client.post(
|
|
176
|
+
f"{_BASE}/list_note_folder_by_cursor",
|
|
177
|
+
{"cursor": cursor, "limit": limit},
|
|
178
|
+
)
|
|
179
|
+
folders = [
|
|
180
|
+
NoteFolderItem.from_dict(f)
|
|
181
|
+
for f in data.get("note_book_folders", [])
|
|
182
|
+
]
|
|
183
|
+
all_folders.extend(folders)
|
|
184
|
+
if data.get("is_end", True):
|
|
185
|
+
break
|
|
186
|
+
next_cursor = data.get("next_cursor", "")
|
|
187
|
+
if not next_cursor:
|
|
188
|
+
break
|
|
189
|
+
cursor = next_cursor
|
|
190
|
+
logger.log(f" 共获取 {len(all_folders)} 个笔记本")
|
|
191
|
+
return all_folders
|
|
192
|
+
|
|
193
|
+
# ── 列出笔记 ──────────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
def list_notes(
|
|
196
|
+
self,
|
|
197
|
+
*,
|
|
198
|
+
folder_id: Optional[str] = None,
|
|
199
|
+
limit: int = 50,
|
|
200
|
+
) -> Tuple[List[NoteBookInfo], bool, str]:
|
|
201
|
+
"""列出笔记本下的笔记(单页)。
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
folder_id: 笔记本 ID(省略则列出根目录)
|
|
205
|
+
limit: 数量限制
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
(笔记列表, 是否最后一页, 下一页游标) 的元组
|
|
209
|
+
"""
|
|
210
|
+
logger.log_step("列出笔记", f"folder_id={folder_id} limit={limit}")
|
|
211
|
+
body: dict = {"cursor": "", "limit": limit}
|
|
212
|
+
if folder_id:
|
|
213
|
+
body["folder_id"] = folder_id
|
|
214
|
+
data = self._client.post(f"{_BASE}/list_note_by_folder_id", body)
|
|
215
|
+
notes = [NoteBookInfo.from_dict(n) for n in data.get("note_book_list", [])]
|
|
216
|
+
is_end = data.get("is_end", True)
|
|
217
|
+
next_cursor = data.get("next_cursor", "")
|
|
218
|
+
logger.log(f" 获取 {len(notes)} 条笔记,is_end={is_end}")
|
|
219
|
+
return notes, is_end, next_cursor
|
|
220
|
+
|
|
221
|
+
def list_all_notes(
|
|
222
|
+
self,
|
|
223
|
+
*,
|
|
224
|
+
folder_id: Optional[str] = None,
|
|
225
|
+
limit: int = 50,
|
|
226
|
+
) -> List[NoteBookInfo]:
|
|
227
|
+
"""列出笔记本下的所有笔记(自动翻页)。
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
folder_id: 笔记本 ID
|
|
231
|
+
limit: 每页数量
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
所有笔记列表
|
|
235
|
+
"""
|
|
236
|
+
logger.log_step("列出全部笔记", f"folder_id={folder_id}")
|
|
237
|
+
all_notes: List[NoteBookInfo] = []
|
|
238
|
+
cursor = ""
|
|
239
|
+
while True:
|
|
240
|
+
body: dict = {"cursor": cursor, "limit": limit}
|
|
241
|
+
if folder_id:
|
|
242
|
+
body["folder_id"] = folder_id
|
|
243
|
+
data = self._client.post(f"{_BASE}/list_note_by_folder_id", body)
|
|
244
|
+
notes = [
|
|
245
|
+
NoteBookInfo.from_dict(n) for n in data.get("note_book_list", [])
|
|
246
|
+
]
|
|
247
|
+
all_notes.extend(notes)
|
|
248
|
+
if data.get("is_end", True):
|
|
249
|
+
break
|
|
250
|
+
next_cursor = data.get("next_cursor", "")
|
|
251
|
+
if not next_cursor:
|
|
252
|
+
break
|
|
253
|
+
cursor = next_cursor
|
|
254
|
+
logger.log(f" 共获取 {len(all_notes)} 条笔记")
|
|
255
|
+
return all_notes
|
|
256
|
+
|
|
257
|
+
# ── 创建笔记 ──────────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
def create(
|
|
260
|
+
self,
|
|
261
|
+
content: str,
|
|
262
|
+
*,
|
|
263
|
+
folder_id: Optional[str] = None,
|
|
264
|
+
) -> str:
|
|
265
|
+
"""创建新笔记(Markdown 格式)。
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
content: 笔记正文内容(Markdown 格式)
|
|
269
|
+
folder_id: 关联的笔记本 ID
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
新建笔记的 doc_id
|
|
273
|
+
"""
|
|
274
|
+
content = _ensure_utf8(content)
|
|
275
|
+
logger.log_step("创建笔记", f"content_len={len(content)} folder_id={folder_id}")
|
|
276
|
+
body: dict = {
|
|
277
|
+
"content_format": ContentFormat.MARKDOWN,
|
|
278
|
+
"content": content,
|
|
279
|
+
}
|
|
280
|
+
if folder_id:
|
|
281
|
+
body["folder_id"] = folder_id
|
|
282
|
+
data = self._client.post(f"{_BASE}/import_doc", body)
|
|
283
|
+
doc_id = data.get("doc_id", "")
|
|
284
|
+
logger.log(f" 创建成功 doc_id={doc_id}")
|
|
285
|
+
return doc_id
|
|
286
|
+
|
|
287
|
+
# ── 追加内容到笔记 ────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
def append(self, doc_id: str, content: str) -> str:
|
|
290
|
+
"""在笔记末尾追加内容。
|
|
291
|
+
|
|
292
|
+
注意:追加操作不可撤销。
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
doc_id: 目标笔记 ID
|
|
296
|
+
content: 要追加的内容(Markdown 格式)
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
目标笔记的 doc_id
|
|
300
|
+
"""
|
|
301
|
+
content = _ensure_utf8(content)
|
|
302
|
+
logger.log_step("追加笔记内容", f"doc_id={doc_id} content_len={len(content)}")
|
|
303
|
+
data = self._client.post(
|
|
304
|
+
f"{_BASE}/append_doc",
|
|
305
|
+
{
|
|
306
|
+
"doc_id": doc_id,
|
|
307
|
+
"content_format": ContentFormat.MARKDOWN,
|
|
308
|
+
"content": content,
|
|
309
|
+
},
|
|
310
|
+
)
|
|
311
|
+
result_id = data.get("doc_id", "")
|
|
312
|
+
logger.log(f" 追加成功 doc_id={result_id}")
|
|
313
|
+
return result_id
|
|
314
|
+
|
|
315
|
+
# ── 获取笔记内容 ──────────────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
def get_content(
|
|
318
|
+
self,
|
|
319
|
+
doc_id: str,
|
|
320
|
+
*,
|
|
321
|
+
target_format: int = ContentFormat.PLAINTEXT,
|
|
322
|
+
) -> str:
|
|
323
|
+
"""获取笔记内容。
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
doc_id: 目标笔记 ID
|
|
327
|
+
target_format: 目标格式,0=纯文本(推荐),2=JSON
|
|
328
|
+
注意:1=Markdown 不支持
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
笔记内容文本
|
|
332
|
+
"""
|
|
333
|
+
logger.log_step("读取笔记内容", f"doc_id={doc_id} format={target_format}")
|
|
334
|
+
data = self._client.post(
|
|
335
|
+
f"{_BASE}/get_doc_content",
|
|
336
|
+
{
|
|
337
|
+
"doc_id": doc_id,
|
|
338
|
+
"target_content_format": target_format,
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
content = data.get("content", "")
|
|
342
|
+
logger.log(f" 内容长度: {len(content)} 字符")
|
|
343
|
+
return content
|