@hed-hog/core 0.0.222 → 0.0.232

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 (61) hide show
  1. package/dist/ai/ai.controller.d.ts +90 -0
  2. package/dist/ai/ai.controller.d.ts.map +1 -0
  3. package/dist/ai/ai.controller.js +119 -0
  4. package/dist/ai/ai.controller.js.map +1 -0
  5. package/dist/ai/ai.module.d.ts +3 -0
  6. package/dist/ai/ai.module.d.ts.map +1 -0
  7. package/dist/ai/ai.module.js +26 -0
  8. package/dist/ai/ai.module.js.map +1 -0
  9. package/dist/ai/ai.service.d.ts +69 -0
  10. package/dist/ai/ai.service.d.ts.map +1 -0
  11. package/dist/ai/ai.service.js +394 -0
  12. package/dist/ai/ai.service.js.map +1 -0
  13. package/dist/ai/dto/chat-agent.dto.d.ts +4 -0
  14. package/dist/ai/dto/chat-agent.dto.d.ts.map +1 -0
  15. package/dist/ai/dto/chat-agent.dto.js +22 -0
  16. package/dist/ai/dto/chat-agent.dto.js.map +1 -0
  17. package/dist/ai/dto/chat.dto.d.ts +7 -0
  18. package/dist/ai/dto/chat.dto.d.ts.map +1 -0
  19. package/dist/ai/dto/chat.dto.js +38 -0
  20. package/dist/ai/dto/chat.dto.js.map +1 -0
  21. package/dist/ai/dto/create-agent.dto.d.ts +7 -0
  22. package/dist/ai/dto/create-agent.dto.d.ts.map +1 -0
  23. package/dist/ai/dto/create-agent.dto.js +38 -0
  24. package/dist/ai/dto/create-agent.dto.js.map +1 -0
  25. package/dist/ai/dto/update-agent.dto.d.ts +7 -0
  26. package/dist/ai/dto/update-agent.dto.d.ts.map +1 -0
  27. package/dist/ai/dto/update-agent.dto.js +38 -0
  28. package/dist/ai/dto/update-agent.dto.js.map +1 -0
  29. package/dist/auth/auth.controller.d.ts +2 -2
  30. package/dist/auth/auth.service.d.ts +7 -7
  31. package/dist/core.module.d.ts.map +1 -1
  32. package/dist/core.module.js +3 -0
  33. package/dist/core.module.js.map +1 -1
  34. package/dist/file/file.service.d.ts +2 -0
  35. package/dist/file/file.service.d.ts.map +1 -1
  36. package/dist/file/file.service.js +17 -2
  37. package/dist/file/file.service.js.map +1 -1
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/session/session.controller.d.ts +1 -1
  43. package/dist/session/session.service.d.ts +3 -3
  44. package/dist/user/user.controller.d.ts +1 -1
  45. package/dist/user/user.service.d.ts +3 -3
  46. package/hedhog/data/menu.yaml +17 -3
  47. package/hedhog/data/route.yaml +64 -0
  48. package/hedhog/data/setting_group.yaml +33 -1
  49. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +468 -0
  50. package/hedhog/table/ai_agent.yaml +24 -0
  51. package/package.json +5 -5
  52. package/src/ai/ai.controller.ts +61 -0
  53. package/src/ai/ai.module.ts +13 -0
  54. package/src/ai/ai.service.ts +546 -0
  55. package/src/ai/dto/chat-agent.dto.ts +7 -0
  56. package/src/ai/dto/chat.dto.ts +20 -0
  57. package/src/ai/dto/create-agent.dto.ts +20 -0
  58. package/src/ai/dto/update-agent.dto.ts +20 -0
  59. package/src/core.module.ts +3 -0
  60. package/src/file/file.service.ts +34 -8
  61. package/src/index.ts +1 -0
