@promptbook/cli 0.103.0-54 → 0.103.0-55
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/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]/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/openrouter/chat/completions/route.ts +10 -0
- package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +218 -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/components/ChangePasswordDialog/ChangePasswordDialog.tsx +41 -0
- package/apps/agents-server/src/components/ChangePasswordForm/ChangePasswordForm.tsx +159 -0
- package/apps/agents-server/src/components/Header/Header.tsx +94 -38
- 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 +183 -0
- package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +93 -0
- package/esm/index.es.js +770 -181
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/_packages/core.index.d.ts +8 -6
- package/esm/typings/src/_packages/types.index.d.ts +1 -1
- package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +4 -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/IMPORTANT/IMPORTANT.d.ts +26 -0
- package/esm/typings/src/commitments/LANGUAGE/LANGUAGE.d.ts +35 -0
- package/esm/typings/src/commitments/OPEN/OPEN.d.ts +35 -0
- package/esm/typings/src/commitments/index.d.ts +1 -82
- package/esm/typings/src/commitments/registry.d.ts +68 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +770 -181
- package/umd/index.umd.js.map +1 -1
package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
2
|
+
import { resolveInheritedAgentSource } from '@/src/utils/resolveInheritedAgentSource';
|
|
2
3
|
import { createAgentModelRequirements } from '@promptbook-local/core';
|
|
3
4
|
import { serializeError } from '@promptbook-local/utils';
|
|
4
5
|
import { assertsError } from '../../../../../../../../../src/errors/assertsError';
|
|
@@ -12,7 +13,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
12
13
|
try {
|
|
13
14
|
const collection = await $provideAgentCollectionForServer();
|
|
14
15
|
const agentSource = await collection.getAgentSource(agentName);
|
|
15
|
-
const
|
|
16
|
+
const effectiveAgentSource = await resolveInheritedAgentSource(agentSource, collection);
|
|
17
|
+
const modelRequirements = await createAgentModelRequirements(effectiveAgentSource);
|
|
16
18
|
const { systemMessage } = modelRequirements;
|
|
17
19
|
|
|
18
20
|
return new Response(systemMessage, {
|
|
@@ -1,176 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Agent } from '@promptbook-local/core';
|
|
4
|
-
import { ChatMessage, ChatPromptResult, Prompt, TODO_any } from '@promptbook-local/types';
|
|
5
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
1
|
+
import { handleChatCompletion } from '@/src/utils/handleChatCompletion';
|
|
2
|
+
import { NextRequest } from 'next/server';
|
|
6
3
|
|
|
7
4
|
export async function POST(
|
|
8
5
|
request: NextRequest,
|
|
9
6
|
{ params }: { params: Promise<{ agentName: string }> },
|
|
10
7
|
) {
|
|
11
8
|
const { agentName } = await params;
|
|
12
|
-
|
|
13
|
-
// Note: Authentication is handled by middleware
|
|
14
|
-
// If we are here, the request is either authenticated or public access is allowed (but middleware blocks it if not)
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const body = await request.json();
|
|
18
|
-
const { messages, stream, model } = body;
|
|
19
|
-
|
|
20
|
-
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
21
|
-
return NextResponse.json(
|
|
22
|
-
{ error: { message: 'Messages array is required and cannot be empty.', type: 'invalid_request_error' } },
|
|
23
|
-
{ status: 400 },
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const collection = await $provideAgentCollectionForServer();
|
|
28
|
-
let agentSource;
|
|
29
|
-
try {
|
|
30
|
-
agentSource = await collection.getAgentSource(agentName);
|
|
31
|
-
} catch (error) {
|
|
32
|
-
return NextResponse.json(
|
|
33
|
-
{ error: { message: `Agent '${agentName}' not found.`, type: 'invalid_request_error' } },
|
|
34
|
-
{ status: 404 },
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (!agentSource) {
|
|
39
|
-
return NextResponse.json(
|
|
40
|
-
{ error: { message: `Agent '${agentName}' not found.`, type: 'invalid_request_error' } },
|
|
41
|
-
{ status: 404 },
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const executionTools = await $provideExecutionToolsForServer();
|
|
46
|
-
const agent = new Agent({
|
|
47
|
-
agentSource,
|
|
48
|
-
executionTools,
|
|
49
|
-
isVerbose: true, // or false
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Prepare thread and content
|
|
53
|
-
const lastMessage = messages[messages.length - 1];
|
|
54
|
-
const previousMessages = messages.slice(0, -1);
|
|
55
|
-
|
|
56
|
-
const thread: ChatMessage[] = previousMessages.map((msg: TODO_any, index: number) => ({
|
|
57
|
-
id: `msg-${index}`, // Placeholder ID
|
|
58
|
-
from: msg.role === 'assistant' ? 'agent' : 'user', // Mapping standard OpenAI roles
|
|
59
|
-
content: msg.content,
|
|
60
|
-
isComplete: true,
|
|
61
|
-
date: new Date(), // We don't have the real date, using current
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
|
-
const prompt: Prompt = {
|
|
65
|
-
title: 'OpenAI API Chat Completion',
|
|
66
|
-
content: lastMessage.content,
|
|
67
|
-
modelRequirements: {
|
|
68
|
-
modelVariant: 'CHAT',
|
|
69
|
-
// We could pass 'model' from body if we wanted to enforce it, but Agent usually has its own config
|
|
70
|
-
},
|
|
71
|
-
parameters: {},
|
|
72
|
-
thread,
|
|
73
|
-
} as Prompt;
|
|
74
|
-
// Note: Casting as Prompt because the type definition might require properties we don't strictly use or that are optional but TS complains
|
|
75
|
-
|
|
76
|
-
if (stream) {
|
|
77
|
-
const encoder = new TextEncoder();
|
|
78
|
-
const readableStream = new ReadableStream({
|
|
79
|
-
async start(controller) {
|
|
80
|
-
const runId = `chatcmpl-${Math.random().toString(36).substring(2, 15)}`;
|
|
81
|
-
const created = Math.floor(Date.now() / 1000);
|
|
82
|
-
|
|
83
|
-
let previousContent = '';
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
await agent.callChatModelStream(prompt, (chunk: ChatPromptResult) => {
|
|
87
|
-
const fullContent = chunk.content;
|
|
88
|
-
const deltaContent = fullContent.substring(previousContent.length);
|
|
89
|
-
previousContent = fullContent;
|
|
90
|
-
|
|
91
|
-
if (deltaContent) {
|
|
92
|
-
const chunkData = {
|
|
93
|
-
id: runId,
|
|
94
|
-
object: 'chat.completion.chunk',
|
|
95
|
-
created,
|
|
96
|
-
model: model || 'promptbook-agent',
|
|
97
|
-
choices: [
|
|
98
|
-
{
|
|
99
|
-
index: 0,
|
|
100
|
-
delta: {
|
|
101
|
-
content: deltaContent,
|
|
102
|
-
},
|
|
103
|
-
finish_reason: null,
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
};
|
|
107
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunkData)}\n\n`));
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const doneChunkData = {
|
|
112
|
-
id: runId,
|
|
113
|
-
object: 'chat.completion.chunk',
|
|
114
|
-
created,
|
|
115
|
-
model: model || 'promptbook-agent',
|
|
116
|
-
choices: [
|
|
117
|
-
{
|
|
118
|
-
index: 0,
|
|
119
|
-
delta: {},
|
|
120
|
-
finish_reason: 'stop',
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
};
|
|
124
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify(doneChunkData)}\n\n`));
|
|
125
|
-
controller.enqueue(encoder.encode('[DONE]'));
|
|
126
|
-
} catch (error) {
|
|
127
|
-
console.error('Error during streaming:', error);
|
|
128
|
-
// OpenAI stream doesn't usually send error JSON in stream, just closes or sends error text?
|
|
129
|
-
// But we should try to close gracefully or error.
|
|
130
|
-
controller.error(error);
|
|
131
|
-
}
|
|
132
|
-
controller.close();
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
return new Response(readableStream, {
|
|
137
|
-
headers: {
|
|
138
|
-
'Content-Type': 'text/event-stream',
|
|
139
|
-
'Cache-Control': 'no-cache',
|
|
140
|
-
Connection: 'keep-alive',
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
} else {
|
|
144
|
-
const result = await agent.callChatModel(prompt);
|
|
145
|
-
|
|
146
|
-
return NextResponse.json({
|
|
147
|
-
id: `chatcmpl-${Math.random().toString(36).substring(2, 15)}`,
|
|
148
|
-
object: 'chat.completion',
|
|
149
|
-
created: Math.floor(Date.now() / 1000),
|
|
150
|
-
model: model || 'promptbook-agent',
|
|
151
|
-
choices: [
|
|
152
|
-
{
|
|
153
|
-
index: 0,
|
|
154
|
-
message: {
|
|
155
|
-
role: 'assistant',
|
|
156
|
-
content: result.content,
|
|
157
|
-
},
|
|
158
|
-
finish_reason: 'stop',
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
usage: {
|
|
162
|
-
prompt_tokens: result.usage?.input?.tokensCount?.value || 0,
|
|
163
|
-
completion_tokens: result.usage?.output?.tokensCount?.value || 0,
|
|
164
|
-
total_tokens: (result.usage?.input?.tokensCount?.value || 0) + (result.usage?.output?.tokensCount?.value || 0),
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
} catch (error) {
|
|
170
|
-
console.error('Error in OpenAI API handler:', error);
|
|
171
|
-
return NextResponse.json(
|
|
172
|
-
{ error: { message: (error as Error).message || 'Internal Server Error', type: 'server_error' } },
|
|
173
|
-
{ status: 500 },
|
|
174
|
-
);
|
|
175
|
-
}
|
|
9
|
+
return handleChatCompletion(request, { agentName }, 'OpenAI API Chat Completion');
|
|
176
10
|
}
|
package/apps/agents-server/src/app/agents/[agentName]/api/openrouter/chat/completions/route.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { handleChatCompletion } from '@/src/utils/handleChatCompletion';
|
|
2
|
+
import { NextRequest } from 'next/server';
|
|
3
|
+
|
|
4
|
+
export async function POST(
|
|
5
|
+
request: NextRequest,
|
|
6
|
+
{ params }: { params: Promise<{ agentName: string }> },
|
|
7
|
+
) {
|
|
8
|
+
const { agentName } = await params;
|
|
9
|
+
return handleChatCompletion(request, { agentName }, 'OpenRouter API Chat Completion');
|
|
10
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
4
|
+
import { Color } from '../../../../../../../src/utils/color/Color';
|
|
5
|
+
import { withAlpha } from '../../../../../../../src/utils/color/operators/withAlpha';
|
|
6
|
+
import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
|
|
7
|
+
import { ArrowLeftIcon, BookOpenIcon, CodeIcon, HistoryIcon, HomeIcon, LinkIcon, MessageSquareIcon, ShareIcon } from 'lucide-react';
|
|
8
|
+
import { headers } from 'next/headers';
|
|
9
|
+
import Link from 'next/link';
|
|
10
|
+
import { notFound } from 'next/navigation';
|
|
11
|
+
import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
|
|
12
|
+
import { CopyField } from '../CopyField';
|
|
13
|
+
import { getAgentName, getAgentProfile } from '../_utils';
|
|
14
|
+
import { generateAgentMetadata } from '../generateAgentMetadata';
|
|
15
|
+
|
|
16
|
+
export const generateMetadata = generateAgentMetadata;
|
|
17
|
+
|
|
18
|
+
export default async function AgentLinksPage({
|
|
19
|
+
params,
|
|
20
|
+
}: {
|
|
21
|
+
params: Promise<{ agentName: string }>;
|
|
22
|
+
}) {
|
|
23
|
+
$sideEffect(headers());
|
|
24
|
+
const agentName = await getAgentName(params);
|
|
25
|
+
|
|
26
|
+
let agentProfile;
|
|
27
|
+
try {
|
|
28
|
+
agentProfile = await getAgentProfile(agentName);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (
|
|
31
|
+
error instanceof Error &&
|
|
32
|
+
(error.message.includes('Cannot coerce the result to a single JSON object') ||
|
|
33
|
+
error.message.includes('JSON object requested, multiple (or no) results returned'))
|
|
34
|
+
) {
|
|
35
|
+
notFound();
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { publicUrl } = await $provideServer();
|
|
41
|
+
const baseUrl = `${publicUrl.href}agents/${encodeURIComponent(agentName)}`;
|
|
42
|
+
|
|
43
|
+
// Extract brand color from meta
|
|
44
|
+
const brandColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
|
|
45
|
+
const backgroundColor = (await brandColor.then(withAlpha(0.05))).toHex();
|
|
46
|
+
const borderColor = (await brandColor.then(withAlpha(0.1))).toHex();
|
|
47
|
+
const primaryColor = (await brandColor).toHex();
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
className="min-h-screen p-6 md:p-12 flex flex-col items-center"
|
|
52
|
+
style={{
|
|
53
|
+
backgroundColor,
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<div className="w-full max-w-3xl bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
|
57
|
+
|
|
58
|
+
{/* Header */}
|
|
59
|
+
<div
|
|
60
|
+
className="p-6 border-b flex items-center gap-4"
|
|
61
|
+
style={{ borderColor }}
|
|
62
|
+
>
|
|
63
|
+
{agentProfile.meta.image && (
|
|
64
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
65
|
+
<img
|
|
66
|
+
src={agentProfile.meta.image as string}
|
|
67
|
+
alt={agentProfile.meta.fullname || agentName}
|
|
68
|
+
className="w-16 h-16 rounded-full object-cover border-2"
|
|
69
|
+
style={{ borderColor: primaryColor }}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
<div className="flex-1">
|
|
73
|
+
<h1 className="text-2xl font-bold text-gray-900">
|
|
74
|
+
{agentProfile.meta.fullname || agentName}
|
|
75
|
+
</h1>
|
|
76
|
+
<p className="text-gray-500 flex items-center gap-2">
|
|
77
|
+
<LinkIcon className="w-4 h-4" />
|
|
78
|
+
Signpost & Links
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
<Link
|
|
82
|
+
href={`/agents/${encodeURIComponent(agentName)}`}
|
|
83
|
+
className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors"
|
|
84
|
+
title="Back to Agent"
|
|
85
|
+
>
|
|
86
|
+
<ArrowLeftIcon className="w-6 h-6" />
|
|
87
|
+
</Link>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div className="divide-y divide-gray-100">
|
|
91
|
+
|
|
92
|
+
{/* API Endpoints */}
|
|
93
|
+
<div className="p-6">
|
|
94
|
+
<h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
|
95
|
+
<CodeIcon className="w-5 h-5 text-gray-500" />
|
|
96
|
+
API Endpoints
|
|
97
|
+
</h2>
|
|
98
|
+
<div className="grid gap-4">
|
|
99
|
+
<div>
|
|
100
|
+
<h3 className="text-sm font-medium text-gray-700 mb-1">OpenAI Compatible Chat Completion</h3>
|
|
101
|
+
<p className="text-xs text-gray-500 mb-2">Standard OpenAI API endpoint for chat completions.</p>
|
|
102
|
+
<CopyField label="Endpoint URL" value={`${baseUrl}/api/openai/chat/completions`} />
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div>
|
|
106
|
+
<h3 className="text-sm font-medium text-gray-700 mb-1">OpenRouter Compatible</h3>
|
|
107
|
+
<p className="text-xs text-gray-500 mb-2">Endpoint compatible with OpenRouter API format.</p>
|
|
108
|
+
<CopyField label="Endpoint URL" value={`${baseUrl}/api/openrouter/chat/completions`} />
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div>
|
|
112
|
+
<h3 className="text-sm font-medium text-gray-700 mb-1">Model Context Protocol (MCP)</h3>
|
|
113
|
+
<p className="text-xs text-gray-500 mb-2">Endpoint for Model Context Protocol integration.</p>
|
|
114
|
+
<CopyField label="Endpoint URL" value={`${baseUrl}/api/mcp`} />
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div>
|
|
118
|
+
<h3 className="text-sm font-medium text-gray-700 mb-1">Model Requirements</h3>
|
|
119
|
+
<p className="text-xs text-gray-500 mb-2">Get requirements and capabilities of the model.</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
|
+
<Link
|
|
133
|
+
href={`/agents/${encodeURIComponent(agentName)}`}
|
|
134
|
+
className="block p-4 rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all group bg-white"
|
|
135
|
+
>
|
|
136
|
+
<div className="flex items-center gap-3 mb-2">
|
|
137
|
+
<div className="p-2 rounded-lg bg-blue-50 text-blue-600 group-hover:bg-blue-100 transition-colors">
|
|
138
|
+
<MessageSquareIcon className="w-5 h-5" />
|
|
139
|
+
</div>
|
|
140
|
+
<span className="font-medium text-gray-900">Chat with Agent</span>
|
|
141
|
+
</div>
|
|
142
|
+
<p className="text-sm text-gray-500">
|
|
143
|
+
Direct interface to converse with {agentProfile.meta.fullname || agentName}.
|
|
144
|
+
</p>
|
|
145
|
+
</Link>
|
|
146
|
+
|
|
147
|
+
<Link
|
|
148
|
+
href={`/agents/${encodeURIComponent(agentName)}/history`}
|
|
149
|
+
className="block p-4 rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all group bg-white"
|
|
150
|
+
>
|
|
151
|
+
<div className="flex items-center gap-3 mb-2">
|
|
152
|
+
<div className="p-2 rounded-lg bg-purple-50 text-purple-600 group-hover:bg-purple-100 transition-colors">
|
|
153
|
+
<HistoryIcon className="w-5 h-5" />
|
|
154
|
+
</div>
|
|
155
|
+
<span className="font-medium text-gray-900">History & Feedback</span>
|
|
156
|
+
</div>
|
|
157
|
+
<p className="text-sm text-gray-500">
|
|
158
|
+
View past conversations and provide feedback.
|
|
159
|
+
</p>
|
|
160
|
+
</Link>
|
|
161
|
+
|
|
162
|
+
<Link
|
|
163
|
+
href={`/agents/${encodeURIComponent(agentName)}/integration`}
|
|
164
|
+
className="block p-4 rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all group bg-white"
|
|
165
|
+
>
|
|
166
|
+
<div className="flex items-center gap-3 mb-2">
|
|
167
|
+
<div className="p-2 rounded-lg bg-green-50 text-green-600 group-hover:bg-green-100 transition-colors">
|
|
168
|
+
<CodeIcon className="w-5 h-5" />
|
|
169
|
+
</div>
|
|
170
|
+
<span className="font-medium text-gray-900">Integration Guide</span>
|
|
171
|
+
</div>
|
|
172
|
+
<p className="text-sm text-gray-500">
|
|
173
|
+
Learn how to integrate this agent into your applications.
|
|
174
|
+
</p>
|
|
175
|
+
</Link>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
{/* Ecosystem */}
|
|
180
|
+
<div className="p-6 bg-gray-50">
|
|
181
|
+
<h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
|
182
|
+
<ShareIcon className="w-5 h-5 text-gray-500" />
|
|
183
|
+
Promptbook Ecosystem
|
|
184
|
+
</h2>
|
|
185
|
+
<div className="grid md:grid-cols-2 gap-4">
|
|
186
|
+
<a
|
|
187
|
+
href="https://promptbook.studio"
|
|
188
|
+
target="_blank"
|
|
189
|
+
rel="noopener noreferrer"
|
|
190
|
+
className="flex items-center gap-3 p-3 rounded-lg bg-white border border-gray-200 hover:border-gray-300 transition-colors"
|
|
191
|
+
>
|
|
192
|
+
<BookOpenIcon className="w-5 h-5 text-gray-400" />
|
|
193
|
+
<div>
|
|
194
|
+
<div className="font-medium text-gray-900">Promptbook Studio</div>
|
|
195
|
+
<div className="text-xs text-gray-500">Create and manage your own agents</div>
|
|
196
|
+
</div>
|
|
197
|
+
</a>
|
|
198
|
+
|
|
199
|
+
<a
|
|
200
|
+
href="https://github.com/webgptorg/promptbook"
|
|
201
|
+
target="_blank"
|
|
202
|
+
rel="noopener noreferrer"
|
|
203
|
+
className="flex items-center gap-3 p-3 rounded-lg bg-white border border-gray-200 hover:border-gray-300 transition-colors"
|
|
204
|
+
>
|
|
205
|
+
<CodeIcon className="w-5 h-5 text-gray-400" />
|
|
206
|
+
<div>
|
|
207
|
+
<div className="font-medium text-gray-900">GitHub Repository</div>
|
|
208
|
+
<div className="text-xs text-gray-500">Star us and contribute to the project</div>
|
|
209
|
+
</div>
|
|
210
|
+
</a>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { $getTableName } from '../../../../database/$getTableName';
|
|
3
|
+
import { $provideSupabase } from '../../../../database/$provideSupabase';
|
|
4
|
+
import { convertToCsv } from '../../../../utils/convertToCsv';
|
|
5
|
+
import { isUserAdmin } from '../../../../utils/isUserAdmin';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Export chat feedback as CSV.
|
|
9
|
+
*
|
|
10
|
+
* Query params:
|
|
11
|
+
* - agentName: filter by agent name (optional)
|
|
12
|
+
*/
|
|
13
|
+
export async function GET(request: NextRequest) {
|
|
14
|
+
if (!(await isUserAdmin())) {
|
|
15
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const searchParams = request.nextUrl.searchParams;
|
|
20
|
+
const agentName = searchParams.get('agentName');
|
|
21
|
+
|
|
22
|
+
const supabase = $provideSupabase();
|
|
23
|
+
const table = await $getTableName('ChatFeedback');
|
|
24
|
+
|
|
25
|
+
let query = supabase.from(table).select('*');
|
|
26
|
+
|
|
27
|
+
if (agentName) {
|
|
28
|
+
query = query.eq('agentName', agentName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
query = query.order('createdAt', { ascending: false });
|
|
32
|
+
|
|
33
|
+
const { data, error } = await query;
|
|
34
|
+
|
|
35
|
+
if (error) {
|
|
36
|
+
console.error('Export chat feedback error:', error);
|
|
37
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const csv = convertToCsv((data || []) as Record<string, unknown>[]);
|
|
41
|
+
const filename = agentName
|
|
42
|
+
? `chat-feedback-${agentName}-${new Date().toISOString()}.csv`
|
|
43
|
+
: `chat-feedback-${new Date().toISOString()}.csv`;
|
|
44
|
+
|
|
45
|
+
return new NextResponse(csv, {
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'text/csv',
|
|
48
|
+
'Content-Disposition': `attachment; filename="${filename}"`,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Export chat feedback error:', error);
|
|
53
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { $getTableName } from '../../../../database/$getTableName';
|
|
3
|
+
import { $provideSupabase } from '../../../../database/$provideSupabase';
|
|
4
|
+
import { convertToCsv } from '../../../../utils/convertToCsv';
|
|
5
|
+
import { isUserAdmin } from '../../../../utils/isUserAdmin';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Export chat history as CSV.
|
|
9
|
+
*
|
|
10
|
+
* Query params:
|
|
11
|
+
* - agentName: filter by agent name (optional)
|
|
12
|
+
*/
|
|
13
|
+
export async function GET(request: NextRequest) {
|
|
14
|
+
if (!(await isUserAdmin())) {
|
|
15
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const searchParams = request.nextUrl.searchParams;
|
|
20
|
+
const agentName = searchParams.get('agentName');
|
|
21
|
+
|
|
22
|
+
const supabase = $provideSupabase();
|
|
23
|
+
const table = await $getTableName('ChatHistory');
|
|
24
|
+
|
|
25
|
+
let query = supabase.from(table).select('*');
|
|
26
|
+
|
|
27
|
+
if (agentName) {
|
|
28
|
+
query = query.eq('agentName', agentName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
query = query.order('createdAt', { ascending: false });
|
|
32
|
+
|
|
33
|
+
const { data, error } = await query;
|
|
34
|
+
|
|
35
|
+
if (error) {
|
|
36
|
+
console.error('Export chat history error:', error);
|
|
37
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const csv = convertToCsv((data || []) as Record<string, unknown>[]);
|
|
41
|
+
const filename = agentName
|
|
42
|
+
? `chat-history-${agentName}-${new Date().toISOString()}.csv`
|
|
43
|
+
: `chat-history-${new Date().toISOString()}.csv`;
|
|
44
|
+
|
|
45
|
+
return new NextResponse(csv, {
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'text/csv',
|
|
48
|
+
'Content-Disposition': `attachment; filename="${filename}"`,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Export chat history error:', error);
|
|
53
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import { ChangePasswordForm } from '../ChangePasswordForm/ChangePasswordForm';
|
|
5
|
+
import { Portal } from '../Portal/Portal';
|
|
6
|
+
|
|
7
|
+
type ChangePasswordDialogProps = {
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function ChangePasswordDialog(props: ChangePasswordDialogProps) {
|
|
13
|
+
const { isOpen, onClose } = props;
|
|
14
|
+
|
|
15
|
+
if (!isOpen) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Portal>
|
|
21
|
+
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm animate-in fade-in duration-200">
|
|
22
|
+
<div className="relative w-full max-w-md bg-white rounded-lg shadow-lg border border-gray-200 p-6 animate-in zoom-in-95 duration-200">
|
|
23
|
+
<button
|
|
24
|
+
onClick={onClose}
|
|
25
|
+
className="absolute top-4 right-4 text-gray-400 hover:text-gray-500 transition-colors"
|
|
26
|
+
>
|
|
27
|
+
<X className="w-5 h-5" />
|
|
28
|
+
<span className="sr-only">Close</span>
|
|
29
|
+
</button>
|
|
30
|
+
|
|
31
|
+
<div className="mb-6">
|
|
32
|
+
<h2 className="text-xl font-semibold text-gray-900">Change Password</h2>
|
|
33
|
+
<p className="text-sm text-gray-500 mt-1">Update your password to keep your account secure</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<ChangePasswordForm onSuccess={onClose} />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</Portal>
|
|
40
|
+
);
|
|
41
|
+
}
|