1
+ 'use client'
2
+ import { UseChatHelpers } from 'ai/react'
3
+ import {
4
+ Card ,
5
+ CardContent ,
6
+ CardDescription ,
7
+ CardFooter ,
8
+ CardHeader ,
9
+ CardTitle ,
10
+ } from "@/components/ui/card"
11
+ import { Button } from "@/components/ui/button"
12
+ import {
13
+ Dialog ,
14
+ DialogContent ,
15
+ DialogDescription ,
16
+ DialogFooter ,
17
+ DialogHeader ,
18
+ DialogTitle
19
+ } from '@/components/ui/dialog'
20
+ import { Input } from './ui/input'
21
+ import { Label } from "@/components/ui/label"
22
+ import { useState , useTransition } from 'react'
23
+ import {
24
+ AlertDialog ,
25
+ AlertDialogAction ,
26
+ AlertDialogCancel ,
27
+ AlertDialogContent ,
28
+ AlertDialogDescription ,
29
+ AlertDialogFooter ,
30
+ AlertDialogHeader ,
31
+ AlertDialogTitle
32
+ } from '@/components/ui/alert-dialog'
33
+ import { Checkbox } from "@/components/ui/checkbox"
34
+ import { IconSpinner , IconTrash } from '@/components/ui/icons'
35
+ import { Textarea } from "@/components/ui/textarea"
36
+ import { IconEdit , IconPlus } from '@/components/ui/icons'
37
+ import { useRouter } from 'next/navigation'
38
+ import { ExternalLink } from '@/components/external-link'
39
+ function getRandomColor ( ) : string {
40
+ const letters = '0123456789ABCDEF' ;
41
+ let color = '#' ;
42
+ for ( let i = 0 ; i < 6 ; i ++ ) {
43
+ color += letters [ Math . floor ( Math . random ( ) * 16 ) ] ;
44
+ }
45
+ return color ;
46
+ }
47
+
48
+ interface Agent {
49
+ id : string ;
50
+ name : string ;
51
+ prompt : string ;
52
+ }
53
+
54
+ export default function Agents ( { setInput } : Pick < UseChatHelpers , 'setInput' > ) {
55
+ const router = useRouter ( )
56
+ const [ editorOpen , setEditorOpen ] = useState ( false )
57
+ const [ deleteDialogOpen , setDeleteDialogOpen ] = useState ( false )
58
+ const [ isRemovePending , startRemoveTransition ] = useTransition ( )
59
+ const [ currentAgent , setCurrentAgent ] = useState ( { id : '' , name : '' , prompt : '' } )
60
+ const [ agents , setAgents ] = useState ( ( ) => {
61
+ const storedAgents = localStorage . getItem ( 'Agents' ) ;
62
+ if ( storedAgents ) {
63
+ return JSON . parse ( storedAgents ) ;
64
+ } else {
65
+ const newAgent = `{"#666666":{"name":"Search","prompt":"Get Info from Internet[//]: (ReAct-Tools)"},"#666777":{"name":"CoT","prompt":"Let's think step by step."}}`
66
+ localStorage . setItem ( 'Agents' , newAgent ) ;
67
+ return JSON . parse ( newAgent ) ; // or you can return an empty object {} if that's the desired initial state
68
+ }
69
+ } ) ;
70
+ const [ usetool , setUsetool ] = useState ( false ) // Load agents from localStorage or initialize as an empty object
71
+
72
+ // Function to open the editor with the selected agent's details
73
+ const handleEditAgent = ( agentId :string ) => {
74
+ setCurrentAgent ( { ...agents [ agentId ] , id : agentId } )
75
+ setEditorOpen ( true )
76
+ }
77
+
78
+ // Function to handle saving the current agent to the local state and localStorage
79
+ const handleSaveAgents = ( ) => {
80
+ const tool = '[//]: (ReAct-Tools)'
81
+ var prmt = currentAgent . prompt
82
+ if ( usetool ) {
83
+ if ( ! prmt . endsWith ( tool ) ) {
84
+ prmt = prmt + tool
85
+ }
86
+ } else {
87
+ if ( prmt . endsWith ( tool ) ) {
88
+ prmt = prmt . replace ( tool , '' )
89
+ }
90
+ }
91
+ const updatedAgents = {
92
+ ...agents ,
93
+ [ currentAgent . id ] : { name : currentAgent . name , prompt :prmt }
94
+ }
95
+ setAgents ( updatedAgents )
96
+ localStorage . setItem ( 'Agents' , JSON . stringify ( updatedAgents ) ) // Save to localStorage
97
+ setEditorOpen ( false ) // Close the editor
98
+ router . replace ( '/' )
99
+ router . refresh ( )
100
+ }
101
+
102
+ // Function to open the editor for creating a new agent
103
+ const handleNewAgent = ( ) => {
104
+ setCurrentAgent ( { id : getRandomColor ( ) , name : '' , prompt : '' } )
105
+ setEditorOpen ( true )
106
+ }
107
+
108
+ return (
109
+ < >
110
+ < div className = "flex flex-wrap gap-4 m-4" >
111
+ { Object . entries ( agents ) . map ( ( [ key , agent ] ) => (
112
+ < >
113
+ < Card key = { key } className = "w-[300px] h-[200px]" >
114
+ < CardHeader >
115
+ < CardTitle > { ( agent as Agent ) . name } </ CardTitle >
116
+ < CardDescription className = 'h-[64px]' > { ( agent as Agent ) . prompt . slice ( 0 , 70 ) + ' ...' } </ CardDescription >
117
+ </ CardHeader >
118
+ < CardFooter className = "flex gap-2" >
119
+ < Button onClick = { ( ) => handleEditAgent ( key ) } > < IconEdit /> </ Button >
120
+ < Button variant = "outline" onClick = { ( ) => setInput ( `@${ ( agent as Agent ) . name } ` ) } > @</ Button >
121
+ < Button
122
+ variant = "ghost"
123
+ className = "ml-auto size-6 p-0 hover:bg-background"
124
+ disabled = { isRemovePending }
125
+ onClick = { ( ) => setDeleteDialogOpen ( true ) }
126
+ >
127
+ < IconTrash />
128
+ < span className = "sr-only" > Delete</ span >
129
+ </ Button >
130
+ </ CardFooter >
131
+ </ Card >
132
+ < AlertDialog open = { deleteDialogOpen } onOpenChange = { setDeleteDialogOpen } >
133
+ < AlertDialogContent >
134
+ < AlertDialogHeader >
135
+ < AlertDialogTitle > Are you absolutely sure?</ AlertDialogTitle >
136
+ < AlertDialogDescription >
137
+ This will permanently delete your chat message and remove your
138
+ data from our servers.
139
+ </ AlertDialogDescription >
140
+ </ AlertDialogHeader >
141
+ < AlertDialogFooter >
142
+ < AlertDialogCancel disabled = { isRemovePending } >
143
+ Cancel
144
+ </ AlertDialogCancel >
145
+ < AlertDialogAction
146
+ disabled = { isRemovePending }
147
+ onClick = { ( ) => {
148
+ const updatedAgents = { ...agents }
149
+ delete updatedAgents [ key ] // Remove the agent from the object
150
+ setAgents ( updatedAgents ) // Update local state
151
+ localStorage . setItem ( 'Agents' , JSON . stringify ( updatedAgents ) ) // Update localStorage
152
+ }
153
+ }
154
+ >
155
+ { isRemovePending && < IconSpinner className = "mr-2 animate-spin" /> }
156
+ Delete
157
+ </ AlertDialogAction >
158
+ </ AlertDialogFooter >
159
+ </ AlertDialogContent >
160
+ </ AlertDialog >
161
+ </ >
162
+ ) ) }
163
+ < Dialog open = { editorOpen } onOpenChange = { setEditorOpen } >
164
+ < DialogContent className = "sm:max-w-xl" >
165
+ < DialogHeader >
166
+ < DialogTitle > Edit Agent</ DialogTitle >
167
+ </ DialogHeader >
168
+ < div className = "grid gap-2 py-4" >
169
+ < div className = "grid grid-cols-5 items-center gap-2" >
170
+ < Label htmlFor = "name" className = "text-right" >
171
+ * Name
172
+ </ Label >
173
+ < Input className = "col-span-4"
174
+ value = { currentAgent . name }
175
+ placeholder = "Input an Agent Name"
176
+ onChange = { ( e ) => {
177
+ const newName = e . target . value ;
178
+ // const usernamePattern = /^[A-Za-z][A-Za-z0-9_-]{2,15}$/;
179
+ // if (usernamePattern.test(newName)) {
180
+ setCurrentAgent ( { ...currentAgent , name : newName } ) ;
181
+ // }
182
+ } }
183
+ />
184
+ </ div >
185
+ < div className = "grid grid-cols-5 items-center gap-2" >
186
+ < Label htmlFor = "name" className = "text-right" >
187
+ Prompt
188
+ </ Label >
189
+ < Textarea className = "col-span-4 h-[200px]"
190
+ value = { currentAgent . prompt }
191
+ placeholder = "Agent System Role Prompt"
192
+ onChange = { ( e ) => setCurrentAgent ( { ...currentAgent , prompt : e . target . value } ) }
193
+ />
194
+ </ div >
195
+ </ div >
196
+ < DialogFooter className = "items-center" >
197
+ < Checkbox id = "usetool" checked = { usetool } onCheckedChange = { ( ) => setUsetool ( ! usetool ) } />
198
+ < label
199
+ htmlFor = "terms"
200
+ className = "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
201
+ >
202
+ Use Tools
203
+ </ label >
204
+ < Button onClick = { handleSaveAgents } > Save Agent</ Button >
205
+ </ DialogFooter >
206
+ </ DialogContent >
207
+ </ Dialog >
208
+ < Card className = "w-[300px] h-[200px] text-center" >
209
+ < button className = "mt-20" onClick = { handleNewAgent } > + New Agent</ button >
210
+ </ Card >
211
+ </ div >
212
+ < div className = "mx-auto px-4 text-center mt-12" >
213
+ < p className = "mb-2 leading-normal text-muted-foreground" >
214
+ This is an open source AI app built with{ ' ' }
215
+ < ExternalLink href = "https://nextjs.org" > ▲ Next.js</ ExternalLink > and{ ' ' }
216
+ < ExternalLink href = "https://js.langchain.com/docs" >
217
+ LangChain.js 🦜🔗
218
+ </ ExternalLink >
219
+ .
220
+ </ p >
221
+ < p className = "leading-normal text-muted-foreground" >
222
+ You can add your own agents and use '@' to mention them for conversation.
223
+ </ p >
224
+ </ div >
225
+ </ >
226
+ )
227
+ }
0 commit comments