@@ -0,0 +1,468 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Page,
5
+ PageHeader,
6
+ PaginationFooter,
7
+ SearchBar,
8
+ } from '@/components/entity-list';
9
+ import {
10
+ AlertDialog,
11
+ AlertDialogAction,
12
+ AlertDialogCancel,
13
+ AlertDialogContent,
14
+ AlertDialogDescription,
15
+ AlertDialogFooter,
16
+ AlertDialogHeader,
17
+ AlertDialogTitle,
18
+ } from '@/components/ui/alert-dialog';
19
+ import { Button } from '@/components/ui/button';
20
+ import { Card, CardContent } from '@/components/ui/card';
21
+ import {
22
+ Dialog,
23
+ DialogContent,
24
+ DialogDescription,
25
+ DialogFooter,
26
+ DialogHeader,
27
+ DialogTitle,
28
+ } from '@/components/ui/dialog';
29
+ import {
30
+ Form,
31
+ FormControl,
32
+ FormField,
33
+ FormItem,
34
+ FormLabel,
35
+ FormMessage,
36
+ } from '@/components/ui/form';
37
+ import { Input } from '@/components/ui/input';
38
+ import {
39
+ Select,
40
+ SelectContent,
41
+ SelectItem,
42
+ SelectTrigger,
43
+ SelectValue,
44
+ } from '@/components/ui/select';
45
+ import {
46
+ Table,
47
+ TableBody,
48
+ TableCell,
49
+ TableHead,
50
+ TableHeader,
51
+ TableRow,
52
+ } from '@/components/ui/table';
53
+ import { Textarea } from '@/components/ui/textarea';
54
+ import { useDebounce } from '@/hooks/use-debounce';
55
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
56
+ import { zodResolver } from '@hookform/resolvers/zod';
57
+ import { Bot, Pencil, Plus, Trash2 } from 'lucide-react';
58
+ import { useTranslations } from 'next-intl';
59
+ import { useEffect, useState } from 'react';
60
+ import { useForm } from 'react-hook-form';
61
+ import { toast } from 'sonner';
62
+ import { z } from 'zod';
63
+
64
+ type Agent = {
65
+ id: number;
66
+ slug: string;
67
+ provider: 'openai' | 'gemini';
68
+ model: string | null;
69
+ instructions: string | null;
70
+ external_agent_id: string | null;
71
+ created_at: string;
72
+ updated_at: string;
73
+ };
74
+
75
+ type PaginatedResponse<T> = {
76
+ data: T[];
77
+ total: number;
78
+ page: number;
79
+ pageSize: number;
80
+ totalPages: number;
81
+ };
82
+
83
+ type FormValues = {
84
+ slug: string;
85
+ provider: 'openai' | 'gemini';
86
+ model?: string;
87
+ instructions?: string;
88
+ };
89
+
90
+ export default function AiAgentPage() {
91
+ const { request } = useApp();
92
+ const t = useTranslations('core.AiAgentPage');
93
+ const formSchema = z.object({
94
+ slug: z.string().min(2, t('validationSlugMinLength')),
95
+ provider: z.enum(['openai', 'gemini']),
96
+ model: z.string().optional(),
97
+ instructions: z.string().optional(),
98
+ });
99
+ const [page, setPage] = useState(1);
100
+ const [pageSize, setPageSize] = useState(10);
101
+ const [searchQuery, setSearchQuery] = useState('');
102
+ const debouncedSearch = useDebounce(searchQuery, 400);
103
+
104
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
105
+ const [editingAgent, setEditingAgent] = useState<Agent | null>(null);
106
+ const [agentToDelete, setAgentToDelete] = useState<Agent | null>(null);
107
+
108
+ const form = useForm<FormValues>({
109
+ resolver: zodResolver(formSchema),
110
+ defaultValues: {
111
+ slug: '',
112
+ provider: 'openai',
113
+ model: '',
114
+ instructions: '',
115
+ },
116
+ });
117
+
118
+ const { data, isLoading, refetch } = useQuery<PaginatedResponse<Agent>>({
119
+ queryKey: ['ai-agents', page, pageSize, debouncedSearch],
120
+ queryFn: async () => {
121
+ const params = new URLSearchParams();
122
+ params.set('page', String(page));
123
+ params.set('pageSize', String(pageSize));
124
+ if (debouncedSearch) params.set('search', debouncedSearch);
125
+
126
+ const response = await request<PaginatedResponse<Agent>>({
127
+ url: `/ai/agent?${params.toString()}`,
128
+ method: 'GET',
129
+ });
130
+
131
+ return response.data;
132
+ },
133
+ initialData: {
134
+ data: [],
135
+ total: 0,
136
+ page: 1,
137
+ pageSize: 10,
138
+ totalPages: 1,
139
+ },
140
+ });
141
+
142
+ useEffect(() => {
143
+ if (editingAgent) {
144
+ form.reset({
145
+ slug: editingAgent.slug,
146
+ provider: editingAgent.provider,
147
+ model: editingAgent.model || '',
148
+ instructions: editingAgent.instructions || '',
149
+ });
150
+ return;
151
+ }
152
+
153
+ form.reset({
154
+ slug: '',
155
+ provider: 'openai',
156
+ model: '',
157
+ instructions: '',
158
+ });
159
+ }, [editingAgent, form]);
160
+
161
+ const handleOpenCreate = () => {
162
+ setEditingAgent(null);
163
+ setIsDialogOpen(true);
164
+ };
165
+
166
+ const handleOpenEdit = (agent: Agent) => {
167
+ setEditingAgent(agent);
168
+ setIsDialogOpen(true);
169
+ };
170
+
171
+ const onSubmit = async (values: FormValues) => {
172
+ try {
173
+ if (editingAgent) {
174
+ await request({
175
+ url: `/ai/agent/${editingAgent.id}`,
176
+ method: 'PATCH',
177
+ data: {
178
+ slug: values.slug,
179
+ provider: values.provider,
180
+ model: values.model || undefined,
181
+ instructions: values.instructions || undefined,
182
+ },
183
+ });
184
+ toast.success(t('toastUpdatedSuccess'));
185
+ } else {
186
+ await request({
187
+ url: '/ai/agent',
188
+ method: 'POST',
189
+ data: {
190
+ slug: values.slug,
191
+ provider: values.provider,
192
+ model: values.model || undefined,
193
+ instructions: values.instructions || undefined,
194
+ },
195
+ });
196
+ toast.success(t('toastCreatedSuccess'));
197
+ }
198
+
199
+ setIsDialogOpen(false);
200
+ setEditingAgent(null);
201
+ await refetch();
202
+ } catch (error: any) {
203
+ const message =
204
+ error?.response?.data?.message ||
205
+ error?.response?.data?.error ||
206
+ error?.message ||
207
+ t('toastSaveError');
208
+ toast.error(String(message));
209
+ }
210
+ };
211
+
212
+ const handleDelete = async () => {
213
+ if (!agentToDelete) return;
214
+
215
+ try {
216
+ await request({
217
+ url: '/ai/agent',
218
+ method: 'DELETE',
219
+ data: { ids: [agentToDelete.id] },
220
+ });
221
+
222
+ toast.success(t('toastDeletedSuccess'));
223
+ setAgentToDelete(null);
224
+ await refetch();
225
+ } catch (error: any) {
226
+ const message =
227
+ error?.response?.data?.message ||
228
+ error?.response?.data?.error ||
229
+ error?.message ||
230
+ t('toastDeleteError');
231
+ toast.error(String(message));
232
+ }
233
+ };
234
+
235
+ return (
236
+ <Page>
237
+ <PageHeader
238
+ breadcrumbs={[
239
+ { label: t('breadcrumbDashboard'), href: '/' },
240
+ { label: t('breadcrumbManagement'), href: '/core/management' },
241
+ { label: t('breadcrumbCurrent') },
242
+ ]}
243
+ title={t('title')}
244
+ description={t('description')}
245
+ actions={[
246
+ {
247
+ label: t('newAgent'),
248
+ onClick: handleOpenCreate,
249
+ icon: <Plus className="h-4 w-4" />,
250
+ },
251
+ ]}
252
+ />
253
+
254
+ <SearchBar
255
+ searchQuery={searchQuery}
256
+ onSearchChange={(value) => {
257
+ setSearchQuery(value);
258
+ setPage(1);
259
+ }}
260
+ onSearch={() => setPage(1)}
261
+ placeholder={t('searchPlaceholder')}
262
+ />
263
+
264
+ <Card>
265
+ <CardContent className="p-0">
266
+ <Table>
267
+ <TableHeader>
268
+ <TableRow>
269
+ <TableHead>Slug</TableHead>
270
+ <TableHead>{t('columnProvider')}</TableHead>
271
+ <TableHead>{t('columnModel')}</TableHead>
272
+ <TableHead>{t('columnExternalId')}</TableHead>
273
+ <TableHead className="text-right">
274
+ {t('columnActions')}
275
+ </TableHead>
276
+ </TableRow>
277
+ </TableHeader>
278
+ <TableBody>
279
+ {isLoading ? (
280
+ <TableRow>
281
+ <TableCell colSpan={5}>{t('loading')}</TableCell>
282
+ </TableRow>
283
+ ) : data.data.length === 0 ? (
284
+ <TableRow>
285
+ <TableCell colSpan={5}>{t('empty')}</TableCell>
286
+ </TableRow>
287
+ ) : (
288
+ data.data.map((agent) => (
289
+ <TableRow key={agent.id}>
290
+ <TableCell>{agent.slug}</TableCell>
291
+ <TableCell className="uppercase">
292
+ {agent.provider}
293
+ </TableCell>
294
+ <TableCell>{agent.model || '-'}</TableCell>
295
+ <TableCell>{agent.external_agent_id || '-'}</TableCell>
296
+ <TableCell className="text-right">
297
+ <div className="flex items-center justify-end gap-2">
298
+ <Button
299
+ variant="outline"
300
+ size="icon"
301
+ onClick={() => handleOpenEdit(agent)}
302
+ >
303
+ <Pencil className="h-4 w-4" />
304
+ </Button>
305
+ <Button
306
+ variant="destructive"
307
+ size="icon"
308
+ onClick={() => setAgentToDelete(agent)}
309
+ >
310
+ <Trash2 className="h-4 w-4" />
311
+ </Button>
312
+ </div>
313
+ </TableCell>
314
+ </TableRow>
315
+ ))
316
+ )}
317
+ </TableBody>
318
+ </Table>
319
+ </CardContent>
320
+ </Card>
321
+
322
+ <PaginationFooter
323
+ currentPage={page}
324
+ pageSize={pageSize}
325
+ totalItems={data.total}
326
+ onPageChange={setPage}
327
+ onPageSizeChange={(value) => {
328
+ setPageSize(value);
329
+ setPage(1);
330
+ }}
331
+ />
332
+
333
+ <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
334
+ <DialogContent>
335
+ <DialogHeader>
336
+ <DialogTitle>
337
+ {editingAgent ? t('editTitle') : t('createTitle')}
338
+ </DialogTitle>
339
+ <DialogDescription>{t('dialogDescription')}</DialogDescription>
340
+ </DialogHeader>
341
+
342
+ <Form {...form}>
343
+ <form className="space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
344
+ <FormField
345
+ control={form.control}
346
+ name="slug"
347
+ render={({ field }) => (
348
+ <FormItem>
349
+ <FormLabel>{t('fieldSlug')}</FormLabel>
350
+ <FormControl>
351
+ <Input
352
+ placeholder={t('fieldSlugPlaceholder')}
353
+ {...field}
354
+ />
355
+ </FormControl>
356
+ <FormMessage />
357
+ </FormItem>
358
+ )}
359
+ />
360
+
361
+ <FormField
362
+ control={form.control}
363
+ name="provider"
364
+ render={({ field }) => (
365
+ <FormItem>
366
+ <FormLabel>{t('fieldProvider')}</FormLabel>
367
+ <FormControl>
368
+ <Select
369
+ value={field.value}
370
+ onValueChange={field.onChange}
371
+ >
372
+ <SelectTrigger>
373
+ <SelectValue
374
+ placeholder={t('fieldProviderPlaceholder')}
375
+ />
376
+ </SelectTrigger>
377
+ <SelectContent>
378
+ <SelectItem value="openai">
379
+ {t('providerOpenai')}
380
+ </SelectItem>
381
+ <SelectItem value="gemini">
382
+ {t('providerGemini')}
383
+ </SelectItem>
384
+ </SelectContent>
385
+ </Select>
386
+ </FormControl>
387
+ <FormMessage />
388
+ </FormItem>
389
+ )}
390
+ />
391
+
392
+ <FormField
393
+ control={form.control}
394
+ name="model"
395
+ render={({ field }) => (
396
+ <FormItem>
397
+ <FormLabel>{t('fieldModel')}</FormLabel>
398
+ <FormControl>
399
+ <Input
400
+ placeholder={t('fieldModelPlaceholder')}
401
+ {...field}
402
+ />
403
+ </FormControl>
404
+ <FormMessage />
405
+ </FormItem>
406
+ )}
407
+ />
408
+
409
+ <FormField
410
+ control={form.control}
411
+ name="instructions"
412
+ render={({ field }) => (
413
+ <FormItem>
414
+ <FormLabel>{t('fieldInstructions')}</FormLabel>
415
+ <FormControl>
416
+ <Textarea
417
+ placeholder={t('fieldInstructionsPlaceholder')}
418
+ rows={4}
419
+ {...field}
420
+ />
421
+ </FormControl>
422
+ <FormMessage />
423
+ </FormItem>
424
+ )}
425
+ />
426
+
427
+ <DialogFooter>
428
+ <Button
429
+ type="button"
430
+ variant="outline"
431
+ onClick={() => setIsDialogOpen(false)}
432
+ >
433
+ {t('cancel')}
434
+ </Button>
435
+ <Button type="submit">
436
+ <Bot className="h-4 w-4 mr-2" />
437
+ {editingAgent ? t('saveChanges') : t('createAgent')}
438
+ </Button>
439
+ </DialogFooter>
440
+ </form>
441
+ </Form>
442
+ </DialogContent>
443
+ </Dialog>
444
+
445
+ <AlertDialog
446
+ open={Boolean(agentToDelete)}
447
+ onOpenChange={(open) => {
448
+ if (!open) setAgentToDelete(null);
449
+ }}
450
+ >
451
+ <AlertDialogContent>
452
+ <AlertDialogHeader>
453
+ <AlertDialogTitle>{t('deleteTitle')}</AlertDialogTitle>
454
+ <AlertDialogDescription>
455
+ {t('deleteDescription')}
456
+ </AlertDialogDescription>
457
+ </AlertDialogHeader>
458
+ <AlertDialogFooter>
459
+ <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
460
+ <AlertDialogAction onClick={handleDelete}>
461
+ {t('delete')}
462
+ </AlertDialogAction>
463
+ </AlertDialogFooter>
464
+ </AlertDialogContent>
465
+ </AlertDialog>
466
+ </Page>
467
+ );
468
+ }
@@ -0,0 +1,24 @@
1
+ columns:
2
+ - type: pk
3
+ - type: slug
4
+ - name: provider
5
+ type: enum
6
+ enum:
7
+ - openai
8
+ - gemini
9
+ default: openai
10
+ - name: model
11
+ isNullable: true
12
+ length: 127
13
+ - name: instructions
14
+ type: text
15
+ isNullable: true
16
+ - name: external_agent_id
17
+ isNullable: true
18
+ length: 127
19
+ - type: created_at
20
+ - type: updated_at
21
+ indices:
22
+ - columns:
23
+ - slug
24
+ isUnique: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/core",
3
- "version": "0.0.222",
3
+ "version": "0.0.232",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -29,12 +29,12 @@
29
29
  "sharp": "^0.34.2",
