@next-open-ai/openbot 0.2.8 → 0.6.6

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.
Files changed (94) hide show
  1. package/README.md +13 -3
  2. package/apps/desktop/renderer/dist/assets/index-BNuvb6Ay.css +10 -0
  3. package/apps/desktop/renderer/dist/assets/index-DvQjslfT.js +89 -0
  4. package/apps/desktop/renderer/dist/index.html +2 -2
  5. package/dist/core/agent/agent-manager.d.ts +17 -6
  6. package/dist/core/agent/agent-manager.js +62 -25
  7. package/dist/core/agent/run.js +2 -2
  8. package/dist/core/config/desktop-config.d.ts +17 -0
  9. package/dist/core/config/desktop-config.js +23 -1
  10. package/dist/core/installer/index.d.ts +1 -1
  11. package/dist/core/installer/index.js +1 -1
  12. package/dist/core/installer/skill-installer.d.ts +9 -0
  13. package/dist/core/installer/skill-installer.js +94 -0
  14. package/dist/core/mcp/adapter.d.ts +17 -0
  15. package/dist/core/mcp/adapter.js +49 -0
  16. package/dist/core/mcp/client.d.ts +24 -0
  17. package/dist/core/mcp/client.js +70 -0
  18. package/dist/core/mcp/config.d.ts +22 -0
  19. package/dist/core/mcp/config.js +69 -0
  20. package/dist/core/mcp/index.d.ts +18 -0
  21. package/dist/core/mcp/index.js +20 -0
  22. package/dist/core/mcp/operator.d.ts +15 -0
  23. package/dist/core/mcp/operator.js +72 -0
  24. package/dist/core/mcp/transport/index.d.ts +11 -0
  25. package/dist/core/mcp/transport/index.js +16 -0
  26. package/dist/core/mcp/transport/sse.d.ts +20 -0
  27. package/dist/core/mcp/transport/sse.js +82 -0
  28. package/dist/core/mcp/transport/stdio.d.ts +32 -0
  29. package/dist/core/mcp/transport/stdio.js +132 -0
  30. package/dist/core/mcp/types.d.ts +72 -0
  31. package/dist/core/mcp/types.js +5 -0
  32. package/dist/core/session-current-agent.d.ts +34 -0
  33. package/dist/core/session-current-agent.js +32 -0
  34. package/dist/core/tools/bookmark-tool.d.ts +9 -0
  35. package/dist/core/tools/bookmark-tool.js +118 -0
  36. package/dist/core/tools/create-agent-tool.d.ts +6 -0
  37. package/dist/core/tools/create-agent-tool.js +97 -0
  38. package/dist/core/tools/index.d.ts +4 -0
  39. package/dist/core/tools/index.js +4 -0
  40. package/dist/core/tools/list-agents-tool.d.ts +5 -0
  41. package/dist/core/tools/list-agents-tool.js +45 -0
  42. package/dist/core/tools/switch-agent-tool.d.ts +6 -0
  43. package/dist/core/tools/switch-agent-tool.js +54 -0
  44. package/dist/gateway/channel/adapters/feishu.d.ts +11 -0
  45. package/dist/gateway/channel/adapters/feishu.js +218 -0
  46. package/dist/gateway/channel/channel-core.d.ts +9 -0
  47. package/dist/gateway/channel/channel-core.js +127 -0
  48. package/dist/gateway/channel/registry.d.ts +16 -0
  49. package/dist/gateway/channel/registry.js +54 -0
  50. package/dist/gateway/channel/run-agent.d.ts +26 -0
  51. package/dist/gateway/channel/run-agent.js +137 -0
  52. package/dist/gateway/channel/session-persistence.d.ts +36 -0
  53. package/dist/gateway/channel/session-persistence.js +46 -0
  54. package/dist/gateway/channel/types.d.ts +70 -0
  55. package/dist/gateway/channel/types.js +4 -0
  56. package/dist/gateway/channel-handler.d.ts +3 -4
  57. package/dist/gateway/channel-handler.js +8 -2
  58. package/dist/gateway/methods/agent-chat.js +31 -12
  59. package/dist/gateway/methods/install-skill-from-upload.d.ts +14 -0
  60. package/dist/gateway/methods/install-skill-from-upload.js +13 -0
  61. package/dist/gateway/methods/run-scheduled-task.js +5 -2
  62. package/dist/gateway/server.js +74 -1
  63. package/dist/server/agent-config/agent-config.controller.d.ts +6 -1
  64. package/dist/server/agent-config/agent-config.service.d.ts +15 -1
  65. package/dist/server/agent-config/agent-config.service.js +12 -3
  66. package/dist/server/agents/agents.controller.d.ts +10 -0
  67. package/dist/server/agents/agents.controller.js +36 -4
  68. package/dist/server/agents/agents.gateway.js +18 -4
  69. package/dist/server/agents/agents.service.d.ts +5 -1
  70. package/dist/server/agents/agents.service.js +20 -2
  71. package/dist/server/app.module.js +2 -0
  72. package/dist/server/config/config.controller.d.ts +2 -0
  73. package/dist/server/config/config.service.d.ts +3 -0
  74. package/dist/server/config/config.service.js +3 -1
  75. package/dist/server/database/database.service.d.ts +7 -0
  76. package/dist/server/database/database.service.js +54 -5
  77. package/dist/server/saved-items/saved-items.controller.d.ts +57 -0
  78. package/dist/server/saved-items/saved-items.controller.js +229 -0
  79. package/dist/server/saved-items/saved-items.module.d.ts +2 -0
  80. package/dist/server/saved-items/saved-items.module.js +25 -0
  81. package/dist/server/saved-items/saved-items.service.d.ts +31 -0
  82. package/dist/server/saved-items/saved-items.service.js +105 -0
  83. package/dist/server/saved-items/tags.controller.d.ts +30 -0
  84. package/dist/server/saved-items/tags.controller.js +85 -0
  85. package/dist/server/saved-items/tags.service.d.ts +24 -0
  86. package/dist/server/saved-items/tags.service.js +84 -0
  87. package/dist/server/skills/skills.service.d.ts +2 -0
  88. package/dist/server/skills/skills.service.js +80 -16
  89. package/dist/server/workspace/workspace.service.d.ts +11 -0
  90. package/dist/server/workspace/workspace.service.js +40 -1
  91. package/package.json +6 -1
  92. package/skills/url-bookmark/SKILL.md +36 -0
  93. package/apps/desktop/renderer/dist/assets/index-BOS-F8a4.js +0 -89
  94. package/apps/desktop/renderer/dist/assets/index-DxqxayUL.css +0 -10
