@kevisual/cnb 0.0.75 → 0.0.77

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevisual/cnb",
3
- "version": "0.0.75",
3
+ "version": "0.0.77",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "basename": "/root/cnb",
@@ -48,7 +48,7 @@
48
48
  "ai": "^6.0.158",
49
49
  "commander": "^14.0.3",
50
50
  "dayjs": "^1.11.20",
51
- "dotenv": "^17.4.1",
51
+ "dotenv": "^17.4.2",
52
52
  "fast-glob": "^3.3.3",
53
53
  "zod": "^4.3.6"
54
54
  },
package/src/cnb-core.ts CHANGED
@@ -7,7 +7,8 @@ export type CNBCoreOptions<T = {}> = {
7
7
  cnb?: CNBCore;
8
8
  cors?: {
9
9
  baseUrl?: string
10
- }
10
+ },
11
+ openapi?: boolean;
11
12
  } & T;
12
13
 
13
14
  export type RequestOptions = {
@@ -17,20 +18,24 @@ export type RequestOptions = {
17
18
  body?: any;
18
19
  params?: Record<string, any>;
19
20
  headers?: Record<string, any>;
21
+ token?: string;
20
22
  useCookie?: boolean;
21
23
  useOrigin?: boolean;
22
24
  };
23
- const API_BASER_URL = 'https://api.cnb.cool'
25
+ const API_BASE_URL = 'https://api.cnb.cool'
24
26
  const API_HACK_URL = 'https://cnb.cool'
27
+ const API_BASE_OPEN_URL = 'https://cn.cool/openapi'
25
28
  export class CNBCore {
26
- baseURL = API_BASER_URL;
29
+ baseURL = API_BASE_URL;
27
30
  hackURL = API_HACK_URL;
28
31
  public token: string;
29
32
  public cookie?: string;
30
33
  isCors: boolean;
34
+ openapi?: boolean;
31
35
  constructor(options: CNBCoreOptions) {
32
36
  this.token = options.token;
33
37
  this.cookie = options.cookie;
38
+ this.openapi = options.openapi;
34
39
  if (options?.cnb) {
35
40
  if (!options.token) {
36
41
  this.token = options.cnb.token;
@@ -39,14 +44,20 @@ export class CNBCore {
39
44
  this.cookie = options.cnb.cookie;
40
45
  }
41
46
  }
47
+ if (options?.openapi) {
48
+ this.baseURL = API_BASE_OPEN_URL;
49
+ }
42
50
  if (options?.cors?.baseUrl) {
43
- this.baseURL = options.cors.baseUrl + '/' + API_BASER_URL.replace('https://', '');
44
- this.hackURL = options.cors.baseUrl + '/' + API_HACK_URL.replace('https://', '');
51
+ this.baseURL = options.cors.baseUrl + '/' + this.baseURL.replace('https://', '');
52
+ this.hackURL = options.cors.baseUrl + '/' + this.hackURL.replace('https://', '');
45
53
  }
46
54
  this.isCors = !!options?.cors?.baseUrl;
47
55
  }
48
56
 
49
- async request({ url, method = 'GET', data, params, headers, body, useCookie, useOrigin }: RequestOptions): Promise<any> {
57
+ async request({ url, method = 'GET', data, params, headers, body, useCookie, useOrigin, ...rest }: RequestOptions): Promise<any> {
58
+ if (!url.startsWith('http')) {
59
+ url = this.makeUrl(url);
60
+ }
50
61
  const defaultHeaders: Record<string, string> = {
51
62
  'Content-Type': 'application/json',
52
63
  // 'Accept': 'application/json, application/vnd.cnb.api+json, application/vnd.cnb.web+json',
@@ -55,10 +66,13 @@ export class CNBCore {
55
66
  if (this.token) {
56
67
  defaultHeaders['Authorization'] = `Bearer ${this.token}`;
57
68
  }
69
+ // 如果请求参数中有 token,则覆盖默认 token
70
+ if (rest.token) {
71
+ defaultHeaders['Authorization'] = `Bearer ${rest.token}`;
72
+ }
58
73
  if (params) {
59
74
  const queryString = new URLSearchParams(params).toString();
60
75
  url += `?${queryString}`;
61
- defaultHeaders['Accept'] = 'application/json';
62
76
  }
63
77
  const _headers = { ...defaultHeaders, ...headers };
64
78
  let _body = undefined;
@@ -142,10 +156,11 @@ export class CNBCore {
142
156
  return this.request({ url: fullUrl, method: 'PATCH', ...REST });
143
157
  }
144
158
  /**
145
- * 通过 PUT 请求上传文件内容
146
- * @param data 包含 URL、token 和文件内容
147
- * @returns 上传结果
148
- */
159
+ * 通过 PUT 请求上传文件内容
160
+ * assets上传的时候,第一次会获取到一个上传 URL token,然后使用这个方法将文件内容上传到指定的 URL
161
+ * @param data 包含 URL、token 和文件内容
162
+ * @returns 上传结果
163
+ */
149
164
  async putFile(data: { url: string, token: string, content: string | Buffer }): Promise<any> {
150
165
  return this.request({
151
166
  url: data.url,
@@ -1,4 +1,4 @@
1
- import { CNBCore, CNBCoreOptions, Result } from "../cnb-core.ts";
1
+ import { CNBCore, CNBCoreOptions, RequestOptions, Result } from "../cnb-core.ts";
2
2
 
3
3
  // https://cnb.cool/cnb/plugins/cnbcool/knowledge-base/-/blob/main/src/api.py
4
4
  export class KnowledgeBase extends CNBCore {
@@ -27,31 +27,56 @@ export class KnowledgeBase extends CNBCore {
27
27
  }
28
28
  deleteBase(repo: string): Promise<Result<any>> {
29
29
  const url = `/${repo}/-/knowledge/base`;
30
- return this.request({ url, method: 'DELETE' });
30
+ return this.delete({ url });
31
31
  }
32
32
 
33
33
  /**
34
34
  * 未暴露
35
+ *
36
+ * 创建知识库接口,cnb 界面操作定制模块功能依赖该接口实现
35
37
  * @param repo
36
38
  * @param data
37
39
  * @returns
38
40
  */
39
- createKnowledgeBase(repo: string, data: {
40
- embedding_model_name: string;
41
+
42
+ createKnowledgeBase(repo: string, params: {
43
+ /**
44
+ * hunyuan | bge-m3
45
+ */
46
+ model_name: string;
41
47
  include: string;
42
48
  exclude: string;
49
+ chunk_size: number;
50
+ chunk_overlap: number;
51
+ text_separator: string;
43
52
  issue_sync_enabled?: boolean;
44
- processing?: {
45
- chunk_size: number;
46
- chunk_overlap: number;
47
- text_separator: string;
48
- };
49
- issue?: {
50
- labels: string;
51
- state: string;
52
- };
53
+ issue_labels?: string;
54
+ issue_exclude_labels?: string;
55
+ issue_state?: string;
53
56
  }): Promise<Result<any>> {
54
57
  const url = `/${repo}/-/knowledge/base`;
58
+ const metadata: any = {
59
+ processing: {
60
+ chunk_size: params.chunk_size,
61
+ chunk_overlap: params.chunk_overlap,
62
+ text_separator: params.text_separator
63
+ },
64
+ version: "1.0"
65
+ };
66
+ if (params.issue_sync_enabled) {
67
+ metadata.issue = {
68
+ labels: params.issue_labels || "",
69
+ exclude_labels: params.issue_exclude_labels || "",
70
+ state: params.issue_state || ""
71
+ };
72
+ }
73
+ const data = {
74
+ embedding_model_name: params.model_name,
75
+ include: params.include,
76
+ exclude: params.exclude,
77
+ issue_sync_enabled: params.issue_sync_enabled || false,
78
+ metadata
79
+ };
55
80
  return this.post({ url, data });
56
81
  }
57
82
  /**
@@ -74,22 +99,27 @@ export class KnowledgeBase extends CNBCore {
74
99
  * @param text
75
100
  * @returns
76
101
  */
77
- getEmbedding(repo: string, text: string): Promise<Result<{ embedding: number[] }>> {
102
+ getEmbedding(repo: string, text: string): Promise<Result<{ embeddings: number[] }>> {
78
103
  const url = `/${repo}/-/knowledge/embedding`;
79
104
  return this.post({ url, data: { text } });
80
105
  }
81
106
  /**
82
- * 未暴露
107
+ * 未暴露
108
+ * 只能运行在流水线,使用流水线的CNB_TOKEN去使用
109
+ * 否则会报错:sn not found in meta
110
+ * opts的token必须是流水线的CNB_TOKEN,不能是用户的token
83
111
  * @param repo
84
112
  * @param chunksData
85
113
  * @returns
86
114
  */
87
- addDocument(repo: string, chunksData: {
88
- path: string;
89
- chunks: Array<{
90
- content: string;
91
- hash: string;
92
- offset: number;
115
+ addDocument(repo: string, AddDocument: AddDocument, opts?: RequestOptions): Promise<Result<null>> {
116
+ const url = `/${repo}/-/knowledge/documents/upsert-document-with-chunks`;
117
+ return this.post({ url, data: AddDocument, ...opts });
118
+ }
119
+ /**
120
+ * 未暴露
121
+ * @param repo
122
+ * @param paths
93
123
  size: number;
94
124
  }>;
95
125
  }): Promise<Result<any>> {
@@ -114,7 +144,7 @@ export class KnowledgeBase extends CNBCore {
114
144
  * @param page_size
115
145
  * @returns
116
146
  */
117
- listDocument(repo: string, page: number = 1, page_size: number = 50): Promise<Result<any[]>> {
147
+ listDocuments(repo: string, page: number = 1, page_size: number = 50): Promise<Result<any[]>> {
118
148
  const url = `/${repo}/-/knowledge/documents`;
119
149
  return this.get({ url, params: { page, page_size } });
120
150
  }
@@ -143,4 +173,33 @@ type QueryRag = {
143
173
  type: string; // code, text
144
174
  url: string;
145
175
  }
176
+ }
177
+
178
+ type AddDocument = {
179
+ // 文档路径,必填, 例如:docs/xxx.md
180
+ path: string;
181
+ // 文件名 xxx
182
+ name: string;
183
+ // 文件扩展名,例如:md
184
+ extension: string;
185
+ // 文件大小,单位:字节
186
+ size: number;
187
+ // 文件内容的 hash,(hashlib.md5(content.encode('utf-8')).hexdigest())
188
+ hash: string;
189
+ type: "code" | "issue"; // code, pdf
190
+ // 类似 https://cnb.cool/kevisual/starred-auto/-/blob/62ce3d724f6e5f2cfd4b84a1df7cf4a6eaf441d6/docs/11730342.md
191
+ url: string;
192
+ chunks: Array<{
193
+ // 文档的分块的内容
194
+ "content": string,
195
+ "hash": string,
196
+ "position": number,
197
+ "embedding": number[]
198
+ }>;
199
+ // 处理当前批次的索引, 从0开始
200
+ batch_index: number;
201
+ // 当前批次的总数, 必须大于0
202
+ batch_count: number;
203
+ // 是否是最后一批
204
+ is_last_batch: boolean;
146
205
  }
@@ -77,7 +77,44 @@ export class RepoLabel extends CNBCore {
77
77
  }
78
78
  });
79
79
  }
80
-
80
+ async getAll(repo: string): Promise<Result<Label[]>> {
81
+ let labels: Label[] = [];
82
+ const pageSize = 99;
83
+ for (let page = 1; ; page++) {
84
+ const res = await this.list(repo, { page, page_size: pageSize });
85
+ if (res.code !== 200) {
86
+ return { code: res.code, message: res.message, data: labels };
87
+ }
88
+ labels = labels.concat(res.data);
89
+ if (res.data.length < pageSize) {
90
+ break;
91
+ }
92
+ }
93
+ return { code: 200, data: labels, message: '获取标签列表成功' };
94
+ }
95
+ async importLabels(opts: { repo: string, labels: PostLabelForm[] }): Promise<Result> {
96
+ const labels = opts.labels;
97
+ const repo = opts.repo;
98
+ const addLabels: Label[] = [];
99
+ for (const label of labels) {
100
+ try {
101
+ const res = await this.create(repo, label);
102
+ if (res.code === 200) {
103
+ addLabels.push(res.data);
104
+ } else {
105
+ if (res.code === 409) {
106
+ console.warn(`标签已存在 ${label.name},跳过创建`);
107
+ } else {
108
+ console.error(`创建标签失败 ${label.name}:`, res.message);
109
+ }
110
+ }
111
+ } catch (e) {
112
+ console.error(`创建标签失败 ${label.name}:`, e);
113
+ break;
114
+ }
115
+ }
116
+ return { code: 200, data: { addLabels }, message: '导入标签成功' };
117
+ }
81
118
  /**
82
119
  * 创建一个标签
83
120
  * @param repo 仓库路径
@@ -114,6 +151,22 @@ export class RepoLabel extends CNBCore {
114
151
  const url = `/${repo}/-/labels/${encodeURIComponent(name)}`;
115
152
  return this.delete({ url });
116
153
  }
154
+ async removeLabels(opts: { repo: string, labels: string[] }): Promise<Result> {
155
+ const { repo, labels } = opts;
156
+ try {
157
+ for (const label of labels) {
158
+ const res = await this.remove(repo, label);
159
+ await new Promise(resolve => setTimeout(resolve, 100)); // 避免请求过快导致服务器拒绝服务
160
+ if (res.code !== 200) {
161
+ console.error(`删除标签失败 ${label}:`, res.message);
162
+ break;
163
+ }
164
+ }
165
+ return { code: 200, data: labels, message: '删除标签成功' };
166
+ } catch (error) {
167
+ return { code: 500, message: '删除标签失败', data: error };
168
+ }
169
+ }
117
170
  }
118
171
 
119
172
  type ListLabelsParams = {
@@ -112,10 +112,10 @@ export class Mission extends CNBCore {
112
112
  /**
113
113
  * 改变任务集可见性
114
114
  * @param mission 任务集路径
115
- * @param visibility 可见性 (PrivatePublic)
115
+ * @param visibility 可见性 (privatepublic)
116
116
  * @returns 操作结果
117
117
  */
118
- setVisibility(mission: string, visibility: 'Private' | 'Public'): Promise<Result<any>> {
118
+ setVisibility(mission: string, visibility: 'private' | 'public'): Promise<Result<any>> {
119
119
  const url = `/${mission}/-/settings/set_visibility`;
120
120
  return this.post({
121
121
  url,
@@ -146,9 +146,16 @@ type GetMissionsParams = {
146
146
  };
147
147
 
148
148
  type CreateMissionData = {
149
+ /**
150
+ * 任务集名称,必填
151
+ */
149
152
  name: string;
150
153
  description?: string;
151
- visibility?: 'Private' | 'Public';
154
+ visibility?: 'private' | 'public';
155
+ /**
156
+ * 关联的仓库列表,格式为 `${group}/${repo}`,如 `my-group/my-repo`
157
+ */
158
+ repos: string[];
152
159
  };
153
160
 
154
161
  type Missions4User = {
@@ -156,7 +163,7 @@ type Missions4User = {
156
163
  name: string;
157
164
  slug_path: string;
158
165
  description: string;
159
- visibility: 'Private' | 'Public';
166
+ visibility: 'private' | 'public';
160
167
  created_at: string;
161
168
  updated_at: string;
162
169
  web_url: string;