@promptbook/cli 0.103.0-54 → 0.103.0-56
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/config.ts +0 -2
- package/apps/agents-server/package-lock.json +1163 -0
- package/apps/agents-server/package.json +6 -0
- package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +79 -6
- package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +171 -69
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +3 -1
- package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +216 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +78 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileView.tsx +233 -0
- package/apps/agents-server/src/app/agents/[agentName]/CloneAgentButton.tsx +4 -4
- package/apps/agents-server/src/app/agents/[agentName]/InstallPwaButton.tsx +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/QrCodeModal.tsx +90 -0
- package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +80 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +3 -1
- package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +11 -1
- package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +203 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +3 -1
- package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -1
- package/apps/agents-server/src/app/agents/[agentName]/api/openai/chat/completions/route.ts +3 -169
- package/apps/agents-server/src/app/agents/[agentName]/api/openai/models/route.ts +93 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/openai/v1/chat/completions/route.ts +10 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/openai/v1/models/route.ts +93 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/openrouter/chat/completions/route.ts +10 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +4 -0
- package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +9 -2
- package/apps/agents-server/src/app/agents/[agentName]/integration/SdkCodeTabs.tsx +31 -0
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +271 -30
- package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +182 -0
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +108 -165
- package/apps/agents-server/src/app/agents/[agentName]/website-integration/page.tsx +61 -0
- package/apps/agents-server/src/app/api/auth/change-password/route.ts +75 -0
- package/apps/agents-server/src/app/api/chat-feedback/export/route.ts +55 -0
- package/apps/agents-server/src/app/api/chat-history/export/route.ts +55 -0
- package/apps/agents-server/src/app/api/openai/v1/chat/completions/route.ts +6 -0
- package/apps/agents-server/src/app/api/openai/v1/models/route.ts +65 -0
- package/apps/agents-server/src/app/docs/[docId]/page.tsx +12 -32
- package/apps/agents-server/src/app/docs/page.tsx +42 -17
- package/apps/agents-server/src/app/globals.css +129 -0
- package/apps/agents-server/src/app/layout.tsx +8 -2
- package/apps/agents-server/src/app/manifest.ts +1 -1
- package/apps/agents-server/src/components/ChangePasswordDialog/ChangePasswordDialog.tsx +41 -0
- package/apps/agents-server/src/components/ChangePasswordForm/ChangePasswordForm.tsx +159 -0
- package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +87 -0
- package/apps/agents-server/src/components/Header/Header.tsx +94 -38
- package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +20 -0
- package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +18 -0
- package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +18 -0
- package/apps/agents-server/src/database/migrations/2025-12-0070-chat-history-source.sql +2 -0
- package/apps/agents-server/src/database/schema.ts +6 -0
- package/apps/agents-server/src/middleware.ts +1 -1
- package/apps/agents-server/src/utils/convertToCsv.ts +31 -0
- package/apps/agents-server/src/utils/handleChatCompletion.ts +355 -0
- package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +100 -0
- package/apps/agents-server/src/utils/validateApiKey.ts +128 -0
- package/apps/agents-server/tailwind.config.ts +1 -1
- package/esm/index.es.js +1188 -175
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +4 -0
- package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +5 -0
- package/esm/typings/src/commitments/CLOSED/CLOSED.d.ts +35 -0
- package/esm/typings/src/commitments/COMPONENT/COMPONENT.d.ts +28 -0
- package/esm/typings/src/commitments/FROM/FROM.d.ts +34 -0
- package/esm/typings/src/commitments/LANGUAGE/LANGUAGE.d.ts +35 -0
- package/esm/typings/src/commitments/META_COLOR/META_COLOR.d.ts +6 -0
- package/esm/typings/src/commitments/META_FONT/META_FONT.d.ts +42 -0
- package/esm/typings/src/commitments/OPEN/OPEN.d.ts +35 -0
- package/esm/typings/src/commitments/USE/USE.d.ts +53 -0
- package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +38 -0
- package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.test.d.ts +1 -0
- package/esm/typings/src/commitments/USE_MCP/USE_MCP.d.ts +37 -0
- package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.d.ts +38 -0
- package/esm/typings/src/commitments/index.d.ts +12 -1
- package/esm/typings/src/playground/playground.d.ts +3 -0
- package/esm/typings/src/utils/color/Color.d.ts +8 -0
- package/esm/typings/src/utils/color/css-colors.d.ts +1 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +1180 -167
- package/umd/index.umd.js.map +1 -1
- package/esm/typings/src/playground/playground1.d.ts +0 -2
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
4
|
+
import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
|
|
5
|
+
import { ArrowLeftIcon, CodeIcon, HomeIcon, LinkIcon, ShareIcon } from 'lucide-react';
|
|
6
|
+
import { headers } from 'next/headers';
|
|
7
|
+
import Link from 'next/link';
|
|
8
|
+
import { notFound } from 'next/navigation';
|
|
9
|
+
import { Color } from '../../../../../../../src/utils/color/Color';
|
|
10
|
+
import { withAlpha } from '../../../../../../../src/utils/color/operators/withAlpha';
|
|
11
|
+
import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
|
|
12
|
+
import { getAgentExternalLinks, getAgentLinks } from '../agentLinks';
|
|
13
|
+
import { CopyField } from '../CopyField';
|
|
14
|
+
import { getAgentName, getAgentProfile } from '../_utils';
|
|
15
|
+
import { generateAgentMetadata } from '../generateAgentMetadata';
|
|
16
|
+
|
|
17
|
+
export const generateMetadata = generateAgentMetadata;
|
|
18
|
+
|
|
19
|
+
export default async function AgentLinksPage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
20
|
+
$sideEffect(headers());
|
|
21
|
+
const agentName = await getAgentName(params);
|
|
22
|
+
|
|
23
|
+
let agentProfile;
|
|
24
|
+
try {
|
|
25
|
+
agentProfile = await getAgentProfile(agentName);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (
|
|
28
|
+
error instanceof Error &&
|
|
29
|
+
(error.message.includes('Cannot coerce the result to a single JSON object') ||
|
|
30
|
+
error.message.includes('JSON object requested, multiple (or no) results returned'))
|
|
31
|
+
) {
|
|
32
|
+
notFound();
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { publicUrl } = await $provideServer();
|
|
38
|
+
const baseUrl = `${publicUrl.href}agents/${encodeURIComponent(agentName)}`;
|
|
39
|
+
|
|
40
|
+
// Extract brand color from meta
|
|
41
|
+
const brandColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
|
|
42
|
+
const backgroundColor = (await brandColor.then(withAlpha(0.05))).toHex();
|
|
43
|
+
const borderColor = (await brandColor.then(withAlpha(0.1))).toHex();
|
|
44
|
+
const primaryColor = (await brandColor).toHex();
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
className="min-h-screen p-6 md:p-12 flex flex-col items-center"
|
|
49
|
+
style={{
|
|
50
|
+
backgroundColor,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<div className="w-full max-w-3xl bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
|
54
|
+
{/* Header */}
|
|
55
|
+
<div className="p-6 border-b flex items-center gap-4" style={{ borderColor }}>
|
|
56
|
+
{agentProfile.meta.image && (
|
|
57
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
58
|
+
<img
|
|
59
|
+
src={agentProfile.meta.image as string}
|
|
60
|
+
alt={agentProfile.meta.fullname || agentName}
|
|
61
|
+
className="w-16 h-16 rounded-full object-cover border-2"
|
|
62
|
+
style={{ borderColor: primaryColor }}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
<div className="flex-1">
|
|
66
|
+
<h1 className="text-2xl font-bold text-gray-900">{agentProfile.meta.fullname || agentName}</h1>
|
|
67
|
+
<p className="text-gray-500 flex items-center gap-2">
|
|
68
|
+
<LinkIcon className="w-4 h-4" />
|
|
69
|
+
Signpost & Links
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
<Link
|
|
73
|
+
href={`/agents/${encodeURIComponent(agentName)}`}
|
|
74
|
+
className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors"
|
|
75
|
+
title="Back to Agent"
|
|
76
|
+
>
|
|
77
|
+
<ArrowLeftIcon className="w-6 h-6" />
|
|
78
|
+
</Link>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="divide-y divide-gray-100">
|
|
82
|
+
{/* API Endpoints */}
|
|
83
|
+
<div className="p-6">
|
|
84
|
+
<h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
|
85
|
+
<CodeIcon className="w-5 h-5 text-gray-500" />
|
|
86
|
+
API Endpoints
|
|
87
|
+
</h2>
|
|
88
|
+
<div className="grid gap-4">
|
|
89
|
+
<div>
|
|
90
|
+
<h3 className="text-sm font-medium text-gray-700 mb-1">
|
|
91
|
+
OpenAI Compatible Chat Completion
|
|
92
|
+
</h3>
|
|
93
|
+
<p className="text-xs text-gray-500 mb-2">
|
|
94
|
+
Standard OpenAI API endpoint for chat completions.
|
|
95
|
+
</p>
|
|
96
|
+
<CopyField label="Endpoint URL" value={`${baseUrl}/api/openai/v1/chat/completions`} />
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div>
|
|
100
|
+
<h3 className="text-sm font-medium text-gray-700 mb-1">OpenRouter Compatible</h3>
|
|
101
|
+
<p className="text-xs text-gray-500 mb-2">
|
|
102
|
+
Endpoint compatible with OpenRouter API format.
|
|
103
|
+
</p>
|
|
104
|
+
<CopyField label="Endpoint URL" value={`${baseUrl}/api/openrouter/chat/completions`} />
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div>
|
|
108
|
+
<h3 className="text-sm font-medium text-gray-700 mb-1">Model Context Protocol (MCP)</h3>
|
|
109
|
+
<p className="text-xs text-gray-500 mb-2">
|
|
110
|
+
Endpoint for Model Context Protocol integration.
|
|
111
|
+
</p>
|
|
112
|
+
<CopyField label="Endpoint URL" value={`${baseUrl}/api/mcp`} />
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div>
|
|
116
|
+
<h3 className="text-sm font-medium text-gray-700 mb-1">Model Requirements</h3>
|
|
117
|
+
<p className="text-xs text-gray-500 mb-2">
|
|
118
|
+
Get requirements and capabilities of the model.
|
|
119
|
+
</p>
|
|
120
|
+
<CopyField label="Endpoint URL" value={`${baseUrl}/api/modelRequirements`} />
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Agent Resources */}
|
|
126
|
+
<div className="p-6">
|
|
127
|
+
<h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
|
128
|
+
<HomeIcon className="w-5 h-5 text-gray-500" />
|
|
129
|
+
Agent Resources
|
|
130
|
+
</h2>
|
|
131
|
+
<div className="grid md:grid-cols-2 gap-4">
|
|
132
|
+
{getAgentLinks(agentName)
|
|
133
|
+
.filter((link) =>
|
|
134
|
+
['Chat with Agent', 'History & Feedback', 'Integration'].includes(link.title),
|
|
135
|
+
)
|
|
136
|
+
.map((link) => (
|
|
137
|
+
<Link
|
|
138
|
+
key={link.href}
|
|
139
|
+
href={link.href}
|
|
140
|
+
className="block p-4 rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all group bg-white"
|
|
141
|
+
>
|
|
142
|
+
<div className="flex items-center gap-3 mb-2">
|
|
143
|
+
<div className="p-2 rounded-lg bg-gray-50 text-gray-600 group-hover:bg-gray-100 transition-colors">
|
|
144
|
+
<link.icon className="w-5 h-5" />
|
|
145
|
+
</div>
|
|
146
|
+
<span className="font-medium text-gray-900">{link.title}</span>
|
|
147
|
+
</div>
|
|
148
|
+
<p className="text-sm text-gray-500">{link.description}</p>
|
|
149
|
+
</Link>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Ecosystem */}
|
|
155
|
+
<div className="p-6 bg-gray-50">
|
|
156
|
+
<h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
|
157
|
+
<ShareIcon className="w-5 h-5 text-gray-500" />
|
|
158
|
+
Promptbook Ecosystem
|
|
159
|
+
</h2>
|
|
160
|
+
<div className="grid md:grid-cols-2 gap-4">
|
|
161
|
+
{getAgentExternalLinks().map((link) => (
|
|
162
|
+
<a
|
|
163
|
+
key={link.href}
|
|
164
|
+
href={link.href}
|
|
165
|
+
target={link.target}
|
|
166
|
+
rel={link.rel}
|
|
167
|
+
className="flex items-center gap-3 p-3 rounded-lg bg-white border border-gray-200 hover:border-gray-300 transition-colors"
|
|
168
|
+
>
|
|
169
|
+
<link.icon className="w-5 h-5 text-gray-400" />
|
|
170
|
+
<div>
|
|
171
|
+
<div className="font-medium text-gray-900">{link.title}</div>
|
|
172
|
+
<div className="text-xs text-gray-500">{link.description}</div>
|
|
173
|
+
</div>
|
|
174
|
+
</a>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
@@ -1,45 +1,21 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
|
-
// import { BookEditor } from '@promptbook-local/components';
|
|
4
3
|
import { $provideServer } from '@/src/tools/$provideServer';
|
|
4
|
+
import { isUserAdmin } from '@/src/utils/isUserAdmin';
|
|
5
5
|
import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
|
|
6
|
-
import { CodeIcon, HistoryIcon, MessageCircleQuestionIcon, MessageSquareIcon, NotebookPenIcon } from 'lucide-react';
|
|
7
|
-
import { headers } from 'next/headers';
|
|
8
6
|
import { notFound } from 'next/navigation';
|
|
7
|
+
import spaceTrim from 'spacetrim';
|
|
9
8
|
import { Color } from '../../../../../../src/utils/color/Color';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { AgentQrCode } from './AgentQrCode';
|
|
15
|
-
import { CopyField } from './CopyField';
|
|
9
|
+
import { darken } from '../../../../../../src/utils/color/operators/darken';
|
|
10
|
+
import { lighten } from '../../../../../../src/utils/color/operators/lighten';
|
|
11
|
+
import { getAgentName, getAgentProfile } from './_utils';
|
|
12
|
+
import { AgentProfileView } from './AgentProfileView';
|
|
16
13
|
import { generateAgentMetadata } from './generateAgentMetadata';
|
|
17
|
-
import { InstallPwaButton } from './InstallPwaButton';
|
|
18
14
|
import { ServiceWorkerRegister } from './ServiceWorkerRegister';
|
|
19
|
-
import { ClearAgentChatHistoryButton } from './ClearAgentChatHistoryButton';
|
|
20
|
-
import { ClearAgentChatFeedbackButton } from './ClearAgentChatFeedbackButton';
|
|
21
|
-
import { CloneAgentButton } from './CloneAgentButton';
|
|
22
|
-
import { isUserAdmin } from '../../../utils/isUserAdmin';
|
|
23
|
-
// import { Agent } from '@promptbook-local/core';
|
|
24
|
-
// import { RemoteLlmExecutionTools } from '@promptbook-local/remote-client';
|
|
25
|
-
// import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
|
|
26
15
|
|
|
27
16
|
export const generateMetadata = generateAgentMetadata;
|
|
28
17
|
|
|
29
|
-
export default async function AgentPage({
|
|
30
|
-
params,
|
|
31
|
-
searchParams,
|
|
32
|
-
}: {
|
|
33
|
-
params: Promise<{ agentName: string }>;
|
|
34
|
-
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
|
35
|
-
}) {
|
|
36
|
-
// const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
|
|
37
|
-
// const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
|
|
38
|
-
// const [isApiKeySectionCollapsed, setIsApiKeySectionCollapsed] = useState(!!apiKey);
|
|
39
|
-
|
|
40
|
-
$sideEffect(headers());
|
|
41
|
-
|
|
42
|
-
const { message } = await searchParams;
|
|
18
|
+
export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
43
19
|
const agentName = await getAgentName(params);
|
|
44
20
|
const isAdmin = await isUserAdmin();
|
|
45
21
|
|
|
@@ -66,145 +42,112 @@ export default async function AgentPage({
|
|
|
66
42
|
|
|
67
43
|
const agentEmail = `${agentName}@${publicUrl.hostname}`;
|
|
68
44
|
|
|
69
|
-
|
|
45
|
+
// Extract brand color from meta and create color variations
|
|
46
|
+
const brandColorString = agentProfile.meta.color || PROMPTBOOK_COLOR.toHex();
|
|
47
|
+
const brandColors = brandColorString.split(',').map((c) => Color.fromSafe(c.trim()));
|
|
70
48
|
|
|
71
|
-
//
|
|
72
|
-
|
|
49
|
+
// Ensure at least one color
|
|
50
|
+
if (brandColors.length === 0) {
|
|
51
|
+
brandColors.push(PROMPTBOOK_COLOR);
|
|
52
|
+
}
|
|
73
53
|
|
|
74
|
-
|
|
75
|
-
const
|
|
54
|
+
const brandColor = brandColors[0]!;
|
|
55
|
+
const brandColorHex = brandColor.toHex();
|
|
56
|
+
const brandColorLightHex = brandColor.then(lighten(0.2)).toHex();
|
|
57
|
+
const brandColorDarkHex = brandColor.then(darken(0.15)).toHex();
|
|
58
|
+
const brandColorsHex = brandColors.map((c) => c.toHex());
|
|
59
|
+
|
|
60
|
+
// Generate Noisy SVG Background
|
|
61
|
+
const color1 = brandColors[0]!;
|
|
62
|
+
const color2 = brandColors[1] || brandColors[0]!; // Use secondary color or fallback to primary
|
|
63
|
+
|
|
64
|
+
// [🧠] Make colors much lighter for the background as per feedback
|
|
65
|
+
const color1Light = color1.then(lighten(0.3)).toHex();
|
|
66
|
+
const color1Main = color1.toHex();
|
|
67
|
+
const color1Dark = color1.then(darken(0.3)).toHex();
|
|
68
|
+
|
|
69
|
+
const color2Light = color2.then(lighten(0.3)).toHex();
|
|
70
|
+
const color2Main = color2.toHex();
|
|
71
|
+
const color2Dark = color2.then(darken(0.3)).toHex();
|
|
72
|
+
|
|
73
|
+
const svgContent = spaceTrim(`
|
|
74
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
75
|
+
viewBox="0 0 1920 1080"
|
|
76
|
+
width="1920" height="1080"
|
|
77
|
+
preserveAspectRatio="xMidYMid slice">
|
|
78
|
+
<defs>
|
|
79
|
+
<!-- Bottom-left -->
|
|
80
|
+
<radialGradient id="grad1" cx="0%" cy="100%" r="90%">
|
|
81
|
+
<stop offset="0%" stop-color="${color1Light}" />
|
|
82
|
+
<stop offset="50%" stop-color="${color1Main}" />
|
|
83
|
+
<stop offset="100%" stop-color="${color1Dark}" />
|
|
84
|
+
</radialGradient>
|
|
85
|
+
|
|
86
|
+
<!-- Bottom-right -->
|
|
87
|
+
<radialGradient id="grad2" cx="100%" cy="100%" r="90%">
|
|
88
|
+
<stop offset="0%" stop-color="${color2Light}" />
|
|
89
|
+
<stop offset="50%" stop-color="${color2Main}" />
|
|
90
|
+
<stop offset="100%" stop-color="${color2Dark}" />
|
|
91
|
+
</radialGradient>
|
|
92
|
+
|
|
93
|
+
<!-- White top fade -->
|
|
94
|
+
<linearGradient id="whiteTopGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
95
|
+
<stop offset="0%" stop-color="#ffffff" stop-opacity="1" />
|
|
96
|
+
<stop offset="100%" stop-color="#ffffff" stop-opacity="0.3" />
|
|
97
|
+
</linearGradient>
|
|
98
|
+
|
|
99
|
+
<!-- Strong grain -->
|
|
100
|
+
<filter id="grain" x="-10%" y="-10%" width="120%" height="120%">
|
|
101
|
+
<feTurbulence type="fractalNoise" baseFrequency="3.5" numOctaves="3" seed="8" result="noise" />
|
|
102
|
+
<feComponentTransfer>
|
|
103
|
+
<feFuncR type="linear" slope="3.5" intercept="-1.2" />
|
|
104
|
+
<feFuncG type="linear" slope="3.5" intercept="-1.2" />
|
|
105
|
+
<feFuncB type="linear" slope="3.5" intercept="-1.2" />
|
|
106
|
+
<feFuncA type="table" tableValues="0 0.8" />
|
|
107
|
+
</feComponentTransfer>
|
|
108
|
+
</filter>
|
|
109
|
+
</defs>
|
|
110
|
+
|
|
111
|
+
<!-- White base -->
|
|
112
|
+
<rect width="100%" height="100%" fill="#ffffff" />
|
|
113
|
+
|
|
114
|
+
<!-- Gradients -->
|
|
115
|
+
<rect width="100%" height="100%" fill="url(#grad1)" />
|
|
116
|
+
<rect width="100%" height="100%" fill="url(#grad2)" style="mix-blend-mode:screen; opacity:0.85" />
|
|
117
|
+
|
|
118
|
+
<!-- White fade on top -->
|
|
119
|
+
<rect width="100%" height="100%" fill="url(#whiteTopGrad)" />
|
|
120
|
+
|
|
121
|
+
<!-- Strong visible noise -->
|
|
122
|
+
<rect width="100%" height="100%" filter="url(#grain)"
|
|
123
|
+
style="mix-blend-mode:soft-light; opacity:1.2" />
|
|
124
|
+
</svg>
|
|
125
|
+
`);
|
|
126
|
+
|
|
127
|
+
const backgroundImage = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
|
|
128
|
+
|
|
129
|
+
const fullname = (agentProfile.meta.fullname || agentProfile.agentName || 'Agent') as string;
|
|
130
|
+
const imageUrl = (agentProfile.meta.image as string) || null;
|
|
76
131
|
|
|
77
132
|
return (
|
|
78
|
-
|
|
133
|
+
<>
|
|
79
134
|
<ServiceWorkerRegister scope={`/agents/${encodeURIComponent(agentName)}/`} />
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
height={64}
|
|
97
|
-
className="rounded-full object-cover border-2 aspect-square w-16 h-16"
|
|
98
|
-
style={{ borderColor: brandColor.toHex() }}
|
|
99
|
-
/>
|
|
100
|
-
)}
|
|
101
|
-
<div className="flex-1">
|
|
102
|
-
<h1 className="text-3xl font-bold text-gray-900 break-words">
|
|
103
|
-
{agentProfile.meta.fullname || agentProfile.agentName}
|
|
104
|
-
</h1>
|
|
105
|
-
<span
|
|
106
|
-
className="inline-block mt-1 px-2 py-1 rounded text-xs font-semibold text-white"
|
|
107
|
-
style={{ backgroundColor: brandColor.toHex() }}
|
|
108
|
-
>
|
|
109
|
-
Agent
|
|
110
|
-
</span>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
<p className="text-gray-700">{agentProfile.personaDescription}</p>
|
|
115
|
-
|
|
116
|
-
<div className="flex flex-col gap-2">
|
|
117
|
-
<h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Capabilities</h2>
|
|
118
|
-
<div className="flex flex-wrap gap-2">
|
|
119
|
-
{agentActions.map((action) => (
|
|
120
|
-
<span
|
|
121
|
-
key={action}
|
|
122
|
-
className="px-3 py-1 bg-white text-gray-700 rounded-full text-xs font-medium border border-gray-200 shadow-sm"
|
|
123
|
-
>
|
|
124
|
-
{action}
|
|
125
|
-
</span>
|
|
126
|
-
))}
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
<div className="flex flex-col gap-3 mt-auto">
|
|
131
|
-
<div className="flex gap-2">
|
|
132
|
-
<a
|
|
133
|
-
href={`/agents/${encodeURIComponent(agentName)}/book+chat`}
|
|
134
|
-
// <- TODO: [🧠] Can I append path like this on current browser URL in href?
|
|
135
|
-
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"
|
|
136
|
-
>
|
|
137
|
-
<NotebookPenIcon className="ml-2 w-4 h-4 mr-2" />
|
|
138
|
-
Edit
|
|
139
|
-
</a>
|
|
140
|
-
<a
|
|
141
|
-
href={`/agents/${encodeURIComponent(agentName)}/integration`}
|
|
142
|
-
// <- TODO: [🧠] Can I append path like this on current browser URL in href?
|
|
143
|
-
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"
|
|
144
|
-
>
|
|
145
|
-
<CodeIcon className="ml-2 w-4 h-4 mr-2" />
|
|
146
|
-
Integration
|
|
147
|
-
</a>
|
|
148
|
-
<a
|
|
149
|
-
href={`/agents/${encodeURIComponent(agentName)}/history`}
|
|
150
|
-
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"
|
|
151
|
-
>
|
|
152
|
-
<HistoryIcon className="ml-2 w-4 h-4 mr-2" />
|
|
153
|
-
History
|
|
154
|
-
</a>
|
|
155
|
-
{isAdmin && <CloneAgentButton agentName={agentName} />}
|
|
156
|
-
<InstallPwaButton />
|
|
157
|
-
</div>
|
|
158
|
-
|
|
159
|
-
{isAdmin && (
|
|
160
|
-
<div className="border-t border-dashed border-gray-300 pt-3">
|
|
161
|
-
<h2 className="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-600">
|
|
162
|
-
Maintenance
|
|
163
|
-
</h2>
|
|
164
|
-
<div className="flex flex-col gap-2">
|
|
165
|
-
<a
|
|
166
|
-
href={`/admin/chat-history?agentName=${encodeURIComponent(agentName)}`}
|
|
167
|
-
className="inline-flex items-center justify-center whitespace-nowrap rounded-md border border-gray-300 bg-white px-3 py-1.5 text-xs font-semibold text-gray-700 shadow-sm hover:bg-gray-50"
|
|
168
|
-
>
|
|
169
|
-
<MessageSquareIcon className="mr-2 w-3 h-3" />
|
|
170
|
-
Chat history
|
|
171
|
-
</a>
|
|
172
|
-
<a
|
|
173
|
-
href={`/admin/chat-feedback?agentName=${encodeURIComponent(agentName)}`}
|
|
174
|
-
className="inline-flex items-center justify-center whitespace-nowrap rounded-md border border-gray-300 bg-white px-3 py-1.5 text-xs font-semibold text-gray-700 shadow-sm hover:bg-gray-50"
|
|
175
|
-
>
|
|
176
|
-
<MessageCircleQuestionIcon className="mr-2 w-3 h-3" />
|
|
177
|
-
Chat feedback
|
|
178
|
-
</a>
|
|
179
|
-
<ClearAgentChatHistoryButton agentName={agentName} />
|
|
180
|
-
<ClearAgentChatFeedbackButton agentName={agentName} />
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
)}
|
|
184
|
-
</div>
|
|
185
|
-
|
|
186
|
-
<div className="flex flex-col items-center gap-4 pt-6 border-t border-gray-200 w-full">
|
|
187
|
-
<div className="bg-white rounded-lg p-4 flex flex-col items-center shadow-sm border border-gray-100">
|
|
188
|
-
<AgentQrCode
|
|
189
|
-
agentName={agentProfile.agentName || 'Agent'}
|
|
190
|
-
meta={agentProfile.meta}
|
|
191
|
-
personaDescription={agentProfile.personaDescription}
|
|
192
|
-
agentUrl={agentUrl}
|
|
193
|
-
agentEmail={agentEmail}
|
|
194
|
-
/>
|
|
195
|
-
</div>
|
|
196
|
-
<div className="flex flex-col gap-2 w-full">
|
|
197
|
-
<CopyField label="Agent Page URL" value={agentUrl} />
|
|
198
|
-
<CopyField label="Agent Email" value={agentEmail} />
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
{/* Main content: Chat */}
|
|
204
|
-
<div className="flex-1 relative h-full bg-white">
|
|
205
|
-
<AgentChatWrapper agentUrl={agentUrl} defaultMessage={message as string} />
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
135
|
+
<AgentProfileView
|
|
136
|
+
agentName={agentName}
|
|
137
|
+
fullname={fullname}
|
|
138
|
+
personaDescription={agentProfile.personaDescription || ''}
|
|
139
|
+
imageUrl={imageUrl}
|
|
140
|
+
agentUrl={agentUrl}
|
|
141
|
+
agentEmail={agentEmail}
|
|
142
|
+
brandColorHex={brandColorHex}
|
|
143
|
+
brandColorLightHex={brandColorLightHex}
|
|
144
|
+
brandColorDarkHex={brandColorDarkHex}
|
|
145
|
+
brandColorsHex={brandColorsHex}
|
|
146
|
+
backgroundImage={backgroundImage}
|
|
147
|
+
meta={agentProfile.meta}
|
|
148
|
+
isAdmin={isAdmin}
|
|
149
|
+
/>
|
|
150
|
+
</>
|
|
208
151
|
);
|
|
209
152
|
}
|
|
210
153
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
4
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
5
|
+
import { PromptbookAgent } from '@promptbook-local/components';
|
|
6
|
+
import { parseAgentSource } from '@promptbook-local/core';
|
|
7
|
+
import { headers } from 'next/headers';
|
|
8
|
+
import spaceTrim from 'spacetrim';
|
|
9
|
+
import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
|
|
10
|
+
import { CodePreview } from '../../../../../../_common/components/CodePreview/CodePreview';
|
|
11
|
+
import { generateAgentMetadata } from '../generateAgentMetadata';
|
|
12
|
+
|
|
13
|
+
export const generateMetadata = generateAgentMetadata;
|
|
14
|
+
|
|
15
|
+
export default async function WebsiteIntegrationAgentPage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
16
|
+
$sideEffect(headers());
|
|
17
|
+
let { agentName } = await params;
|
|
18
|
+
agentName = decodeURIComponent(agentName);
|
|
19
|
+
|
|
20
|
+
const collection = await $provideAgentCollectionForServer();
|
|
21
|
+
const agentSource = await collection.getAgentSource(agentName);
|
|
22
|
+
const { meta } = parseAgentSource(agentSource);
|
|
23
|
+
const { fullname, color, image, ...restMeta } = meta;
|
|
24
|
+
const { publicUrl } = await $provideServer();
|
|
25
|
+
const agentUrl = `${publicUrl.href}agents/${encodeURIComponent(agentName)}`;
|
|
26
|
+
|
|
27
|
+
const code = spaceTrim(
|
|
28
|
+
(block) => `
|
|
29
|
+
|
|
30
|
+
import { PromptbookAgent } from '@promptbook/components';
|
|
31
|
+
|
|
32
|
+
export function YourComponent() {
|
|
33
|
+
return(
|
|
34
|
+
<PromptbookAgent
|
|
35
|
+
agentUrl="${agentUrl}"
|
|
36
|
+
meta={${block(JSON.stringify({ fullname, color, image, ...restMeta }, null, 4))}}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
`,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<main className="w-screen h-screen p-4">
|
|
46
|
+
<h1 className="text-2xl font-bold p-4 border-b">{meta.fullname || agentName} Integration Code</h1>
|
|
47
|
+
<p className="mt-4 mb-8 text-gray-600">
|
|
48
|
+
Use the following code to integrate the <strong>{meta.fullname || agentName}</strong> agent into your
|
|
49
|
+
React application using the <code>{'<PromptbookAgent />'}</code> component.
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<CodePreview code={code} />
|
|
53
|
+
<PromptbookAgent agentUrl={agentUrl} meta={meta} />
|
|
54
|
+
</main>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* TODO: Make this page better, bring from Promptbook.Studio
|
|
60
|
+
* TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
|
|
61
|
+
*/
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { $getTableName } from '../../../../database/$getTableName';
|
|
3
|
+
import { $provideSupabaseForServer } from '../../../../database/$provideSupabaseForServer';
|
|
4
|
+
import { AgentsServerDatabase } from '../../../../database/schema';
|
|
5
|
+
import { hashPassword, verifyPassword } from '../../../../utils/auth';
|
|
6
|
+
import { getCurrentUser } from '../../../../utils/getCurrentUser';
|
|
7
|
+
|
|
8
|
+
export async function POST(request: Request) {
|
|
9
|
+
try {
|
|
10
|
+
const user = await getCurrentUser();
|
|
11
|
+
if (!user) {
|
|
12
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const body = await request.json();
|
|
16
|
+
const { currentPassword, newPassword } = body;
|
|
17
|
+
|
|
18
|
+
if (!currentPassword || !newPassword) {
|
|
19
|
+
return NextResponse.json({ error: 'Missing password fields' }, { status: 400 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Special check for environment admin
|
|
23
|
+
if (user.username === 'admin' && process.env.ADMIN_PASSWORD) {
|
|
24
|
+
// Environment admin cannot change password through this API
|
|
25
|
+
// They must change the env variable
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{
|
|
28
|
+
error: 'You cannot change the admin password. Please update the `ADMIN_PASSWORD` environment variable.',
|
|
29
|
+
},
|
|
30
|
+
{ status: 403 },
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const supabase = $provideSupabaseForServer();
|
|
35
|
+
const { data: userData, error: fetchError } = await supabase
|
|
36
|
+
.from(await $getTableName('User'))
|
|
37
|
+
.select('*')
|
|
38
|
+
.eq('username', user.username)
|
|
39
|
+
.single();
|
|
40
|
+
|
|
41
|
+
if (fetchError || !userData) {
|
|
42
|
+
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const userRow = userData as AgentsServerDatabase['public']['Tables']['User']['Row'];
|
|
46
|
+
|
|
47
|
+
// Verify current password
|
|
48
|
+
const isValid = await verifyPassword(currentPassword, userRow.passwordHash);
|
|
49
|
+
if (!isValid) {
|
|
50
|
+
return NextResponse.json({ error: 'Invalid current password' }, { status: 401 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Hash new password
|
|
54
|
+
const newPasswordHash = await hashPassword(newPassword);
|
|
55
|
+
|
|
56
|
+
// Update password
|
|
57
|
+
const { error: updateError } = await supabase
|
|
58
|
+
.from(await $getTableName('User'))
|
|
59
|
+
.update({
|
|
60
|
+
passwordHash: newPasswordHash,
|
|
61
|
+
updatedAt: new Date().toISOString(),
|
|
62
|
+
})
|
|
63
|
+
.eq('id', userRow.id);
|
|
64
|
+
|
|
65
|
+
if (updateError) {
|
|
66
|
+
console.error('Error updating password:', updateError);
|
|
67
|
+
return NextResponse.json({ error: 'Failed to update password' }, { status: 500 });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return NextResponse.json({ success: true });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Change password error:', error);
|
|
73
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
74
|
+
}
|
|
75
|
+
}
|