+
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agents-manage-api/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).optional(),
ENVIRONMENT: z.enum(['development', 'production', 'pentest', 'test']).optional(),
AGENTS_MANAGE_API_URL: z.string().optional().default('http://localhost:3002'),
AGENTS_RUN_API_URL: z.string().optional().default('http://localhost:3003'),
DB_FILE_NAME: z.string().optional(),
TURSO_DATABASE_URL: z.string().optional(),
TURSO_AUTH_TOKEN: z.string().optional(),
Expand Down
101 changes: 97 additions & 4 deletions agents-manage-api/src/routes/dataComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import {
createApiError,
createDataComponent,
DataComponentApiInsertSchema,
DataComponentApiSelectSchema,
DataComponentApiUpdateSchema,
DataComponentListResponse,
DataComponentResponse,
deleteDataComponent,
ErrorResponseSchema,
getDataComponent,
DataComponentListResponse,
DataComponentResponse,
listDataComponentsPaginated,
PaginationQueryParamsSchema,

TenantProjectIdParamsSchema,
TenantProjectParamsSchema,
updateDataComponent,
validatePropsAsJsonSchema,
} from '@inkeep/agents-core';
import { stream } from 'hono/streaming';
import dbClient from '../data/db/dbClient';
import { env } from '../env';

const app = new OpenAPIHono();

Expand Down Expand Up @@ -263,4 +263,97 @@ app.openapi(
}
);

app.openapi(
createRoute({
method: 'post',
path: '/{id}/generate-preview',
summary: 'Generate Component Preview',
operationId: 'generate-component-preview',
tags: ['Data Component'],
request: {
params: TenantProjectIdParamsSchema,
},
responses: {
200: {
description: 'Streaming component code generation',
content: {
'text/plain': {
schema: { type: 'string' },
},
},
},
...commonGetErrorResponses,
},
}),
async (c) => {
const { tenantId, projectId, id } = c.req.valid('param');

const runApiUrl = env.AGENTS_RUN_API_URL;
const url = `${runApiUrl}/v1/${tenantId}/projects/${projectId}/data-components/${id}/generate-preview`;

try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});

if (!response.ok) {
throw createApiError({
code: 'internal_server_error',
message: `Failed to generate preview: ${response.statusText}`,
});
}

if (!response.body) {
throw createApiError({
code: 'internal_server_error',
message: 'No response body from preview generation',
});
}

c.header('Content-Type', 'text/plain; charset=utf-8');
c.header('Cache-Control', 'no-cache');
c.header('Connection', 'keep-alive');

return stream(c, async (stream) => {
const responseBody = response.body;
if (!responseBody) {
throw createApiError({
code: 'internal_server_error',
message: 'Response body is null',
});
}

const reader = responseBody.getReader();
const decoder = new TextDecoder();

try {
while (true) {
const { done, value } = await reader.read();
if (done) break;

const text = decoder.decode(value, { stream: true });
await stream.write(text);
}
} catch {
throw createApiError({
code: 'internal_server_error',
message: 'Error streaming preview generation',
});
}
}) as any;
} catch (error) {
if (error instanceof Error && 'code' in error) {
throw error;
}
throw createApiError({
code: 'internal_server_error',
message: 'Failed to generate component preview',
});
}
}
);

