@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.
Files changed (54) hide show
  1. package/apps/agents-server/README.md +3 -0
  2. package/apps/agents-server/TODO.txt +6 -0
  3. package/apps/agents-server/config.ts.todo +312 -0
  4. package/apps/agents-server/next.config.ts +42 -0
  5. package/apps/agents-server/package.json +11 -0
  6. package/apps/agents-server/postcss.config.mjs +8 -0
  7. package/apps/agents-server/public/.gitkeep +0 -0
  8. package/apps/agents-server/public/favicon.ico +0 -0
  9. package/apps/agents-server/public/logo-blue-white-256.png +0 -0
  10. package/apps/agents-server/src/app/AddAgentButton.tsx +20 -0
  11. package/apps/agents-server/src/app/actions.ts +14 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +41 -0
  13. package/apps/agents-server/src/app/agents/[agentName]/TODO.txt +1 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +86 -0
  15. package/apps/agents-server/src/app/agents/[agentName]/api/book/test.http +37 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +60 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +74 -0
  18. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +21 -0
  19. package/apps/agents-server/src/app/agents/[agentName]/book+chat/SelfLearningBook.tsx +203 -0
  20. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +18 -0
  21. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +160 -0
  22. package/apps/agents-server/src/app/api/chat/route.ts +32 -0
  23. package/apps/agents-server/src/app/api/chat-streaming/route.ts +44 -0
  24. package/apps/agents-server/src/app/api/long-running-task/route.ts +7 -0
  25. package/apps/agents-server/src/app/api/long-streaming/route.ts +20 -0
  26. package/apps/agents-server/src/app/globals.css +113 -0
  27. package/apps/agents-server/src/app/layout.tsx +72 -0
  28. package/apps/agents-server/src/app/page.tsx +115 -0
  29. package/apps/agents-server/src/deamons/longRunningTask.ts +37 -0
  30. package/apps/agents-server/src/supabase/TODO.txt +1 -0
  31. package/apps/agents-server/src/supabase/getSupabase.ts +25 -0
  32. package/apps/agents-server/src/supabase/getSupabaseForBrowser.ts +37 -0
  33. package/apps/agents-server/src/supabase/getSupabaseForServer.ts +48 -0
  34. package/apps/agents-server/src/supabase/getSupabaseForWorker.ts +42 -0
  35. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +49 -0
  36. package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +110 -0
  37. package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +35 -0
  38. package/apps/agents-server/tailwind.config.ts +24 -0
  39. package/apps/agents-server/tsconfig.json +29 -0
  40. package/esm/index.es.js +40 -6
  41. package/esm/index.es.js.map +1 -1
  42. package/esm/typings/src/book-2.0/agent-source/padBook.d.ts +2 -0
  43. package/esm/typings/src/book-2.0/agent-source/string_book.d.ts +2 -0
  44. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +4 -0
  45. package/esm/typings/src/conversion/validation/validatePipeline.d.ts +2 -0
  46. package/esm/typings/src/execution/utils/validatePromptResult.d.ts +2 -0
  47. package/esm/typings/src/pipeline/validatePipelineString.d.ts +2 -0
  48. package/esm/typings/src/remote-server/startAgentServer.d.ts +3 -0
  49. package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -0
  50. package/esm/typings/src/utils/validators/parameterName/validateParameterName.d.ts +2 -0
  51. package/esm/typings/src/version.d.ts +1 -1
  52. package/package.json +1 -1
  53. package/umd/index.umd.js +40 -6
  54. 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,7 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getLongRunningTask } from '../../../deamons/longRunningTask';
3
+
4
+ export function GET() {
5
+ const task = getLongRunningTask();
6
+ return NextResponse.json(task);
7
+ }
@@ -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
+ }