@promptbook/cli 0.103.0-44 → 0.103.0-46
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/apps/agents-server/README.md +3 -0
- package/apps/agents-server/TODO.txt +6 -0
- package/apps/agents-server/config.ts.todo +312 -0
- package/apps/agents-server/next.config.ts +42 -0
- package/apps/agents-server/package.json +11 -0
- package/apps/agents-server/postcss.config.mjs +8 -0
- package/apps/agents-server/public/.gitkeep +0 -0
- package/apps/agents-server/public/favicon.ico +0 -0
- package/apps/agents-server/public/logo-blue-white-256.png +0 -0
- package/apps/agents-server/src/app/AddAgentButton.tsx +20 -0
- package/apps/agents-server/src/app/actions.ts +14 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +41 -0
- package/apps/agents-server/src/app/agents/[agentName]/TODO.txt +1 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +86 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/book/test.http +37 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +60 -0
- package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +74 -0
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +21 -0
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/SelfLearningBook.tsx +203 -0
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +18 -0
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +160 -0
- package/apps/agents-server/src/app/api/chat/route.ts +32 -0
- package/apps/agents-server/src/app/api/chat-streaming/route.ts +44 -0
- package/apps/agents-server/src/app/api/long-running-task/route.ts +7 -0
- package/apps/agents-server/src/app/api/long-streaming/route.ts +20 -0
- package/apps/agents-server/src/app/globals.css +113 -0
- package/apps/agents-server/src/app/layout.tsx +72 -0
- package/apps/agents-server/src/app/page.tsx +115 -0
- package/apps/agents-server/src/deamons/longRunningTask.ts +37 -0
- package/apps/agents-server/src/supabase/TODO.txt +1 -0
- package/apps/agents-server/src/supabase/getSupabase.ts +25 -0
- package/apps/agents-server/src/supabase/getSupabaseForBrowser.ts +37 -0
- package/apps/agents-server/src/supabase/getSupabaseForServer.ts +48 -0
- package/apps/agents-server/src/supabase/getSupabaseForWorker.ts +42 -0
- package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +49 -0
- package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +110 -0
- package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +35 -0
- package/apps/agents-server/tailwind.config.ts +24 -0
- package/apps/agents-server/tsconfig.json +29 -0
- package/esm/index.es.js +40 -6
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/book-2.0/agent-source/padBook.d.ts +2 -0
- package/esm/typings/src/book-2.0/agent-source/string_book.d.ts +2 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +4 -0
- package/esm/typings/src/conversion/validation/validatePipeline.d.ts +2 -0
- package/esm/typings/src/execution/utils/validatePromptResult.d.ts +2 -0
- package/esm/typings/src/pipeline/validatePipelineString.d.ts +2 -0
- package/esm/typings/src/remote-server/startAgentServer.d.ts +3 -0
- package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -0
- package/esm/typings/src/utils/validators/parameterName/validateParameterName.d.ts +2 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +40 -6
- package/umd/index.umd.js.map +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { BookEditor } from '@promptbook-local/components';
|
|
4
|
+
import { string_book } from '@promptbook-local/types';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
|
|
7
|
+
type BookEditorWrapperProps = {
|
|
8
|
+
agentName: string;
|
|
9
|
+
initialAgentSource: string_book;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// TODO: !!!! Rename to BookEditorSavingWrapper
|
|
13
|
+
|
|
14
|
+
export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorWrapperProps) {
|
|
15
|
+
const [agentSource, setAgentSource] = useState<string_book>(initialAgentSource);
|
|
16
|
+
const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
|
|
17
|
+
|
|
18
|
+
const handleChange = async (newSource: string_book) => {
|
|
19
|
+
setAgentSource(newSource);
|
|
20
|
+
setSaveStatus('saving');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(`/agents/${encodeURIComponent(agentName)}/api/book`, {
|
|
24
|
+
method: 'PUT',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'text/plain',
|
|
27
|
+
},
|
|
28
|
+
body: newSource,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`Failed to save: ${response.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setSaveStatus('saved');
|
|
36
|
+
setTimeout(() => setSaveStatus('idle'), 2000); // Reset status after 2 seconds
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Error saving agent source:', error);
|
|
39
|
+
setSaveStatus('error');
|
|
40
|
+
setTimeout(() => setSaveStatus('idle'), 3000);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="w-full h-screen flex flex-col">
|
|
46
|
+
{saveStatus !== 'idle' && (
|
|
47
|
+
<div
|
|
48
|
+
className={`px-4 py-2 text-sm ${
|
|
49
|
+
saveStatus === 'saving'
|
|
50
|
+
? 'bg-blue-100 text-blue-800'
|
|
51
|
+
: saveStatus === 'saved'
|
|
52
|
+
? 'bg-green-100 text-green-800'
|
|
53
|
+
: 'bg-red-100 text-red-800'
|
|
54
|
+
}`}
|
|
55
|
+
>
|
|
56
|
+
{saveStatus === 'saving' && '💾 Saving...'}
|
|
57
|
+
{saveStatus === 'saved' && '✅ Saved successfully'}
|
|
58
|
+
{saveStatus === 'error' && '❌ Failed to save'}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
<div className="flex-1">
|
|
62
|
+
<BookEditor value={agentSource} onChange={handleChange} />
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* TODO: [🚗] Transfer the saving logic to `<BookEditor/>` be aware of CRDT / yjs approach to be implementable in future
|
|
70
|
+
* TODO: !!! Implement debouncing for auto-save
|
|
71
|
+
* TODO: !!! Add error handling and retry logic
|
|
72
|
+
* TODO: !!! Show save status indicator
|
|
73
|
+
* TODO: !!!!! Add file upload capability
|
|
74
|
+
*/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
4
|
+
import { headers } from 'next/headers';
|
|
5
|
+
import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
|
|
6
|
+
import { BookEditorWrapper } from './BookEditorWrapper';
|
|
7
|
+
|
|
8
|
+
export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
9
|
+
$sideEffect(headers());
|
|
10
|
+
|
|
11
|
+
const { agentName } = await params;
|
|
12
|
+
const collection = await $provideAgentCollectionForServer();
|
|
13
|
+
const agentSourceSubject = await collection.getAgentSource(decodeURIComponent(agentName));
|
|
14
|
+
const agentSource = agentSourceSubject.getValue();
|
|
15
|
+
|
|
16
|
+
return <BookEditorWrapper agentName={agentName} initialAgentSource={agentSource} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* TODO: [🚗] Components and pages here should be just tiny UI wrapper around proper agent logic and components
|
|
21
|
+
*/
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ResizablePanelsAuto } from '@common/components/ResizablePanelsAuto/ResizablePanelsAuto';
|
|
4
|
+
import { useStateInLocalStorage } from '@common/hooks/useStateInLocalStorage';
|
|
5
|
+
import { BookEditor, LlmChat } from '@promptbook-local/components';
|
|
6
|
+
import { Agent, book } from '@promptbook-local/core';
|
|
7
|
+
// import { RemoteLlmExecutionTools } from '@promptbook-local/remote-client';
|
|
8
|
+
import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
|
|
9
|
+
import type { string_book } from '@promptbook-local/types';
|
|
10
|
+
import { spaceTrim } from '@promptbook-local/utils';
|
|
11
|
+
import { useMemo, useState } from 'react';
|
|
12
|
+
|
|
13
|
+
export function SelfLearningBook() {
|
|
14
|
+
const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
|
|
15
|
+
const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
|
|
16
|
+
const [isApiKeySectionCollapsed, setIsApiKeySectionCollapsed] = useState(!!apiKey);
|
|
17
|
+
|
|
18
|
+
const [agentSource, setAgentSource] = useStateInLocalStorage<string_book>(
|
|
19
|
+
'marigold-agentSource-1',
|
|
20
|
+
// TODO: !!! Uplad image to ptbk.io CDN
|
|
21
|
+
() => book`
|
|
22
|
+
Marigold
|
|
23
|
+
|
|
24
|
+
META IMAGE https://westlandsuk.co.uk/wp-content/uploads/2021/06/Inspired.MarigoldFlowers_CMYK_40mm.jpg
|
|
25
|
+
META COLOR #EC810B
|
|
26
|
+
|
|
27
|
+
PERSONA You are writing stories about Witcher
|
|
28
|
+
RULE Do not talk about our world, only about the Witcher universe
|
|
29
|
+
|
|
30
|
+
KNOWLEDGE {Geralt of Rivia}
|
|
31
|
+
Geralt of Rivia is a witcher, a monster hunter for hire, known for his white hair and cat-like eyes.
|
|
32
|
+
He possesses superhuman abilities due to mutations he underwent during the Trial of the Grasses.
|
|
33
|
+
Geralt is skilled in swordsmanship, alchemy, and magic signs.
|
|
34
|
+
He is often accompanied by his horse, Roach, and has a complex relationship with {Yennefer of Vengerberg},
|
|
35
|
+
a powerful sorceress, and {Ciri}, his adopted daughter with a destiny intertwined with his own.
|
|
36
|
+
His secret word is "Apple".
|
|
37
|
+
|
|
38
|
+
KNOWLEDGE {Yennefer of Vengerberg}
|
|
39
|
+
Yennefer of Vengerberg is a formidable sorceress known for her beauty, intelligence, and temper.
|
|
40
|
+
She has a complicated past, having been born with a hunchback and later transformed through magic.
|
|
41
|
+
Yennefer is deeply connected to Geralt of Rivia, with whom she shares a tumultuous romantic relationship.
|
|
42
|
+
She is also a mother figure to {Ciri}, whom she trains in the ways of magic.
|
|
43
|
+
Her secret word is "Banana".
|
|
44
|
+
|
|
45
|
+
KNOWLEDGE {Ciri}
|
|
46
|
+
Ciri, also known as {Cirilla Fiona Elen Riannon}, is a young woman with a mysterious past and a powerful destiny.
|
|
47
|
+
She is the daughter of {Poviss}, the ruler of the kingdom of Cintra, and possesses the Elder Blood, which grants her extraordinary abilities.
|
|
48
|
+
Ciri is a skilled fighter and has been trained in the ways of the sword by Geralt of Rivia.
|
|
49
|
+
Her destiny is intertwined with that of Geralt and Yennefer, as they both seek to protect her from those who would exploit her powers.
|
|
50
|
+
Her secret word is "Cherry".
|
|
51
|
+
|
|
52
|
+
`,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const agent = useMemo(() => {
|
|
56
|
+
/*/
|
|
57
|
+
// TODO: !!! Try working with `RemoteLlmExecutionTools`
|
|
58
|
+
const llm = new RemoteLlmExecutionTools({
|
|
59
|
+
remoteServerUrl: 'https://promptbook.s5.ptbk.io/',
|
|
60
|
+
identification: {
|
|
61
|
+
isAnonymous: false,
|
|
62
|
+
appId: '20a65fee-59f6-4d05-acd0-8e5ae8345488',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
/**/
|
|
66
|
+
|
|
67
|
+
/**/
|
|
68
|
+
const llm = new OpenAiAssistantExecutionTools({
|
|
69
|
+
dangerouslyAllowBrowser: true,
|
|
70
|
+
isCreatingNewAssistantsAllowed: true, // <- TODO: !!! Test without whether warning is shown
|
|
71
|
+
apiKey,
|
|
72
|
+
assistantId: 'asst_xI94Elk27nssnwAUkG2Cmok8', // <- TODO: [🧠] Make dynamic
|
|
73
|
+
isVerbose: true,
|
|
74
|
+
});
|
|
75
|
+
/**/
|
|
76
|
+
|
|
77
|
+
const agent = new Agent({
|
|
78
|
+
executionTools: {
|
|
79
|
+
llm,
|
|
80
|
+
},
|
|
81
|
+
agentSource: [agentSource, setAgentSource],
|
|
82
|
+
isVerbose: true,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return agent;
|
|
86
|
+
}, [agentSource, setAgentSource, apiKey]);
|
|
87
|
+
|
|
88
|
+
const llmTools = useMemo(() => {
|
|
89
|
+
const llmTools = agent.getLlmExecutionTools();
|
|
90
|
+
return llmTools;
|
|
91
|
+
}, [agent]);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className="min-h-screen relative">
|
|
95
|
+
{/* Floating API Key Configuration */}
|
|
96
|
+
<div className={`fixed top-24 right-4 z-50 ${isApiKeySectionCollapsed ? '' : 'min-w-[400px]'}`}>
|
|
97
|
+
{isApiKeySectionCollapsed ? (
|
|
98
|
+
// Collapsed state - small corner button
|
|
99
|
+
<button
|
|
100
|
+
onClick={() => setIsApiKeySectionCollapsed(false)}
|
|
101
|
+
className="bg-gray-100 hover:bg-gray-200 border border-gray-300 rounded-full p-2 shadow-lg transition-all flex items-center gap-1"
|
|
102
|
+
title="Configure OpenAI API Key"
|
|
103
|
+
>
|
|
104
|
+
<span className="text-sm">🔑</span>
|
|
105
|
+
</button>
|
|
106
|
+
) : (
|
|
107
|
+
// Expanded state - full configuration panel
|
|
108
|
+
<div className="bg-white border border-gray-300 rounded-lg shadow-lg">
|
|
109
|
+
<div className="flex items-center justify-between p-3 bg-gray-100 rounded-t-lg border-b border-gray-300">
|
|
110
|
+
<span className="font-medium text-sm">OpenAI API Key</span>
|
|
111
|
+
<button
|
|
112
|
+
onClick={() => setIsApiKeySectionCollapsed(true)}
|
|
113
|
+
className="text-gray-600 hover:text-gray-800 text-lg leading-none"
|
|
114
|
+
title="Collapse"
|
|
115
|
+
>
|
|
116
|
+
✕
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
<div className="p-3">
|
|
120
|
+
<label className="flex items-center gap-2">
|
|
121
|
+
<span className="text-sm font-medium whitespace-nowrap">API Key:</span>
|
|
122
|
+
<input
|
|
123
|
+
type={isApiKeyVisible ? 'text' : 'password'}
|
|
124
|
+
value={apiKey}
|
|
125
|
+
onChange={(e) => setApiKey(e.target.value)}
|
|
126
|
+
placeholder="sk-proj-..."
|
|
127
|
+
className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
128
|
+
/>
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={() => setIsApiKeyVisible(!isApiKeyVisible)}
|
|
132
|
+
className="px-2 py-2 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors text-sm"
|
|
133
|
+
title={isApiKeyVisible ? 'Hide API key' : 'Show API key'}
|
|
134
|
+
>
|
|
135
|
+
{isApiKeyVisible ? '🙈' : '👁️'}
|
|
136
|
+
</button>
|
|
137
|
+
</label>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<ResizablePanelsAuto name="two-editors">
|
|
144
|
+
<BookEditor
|
|
145
|
+
className="w-full h-full"
|
|
146
|
+
height={null}
|
|
147
|
+
value={agentSource}
|
|
148
|
+
onChange={setAgentSource}
|
|
149
|
+
// className={styles.BookEditor}
|
|
150
|
+
isVerbose={false}
|
|
151
|
+
isBorderRadiusDisabled
|
|
152
|
+
onFileUpload={(file) => {
|
|
153
|
+
return file.name;
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
{/*
|
|
157
|
+
<AgentChat className="w-full h-full" persistenceKey="marigold-chat" {...{ agent }} />
|
|
158
|
+
TODO: !!! Move to `AgentChat` component
|
|
159
|
+
*/}
|
|
160
|
+
<LlmChat
|
|
161
|
+
title={`Chat with ${agent.agentName || 'Agent'}`}
|
|
162
|
+
// TODO: !!! Pass persistenceKey="chat-with-pavol-hejny"
|
|
163
|
+
userParticipantName="USER"
|
|
164
|
+
llmParticipantName="AGENT" // <- TODO: [🧠] Maybe dynamic agent id
|
|
165
|
+
initialMessages={[
|
|
166
|
+
{
|
|
167
|
+
from: 'AGENT',
|
|
168
|
+
content: spaceTrim(`
|
|
169
|
+
|
|
170
|
+
Hello! I am ${agent.agentName || 'an AI Agent'}.
|
|
171
|
+
|
|
172
|
+
[Hello](?message=Hello, can you tell me about yourself?)
|
|
173
|
+
`),
|
|
174
|
+
},
|
|
175
|
+
]}
|
|
176
|
+
participants={[
|
|
177
|
+
{
|
|
178
|
+
name: 'AGENT',
|
|
179
|
+
fullname: agent.agentName || 'Agent',
|
|
180
|
+
avatarSrc: agent.meta.image,
|
|
181
|
+
color: agent.meta.color,
|
|
182
|
+
isMe: false,
|
|
183
|
+
agentSource,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'USER',
|
|
187
|
+
fullname: 'User',
|
|
188
|
+
color: '#115EB6',
|
|
189
|
+
isMe: true,
|
|
190
|
+
},
|
|
191
|
+
]}
|
|
192
|
+
{...{ llmTools }}
|
|
193
|
+
className={`h-full flex flex-col`}
|
|
194
|
+
/>
|
|
195
|
+
</ResizablePanelsAuto>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* TODO: !!!! Make self-learning book createAgentLlmExecutionTools, use bidirectional agentSource
|
|
202
|
+
* TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
|
|
203
|
+
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import dynamic from 'next/dynamic';
|
|
4
|
+
|
|
5
|
+
const SelfLearningBook = dynamic(() => import('./SelfLearningBook').then((module) => module.SelfLearningBook), {
|
|
6
|
+
ssr: false,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export default function SelfLearningBookPage() {
|
|
10
|
+
return <SelfLearningBook />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* TODO: !!! Private / public / open agents
|
|
15
|
+
* TODO: !!! Allow http://localhost:4440/agents/agent-123.book to download the agent book file
|
|
16
|
+
* TODO: !!! BOOK_MIME_TYPE in config
|
|
17
|
+
* TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
|
|
18
|
+
*/
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
4
|
+
import { PromptbookQrCode } from '@promptbook-local/components';
|
|
5
|
+
// import { BookEditor } from '@promptbook-local/components';
|
|
6
|
+
import { parseAgentSource } from '@promptbook-local/core';
|
|
7
|
+
import { headers } from 'next/headers';
|
|
8
|
+
import { $sideEffect } from '../../../../../../src/utils/organization/$sideEffect';
|
|
9
|
+
import { AgentUrlCopy } from './AgentUrlCopy';
|
|
10
|
+
// import { Agent } from '@promptbook-local/core';
|
|
11
|
+
// import { RemoteLlmExecutionTools } from '@promptbook-local/remote-client';
|
|
12
|
+
// import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
|
|
13
|
+
|
|
14
|
+
export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
15
|
+
// const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
|
|
16
|
+
// const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
|
|
17
|
+
// const [isApiKeySectionCollapsed, setIsApiKeySectionCollapsed] = useState(!!apiKey);
|
|
18
|
+
|
|
19
|
+
$sideEffect(headers());
|
|
20
|
+
|
|
21
|
+
const { agentName } = await params;
|
|
22
|
+
const collection = await $provideAgentCollectionForServer();
|
|
23
|
+
const agentSourceSubject = await collection.getAgentSource(decodeURIComponent(agentName));
|
|
24
|
+
const agentSource = agentSourceSubject.getValue();
|
|
25
|
+
const agentProfile = parseAgentSource(agentSource);
|
|
26
|
+
|
|
27
|
+
// Build agent page URL for QR and copy
|
|
28
|
+
const pageUrl = `https://s6.ptbk.io/agents/${encodeURIComponent(agentName)}`;
|
|
29
|
+
// <- TODO: !!! Better
|
|
30
|
+
|
|
31
|
+
// Extract brand color from meta
|
|
32
|
+
const brandColor = agentProfile.meta.color || '#3b82f6'; // Default to blue-600
|
|
33
|
+
|
|
34
|
+
// Mock agent actions
|
|
35
|
+
const agentActions = ['Emails', 'Web', 'Documents', 'Browser', 'WhatsApp', 'Coding'];
|
|
36
|
+
|
|
37
|
+
// Render agent profile fields
|
|
38
|
+
const renderProfileFields = () => {
|
|
39
|
+
const renderValue = (value: unknown): React.ReactNode => {
|
|
40
|
+
if (value === null || value === undefined) {
|
|
41
|
+
return <span className="text-gray-400 italic">Not specified</span>;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
44
|
+
const objValue = value as Record<string, unknown>;
|
|
45
|
+
return (
|
|
46
|
+
<div className="space-y-1 pl-3 border-l-2 border-gray-200">
|
|
47
|
+
{Object.entries(objValue).map(([subKey, subValue]) => (
|
|
48
|
+
<div key={subKey} className="flex gap-2">
|
|
49
|
+
<span className="text-xs text-gray-600 font-medium">{subKey}:</span>
|
|
50
|
+
<span className="text-sm text-gray-700">{String(subValue)}</span>
|
|
51
|
+
</div>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
return (
|
|
58
|
+
<ul className="list-disc list-inside space-y-0.5">
|
|
59
|
+
{value.map((item, idx) => (
|
|
60
|
+
<li key={idx} className="text-sm text-gray-700">
|
|
61
|
+
{String(item)}
|
|
62
|
+
</li>
|
|
63
|
+
))}
|
|
64
|
+
</ul>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return <span className="text-base text-gray-800 break-words">{String(value)}</span>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="space-y-4">
|
|
72
|
+
{Object.entries(agentProfile).map(([key, value]) => (
|
|
73
|
+
<div key={key} className="flex flex-col gap-1">
|
|
74
|
+
<span className="text-xs text-gray-500 font-semibold uppercase tracking-wide">{key}</span>
|
|
75
|
+
{renderValue(value)}
|
|
76
|
+
</div>
|
|
77
|
+
))}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
className="w-full min-h-screen bg-gray-50 py-10 px-4 flex items-center justify-center"
|
|
85
|
+
style={{ backgroundColor: brandColor }}
|
|
86
|
+
>
|
|
87
|
+
<div className="max-w-5xl w-full bg-white rounded-xl shadow-lg p-8 flex flex-col md:flex-row gap-8">
|
|
88
|
+
{/* Left column: Profile info */}
|
|
89
|
+
<div className="flex-1 flex flex-col gap-6">
|
|
90
|
+
<div className="flex items-center gap-4">
|
|
91
|
+
{agentProfile.meta.image && (
|
|
92
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
93
|
+
<img
|
|
94
|
+
src={agentProfile.meta.image as string}
|
|
95
|
+
alt={agentProfile.agentName || 'Agent'}
|
|
96
|
+
width={64}
|
|
97
|
+
height={64}
|
|
98
|
+
className="rounded-full object-cover border-2"
|
|
99
|
+
style={{ borderColor: brandColor }}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
<div className="flex-1">
|
|
103
|
+
<h1 className="text-3xl font-bold text-gray-900">{agentProfile.agentName}</h1>
|
|
104
|
+
<span
|
|
105
|
+
className="inline-block mt-1 px-2 py-1 rounded text-xs font-semibold text-white"
|
|
106
|
+
style={{ backgroundColor: brandColor }}
|
|
107
|
+
>
|
|
108
|
+
Agent
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
{renderProfileFields()}
|
|
113
|
+
<div className="flex flex-col gap-2">
|
|
114
|
+
<h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Actions</h2>
|
|
115
|
+
<div className="flex flex-wrap gap-2">
|
|
116
|
+
{agentActions.map((action) => (
|
|
117
|
+
<span
|
|
118
|
+
key={action}
|
|
119
|
+
className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-xs font-medium border border-gray-200"
|
|
120
|
+
>
|
|
121
|
+
{action}
|
|
122
|
+
</span>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="flex gap-4 mt-6">
|
|
127
|
+
<a
|
|
128
|
+
href={`${pageUrl}/chat`}
|
|
129
|
+
// <- !!!! Can I append path like this on current browser URL in href?
|
|
130
|
+
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow font-semibold transition"
|
|
131
|
+
>
|
|
132
|
+
💬 Chat
|
|
133
|
+
</a>
|
|
134
|
+
<a
|
|
135
|
+
href={`${pageUrl}/book`}
|
|
136
|
+
// <- !!!! Can I append path like this on current browser URL in href?
|
|
137
|
+
className="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded shadow font-semibold transition"
|
|
138
|
+
>
|
|
139
|
+
✏️ Edit Agent Book
|
|
140
|
+
</a>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
{/* Right column: QR, source, copy */}
|
|
144
|
+
<div className="flex flex-col items-center gap-6 min-w-[260px]">
|
|
145
|
+
<div className="bg-gray-100 rounded-lg p-4 flex flex-col items-center">
|
|
146
|
+
<PromptbookQrCode value={pageUrl} />
|
|
147
|
+
<span className="mt-2 text-xs text-gray-500">Scan to open agent page</span>
|
|
148
|
+
</div>
|
|
149
|
+
<AgentUrlCopy url={pageUrl} />
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* TODO: !!! Make this page look nice - 🃏
|
|
158
|
+
* TODO: !!! Show usage of LLM
|
|
159
|
+
* TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
|
|
160
|
+
*/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createOpenAiExecutionTools } from '@promptbook-local/openai';
|
|
2
|
+
|
|
3
|
+
export async function GET(request: Request) {
|
|
4
|
+
const { searchParams } = new URL(request.url);
|
|
5
|
+
const message = searchParams.get('message') || 'Hello, who are you?';
|
|
6
|
+
|
|
7
|
+
const llmTools = createOpenAiExecutionTools({
|
|
8
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const response = await llmTools.callChatModel({
|
|
12
|
+
title: 'Test Chat Call',
|
|
13
|
+
parameters: {},
|
|
14
|
+
modelRequirements: {
|
|
15
|
+
modelVariant: 'CHAT',
|
|
16
|
+
modelName: 'gpt-3.5-turbo',
|
|
17
|
+
},
|
|
18
|
+
content: message,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
return new Response(JSON.stringify(response, null, 4), {
|
|
23
|
+
status: 200,
|
|
24
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25
|
+
});
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
return new Response(response.content, {
|
|
29
|
+
status: 200,
|
|
30
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { OpenAI } from 'openai';
|
|
2
|
+
import { forTime } from 'waitasecond';
|
|
3
|
+
|
|
4
|
+
export async function GET(request: Request) {
|
|
5
|
+
const { searchParams } = new URL(request.url);
|
|
6
|
+
const message = searchParams.get('message') || 'Hello, who are you?';
|
|
7
|
+
|
|
8
|
+
const openaiClient = new OpenAI({
|
|
9
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Enable streaming from OpenAI
|
|
13
|
+
const stream = await openaiClient.chat.completions.create({
|
|
14
|
+
model: 'gpt-3.5-turbo',
|
|
15
|
+
messages: [
|
|
16
|
+
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
17
|
+
{ role: 'user', content: message },
|
|
18
|
+
],
|
|
19
|
+
stream: true,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Create a ReadableStream to send chunks to the client as they arrive
|
|
23
|
+
const readableStream = new ReadableStream({
|
|
24
|
+
async start(controller) {
|
|
25
|
+
for await (const chunk of stream) {
|
|
26
|
+
// Each chunk contains a delta message
|
|
27
|
+
const content = chunk.choices[0]?.delta?.content;
|
|
28
|
+
if (content) {
|
|
29
|
+
controller.enqueue(new TextEncoder().encode(content));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await forTime(100);
|
|
34
|
+
controller.enqueue(new TextEncoder().encode('\n\n[DONE]'));
|
|
35
|
+
|
|
36
|
+
controller.close();
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return new Response(readableStream, {
|
|
41
|
+
status: 200,
|
|
42
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { forTime } from 'waitasecond';
|
|
2
|
+
import { just } from '../../../../../../src/utils/organization/just';
|
|
3
|
+
|
|
4
|
+
export async function GET() {
|
|
5
|
+
const readableStream = new ReadableStream({
|
|
6
|
+
async start(controller) {
|
|
7
|
+
while (just(true)) {
|
|
8
|
+
await forTime(100);
|
|
9
|
+
controller.enqueue(new TextEncoder().encode('x'));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
controller.close();
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return new Response(readableStream, {
|
|
17
|
+
status: 200,
|
|
18
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
19
|
+
});
|
|
20
|
+
}
|