export default app;
15 changes: 9 additions & 6 deletions agents-manage-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@
"test:coverage": "vitest --run --coverage"
},
"dependencies": {
"monaco-editor": "0.54.0",
"@ai-sdk/react": "2.0.11",
"@codemirror/autocomplete": "^6.19.0",
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lint": "^6.8.5",
"@codemirror/view": "^6.38.4",
"@hookform/resolvers": "^5.2.1",
"@inkeep/agents-core": "workspace:^",
"@inkeep/agents-manage-api": "workspace:^",
"@inkeep/agents-run-api": "workspace:^",
"@inkeep/agents-ui": "^0.14.14",
"@inkeep/agents-ui": "^0.14.15",
"@lezer/highlight": "^1.2.1",
"@nangohq/frontend": "^0.66.0",
"@nangohq/node": "^0.66.0",
Expand All @@ -68,6 +68,7 @@
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.7",
"@sinclair/typebox": "^0.34.40",
"@types/dagre": "^0.7.53",
Expand All @@ -82,6 +83,7 @@
"dagre": "^0.8.5",
"date-fns": "^4.1.0",
"lucide-react": "^0.539.0",
"monaco-editor": "0.54.0",
"nanoid": "^5.1.5",
"next": "15.5.4",
"next-themes": "^0.4.6",
Expand All @@ -94,18 +96,17 @@
"react-error-boundary": "^6.0.0",
"react-hook-form": "^7.61.1",
"react-resizable-panels": "^3.0.5",
"react-runner": "^1.0.5",
"recharts": "2.15.4",
"remark-supersub": "^1.0.0",
"sonner": "^2.0.6",
"streamdown": "^1.0.12",
"streamdown": "^1.4.0",
"tailwind-merge": "^3.3.1",
"use-stick-to-bottom": "^1.1.1",
"zod": "^4.1.11",
"zustand": "^5.0.7"
},
"devDependencies": {
"@vitest/web-worker": "3.1.4",
"vitest-canvas-mock": "^0.3.3",
"@tailwindcss/postcss": "^4",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
Expand All @@ -114,12 +115,14 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/web-worker": "3.1.4",
"jsdom": "^26.1.0",
"tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4",
"tw-animate-css": "^1.3.6",
"typescript": "^5",
"vitest": "^3.2.4"
"vitest": "^3.2.4",
"vitest-canvas-mock": "^0.3.3"
},
"engines": {
"node": ">=22.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default async function DataComponentPage({ params }: DataComponentPagePro
);
}