30
30
  "speakeasy": "^2.0.0",
31
31
  "uuid": "^11.1.0",
32
- "@hed-hog/api-mail": "0.0.7",
33
- "@hed-hog/api-types": "0.0.1",
34
32
  "@hed-hog/api": "0.0.3",
35
- "@hed-hog/api-locale": "0.0.11",
36
33
  "@hed-hog/api-prisma": "0.0.4",
37
- "@hed-hog/api-pagination": "0.0.5"
34
+ "@hed-hog/api-types": "0.0.1",
35
+ "@hed-hog/api-locale": "0.0.11",
36
+ "@hed-hog/api-pagination": "0.0.5",
37
+ "@hed-hog/api-mail": "0.0.7"
38
38
  },
39
39
  "exports": {
40
40
  ".": {
@@ -0,0 +1,61 @@
1
+ import { Role } from '@hed-hog/api';
2
+ import { Pagination } from '@hed-hog/api-pagination';
3
+ import { Body, Controller, Delete, Get, Inject, Param, ParseIntPipe, Patch, Post, forwardRef } from '@nestjs/common';
4
+ import { DeleteDTO } from '../dto/delete.dto';
5
+ import { AiService } from './ai.service';
6
+ import { ChatAgentDTO } from './dto/chat-agent.dto';
7
+ import { ChatDTO } from './dto/chat.dto';
8
+ import { CreateAgentDTO } from './dto/create-agent.dto';
9
+ import { UpdateAgentDTO } from './dto/update-agent.dto';
10
+
11
+ @Role()
12
+ @Controller('ai')
13
+ export class AiController {
14
+ constructor(
15
+ @Inject(forwardRef(() => AiService))
16
+ private readonly aiService: AiService,
17
+ ) {}
18
+
19
+ @Post('chat')
20
+ async chat(@Body() data: ChatDTO) {
21
+ return this.aiService.chat(data);
22
+ }
23
+
24
+ @Post('agent')
25
+ async createAgent(@Body() data: CreateAgentDTO) {
26
+ return this.aiService.createAgent(data);
27
+ }
28
+
29
+ @Get('agent')
30
+ async listAgents(@Pagination() paginationParams) {
31
+ return this.aiService.listAgents(paginationParams);
32
+ }
33
+
34
+ @Get('agent/id/:agentId')
35
+ async getAgentById(@Param('agentId', ParseIntPipe) agentId: number) {
36
+ return this.aiService.getAgentById(agentId);
37
+ }
38
+
39
+ @Get('agent/:slug')
40
+ async getAgent(@Param('slug') slug: string) {
41
+ return this.aiService.getAgentBySlug(slug);
42
+ }
43
+
44
+ @Patch('agent/:agentId')
45
+ async updateAgent(
46
+ @Param('agentId', ParseIntPipe) agentId: number,
47
+ @Body() data: UpdateAgentDTO,
48
+ ) {
49
+ return this.aiService.updateAgent(agentId, data);
50
+ }
51
+
52
+ @Delete('agent')
53
+ async deleteAgents(@Body() data: DeleteDTO) {
54
+ return this.aiService.deleteAgents(data);
55
+ }
56
+
57
+ @Post('agent/:slug/chat')
58
+ async chatWithAgent(@Param('slug') slug: string, @Body() data: ChatAgentDTO) {
59
+ return this.aiService.chatWithAgent(slug, data);
60
+ }
61
+ }
@@ -0,0 +1,13 @@
1
+ import { PrismaModule } from '@hed-hog/api-prisma';
2
+ import { Module, forwardRef } from '@nestjs/common';
3
+ import { SettingModule } from '../setting/setting.module';
4
+ import { AiController } from './ai.controller';
5
+ import { AiService } from './ai.service';
6
+
7
+ @Module({
8
+ imports: [forwardRef(() => PrismaModule), forwardRef(() => SettingModule)],
9
+ controllers: [AiController],
10
+ providers: [AiService],
11
+ exports: [AiService],
12
+ })
13
+ export class AiModule {}