@admin-layout/tailwind-chat-pro 12.2.4-alpha.102
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/CHANGELOG.md +77 -0
- package/LICENSE +21 -0
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/messages-container-ui/DefaultInputToolBar.d.ts +21 -0
- package/lib/components/messages-container-ui/DefaultInputToolBar.d.ts.map +1 -0
- package/lib/components/messages-container-ui/DefaultInputToolBar.js +46 -0
- package/lib/components/messages-container-ui/DefaultInputToolBar.js.map +1 -0
- package/lib/components/messages-container-ui/DefaultMessageGroup.d.ts +16 -0
- package/lib/components/messages-container-ui/DefaultMessageGroup.d.ts.map +1 -0
- package/lib/components/messages-container-ui/DefaultMessageGroup.js +64 -0
- package/lib/components/messages-container-ui/DefaultMessageGroup.js.map +1 -0
- package/lib/components/messages-container-ui/ErrorFixCard.d.ts +10 -0
- package/lib/components/messages-container-ui/ErrorFixCard.d.ts.map +1 -0
- package/lib/components/messages-container-ui/ErrorFixCard.js +219 -0
- package/lib/components/messages-container-ui/ErrorFixCard.js.map +1 -0
- package/lib/components/messages-container-ui/FilesList.d.ts +19 -0
- package/lib/components/messages-container-ui/FilesList.d.ts.map +1 -0
- package/lib/components/messages-container-ui/FilesList.js +75 -0
- package/lib/components/messages-container-ui/FilesList.js.map +1 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.d.ts +98 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.d.ts.map +1 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.js +88 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.js.map +1 -0
- package/lib/components/messages-container-ui/ModernMessageGroup.d.ts +46 -0
- package/lib/components/messages-container-ui/ModernMessageGroup.d.ts.map +1 -0
- package/lib/components/messages-container-ui/ModernMessageGroup.js +1568 -0
- package/lib/components/messages-container-ui/ModernMessageGroup.js.map +1 -0
- package/lib/components/messages-container-ui/PlanModeView.d.ts +107 -0
- package/lib/components/messages-container-ui/PlanModeView.d.ts.map +1 -0
- package/lib/components/messages-container-ui/PlanModeView.js +294 -0
- package/lib/components/messages-container-ui/PlanModeView.js.map +1 -0
- package/lib/components/messages-container-ui/index.d.ts +12 -0
- package/lib/components/messages-container-ui/index.d.ts.map +1 -0
- package/lib/components/messages-container-ui/messenger-slot-fill-name-enum.d.ts +16 -0
- package/lib/components/messages-container-ui/messenger-slot-fill-name-enum.d.ts.map +1 -0
- package/lib/components/messages-container-ui/messenger-slot-fill-name-enum.js +16 -0
- package/lib/components/messages-container-ui/messenger-slot-fill-name-enum.js.map +1 -0
- package/lib/components/messages-container-ui/modernStreaming.d.ts +11 -0
- package/lib/components/messages-container-ui/modernStreaming.d.ts.map +1 -0
- package/lib/components/messages-container-ui/modernStreaming.js +17 -0
- package/lib/components/messages-container-ui/modernStreaming.js.map +1 -0
- package/lib/components/messages-container-ui/types.d.ts +63 -0
- package/lib/components/messages-container-ui/types.d.ts.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -0
- package/lib/routes.json +1 -0
- package/package.json +48 -0
- package/rollup.config.mjs +34 -0
- package/src/components/index.ts +1 -0
- package/src/components/messages-container-ui/DefaultInputToolBar.tsx +63 -0
- package/src/components/messages-container-ui/DefaultMessageGroup.tsx +75 -0
- package/src/components/messages-container-ui/ErrorFixCard.tsx +239 -0
- package/src/components/messages-container-ui/FilesList.tsx +89 -0
- package/src/components/messages-container-ui/MessagesContainerUI.tsx +178 -0
- package/src/components/messages-container-ui/ModernMessageGroup.tsx +1885 -0
- package/src/components/messages-container-ui/PlanModeView.tsx +457 -0
- package/src/components/messages-container-ui/index.ts +18 -0
- package/src/components/messages-container-ui/messenger-slot-fill-name-enum.ts +15 -0
- package/src/components/messages-container-ui/modernStreaming.tsx +18 -0
- package/src/components/messages-container-ui/types.ts +68 -0
- package/src/index.ts +1 -0
- package/tsconfig.json +27 -0
package/lib/routes.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@admin-layout/tailwind-chat-pro",
|
|
3
|
+
"version": "12.2.4-alpha.102",
|
|
4
|
+
"description": "Customizable base chat / messages container UI (plan mode). The message renderer is injectable, so consumers plug in their own message-group component.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "CDMBase LLC",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "lib/index.js",
|
|
9
|
+
"module": "lib/index.js",
|
|
10
|
+
"typings": "lib/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "yarn build:clean && yarn build:lib",
|
|
13
|
+
"build:clean": "rimraf lib",
|
|
14
|
+
"build:lib": "rollup -c rollup.config.mjs",
|
|
15
|
+
"build:lib:watch": "yarn build:lib -- --watch",
|
|
16
|
+
"jest": "./node_modules/.bin/jest",
|
|
17
|
+
"prepublish": "yarn build",
|
|
18
|
+
"test": "cross-env ENV_FILE=../../config/test/test.env jest",
|
|
19
|
+
"test:debug": "npm test -- --runInBand",
|
|
20
|
+
"test:watch": "npm test -- --watch",
|
|
21
|
+
"watch": "yarn build:lib:watch"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@admin-layout/tailwind-ui": "12.2.4-alpha.102",
|
|
25
|
+
"clsx": "^2.1.1",
|
|
26
|
+
"date-fns": "^4.1.0",
|
|
27
|
+
"marked": "7.0.5",
|
|
28
|
+
"react-markdown": "^10.1.0",
|
|
29
|
+
"remark-breaks": "^4.0.0",
|
|
30
|
+
"remark-gfm": "^4.0.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"common": "12.2.4-alpha.85"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@admin-layout/tailwind-ui": "12.2.4-alpha.98",
|
|
37
|
+
"@common-stack/components-pro": "*",
|
|
38
|
+
"common": "12.2.4-alpha.85",
|
|
39
|
+
"react": ">=16.14.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"gitHead": "ed86766ff49c78ad32dad10b534d477bead62e9d",
|
|
45
|
+
"typescript": {
|
|
46
|
+
"definition": "lib/index.d.ts"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createRollupConfig } from '../../../rollup.config.base.mjs';
|
|
2
|
+
|
|
3
|
+
const additionalPlugins = [];
|
|
4
|
+
|
|
5
|
+
export default (commandLineArgs) => {
|
|
6
|
+
const isWatchMode = commandLineArgs.watch;
|
|
7
|
+
return [
|
|
8
|
+
createRollupConfig(
|
|
9
|
+
{
|
|
10
|
+
input: ['src/index.ts'],
|
|
11
|
+
plugins: [
|
|
12
|
+
...additionalPlugins,
|
|
13
|
+
],
|
|
14
|
+
output: [
|
|
15
|
+
{
|
|
16
|
+
dir: 'lib',
|
|
17
|
+
format: 'es',
|
|
18
|
+
name: 'tailwind-chat-pro',
|
|
19
|
+
compact: true,
|
|
20
|
+
exports: 'named',
|
|
21
|
+
sourcemap: true,
|
|
22
|
+
preserveModules: true,
|
|
23
|
+
chunkFileNames: '[name]-[hash].[format].js',
|
|
24
|
+
globals: { react: 'React' },
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
isWatchMode,
|
|
30
|
+
generateRoutesJSON: true,
|
|
31
|
+
},
|
|
32
|
+
),
|
|
33
|
+
];
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './messages-container-ui';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { InputToolBar } from '@admin-layout/tailwind-ui';
|
|
3
|
+
import type { PlanModeViewInputToolbarProps } from './types';
|
|
4
|
+
|
|
5
|
+
export interface DefaultInputToolBarProps extends PlanModeViewInputToolbarProps {
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
/** When true, the send button shows a stop affordance (wired to onStop). */
|
|
8
|
+
isLoading?: boolean;
|
|
9
|
+
onStop?: () => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* DefaultInputToolBar — the input composer used when no `renderInputToolbar` is
|
|
15
|
+
* provided. It wraps `@admin-layout/tailwind-ui`'s `InputToolBar` (the shared
|
|
16
|
+
* "ai-tool-box") wired to the base's toolbar props: a built-in textarea
|
|
17
|
+
* (Enter sends, Shift+Enter newlines) plus the mic/send/stop button.
|
|
18
|
+
*
|
|
19
|
+
* Consumers that need the richer composer (mode switcher, templates, attachments,
|
|
20
|
+
* project settings, …) pass their own `renderInputToolbar` instead.
|
|
21
|
+
*/
|
|
22
|
+
export const DefaultInputToolBar: React.FC<DefaultInputToolBarProps> = ({
|
|
23
|
+
value,
|
|
24
|
+
onChange,
|
|
25
|
+
onSend,
|
|
26
|
+
disabled = false,
|
|
27
|
+
placeholder = 'Message',
|
|
28
|
+
isLoading = false,
|
|
29
|
+
onStop,
|
|
30
|
+
className,
|
|
31
|
+
}) => {
|
|
32
|
+
const hasContent = value.trim().length > 0;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<InputToolBar
|
|
36
|
+
className={className}
|
|
37
|
+
inputConfig={{
|
|
38
|
+
value,
|
|
39
|
+
onChange: (e) => onChange(e.target.value),
|
|
40
|
+
placeholder,
|
|
41
|
+
disabled,
|
|
42
|
+
onKeyDown: (e) => {
|
|
43
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
if (hasContent && !disabled && !isLoading) onSend();
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
}}
|
|
49
|
+
micSendButton={{
|
|
50
|
+
hasContent,
|
|
51
|
+
onSend,
|
|
52
|
+
onMic: () => {
|
|
53
|
+
/* voice input not wired in the base default */
|
|
54
|
+
},
|
|
55
|
+
disabled,
|
|
56
|
+
isLoading,
|
|
57
|
+
onStop: isLoading ? onStop : undefined,
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default DefaultInputToolBar;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import type { MessageGroupRenderProps } from './types';
|
|
4
|
+
|
|
5
|
+
const ASSISTANT_BUBBLE_BG = 'bg-card';
|
|
6
|
+
const USER_BUBBLE_BG = 'bg-indigo-50';
|
|
7
|
+
const BORDER_COLOR = 'border-border';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* DefaultMessageGroup — the base renderer used when no `MessageGroupComponent`
|
|
11
|
+
* is injected. Renders the post's text in a left/right aligned bubble with an
|
|
12
|
+
* optional author name, avatar and timestamp. Whitespace is preserved so
|
|
13
|
+
* plain-text replies read naturally.
|
|
14
|
+
*
|
|
15
|
+
* Consumers that need rich rendering (markdown, code blocks, media/travel/
|
|
16
|
+
* workflow widget cards, …) inject their own component via
|
|
17
|
+
* `MessagesContainerUI`'s `MessageGroupComponent` prop — e.g. messenger-box's
|
|
18
|
+
* `ModernMessageGroupComponent`.
|
|
19
|
+
*/
|
|
20
|
+
export const DefaultMessageGroup: React.FC<MessageGroupRenderProps> = ({
|
|
21
|
+
messages,
|
|
22
|
+
onMessageClick,
|
|
23
|
+
showAuthorName = false,
|
|
24
|
+
showAvatar = false,
|
|
25
|
+
showTimestamp = false,
|
|
26
|
+
}) => {
|
|
27
|
+
const post = messages?.[0];
|
|
28
|
+
if (!post) return null;
|
|
29
|
+
|
|
30
|
+
const role = post?.propsConfiguration?.contents?.role;
|
|
31
|
+
const isUser = role === 'USER' || role === 'user' || post?.author?.isCurrentUser === true;
|
|
32
|
+
const text: string = typeof post?.message === 'string' ? post.message : '';
|
|
33
|
+
const author = post?.author ?? {};
|
|
34
|
+
const name: string = author.fullName || author.givenName || author.username || (isUser ? 'You' : 'Assistant');
|
|
35
|
+
const picture: string | null = author.picture ?? null;
|
|
36
|
+
const created = post?.createdAt ? new Date(post.createdAt) : null;
|
|
37
|
+
const timeLabel = created && !Number.isNaN(created.getTime()) ? created.toLocaleTimeString() : '';
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className={clsx('flex w-full', isUser ? 'justify-end' : 'justify-start')}>
|
|
41
|
+
<div className={clsx('flex max-w-[85%] gap-2', isUser ? 'flex-row-reverse' : 'flex-row')}>
|
|
42
|
+
{showAvatar && (
|
|
43
|
+
<div className="flex-shrink-0">
|
|
44
|
+
{picture ? (
|
|
45
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
46
|
+
<img src={picture} alt={name} className="w-7 h-7 rounded-full object-cover" />
|
|
47
|
+
) : (
|
|
48
|
+
<div className="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center text-xs font-medium text-gray-600">
|
|
49
|
+
{name.charAt(0).toUpperCase()}
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
<div className={clsx('min-w-0', isUser ? 'text-right' : 'text-left')}>
|
|
55
|
+
{showAuthorName && <div className="mb-0.5 text-xs font-medium text-gray-500">{name}</div>}
|
|
56
|
+
<div
|
|
57
|
+
onClick={onMessageClick ? () => onMessageClick(post) : undefined}
|
|
58
|
+
className={clsx(
|
|
59
|
+
'inline-block rounded-2xl px-4 py-2.5 text-[15px] leading-relaxed text-foreground',
|
|
60
|
+
'whitespace-pre-wrap break-words border',
|
|
61
|
+
BORDER_COLOR,
|
|
62
|
+
isUser ? USER_BUBBLE_BG : ASSISTANT_BUBBLE_BG,
|
|
63
|
+
onMessageClick && 'cursor-pointer',
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{text}
|
|
67
|
+
</div>
|
|
68
|
+
{showTimestamp && timeLabel && <div className="mt-0.5 text-[11px] text-gray-400">{timeLabel}</div>}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default DefaultMessageGroup;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const IconAlertTriangle: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
|
|
4
|
+
<svg
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="currentColor"
|
|
8
|
+
strokeWidth="2"
|
|
9
|
+
strokeLinecap="round"
|
|
10
|
+
strokeLinejoin="round"
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
|
|
14
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
15
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const IconRefreshCw: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
|
|
20
|
+
<svg
|
|
21
|
+
viewBox="0 0 24 24"
|
|
22
|
+
fill="none"
|
|
23
|
+
stroke="currentColor"
|
|
24
|
+
strokeWidth="2"
|
|
25
|
+
strokeLinecap="round"
|
|
26
|
+
strokeLinejoin="round"
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
<polyline points="23 4 23 10 17 10" />
|
|
30
|
+
<polyline points="1 20 1 14 7 14" />
|
|
31
|
+
<path d="M3.51 9a9 9 0 0114.13-3.36L23 10" />
|
|
32
|
+
<path d="M20.49 15a9 9 0 01-14.13 3.36L1 14" />
|
|
33
|
+
</svg>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const IconCode: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
|
|
37
|
+
<svg
|
|
38
|
+
viewBox="0 0 24 24"
|
|
39
|
+
fill="none"
|
|
40
|
+
stroke="currentColor"
|
|
41
|
+
strokeWidth="2"
|
|
42
|
+
strokeLinecap="round"
|
|
43
|
+
strokeLinejoin="round"
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
<polyline points="16 18 22 12 16 6" />
|
|
47
|
+
<polyline points="8 6 2 12 8 18" />
|
|
48
|
+
</svg>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const IconExternalLink: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
|
|
52
|
+
<svg
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
fill="none"
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
strokeWidth="2"
|
|
57
|
+
strokeLinecap="round"
|
|
58
|
+
strokeLinejoin="round"
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
62
|
+
<polyline points="15 3 21 3 21 9" />
|
|
63
|
+
<line x1="10" y1="14" x2="21" y2="3" />
|
|
64
|
+
</svg>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
interface ErrorFixCardProps {
|
|
68
|
+
error: any;
|
|
69
|
+
onFixError: (errorMessage: string) => Promise<void>;
|
|
70
|
+
currentFiles?: Record<string, string>;
|
|
71
|
+
isFixing?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const ErrorFixCard: React.FC<ErrorFixCardProps> = ({ error, onFixError, currentFiles, isFixing = false }) => {
|
|
75
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
76
|
+
const [isFixingError, setIsFixingError] = useState(false);
|
|
77
|
+
|
|
78
|
+
const handleFixError = async () => {
|
|
79
|
+
setIsFixingError(true);
|
|
80
|
+
try {
|
|
81
|
+
const errorContext = `
|
|
82
|
+
ERROR DETECTED IN SANDBOX:
|
|
83
|
+
|
|
84
|
+
Error Type: ${error.errorType}
|
|
85
|
+
Timestamp: ${error.timestamp}
|
|
86
|
+
Sandbox ID: ${error.sandboxId}
|
|
87
|
+
|
|
88
|
+
Error Message:
|
|
89
|
+
${error.message}
|
|
90
|
+
|
|
91
|
+
${error.stack ? `Stack Trace:\n${error.stack}` : ''}
|
|
92
|
+
|
|
93
|
+
${
|
|
94
|
+
currentFiles && Object.keys(currentFiles).length > 0
|
|
95
|
+
? `
|
|
96
|
+
Current Files in Project:
|
|
97
|
+
${Object.entries(currentFiles)
|
|
98
|
+
.map(
|
|
99
|
+
([path, content]) => `
|
|
100
|
+
--- ${path} ---
|
|
101
|
+
${content}
|
|
102
|
+
`,
|
|
103
|
+
)
|
|
104
|
+
.join('\n')}
|
|
105
|
+
`
|
|
106
|
+
: ''
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
Please analyze this error and fix the code. Make sure to:
|
|
110
|
+
1. Identify the root cause of the error
|
|
111
|
+
2. Update the necessary files to resolve the issue
|
|
112
|
+
3. Ensure the fix maintains the existing functionality
|
|
113
|
+
4. Test that the solution compiles and runs correctly
|
|
114
|
+
`.trim();
|
|
115
|
+
|
|
116
|
+
await onFixError(errorContext);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('Failed to trigger error fix:', error);
|
|
119
|
+
} finally {
|
|
120
|
+
setIsFixingError(false);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const getErrorTypeColor = (errorType: string) => {
|
|
125
|
+
switch (errorType) {
|
|
126
|
+
case 'COMPILATION_ERROR':
|
|
127
|
+
return 'text-red-600 bg-red-50 border-red-200';
|
|
128
|
+
case 'RUNTIME_ERROR':
|
|
129
|
+
return 'text-orange-600 bg-orange-50 border-orange-200';
|
|
130
|
+
case 'DEPENDENCY_ERROR':
|
|
131
|
+
return 'text-yellow-600 bg-yellow-50 border-yellow-200';
|
|
132
|
+
case 'SERVER_START_ERROR':
|
|
133
|
+
return 'text-purple-600 bg-purple-50 border-purple-200';
|
|
134
|
+
case 'SANDBOX_DISCONNECTED':
|
|
135
|
+
return 'text-gray-600 bg-gray-50 border-gray-200';
|
|
136
|
+
case 'TEST_ERROR':
|
|
137
|
+
return 'text-blue-600 bg-blue-50 border-blue-200';
|
|
138
|
+
default:
|
|
139
|
+
return 'text-red-600 bg-red-50 border-red-200';
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const formatErrorType = (errorType: string) => {
|
|
144
|
+
return errorType
|
|
145
|
+
.replace(/_/g, ' ')
|
|
146
|
+
.toLowerCase()
|
|
147
|
+
.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className="flex w-full justify-start mb-2">
|
|
152
|
+
<div
|
|
153
|
+
className={`rounded-lg p-2 border w-fit max-w-[75%] min-w-0 break-all overflow-hidden ${getErrorTypeColor(
|
|
154
|
+
error.errorType,
|
|
155
|
+
)}`}
|
|
156
|
+
>
|
|
157
|
+
<div className="flex items-start justify-between mb-1">
|
|
158
|
+
<div className="flex items-center space-x-1">
|
|
159
|
+
<IconAlertTriangle className="w-3 h-3 text-red-600 flex-shrink-0" />
|
|
160
|
+
<span className="font-semibold text-xs">{formatErrorType(error.errorType)}</span>
|
|
161
|
+
<span className="text-xs opacity-60">{new Date(error.timestamp).toLocaleTimeString()}</span>
|
|
162
|
+
{error.url && (
|
|
163
|
+
<a
|
|
164
|
+
href={error.url}
|
|
165
|
+
target="_blank"
|
|
166
|
+
rel="noopener noreferrer"
|
|
167
|
+
className="text-xs hover:underline flex items-center space-x-1"
|
|
168
|
+
>
|
|
169
|
+
<IconExternalLink className="w-3 h-3" />
|
|
170
|
+
<span>View Sandbox</span>
|
|
171
|
+
</a>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div className="text-sm mb-3">
|
|
177
|
+
<div
|
|
178
|
+
className="break-words whitespace-pre-wrap font-mono text-xs leading-relaxed overflow-hidden"
|
|
179
|
+
style={{
|
|
180
|
+
display: '-webkit-box',
|
|
181
|
+
WebkitLineClamp: isExpanded ? 'none' : 3,
|
|
182
|
+
WebkitBoxOrient: 'vertical',
|
|
183
|
+
maxHeight: isExpanded ? 'none' : '3.6em',
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
{error.message}
|
|
187
|
+
</div>
|
|
188
|
+
{error.message.length > 150 && (
|
|
189
|
+
<button
|
|
190
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
191
|
+
className="text-xs hover:underline mt-1 font-sans"
|
|
192
|
+
>
|
|
193
|
+
{isExpanded ? 'Show less' : 'Show more'}
|
|
194
|
+
</button>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{isExpanded && error.stack && (
|
|
199
|
+
<div className="mt-3 p-3 bg-black bg-opacity-10 rounded text-xs font-mono">
|
|
200
|
+
<div className="flex items-center space-x-2 mb-2">
|
|
201
|
+
<IconCode className="w-3 h-3" />
|
|
202
|
+
<span className="font-semibold">Stack Trace</span>
|
|
203
|
+
</div>
|
|
204
|
+
<pre className="whitespace-pre-wrap text-xs break-all">{error.stack}</pre>
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{error.fixable && (
|
|
209
|
+
<div className="mt-4 flex justify-start">
|
|
210
|
+
<button
|
|
211
|
+
onClick={handleFixError}
|
|
212
|
+
disabled={isFixingError || isFixing}
|
|
213
|
+
className={`
|
|
214
|
+
flex items-center space-x-2 px-4 py-2 rounded-md text-sm font-medium
|
|
215
|
+
transition-colors duration-200
|
|
216
|
+
${
|
|
217
|
+
isFixingError || isFixing
|
|
218
|
+
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
219
|
+
: 'bg-red-600 text-white hover:bg-red-700 shadow-sm hover:shadow-md'
|
|
220
|
+
}
|
|
221
|
+
`}
|
|
222
|
+
>
|
|
223
|
+
<IconRefreshCw className={`w-4 h-4 ${isFixingError || isFixing ? 'animate-spin' : ''}`} />
|
|
224
|
+
<span>{isFixingError || isFixing ? 'Fixing...' : 'Fix Error'}</span>
|
|
225
|
+
</button>
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
{!error.fixable && (
|
|
230
|
+
<div className="mt-3 text-xs opacity-75">
|
|
231
|
+
This error may require manual intervention or environment changes.
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export default ErrorFixCard;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
import { useFileUrlQuery } from 'common/graphql';
|
|
3
|
+
|
|
4
|
+
interface FilesListProps {
|
|
5
|
+
files: any[];
|
|
6
|
+
uploaded?: boolean;
|
|
7
|
+
FileElementProps?: Omit<FileElementProps, 'file' | 'uploaded' | 'onRemove'>;
|
|
8
|
+
onRemove?: () => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface FileElementProps {
|
|
12
|
+
file: any;
|
|
13
|
+
uploaded: boolean;
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
onRemove?: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function FilesList({ files, uploaded = false }: FilesListProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex flex-row">
|
|
22
|
+
{files.map((file, index) => (
|
|
23
|
+
<FilesList.FileElement key={`${index}`} uploaded={uploaded} file={file} />
|
|
24
|
+
))}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
FilesList.FileElement = ({ file, uploaded, width = 120, height = 120 }: FileElementProps) => {
|
|
30
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
31
|
+
const src = useMemo(() => (uploaded ? file.url : URL.createObjectURL(file)), [file, uploaded]);
|
|
32
|
+
const { data } = useFileUrlQuery({
|
|
33
|
+
variables: {
|
|
34
|
+
id: file?.id,
|
|
35
|
+
},
|
|
36
|
+
skip: !file?.id,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const onOpen = () => setIsOpen(true);
|
|
40
|
+
const onClose = () => setIsOpen(false);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
<img
|
|
45
|
+
className="cursor-pointer m-2 object-cover rounded"
|
|
46
|
+
style={{ height: `${height}px`, width: `${width}px` }}
|
|
47
|
+
src={src}
|
|
48
|
+
onClick={onOpen}
|
|
49
|
+
alt="File preview"
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
{/* Modal */}
|
|
53
|
+
{isOpen && (
|
|
54
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
55
|
+
{/* Overlay */}
|
|
56
|
+
<div className="fixed inset-0 bg-black bg-opacity-75" onClick={onClose} />
|
|
57
|
+
|
|
58
|
+
{/* Modal Content */}
|
|
59
|
+
<div className="relative bg-white rounded-lg shadow-xl max-w-4xl max-h-[90vh] overflow-hidden">
|
|
60
|
+
{/* Close Button */}
|
|
61
|
+
<button
|
|
62
|
+
onClick={onClose}
|
|
63
|
+
className="absolute top-4 right-4 z-10 p-2 rounded-full bg-black bg-opacity-50 hover:bg-opacity-70 transition-all duration-200"
|
|
64
|
+
>
|
|
65
|
+
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
66
|
+
<path
|
|
67
|
+
strokeLinecap="round"
|
|
68
|
+
strokeLinejoin="round"
|
|
69
|
+
strokeWidth={2}
|
|
70
|
+
d="M6 18L18 6M6 6l12 12"
|
|
71
|
+
/>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
{/* Image Container */}
|
|
76
|
+
<div className="p-4 flex items-center justify-center">
|
|
77
|
+
<img
|
|
78
|
+
src={data?.fileInfo?.url || src}
|
|
79
|
+
alt="File full view"
|
|
80
|
+
className="max-w-full max-h-[80vh] object-contain cursor-pointer"
|
|
81
|
+
onClick={onClose}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
};
|