@promptbook/cli 0.103.0-52 → 0.103.0-53
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 +1 -1
- package/apps/agents-server/config.ts +3 -3
- package/apps/agents-server/next.config.ts +1 -1
- package/apps/agents-server/public/sw.js +16 -0
- package/apps/agents-server/src/app/AddAgentButton.tsx +24 -4
- package/apps/agents-server/src/app/actions.ts +15 -13
- package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +541 -0
- package/apps/agents-server/src/app/admin/chat-feedback/page.tsx +22 -0
- package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +532 -0
- package/apps/agents-server/src/app/admin/chat-history/page.tsx +21 -0
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +241 -27
- package/apps/agents-server/src/app/admin/models/page.tsx +22 -0
- package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +131 -0
- package/apps/agents-server/src/app/admin/users/[userId]/page.tsx +21 -0
- package/apps/agents-server/src/app/admin/users/page.tsx +18 -0
- package/apps/agents-server/src/app/agents/[agentName]/ClearAgentChatFeedbackButton.tsx +63 -0
- package/apps/agents-server/src/app/agents/[agentName]/ClearAgentChatHistoryButton.tsx +63 -0
- package/apps/agents-server/src/app/agents/[agentName]/CloneAgentButton.tsx +41 -0
- package/apps/agents-server/src/app/agents/[agentName]/InstallPwaButton.tsx +74 -0
- package/apps/agents-server/src/app/agents/[agentName]/ServiceWorkerRegister.tsx +24 -0
- package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +19 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +67 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +3 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +177 -0
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +3 -0
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +53 -1
- package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +11 -11
- package/apps/agents-server/src/app/agents/[agentName]/history/RestoreVersionButton.tsx +46 -0
- package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +12 -0
- package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +62 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +80 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +92 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +92 -0
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +61 -0
- package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +102 -0
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +41 -22
- package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +47 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +19 -0
- package/apps/agents-server/src/app/api/agents/route.ts +22 -13
- package/apps/agents-server/src/app/api/auth/login/route.ts +6 -44
- package/apps/agents-server/src/app/api/chat-feedback/[id]/route.ts +38 -0
- package/apps/agents-server/src/app/api/chat-feedback/route.ts +157 -0
- package/apps/agents-server/src/app/api/chat-history/[id]/route.ts +37 -0
- package/apps/agents-server/src/app/api/chat-history/route.ts +147 -0
- package/apps/agents-server/src/app/api/federated-agents/route.ts +17 -0
- package/apps/agents-server/src/app/api/upload/route.ts +9 -1
- package/apps/agents-server/src/app/docs/[docId]/page.tsx +62 -0
- package/apps/agents-server/src/app/docs/page.tsx +33 -0
- package/apps/agents-server/src/app/layout.tsx +29 -3
- package/apps/agents-server/src/app/manifest.ts +109 -0
- package/apps/agents-server/src/app/page.tsx +8 -45
- package/apps/agents-server/src/app/recycle-bin/RestoreAgentButton.tsx +40 -0
- package/apps/agents-server/src/app/recycle-bin/actions.ts +27 -0
- package/apps/agents-server/src/app/recycle-bin/page.tsx +58 -0
- package/apps/agents-server/src/app/restricted/page.tsx +33 -0
- package/apps/agents-server/src/app/test/og-image/README.md +1 -0
- package/apps/agents-server/src/app/test/og-image/opengraph-image.tsx +37 -0
- package/apps/agents-server/src/app/test/og-image/page.tsx +22 -0
- package/apps/agents-server/src/components/Footer/Footer.tsx +175 -0
- package/apps/agents-server/src/components/Header/Header.tsx +445 -79
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +46 -14
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +58 -0
- package/apps/agents-server/src/components/Homepage/Card.tsx +1 -1
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +21 -0
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +183 -0
- package/apps/agents-server/src/components/Homepage/ModelsSection.tsx +75 -0
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +28 -3
- package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +18 -17
- package/apps/agents-server/src/components/Portal/Portal.tsx +38 -0
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +82 -131
- package/apps/agents-server/src/components/UsersList/useUsersAdmin.ts +139 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +38 -6
- package/apps/agents-server/src/middleware.ts +146 -93
- package/apps/agents-server/src/tools/$provideServer.ts +2 -2
- package/apps/agents-server/src/utils/authenticateUser.ts +42 -0
- package/apps/agents-server/src/utils/chatFeedbackAdmin.ts +96 -0
- package/apps/agents-server/src/utils/chatHistoryAdmin.ts +96 -0
- package/apps/agents-server/src/utils/getEffectiveFederatedServers.ts +22 -0
- package/apps/agents-server/src/utils/getFederatedAgents.ts +31 -8
- package/apps/agents-server/src/utils/getFederatedServersFromMetadata.ts +10 -0
- package/apps/agents-server/src/utils/getVisibleCommitmentDefinitions.ts +12 -0
- package/apps/agents-server/src/utils/isUserAdmin.ts +2 -2
- package/apps/agents-server/vercel.json +7 -0
- package/esm/index.es.js +153 -2
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +8 -1
- package/esm/typings/src/_packages/components.index.d.ts +2 -0
- package/esm/typings/src/_packages/core.index.d.ts +6 -0
- package/esm/typings/src/_packages/types.index.d.ts +2 -0
- package/esm/typings/src/_packages/utils.index.d.ts +2 -0
- package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +7 -0
- package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +4 -0
- package/esm/typings/src/book-components/_common/HamburgerMenu/HamburgerMenu.d.ts +12 -0
- package/esm/typings/src/book-components/icons/MicIcon.d.ts +8 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +17 -0
- package/esm/typings/src/commitments/MESSAGE/AgentMessageCommitmentDefinition.d.ts +28 -0
- package/esm/typings/src/commitments/MESSAGE/UserMessageCommitmentDefinition.d.ts +28 -0
- package/esm/typings/src/commitments/index.d.ts +20 -1
- package/esm/typings/src/execution/LlmExecutionTools.d.ts +9 -0
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +2 -1
- package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +10 -1
- package/esm/typings/src/utils/normalization/normalizeMessageText.d.ts +9 -0
- package/esm/typings/src/utils/normalization/normalizeMessageText.test.d.ts +1 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +153 -2
- package/umd/index.umd.js.map +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { CopyIcon } from 'lucide-react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
|
|
6
|
+
type CloneAgentButtonProps = {
|
|
7
|
+
agentName: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function CloneAgentButton({ agentName }: CloneAgentButtonProps) {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
|
|
13
|
+
const handleClone = async () => {
|
|
14
|
+
if (!window.confirm(`Clone agent "${agentName}"?`)) return;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(`/api/agents/${encodeURIComponent(agentName)}/clone`, { method: 'POST' });
|
|
18
|
+
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new Error('Failed to clone agent');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const newAgent = await response.json();
|
|
24
|
+
router.push(`/${newAgent.agentName}`);
|
|
25
|
+
router.refresh();
|
|
26
|
+
} catch (error) {
|
|
27
|
+
alert('Failed to clone agent');
|
|
28
|
+
console.error(error);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<button
|
|
34
|
+
onClick={handleClone}
|
|
35
|
+
className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
|
|
36
|
+
>
|
|
37
|
+
<CopyIcon className="ml-2 w-4 h-4 mr-2" />
|
|
38
|
+
Clone
|
|
39
|
+
</button>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { TODO_any } from '@promptbook-local/types';
|
|
4
|
+
import { ShoppingBagIcon } from 'lucide-react';
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
type BeforeInstallPromptEvent = Event & {
|
|
8
|
+
prompt: () => Promise<void>;
|
|
9
|
+
userChoice: Promise<{ outcome: 'accepted' | 'dismissed'; platform: string }>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function InstallPwaButton() {
|
|
13
|
+
const [installPromptEvent, setInstallPromptEvent] = useState<BeforeInstallPromptEvent | null>(null);
|
|
14
|
+
const [isInstalled, setIsInstalled] = useState(false);
|
|
15
|
+
const [hasPrompted, setHasPrompted] = useState(false);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
function handleBeforeInstallPrompt(e: Event) {
|
|
19
|
+
// Some browsers (Chrome) fire this event when PWA is installable
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
setInstallPromptEvent(e as BeforeInstallPromptEvent);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function updateInstalledStatus() {
|
|
25
|
+
const mediaMatch = window.matchMedia('(display-mode: standalone)');
|
|
26
|
+
const standalone = mediaMatch.matches || (window.navigator as TODO_any).standalone === true;
|
|
27
|
+
setIsInstalled(standalone);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
|
31
|
+
updateInstalledStatus();
|
|
32
|
+
window.matchMedia('(display-mode: standalone)').addEventListener('change', updateInstalledStatus);
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
|
36
|
+
window.matchMedia('(display-mode: standalone)').removeEventListener('change', updateInstalledStatus);
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const onInstall = useCallback(async () => {
|
|
41
|
+
if (!installPromptEvent) return;
|
|
42
|
+
try {
|
|
43
|
+
installPromptEvent.prompt();
|
|
44
|
+
setHasPrompted(true);
|
|
45
|
+
const choice = await installPromptEvent.userChoice.catch(() => null);
|
|
46
|
+
if (choice?.outcome === 'accepted') {
|
|
47
|
+
setIsInstalled(true);
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
// Clear stored event so button hides if dismissed
|
|
51
|
+
setInstallPromptEvent(null);
|
|
52
|
+
}
|
|
53
|
+
}, [installPromptEvent]);
|
|
54
|
+
|
|
55
|
+
if (isInstalled || (!installPromptEvent && hasPrompted)) return null;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
onClick={onInstall}
|
|
61
|
+
className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
|
|
62
|
+
style={{
|
|
63
|
+
opacity: installPromptEvent ? 1 : 0.5,
|
|
64
|
+
cursor: installPromptEvent ? 'pointer' : 'wait',
|
|
65
|
+
}}
|
|
66
|
+
aria-label="Install App"
|
|
67
|
+
disabled={!installPromptEvent}
|
|
68
|
+
>
|
|
69
|
+
{/* Simple icon substitute: download arrow */}
|
|
70
|
+
<ShoppingBagIcon className="ml-2 w-4 h-4 mr-2" />
|
|
71
|
+
Install
|
|
72
|
+
</button>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
type ServiceWorkerRegisterProps = {
|
|
6
|
+
scope: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function ServiceWorkerRegister({ scope }: ServiceWorkerRegisterProps) {
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if ('serviceWorker' in navigator) {
|
|
12
|
+
navigator.serviceWorker
|
|
13
|
+
.register('/sw.js', { scope })
|
|
14
|
+
.then((registration) => {
|
|
15
|
+
console.log('Service Worker registered with scope:', registration.scope);
|
|
16
|
+
})
|
|
17
|
+
.catch((error) => {
|
|
18
|
+
console.error('Service Worker registration failed:', error);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}, [scope]);
|
|
22
|
+
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
2
|
+
import { parseAgentSource } from '@promptbook-local/core';
|
|
3
|
+
|
|
4
|
+
export const AGENT_ACTIONS = ['Emails', 'Web chat', 'Read documents', 'Browser', 'WhatsApp', '<Coding/>'];
|
|
5
|
+
|
|
6
|
+
export async function getAgentName(params: Promise<{ agentName: string }>) {
|
|
7
|
+
const { agentName } = await params;
|
|
8
|
+
return decodeURIComponent(agentName);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function getAgentProfile(agentName: string) {
|
|
12
|
+
const collection = await $provideAgentCollectionForServer();
|
|
13
|
+
const agentSource = await collection.getAgentSource(agentName);
|
|
14
|
+
return parseAgentSource(agentSource);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* TODO: Split to multiple files
|
|
19
|
+
*/
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
export const dynamic = 'force-dynamic';
|
|
4
|
+
|
|
5
|
+
export async function GET(
|
|
6
|
+
request: NextRequest,
|
|
7
|
+
{ params }: { params: Promise<{ agentName: string }> }
|
|
8
|
+
) {
|
|
9
|
+
try {
|
|
10
|
+
const { agentName } = await params;
|
|
11
|
+
// agentName is likely the federated server URL (e.g., "https://s6.ptbk.io")
|
|
12
|
+
// It comes decoded from the URL params if it was encoded in the request path
|
|
13
|
+
|
|
14
|
+
let serverUrl = agentName;
|
|
15
|
+
|
|
16
|
+
// If the serverUrl doesn't look like a URL, it might be just a hostname or something else
|
|
17
|
+
// But the requirement says we look for /agents/[federated-server]/api/agents
|
|
18
|
+
// The client will likely pass the full URL or hostname.
|
|
19
|
+
// We'll assume if it doesn't start with http, we might need to prepend it, or it's invalid.
|
|
20
|
+
// However, the current federated servers list contains full URLs.
|
|
21
|
+
|
|
22
|
+
// If it was somehow double encoded or something, we might need to handle it, but standard Next.js behavior is single decode.
|
|
23
|
+
|
|
24
|
+
if (!serverUrl.startsWith('http')) {
|
|
25
|
+
// Maybe it is just a hostname?
|
|
26
|
+
// Let's try to assume https if missing
|
|
27
|
+
if (serverUrl.includes('.')) {
|
|
28
|
+
serverUrl = `https://${serverUrl}`;
|
|
29
|
+
} else {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: 'Invalid federated server URL' },
|
|
32
|
+
{ status: 400 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Normalize URL (remove trailing slash)
|
|
38
|
+
serverUrl = serverUrl.replace(/\/$/, '');
|
|
39
|
+
|
|
40
|
+
const response = await fetch(`${serverUrl}/api/agents`, {
|
|
41
|
+
// Forward relevant headers if necessary, or just basic fetch
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
// Add any other needed headers
|
|
45
|
+
},
|
|
46
|
+
next: { revalidate: 600 }, // Cache for 10 minutes
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
console.warn(`Proxy failed to fetch agents from ${serverUrl}: ${response.status} ${response.statusText}`);
|
|
51
|
+
return NextResponse.json(
|
|
52
|
+
{ error: `Failed to fetch from ${serverUrl}` },
|
|
53
|
+
{ status: response.status }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
return NextResponse.json(data);
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Proxy error fetching federated agents:', error);
|
|
62
|
+
return NextResponse.json(
|
|
63
|
+
{ error: 'Internal Server Error' },
|
|
64
|
+
{ status: 500 }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getMetadata } from '@/src/database/getMetadata';
|
|
1
2
|
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
2
3
|
import { computeAgentHash, parseAgentSource } from '@promptbook-local/core';
|
|
3
4
|
import { serializeError } from '@promptbook-local/utils';
|
|
@@ -26,6 +27,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
26
27
|
const agentSource = await collection.getAgentSource(agentName);
|
|
27
28
|
const agentProfile = parseAgentSource(agentSource);
|
|
28
29
|
const agentHash = computeAgentHash(agentSource);
|
|
30
|
+
const isVoiceCallingEnabled = (await getMetadata('IS_VOICE_CALLING_ENABLED')) === 'true';
|
|
29
31
|
|
|
30
32
|
return new Response(
|
|
31
33
|
JSON.stringify(
|
|
@@ -33,6 +35,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
33
35
|
...agentProfile,
|
|
34
36
|
agentHash,
|
|
35
37
|
parameters: [], // <- TODO: [😰] Implement parameters
|
|
38
|
+
isVoiceCallingEnabled, // [✨✷] Add voice calling status
|
|
36
39
|
},
|
|
37
40
|
// <- TODO: [🐱🚀] Rename `serializeError` to `errorToJson`
|
|
38
41
|
null,
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { $getTableName } from '@/src/database/$getTableName';
|
|
2
|
+
import { $provideSupabaseForServer } from '@/src/database/$provideSupabaseForServer';
|
|
3
|
+
import { getMetadata } from '@/src/database/getMetadata';
|
|
4
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
5
|
+
import { $provideOpenAiAssistantExecutionToolsForServer } from '@/src/tools/$provideOpenAiAssistantExecutionToolsForServer';
|
|
6
|
+
import { Agent, computeAgentHash, PROMPTBOOK_ENGINE_VERSION } from '@promptbook-local/core';
|
|
7
|
+
import { computeHash, serializeError } from '@promptbook-local/utils';
|
|
8
|
+
import { assertsError } from '../../../../../../../../src/errors/assertsError';
|
|
9
|
+
|
|
10
|
+
export const maxDuration = 300;
|
|
11
|
+
|
|
12
|
+
export async function OPTIONS(request: Request) {
|
|
13
|
+
return new Response(null, {
|
|
14
|
+
status: 200,
|
|
15
|
+
headers: {
|
|
16
|
+
'Access-Control-Allow-Origin': '*',
|
|
17
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
18
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function POST(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
|
|
24
|
+
// Check if voice calling is enabled
|
|
25
|
+
const isVoiceCallingEnabled = (await getMetadata('IS_VOICE_CALLING_ENABLED')) === 'true';
|
|
26
|
+
if (!isVoiceCallingEnabled) {
|
|
27
|
+
return new Response(JSON.stringify({ error: 'Voice calling is disabled on this server' }), {
|
|
28
|
+
status: 403,
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let { agentName } = await params;
|
|
34
|
+
agentName = decodeURIComponent(agentName);
|
|
35
|
+
|
|
36
|
+
// Note: Parse FormData for audio file
|
|
37
|
+
const formData = await request.formData();
|
|
38
|
+
const audioFile = formData.get('audio') as File | null;
|
|
39
|
+
const threadString = formData.get('thread') as string | null;
|
|
40
|
+
const thread = threadString ? JSON.parse(threadString) : undefined;
|
|
41
|
+
const messageContext = formData.get('message') as string | null; // Optional text context or previous message?
|
|
42
|
+
|
|
43
|
+
if (!audioFile) {
|
|
44
|
+
return new Response(JSON.stringify({ error: 'No audio file provided' }), {
|
|
45
|
+
status: 400,
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const collection = await $provideAgentCollectionForServer();
|
|
52
|
+
const openAiAssistantExecutionTools = await $provideOpenAiAssistantExecutionToolsForServer();
|
|
53
|
+
const agentSource = await collection.getAgentSource(agentName);
|
|
54
|
+
const agent = new Agent({
|
|
55
|
+
isVerbose: true,
|
|
56
|
+
executionTools: {
|
|
57
|
+
llm: openAiAssistantExecutionTools,
|
|
58
|
+
},
|
|
59
|
+
agentSource,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 1. Transcribe Audio (STT)
|
|
63
|
+
const client = await openAiAssistantExecutionTools.getClient();
|
|
64
|
+
const transcription = await client.audio.transcriptions.create({
|
|
65
|
+
file: audioFile,
|
|
66
|
+
model: 'whisper-1',
|
|
67
|
+
});
|
|
68
|
+
const message = transcription.text;
|
|
69
|
+
|
|
70
|
+
// --- Common Chat Logic Start (TODO: Extract) ---
|
|
71
|
+
|
|
72
|
+
const agentHash = computeAgentHash(agentSource);
|
|
73
|
+
const userAgent = request.headers.get('user-agent');
|
|
74
|
+
const ip =
|
|
75
|
+
request.headers.get('x-forwarded-for') ||
|
|
76
|
+
request.headers.get('x-real-ip') ||
|
|
77
|
+
request.headers.get('x-client-ip');
|
|
78
|
+
const language = request.headers.get('accept-language');
|
|
79
|
+
const platform = userAgent ? userAgent.match(/\(([^)]+)\)/)?.[1] : undefined;
|
|
80
|
+
|
|
81
|
+
// Identify and Record User Message
|
|
82
|
+
const userMessageContent = {
|
|
83
|
+
role: 'USER',
|
|
84
|
+
content: message,
|
|
85
|
+
isVoiceCall: true, // Mark as voice call
|
|
86
|
+
};
|
|
87
|
+
const supabase = $provideSupabaseForServer();
|
|
88
|
+
await supabase.from(await $getTableName('ChatHistory')).insert({
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
messageHash: computeHash(userMessageContent),
|
|
91
|
+
previousMessageHash: null,
|
|
92
|
+
agentName,
|
|
93
|
+
agentHash,
|
|
94
|
+
message: userMessageContent,
|
|
95
|
+
promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
|
|
96
|
+
url: request.url,
|
|
97
|
+
ip,
|
|
98
|
+
userAgent,
|
|
99
|
+
language,
|
|
100
|
+
platform,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Call Agent
|
|
104
|
+
const response = await agent.callChatModel({
|
|
105
|
+
title: `Voice Chat with agent ${agentName}`,
|
|
106
|
+
parameters: {},
|
|
107
|
+
modelRequirements: {
|
|
108
|
+
modelVariant: 'CHAT',
|
|
109
|
+
},
|
|
110
|
+
content: message,
|
|
111
|
+
thread,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const agentMessageContent = {
|
|
115
|
+
role: 'MODEL',
|
|
116
|
+
content: response.content,
|
|
117
|
+
isVoiceCall: true,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Record Agent Message
|
|
121
|
+
await supabase.from(await $getTableName('ChatHistory')).insert({
|
|
122
|
+
createdAt: new Date().toISOString(),
|
|
123
|
+
messageHash: computeHash(agentMessageContent),
|
|
124
|
+
previousMessageHash: computeHash(userMessageContent),
|
|
125
|
+
agentName,
|
|
126
|
+
agentHash,
|
|
127
|
+
message: agentMessageContent,
|
|
128
|
+
promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
|
|
129
|
+
url: request.url,
|
|
130
|
+
ip,
|
|
131
|
+
userAgent,
|
|
132
|
+
language,
|
|
133
|
+
platform,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Learning
|
|
137
|
+
const newAgentSource = agent.agentSource.value;
|
|
138
|
+
if (newAgentSource !== agentSource) {
|
|
139
|
+
await collection.updateAgentSource(agentName, newAgentSource);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- Common Chat Logic End ---
|
|
143
|
+
|
|
144
|
+
// 2. Synthesize Audio (TTS)
|
|
145
|
+
const mp3 = await client.audio.speech.create({
|
|
146
|
+
model: 'tts-1',
|
|
147
|
+
voice: 'alloy',
|
|
148
|
+
input: response.content,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const buffer = Buffer.from(await mp3.arrayBuffer());
|
|
152
|
+
const base64Audio = buffer.toString('base64');
|
|
153
|
+
|
|
154
|
+
return new Response(
|
|
155
|
+
JSON.stringify({
|
|
156
|
+
userMessage: message,
|
|
157
|
+
agentMessage: response.content,
|
|
158
|
+
audio: base64Audio,
|
|
159
|
+
audioFormat: 'mp3',
|
|
160
|
+
}),
|
|
161
|
+
{
|
|
162
|
+
status: 200,
|
|
163
|
+
headers: {
|
|
164
|
+
'Content-Type': 'application/json',
|
|
165
|
+
'Access-Control-Allow-Origin': '*',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
assertsError(error);
|
|
171
|
+
console.error(error);
|
|
172
|
+
return new Response(JSON.stringify(serializeError(error), null, 4), {
|
|
173
|
+
status: 400,
|
|
174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -5,8 +5,11 @@ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentColle
|
|
|
5
5
|
import { isUserAdmin } from '@/src/utils/isUserAdmin';
|
|
6
6
|
import { headers } from 'next/headers';
|
|
7
7
|
import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
|
|
8
|
+
import { generateAgentMetadata } from '../generateAgentMetadata';
|
|
8
9
|
import { BookEditorWrapper } from './BookEditorWrapper';
|
|
9
10
|
|
|
11
|
+
export const generateMetadata = generateAgentMetadata;
|
|
12
|
+
|
|
10
13
|
export default async function AgentBookPage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
11
14
|
$sideEffect(headers());
|
|
12
15
|
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { ResizablePanelsAuto } from '@common/components/ResizablePanelsAuto/ResizablePanelsAuto';
|
|
4
4
|
import { string_agent_url, string_book } from '@promptbook-local/types';
|
|
5
|
-
import {
|
|
5
|
+
import { Book, MessageSquare } from 'lucide-react';
|
|
6
|
+
import { useEffect, useState } from 'react';
|
|
6
7
|
import { AgentChatWrapper } from '../AgentChatWrapper';
|
|
8
|
+
import { BookEditorWrapper } from '../book/BookEditorWrapper';
|
|
7
9
|
|
|
8
10
|
type AgentBookAndChatProps = {
|
|
9
11
|
agentName: string;
|
|
@@ -13,6 +15,56 @@ type AgentBookAndChatProps = {
|
|
|
13
15
|
|
|
14
16
|
export function AgentBookAndChat(props: AgentBookAndChatProps) {
|
|
15
17
|
const { agentName, initialAgentSource, agentUrl } = props;
|
|
18
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
19
|
+
const [activeTab, setActiveTab] = useState<'book' | 'chat'>('chat');
|
|
20
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setIsMounted(true);
|
|
24
|
+
const checkMobile = () => setIsMobile(window.innerWidth < 1024);
|
|
25
|
+
checkMobile();
|
|
26
|
+
window.addEventListener('resize', checkMobile);
|
|
27
|
+
return () => window.removeEventListener('resize', checkMobile);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
if (!isMounted) {
|
|
31
|
+
return <div className="w-full h-full bg-white" />;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isMobile) {
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex flex-col h-full w-full bg-white">
|
|
37
|
+
<div className="flex-grow overflow-hidden relative">
|
|
38
|
+
<div className={`w-full h-full ${activeTab === 'book' ? 'block' : 'hidden'}`}>
|
|
39
|
+
<BookEditorWrapper agentName={agentName} initialAgentSource={initialAgentSource} />
|
|
40
|
+
</div>
|
|
41
|
+
<div className={`w-full h-full ${activeTab === 'chat' ? 'block' : 'hidden'}`}>
|
|
42
|
+
<AgentChatWrapper agentUrl={agentUrl} />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="flex-shrink-0 h-16 bg-white border-t border-gray-200 flex shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] z-10">
|
|
46
|
+
<button
|
|
47
|
+
onClick={() => setActiveTab('book')}
|
|
48
|
+
className={`flex-1 flex flex-col items-center justify-center gap-1 transition-colors ${
|
|
49
|
+
activeTab === 'book' ? 'text-blue-600 bg-blue-50/50' : 'text-gray-500 hover:bg-gray-50'
|
|
50
|
+
}`}
|
|
51
|
+
>
|
|
52
|
+
<Book className="w-5 h-5" />
|
|
53
|
+
<span className="text-xs font-medium">Info</span>
|
|
54
|
+
</button>
|
|
55
|
+
<button
|
|
56
|
+
onClick={() => setActiveTab('chat')}
|
|
57
|
+
className={`flex-1 flex flex-col items-center justify-center gap-1 transition-colors ${
|
|
58
|
+
activeTab === 'chat' ? 'text-blue-600 bg-blue-50/50' : 'text-gray-500 hover:bg-gray-50'
|
|
59
|
+
}`}
|
|
60
|
+
>
|
|
61
|
+
<MessageSquare className="w-5 h-5" />
|
|
62
|
+
<span className="text-xs font-medium">Chat</span>
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
16
68
|
|
|
17
69
|
return (
|
|
18
70
|
<ResizablePanelsAuto name={`agent-book-and-chat-${agentName}`} className="w-full h-full">
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { $
|
|
2
|
-
import { parseAgentSource } from '@promptbook-local/core';
|
|
1
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
3
2
|
import { Metadata } from 'next';
|
|
3
|
+
import { getAgentName, getAgentProfile } from './_utils';
|
|
4
4
|
|
|
5
5
|
export async function generateAgentMetadata({ params }: { params: Promise<{ agentName: string }> }): Promise<Metadata> {
|
|
6
|
-
|
|
7
|
-
agentName =
|
|
6
|
+
const { publicUrl } = await $provideServer();
|
|
7
|
+
const agentName = await getAgentName(params);
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
|
-
const
|
|
11
|
-
const agentSource = await collection.getAgentSource(agentName);
|
|
12
|
-
const agentProfile = parseAgentSource(agentSource);
|
|
10
|
+
const agentProfile = await getAgentProfile(agentName);
|
|
13
11
|
|
|
14
12
|
const title = agentProfile.meta.fullname || agentProfile.agentName;
|
|
15
13
|
const description = agentProfile.meta.description || agentProfile.personaDescription || undefined;
|
|
@@ -17,22 +15,24 @@ export async function generateAgentMetadata({ params }: { params: Promise<{ agen
|
|
|
17
15
|
// Extract image from meta
|
|
18
16
|
const image = agentProfile.meta.image;
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
const metadata = {
|
|
19
|
+
metadataBase: publicUrl,
|
|
21
20
|
title,
|
|
22
21
|
description,
|
|
22
|
+
icons: image ? { icon: image } : undefined,
|
|
23
23
|
openGraph: {
|
|
24
24
|
title,
|
|
25
25
|
description,
|
|
26
26
|
type: 'website',
|
|
27
|
-
images: image ? [{ url: image }] : undefined,
|
|
28
27
|
},
|
|
29
28
|
twitter: {
|
|
30
29
|
card: 'summary_large_image',
|
|
31
30
|
title,
|
|
32
31
|
description,
|
|
33
|
-
images: image ? [image] : undefined,
|
|
34
32
|
},
|
|
35
|
-
};
|
|
33
|
+
} satisfies Metadata;
|
|
34
|
+
|
|
35
|
+
return metadata;
|
|
36
36
|
} catch (error) {
|
|
37
37
|
console.warn(`Failed to generate metadata for agent ${agentName}`, error);
|
|
38
38
|
return {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { RefreshCcwIcon } from 'lucide-react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { restoreAgentVersion } from './actions';
|
|
7
|
+
|
|
8
|
+
type RestoreVersionButtonProps = {
|
|
9
|
+
agentName: string;
|
|
10
|
+
historyId: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function RestoreVersionButton({ agentName, historyId }: RestoreVersionButtonProps) {
|
|
14
|
+
const router = useRouter();
|
|
15
|
+
const [isRestoring, setIsRestoring] = useState(false);
|
|
16
|
+
|
|
17
|
+
const handleRestore = async () => {
|
|
18
|
+
if (!confirm('Are you sure you want to restore this version? Current changes will be saved to history.')) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
setIsRestoring(true);
|
|
24
|
+
await restoreAgentVersion(agentName, historyId);
|
|
25
|
+
router.refresh();
|
|
26
|
+
router.push(`/agents/${agentName}`);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Failed to restore version:', error);
|
|
29
|
+
alert('Failed to restore version');
|
|
30
|
+
} finally {
|
|
31
|
+
setIsRestoring(false);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<button
|
|
37
|
+
onClick={handleRestore}
|
|
38
|
+
disabled={isRestoring}
|
|
39
|
+
className="flex items-center gap-2 px-3 py-1 text-sm bg-white border border-gray-300 rounded hover:bg-gray-50 text-gray-700 disabled:opacity-50"
|
|
40
|
+
title="Restore this version"
|
|
41
|
+
>
|
|
42
|
+
<RefreshCcwIcon className={`w-3 h-3 ${isRestoring ? 'animate-spin' : ''}`} />
|
|
43
|
+
{isRestoring ? 'Restoring...' : 'Restore'}
|
|
44
|
+
</button>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
4
|
+
import { revalidatePath } from 'next/cache';
|
|
5
|
+
|
|
6
|
+
export async function restoreAgentVersion(agentName: string, historyId: number) {
|
|
7
|
+
const collection = await $provideAgentCollectionForServer();
|
|
8
|
+
await collection.restoreAgent(historyId);
|
|
9
|
+
|
|
10
|
+
revalidatePath(`/agents/${agentName}`);
|
|
11
|
+
revalidatePath(`/agents/${agentName}/history`);
|
|
12
|
+
}
|