const { name, description, props } = dataComponent;
const { name, description, props, preview } = dataComponent;
return (
<BodyTemplate
breadcrumbs={[
Expand All @@ -53,6 +53,7 @@ export default async function DataComponentPage({ params }: DataComponentPagePro
name,
description: description ?? '',
props,
preview,
}}
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions agents-manage-ui/src/components/agent/agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ function Flow({
tenantId={tenantId}
setShowPlayground={setShowPlayground}
closeSidePane={closeSidePane}
dataComponentLookup={dataComponentLookup}
/>
)}
</div>
Expand Down
33 changes: 30 additions & 3 deletions agents-manage-ui/src/components/agent/playground/chat-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { InkeepEmbeddedChat } from '@inkeep/agents-ui';
import type { ComponentsConfig, InkeepCallbackEvent } from '@inkeep/agents-ui/types';
import { nanoid } from 'nanoid';
import { useCallback, useEffect, useRef } from 'react';
import { DynamicComponentRenderer } from '@/components/data-components/preview/dynamic-component-renderer';
import type { ConversationDetail } from '@/components/traces/timeline/types';
import { useRuntimeConfig } from '@/contexts/runtime-config-context';
import type { DataComponent } from '@/lib/api/data-components';
import { IkpMessage as IkpMessageComponent } from './ikp-message';

interface ChatWidgetProps {
Expand All @@ -17,6 +19,7 @@ interface ChatWidgetProps {
stopPolling: () => void;
customHeaders?: Record<string, string>;
chatActivities: ConversationDetail | null;
dataComponentLookup?: Record<string, DataComponent>;
}

const styleOverrides = `
Expand Down Expand Up @@ -130,7 +133,9 @@ export function ChatWidget({
stopPolling,
customHeaders = {},
chatActivities,
dataComponentLookup = {},
}: ChatWidgetProps) {
console.log('dataComponentLookup', dataComponentLookup);
const { PUBLIC_INKEEP_AGENTS_RUN_API_URL, PUBLIC_INKEEP_AGENTS_RUN_API_BYPASS_SECRET } =
useRuntimeConfig();
const stopPollingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
Expand Down Expand Up @@ -257,9 +262,31 @@ export function ChatWidget({
Authorization: `Bearer ${PUBLIC_INKEEP_AGENTS_RUN_API_BYPASS_SECRET}`,
...customHeaders,
},
// components: {
// IkpMessage,
// },

components: new Proxy(
{},
{
get: (_, componentName) => {
const matchingComponent = Object.values(dataComponentLookup).find(
(component) => component.name === componentName
);

if (!matchingComponent) {
return undefined;
}

const Component = function Component(props: any) {
return (
<DynamicComponentRenderer
code={matchingComponent.preview?.code || ''}
props={props || {}}
/>
);
};
return Component;
},
}
),
introMessage: 'Hi! How can I help?',
}}
/>
Expand Down
27 changes: 22 additions & 5 deletions agents-manage-ui/src/components/agent/playground/ikp-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { BookOpen, Check, ChevronRight, LoaderCircle } from 'lucide-react';
import { type FC, useEffect, useRef, useState } from 'react';
import supersub from 'remark-supersub';
import { Streamdown } from 'streamdown';
import { DynamicComponentRenderer } from '@/components/data-components/preview/dynamic-component-renderer';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import type { DataComponent } from '@/lib/api/data-components';
import { cn } from '@/lib/utils';

interface IkpMessageProps {
message: Message;
isStreaming?: boolean;
renderMarkdown: (text: string) => React.ReactNode;
renderComponent: (name: string, props: any) => React.ReactNode;
dataComponentLookup?: Record<string, DataComponent>;
}

// Citation Badge Component
Expand Down Expand Up @@ -319,6 +322,7 @@ export const IkpMessage: FC<IkpMessageProps> = ({
message,
isStreaming = false,
renderMarkdown: _renderMarkdown,
dataComponentLookup = {},
}) => {
const { operations, textContent, artifacts } = useProcessedOperations(message.parts);

Expand Down Expand Up @@ -411,7 +415,13 @@ export const IkpMessage: FC<IkpMessageProps> = ({
</div>
);
} else if (group.type === 'data-component') {
// Regular data component - render as component box
// Regular data component - render with preview if available
const dataComponentId = group.data.id;
const dataComponent = dataComponentId
? dataComponentLookup[dataComponentId]
: undefined;
const hasPreview = dataComponent?.preview?.code;

return (
<div
key={`component-${index}`}
Expand All @@ -421,14 +431,21 @@ export const IkpMessage: FC<IkpMessageProps> = ({
<div className="flex items-center gap-1.5">
<div className="w-2 h-2 rounded-full bg-blue-400" />
<span className="text-xs font-medium text-gray-700 dark:text-foreground">
Component: {group.data.name || 'Unnamed'}
{group.data.name || 'Unnamed'}
</span>
</div>
</div>
<div className="p-3">
<pre className="whitespace-pre-wrap text-xs text-gray-600 dark:text-muted-foreground font-mono">
{JSON.stringify(group.data.props, null, 2)}
</pre>
{hasPreview && dataComponent.preview ? (
<DynamicComponentRenderer
code={dataComponent.preview.code}
props={group.data.props || {}}
/>
) : (
<pre className="whitespace-pre-wrap text-xs text-gray-600 dark:text-muted-foreground font-mono">
{JSON.stringify(group.data.props, null, 2)}
</pre>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TimelineWrapper } from '@/components/traces/timeline/timeline-wrapper';
import { Button } from '@/components/ui/button';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
import { useChatActivitiesPolling } from '@/hooks/use-chat-activities-polling';
import type { DataComponent } from '@/lib/api/data-components';
import { ChatWidget } from './chat-widget';
import CustomHeadersDialog from './custom-headers-dialog';

Expand All @@ -14,6 +15,7 @@ interface PlaygroundProps {
tenantId: string;
setShowPlayground: (show: boolean) => void;
closeSidePane: () => void;
dataComponentLookup?: Record<string, DataComponent>;
}

export const Playground = ({
Expand All @@ -22,6 +24,7 @@ export const Playground = ({
tenantId,
closeSidePane,
setShowPlayground,
dataComponentLookup = {},
}: PlaygroundProps) => {
const [conversationId, setConversationId] = useState<string>(nanoid());
const [customHeaders, setCustomHeaders] = useState<Record<string, string>>({});
Expand Down Expand Up @@ -83,6 +86,7 @@ export const Playground = ({
tenantId={tenantId}
customHeaders={customHeaders}
chatActivities={chatActivities}
dataComponentLookup={dataComponentLookup}
key={JSON.stringify(customHeaders)}
/>
</ResizablePanel>
Expand Down
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载