@@ -0,0 +1,57 @@
1
+ import type { Response } from 'express';
2
+ import { SavedItemsService } from './saved-items.service.js';
3
+ import { WorkspaceService } from '../workspace/workspace.service.js';
4
+ import { ConfigService } from '../config/config.service.js';
5
+ export declare class SavedItemsController {
6
+ private readonly savedItemsService;
7
+ private readonly workspaceService;
8
+ private readonly configService;
9
+ constructor(savedItemsService: SavedItemsService, workspaceService: WorkspaceService, configService: ConfigService);
10
+ list(tagId?: string, workspace?: string): Promise<{
11
+ success: boolean;
12
+ data: import("./saved-items.service.js").SavedItem[];
13
+ }>;
14
+ imageProxy(url: string, res: Response): Promise<void>;
15
+ get(id: string): Promise<{
16
+ success: boolean;
17
+ data: import("./saved-items.service.js").SavedItem;
18
+ }>;
19
+ create(body: {
20
+ url: string;
21
+ title?: string;
22
+ workspace?: string;
23
+ tagNames?: string[];
24
+ tagIds?: string[];
25
+ }): Promise<{
26
+ success: boolean;
27
+ data: import("./saved-items.service.js").SavedItem;
28
+ }>;
29
+ delete(id: string): Promise<{
30
+ success: boolean;
31
+ }>;
32
+ /**
33
+ * Download the saved item URL to workspace .favorite or to a user-chosen directory.
34
+ * Body: { workspace?: string, targetDir?: string }.
35
+ * If targetDir (absolute path) is provided, file is written there; otherwise to workspace/.favorite.
36
+ */
37
+ downloadToWorkspace(id: string, body: {
38
+ workspace?: string;
39
+ targetDir?: string;
40
+ }): Promise<{
41
+ success: boolean;
42
+ data: {
43
+ absolutePath: string;
44
+ targetDir: string;
45
+ relativePath?: undefined;
46
+ workspace?: undefined;
47
+ };
48
+ } | {
49
+ success: boolean;
50
+ data: {
51
+ relativePath: string;
52
+ workspace: string;
53
+ absolutePath?: undefined;
54
+ targetDir?: undefined;
55
+ };
56
+ }>;
57
+ }
@@ -0,0 +1,229 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Controller, Get, Post, Delete, Body, Param, Query, Res, HttpException, HttpStatus, } from '@nestjs/common';
14
+ import { readFile } from 'fs/promises';
15
+ import { resolve } from 'path';
16
+ import { homedir } from 'os';
17
+ import { existsSync } from 'fs';
18
+ import { SavedItemsService } from './saved-items.service.js';
19
+ import { WorkspaceService } from '../workspace/workspace.service.js';
20
+ import { ConfigService } from '../config/config.service.js';
21
+ import { getOpenbotWorkspaceDir } from '../../core/agent/agent-dir.js';
22
+ const IMAGE_EXT_MIME = {
23
+ jpg: 'image/jpeg',
24
+ jpeg: 'image/jpeg',
25
+ png: 'image/png',
26
+ gif: 'image/gif',
27
+ webp: 'image/webp',
28
+ svg: 'image/svg+xml',
29
+ bmp: 'image/bmp',
30
+ ico: 'image/x-icon',
31
+ };
32
+ function mimeFromPath(filePath) {
33
+ const ext = filePath.split('.').pop()?.toLowerCase() || '';
34
+ return IMAGE_EXT_MIME[ext] || 'application/octet-stream';
35
+ }
36
+ function safeFilenameFromUrl(url, contentType) {
37
+ try {
38
+ const u = new URL(url);
39
+ const pathname = u.pathname || '';
40
+ const segment = pathname.split('/').filter(Boolean).pop() || '';
41
+ const decoded = decodeURIComponent(segment);
42
+ const base = decoded.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 180);
43
+ if (base)
44
+ return base;
45
+ }
46
+ catch (_) { }
47
+ const ct = (contentType || '').split(';')[0].trim().toLowerCase();
48
+ let ext = 'bin';
49
+ if (ct.includes('image/'))
50
+ ext = ct.replace('image/', '').trim() || 'png';
51
+ else if (ct.includes('text/html'))
52
+ ext = 'html';
53
+ else if (ct.includes('text/plain'))
54
+ ext = 'txt';
55
+ return `download_${Date.now()}.${ext}`;
56
+ }
57
+ let SavedItemsController = class SavedItemsController {
58
+ savedItemsService;
59
+ workspaceService;
60
+ configService;
61
+ constructor(savedItemsService, workspaceService, configService) {
62
+ this.savedItemsService = savedItemsService;
63
+ this.workspaceService = workspaceService;
64
+ this.configService = configService;
65
+ }
66
+ async list(tagId, workspace) {
67
+ const data = await this.savedItemsService.findAll({ tagId, workspace });
68
+ return { success: true, data };
69
+ }
70
+ async imageProxy(url, res) {
71
+ if (!url?.trim())
72
+ throw new HttpException('url is required', HttpStatus.BAD_REQUEST);
73
+ const decoded = decodeURIComponent(url.trim());
74
+ if (decoded.startsWith('file://') || decoded.startsWith('file:/')) {
75
+ try {
76
+ let pathPart;
77
+ try {
78
+ const u = new URL(decoded);
79
+ pathPart = (u.pathname || decoded).replace(/^\/([a-z]:)/i, '$1');
80
+ }
81
+ catch {
82
+ pathPart = decoded.replace(/^file:\/+/, '').replace(/^\/([a-z]:)/i, '$1');
83
+ }
84
+ const absolutePath = resolve(pathPart);
85
+ const home = homedir();
86
+ const workspaceRoot = getOpenbotWorkspaceDir();
87
+ if (!absolutePath.startsWith(resolve(home)) &&
88
+ !absolutePath.startsWith(resolve(workspaceRoot))) {
89
+ throw new HttpException('Local file path not allowed', HttpStatus.FORBIDDEN);
90
+ }
91
+ if (!existsSync(absolutePath)) {
92
+ throw new HttpException('File not found', HttpStatus.NOT_FOUND);
93
+ }
94
+ const buffer = await readFile(absolutePath);
95
+ const contentType = mimeFromPath(absolutePath);
96
+ res.setHeader('Content-Type', contentType);
97
+ res.send(buffer);
98
+ }
99
+ catch (e) {
100
+ if (e instanceof HttpException)
101
+ throw e;
102
+ throw new HttpException('Local file read failed', HttpStatus.BAD_GATEWAY);
103
+ }
104
+ return;
105
+ }
106
+ try {
107
+ const resFetch = await fetch(decoded, { redirect: 'follow' });
108
+ if (!resFetch.ok)
109
+ throw new HttpException('Upstream failed', HttpStatus.BAD_GATEWAY);
110
+ const contentType = resFetch.headers.get('content-type') || 'application/octet-stream';
111
+ res.setHeader('Content-Type', contentType);
112
+ const arr = new Uint8Array(await resFetch.arrayBuffer());
113
+ res.send(Buffer.from(arr));
114
+ }
115
+ catch (e) {
116
+ if (e instanceof HttpException)
117
+ throw e;
118
+ throw new HttpException('Proxy failed', HttpStatus.BAD_GATEWAY);
119
+ }
120
+ }
121
+ async get(id) {
122
+ const data = await this.savedItemsService.findById(id);
123
+ if (!data)
124
+ throw new HttpException('Saved item not found', HttpStatus.NOT_FOUND);
125
+ return { success: true, data };
126
+ }
127
+ async create(body) {
128
+ const data = await this.savedItemsService.create({
129
+ url: body.url,
130
+ title: body.title,
131
+ workspace: body.workspace,
132
+ tagNames: body.tagNames,
133
+ tagIds: body.tagIds,
134
+ });
135
+ return { success: true, data };
136
+ }
137
+ async delete(id) {
138
+ await this.savedItemsService.delete(id);
139
+ return { success: true };
140
+ }
141
+ /**
142
+ * Download the saved item URL to workspace .favorite or to a user-chosen directory.
143
+ * Body: { workspace?: string, targetDir?: string }.
144
+ * If targetDir (absolute path) is provided, file is written there; otherwise to workspace/.favorite.
145
+ */
146
+ async downloadToWorkspace(id, body) {
147
+ const item = await this.savedItemsService.findById(id);
148
+ if (!item)
149
+ throw new HttpException('Saved item not found', HttpStatus.NOT_FOUND);
150
+ const workspace = (body?.workspace ?? '').trim() ||
151
+ this.configService.getDefaultAgentId(await this.configService.getConfig());
152
+ let buffer;
153
+ let contentType;
154
+ try {
155
+ const res = await fetch(item.url, { redirect: 'follow' });
156
+ if (!res.ok) {
157
+ throw new HttpException(`Failed to fetch URL: ${res.status} ${res.statusText}`, HttpStatus.BAD_GATEWAY);
158
+ }
159
+ contentType = res.headers.get('content-type') || undefined;
160
+ const arr = new Uint8Array(await res.arrayBuffer());
161
+ buffer = Buffer.from(arr);
162
+ }
163
+ catch (err) {
164
+ if (err instanceof HttpException)
165
+ throw err;
166
+ const msg = err instanceof Error ? err.message : String(err);
167
+ throw new HttpException(`Download failed: ${msg}`, HttpStatus.BAD_GATEWAY);
168
+ }
169
+ const filename = safeFilenameFromUrl(item.url, contentType);
170
+ if ((body?.targetDir ?? '').trim()) {
171
+ const absolutePath = await this.workspaceService.writeFileToDir(body.targetDir.trim(), filename, buffer);
172
+ return { success: true, data: { absolutePath, targetDir: body.targetDir.trim() } };
173
+ }
174
+ const relativePath = await this.workspaceService.writeFileInFavorite(workspace, filename, buffer);
175
+ return { success: true, data: { relativePath, workspace } };
176
+ }
177
+ };
178
+ __decorate([
179
+ Get(),
180
+ __param(0, Query('tagId')),
181
+ __param(1, Query('workspace')),
182
+ __metadata("design:type", Function),
183
+ __metadata("design:paramtypes", [String, String]),
184
+ __metadata("design:returntype", Promise)
185
+ ], SavedItemsController.prototype, "list", null);
186
+ __decorate([
187
+ Get('image-proxy'),
188
+ __param(0, Query('url')),
189
+ __param(1, Res({ passthrough: false })),
190
+ __metadata("design:type", Function),
191
+ __metadata("design:paramtypes", [String, Object]),
192
+ __metadata("design:returntype", Promise)
193
+ ], SavedItemsController.prototype, "imageProxy", null);
194
+ __decorate([
195
+ Get(':id'),
196
+ __param(0, Param('id')),
197
+ __metadata("design:type", Function),
198
+ __metadata("design:paramtypes", [String]),
199
+ __metadata("design:returntype", Promise)
200
+ ], SavedItemsController.prototype, "get", null);
201
+ __decorate([
202
+ Post(),
203
+ __param(0, Body()),
204
+ __metadata("design:type", Function),
205
+ __metadata("design:paramtypes", [Object]),
206
+ __metadata("design:returntype", Promise)
207
+ ], SavedItemsController.prototype, "create", null);
208
+ __decorate([
209
+ Delete(':id'),
210
+ __param(0, Param('id')),
211
+ __metadata("design:type", Function),
212
+ __metadata("design:paramtypes", [String]),
213
+ __metadata("design:returntype", Promise)
214
+ ], SavedItemsController.prototype, "delete", null);
215
+ __decorate([
216
+ Post(':id/download-to-workspace'),
217
+ __param(0, Param('id')),
218
+ __param(1, Body()),
219
+ __metadata("design:type", Function),
220
+ __metadata("design:paramtypes", [String, Object]),
221
+ __metadata("design:returntype", Promise)
222
+ ], SavedItemsController.prototype, "downloadToWorkspace", null);
223
+ SavedItemsController = __decorate([
224
+ Controller('saved-items'),
225
+ __metadata("design:paramtypes", [SavedItemsService,
226
+ WorkspaceService,
227
+ ConfigService])
228
+ ], SavedItemsController);
229
+ export { SavedItemsController };
@@ -0,0 +1,2 @@
1
+ export declare class SavedItemsModule {
2
+ }
@@ -0,0 +1,25 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Module } from '@nestjs/common';
8
+ import { DatabaseModule } from '../database/database.module.js';
9
+ import { ConfigModule } from '../config/config.module.js';
10
+ import { WorkspaceModule } from '../workspace/workspace.module.js';
11
+ import { TagsService } from './tags.service.js';
12
+ import { TagsController } from './tags.controller.js';
13
+ import { SavedItemsService } from './saved-items.service.js';
14
+ import { SavedItemsController } from './saved-items.controller.js';
15
+ let SavedItemsModule = class SavedItemsModule {
16
+ };
17
+ SavedItemsModule = __decorate([
18
+ Module({
19
+ imports: [DatabaseModule, ConfigModule, WorkspaceModule],
20
+ controllers: [TagsController, SavedItemsController],
21
+ providers: [TagsService, SavedItemsService],
22
+ exports: [TagsService, SavedItemsService],
23
+ })
24
+ ], SavedItemsModule);
25
+ export { SavedItemsModule };
@@ -0,0 +1,31 @@
1
+ import { DatabaseService } from '../database/database.service.js';
2
+ import { TagsService } from './tags.service.js';
3
+ export interface SavedItem {
4
+ id: string;
5
+ url: string;
6
+ title: string | null;
7
+ workspace: string;
8
+ createdAt: number;
9
+ tagIds: string[];
10
+ tagNames?: string[];
11
+ }
12
+ export declare class SavedItemsService {
13
+ private readonly db;
14
+ private readonly tagsService;
15
+ constructor(db: DatabaseService, tagsService: TagsService);
16
+ private getTagIdsForItem;
17
+ private rowToSavedItem;
18
+ create(dto: {
19
+ url: string;
20
+ title?: string;
21
+ workspace?: string;
22
+ tagNames?: string[];
23
+ tagIds?: string[];
24
+ }): Promise<SavedItem>;
25
+ findAll(options?: {
26
+ tagId?: string;
27
+ workspace?: string;
28
+ }): Promise<SavedItem[]>;
29
+ findById(id: string): Promise<SavedItem | null>;
30
+ delete(id: string): Promise<void>;
31
+ }
@@ -0,0 +1,105 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Injectable, NotFoundException } from '@nestjs/common';
11
+ import { DatabaseService } from '../database/database.service.js';
12
+ import { TagsService } from './tags.service.js';
13
+ import { randomUUID } from 'crypto';
14
+ let SavedItemsService = class SavedItemsService {
15
+ db;
16
+ tagsService;
17
+ constructor(db, tagsService) {
18
+ this.db = db;
19
+ this.tagsService = tagsService;
20
+ }
21
+ async getTagIdsForItem(savedItemId) {
22
+ const rows = this.db.all('SELECT tag_id FROM saved_item_tags WHERE saved_item_id = ?', [savedItemId]);
23
+ return rows.map((r) => r.tag_id);
24
+ }
25
+ async rowToSavedItem(row) {
26
+ const tagIds = await this.getTagIdsForItem(row.id);
27
+ const tagNames = [];
28
+ for (const tagId of tagIds) {
29
+ const tag = await this.tagsService.findById(tagId);
30
+ if (tag)
31
+ tagNames.push(tag.name);
32
+ }
33
+ return {
34
+ id: row.id,
35
+ url: row.url,
36
+ title: row.title,
37
+ workspace: row.workspace,
38
+ createdAt: row.created_at,
39
+ tagIds,
40
+ tagNames,
41
+ };
42
+ }
43
+ async create(dto) {
44
+ const url = (dto.url ?? '').trim();
45
+ if (!url)
46
+ throw new Error('url is required');
47
+ const workspace = (dto.workspace ?? 'default').trim() || 'default';
48
+ const title = dto.title?.trim() ?? null;
49
+ const id = randomUUID();
50
+ const createdAt = Date.now();
51
+ this.db.run('INSERT INTO saved_items (id, url, title, workspace, created_at) VALUES (?, ?, ?, ?, ?)', [id, url, title, workspace, createdAt]);
52
+ let tagIds = [];
53
+ if (dto.tagIds?.length) {
54
+ tagIds = dto.tagIds;
55
+ }
56
+ else if (dto.tagNames?.length) {
57
+ for (const name of dto.tagNames) {
58
+ const tag = await this.tagsService.findByName(name.trim());
59
+ if (tag)
60
+ tagIds.push(tag.id);
61
+ }
62
+ }
63
+ for (const tagId of tagIds) {
64
+ this.db.run('INSERT INTO saved_item_tags (saved_item_id, tag_id) VALUES (?, ?)', [id, tagId]);
65
+ }
66
+ const row = { id, url, title, workspace, created_at: createdAt };
67
+ return this.rowToSavedItem(row);
68
+ }
69
+ async findAll(options) {
70
+ let sql = 'SELECT id, url, title, workspace, created_at FROM saved_items WHERE 1=1';
71
+ const params = [];
72
+ if (options?.workspace) {
73
+ sql += ' AND workspace = ?';
74
+ params.push(options.workspace);
75
+ }
76
+ if (options?.tagId) {
77
+ sql += ' AND id IN (SELECT saved_item_id FROM saved_item_tags WHERE tag_id = ?)';
78
+ params.push(options.tagId);
79
+ }
80
+ sql += ' ORDER BY created_at DESC';
81
+ const rows = this.db.all(sql, params);
82
+ const result = [];
83
+ for (const row of rows) {
84
+ result.push(await this.rowToSavedItem(row));
85
+ }
86
+ return result;
87
+ }
88
+ async findById(id) {
89
+ const row = this.db.get('SELECT id, url, title, workspace, created_at FROM saved_items WHERE id = ?', [id]);
90
+ return row ? this.rowToSavedItem(row) : null;
91
+ }
92
+ async delete(id) {
93
+ const item = await this.findById(id);
94
+ if (!item)
95
+ throw new NotFoundException('收藏不存在');
96
+ this.db.run('DELETE FROM saved_item_tags WHERE saved_item_id = ?', [id]);
97
+ this.db.run('DELETE FROM saved_items WHERE id = ?', [id]);
98
+ }
99
+ };
100
+ SavedItemsService = __decorate([
101
+ Injectable(),
102
+ __metadata("design:paramtypes", [DatabaseService,
103
+ TagsService])
104
+ ], SavedItemsService);
105
+ export { SavedItemsService };
@@ -0,0 +1,30 @@
1
+ import { TagsService } from './tags.service.js';
2
+ export declare class TagsController {
3
+ private readonly tagsService;
4
+ constructor(tagsService: TagsService);
5
+ list(): Promise<{
6
+ success: boolean;
7
+ data: import("./tags.service.js").Tag[];
8
+ }>;
9
+ get(id: string): Promise<{
10
+ success: boolean;
11
+ data: import("./tags.service.js").Tag;
12
+ }>;
13
+ create(body: {
14
+ name: string;
15
+ sortOrder?: number;
16
+ }): Promise<{
17
+ success: boolean;
18
+ data: import("./tags.service.js").Tag;
19
+ }>;
20
+ update(id: string, body: {
21
+ name?: string;
22
+ sortOrder?: number;
23
+ }): Promise<{
24
+ success: boolean;
25
+ data: import("./tags.service.js").Tag;
26
+ }>;
27
+ delete(id: string): Promise<{
28
+ success: boolean;
29
+ }>;
30
+ }
@@ -0,0 +1,85 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Controller, Get, Post, Put, Delete, Body, Param, HttpException, HttpStatus } from '@nestjs/common';
14
+ import { TagsService } from './tags.service.js';
15
+ let TagsController = class TagsController {
16
+ tagsService;
17
+ constructor(tagsService) {
18
+ this.tagsService = tagsService;
19
+ }
20
+ async list() {
21
+ const data = await this.tagsService.findAll();
22
+ return { success: true, data };
23
+ }
24
+ async get(id) {
25
+ const data = await this.tagsService.findById(id);
26
+ if (!data)
27
+ throw new HttpException('Tag not found', HttpStatus.NOT_FOUND);
28
+ return { success: true, data };
29
+ }
30
+ async create(body) {
31
+ const data = await this.tagsService.create({
32
+ name: body.name,
33
+ sortOrder: body.sortOrder,
34
+ });
35
+ return { success: true, data };
36
+ }
37
+ async update(id, body) {
38
+ const data = await this.tagsService.update(id, body);
39
+ return { success: true, data };
40
+ }
41
+ async delete(id) {
42
+ await this.tagsService.delete(id);
43
+ return { success: true };
44
+ }
45
+ };
46
+ __decorate([
47
+ Get(),
48
+ __metadata("design:type", Function),
49
+ __metadata("design:paramtypes", []),
50
+ __metadata("design:returntype", Promise)
51
+ ], TagsController.prototype, "list", null);
52
+ __decorate([
53
+ Get(':id'),
54
+ __param(0, Param('id')),
55
+ __metadata("design:type", Function),
56
+ __metadata("design:paramtypes", [String]),
57
+ __metadata("design:returntype", Promise)
58
+ ], TagsController.prototype, "get", null);
59
+ __decorate([
60
+ Post(),
61
+ __param(0, Body()),
62
+ __metadata("design:type", Function),
63
+ __metadata("design:paramtypes", [Object]),
64
+ __metadata("design:returntype", Promise)
65
+ ], TagsController.prototype, "create", null);
66
+ __decorate([
67
+ Put(':id'),
68
+ __param(0, Param('id')),
69
+ __param(1, Body()),
70
+ __metadata("design:type", Function),
71
+ __metadata("design:paramtypes", [String, Object]),
72
+ __metadata("design:returntype", Promise)
73
+ ], TagsController.prototype, "update", null);
74
+ __decorate([
75
+ Delete(':id'),
76
+ __param(0, Param('id')),
77
+ __metadata("design:type", Function),
78
+ __metadata("design:paramtypes", [String]),
79
+ __metadata("design:returntype", Promise)
80
+ ], TagsController.prototype, "delete", null);
81
+ TagsController = __decorate([
82
+ Controller('tags'),
83
+ __metadata("design:paramtypes", [TagsService])
84
+ ], TagsController);
85
+ export { TagsController };
@@ -0,0 +1,24 @@
1
+ import { DatabaseService } from '../database/database.service.js';
2
+ export interface Tag {
3
+ id: string;
4
+ name: string;
5
+ sortOrder: number;
6
+ createdAt: number;
7
+ }
8
+ export declare class TagsService {
9
+ private readonly db;
10
+ constructor(db: DatabaseService);
11
+ private rowToTag;
12
+ findAll(): Promise<Tag[]>;
13
+ findById(id: string): Promise<Tag | null>;
14
+ findByName(name: string): Promise<Tag | null>;
15
+ create(dto: {
16
+ name: string;
17
+ sortOrder?: number;
18
+ }): Promise<Tag>;
19
+ update(id: string, dto: {
20
+ name?: string;
21
+ sortOrder?: number;
22
+ }): Promise<Tag>;
23
+ delete(id: string): Promise<void>;
24
+ }
@@ -0,0 +1,84 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Injectable, ConflictException, NotFoundException } from '@nestjs/common';
11
+ import { DatabaseService } from '../database/database.service.js';
12
+ import { randomUUID } from 'crypto';
13
+ let TagsService = class TagsService {
14
+ db;
15
+ constructor(db) {
16
+ this.db = db;
17
+ }
18
+ rowToTag(row) {
19
+ return {
20
+ id: row.id,
21
+ name: row.name,
22
+ sortOrder: row.sort_order,
23
+ createdAt: row.created_at,
24
+ };
25
+ }
26
+ async findAll() {
27
+ const rows = this.db.all('SELECT id, name, sort_order, created_at FROM tags ORDER BY sort_order ASC, name ASC', []);
28
+ return rows.map((r) => this.rowToTag(r));
29
+ }
30
+ async findById(id) {
31
+ const row = this.db.get('SELECT id, name, sort_order, created_at FROM tags WHERE id = ?', [id]);
32
+ return row ? this.rowToTag(row) : null;
33
+ }
34
+ async findByName(name) {
35
+ const row = this.db.get('SELECT id, name, sort_order, created_at FROM tags WHERE name = ?', [
36
+ name.trim(),
37
+ ]);
38
+ return row ? this.rowToTag(row) : null;
39
+ }
40
+ async create(dto) {
41
+ const name = (dto.name ?? '').trim();
42
+ if (!name)
43
+ throw new ConflictException('标签名不能为空');
44
+ const existing = await this.findByName(name);
45
+ if (existing)
46
+ throw new ConflictException('该标签名已存在');
47
+ const id = randomUUID();
48
+ const sortOrder = dto.sortOrder ?? 0;
49
+ const createdAt = Date.now();
50
+ this.db.run('INSERT INTO tags (id, name, sort_order, created_at) VALUES (?, ?, ?, ?)', [id, name, sortOrder, createdAt]);
51
+ return { id, name, sortOrder, createdAt };
52
+ }
53
+ async update(id, dto) {
54
+ const tag = await this.findById(id);
55
+ if (!tag)
56
+ throw new NotFoundException('标签不存在');
57
+ if (dto.name !== undefined) {
58
+ const name = dto.name.trim();
59
+ if (!name)
60
+ throw new ConflictException('标签名不能为空');
61
+ const existing = await this.findByName(name);
62
+ if (existing && existing.id !== id)
63
+ throw new ConflictException('该标签名已存在');
64
+ this.db.run('UPDATE tags SET name = ? WHERE id = ?', [name, id]);
65
+ tag.name = name;
66
+ }
67
+ if (dto.sortOrder !== undefined) {
68
+ this.db.run('UPDATE tags SET sort_order = ? WHERE id = ?', [dto.sortOrder, id]);
69
+ tag.sortOrder = dto.sortOrder;
70
+ }
71
+ return tag;
72
+ }
73
+ async delete(id) {
74
+ const tag = await this.findById(id);
75
+ if (!tag)
76
+ throw new NotFoundException('标签不存在');
77
+ this.db.run('DELETE FROM tags WHERE id = ?', [id]);
78
+ }
79
+ };
80
+ TagsService = __decorate([
81
+ Injectable(),
82
+ __metadata("design:paramtypes", [DatabaseService])
83
+ ], TagsService);
84
+ export { TagsService };