diff --git a/apps/console/src/app/components/prompts/editor/chat/ChatEditMode.tsx b/apps/console/src/app/components/prompts/editor/chat/ChatEditMode.tsx
index 0e5563b4..d137181c 100644
--- a/apps/console/src/app/components/prompts/editor/chat/ChatEditMode.tsx
+++ b/apps/console/src/app/components/prompts/editor/chat/ChatEditMode.tsx
@@ -1,4 +1,4 @@
-import { Button, Form } from "antd";
+import { Button, Card, Form, List, FormListFieldData } from "antd";
import { ChatMessage } from "./ChatMessage";
import { PlusOutlined } from "@ant-design/icons";
import { usePromptVersionEditorContext } from "../../../../lib/providers/PromptVersionEditorContext";
@@ -6,11 +6,12 @@ import { trackEvent } from "../../../../lib/utils/analytics";
import { useCurrentPrompt } from "../../../../lib/providers/CurrentPromptContext";
import { useEffect } from "react";
import { findVariables } from "../../../../lib/utils/find-variables";
+import { DndProvider } from "react-dnd";
+import { HTML5Backend } from "react-dnd-html5-backend";
export const ChatEditMode = () => {
const { promptId } = useCurrentPrompt();
const { form, setVariables } = usePromptVersionEditorContext();
-
const content = Form.useWatch(["content"], { form });
useEffect(() => {
@@ -52,14 +53,37 @@ export const ChatEditMode = () => {
add();
}
+ const moveField = (fromIndex: number, toIndex: number) => {
+ const currentMessages = form.getFieldValue(["content", "messages"]);
+
+ // Use form.setFieldsValue to clear the form fields
+
+ form.setFieldValue(["content", "messages"], []);
+
+ const movedItem = currentMessages[fromIndex]; // Extract the item to move
+
+ // Create a new array with the item moved to the new index
+ const newArray = currentMessages.filter(
+ (_, index) => index !== fromIndex
+ );
+ newArray.splice(toIndex, 0, movedItem);
+
+ // Update the messages with the new array
+ form.setFieldValue(["content", "messages"], newArray);
+ };
+
return (
- {fields.map((field, index) => {
- return (
+
+ {fields.map((field, index) => (
{
+ moveField(dragIndex, hoverIndex);
+ }}
+ isOver={false}
onDelete={() => {
remove(index);
trackEvent("prompt_chat_completion_message_deleted", {
@@ -67,8 +91,8 @@ export const ChatEditMode = () => {
});
}}
/>
- );
- })}
+ ))}
+
} onClick={() => handleAdd()}>
New Message
diff --git a/apps/console/src/app/components/prompts/editor/chat/ChatMessage.tsx b/apps/console/src/app/components/prompts/editor/chat/ChatMessage.tsx
index ff06532d..ddd5bfda 100644
--- a/apps/console/src/app/components/prompts/editor/chat/ChatMessage.tsx
+++ b/apps/console/src/app/components/prompts/editor/chat/ChatMessage.tsx
@@ -1,15 +1,17 @@
import { Button, Card, Form, Select } from "antd";
-import { CloseOutlined, SwapOutlined } from "@ant-design/icons";
+import { CloseOutlined, SwapOutlined, HolderOutlined } from "@ant-design/icons";
import { colors } from "../../../../lib/theme/colors";
import { PromptEditorTextArea } from "../../../common/PromptEditorTextArea";
import styled from "@emotion/styled";
import { useCurrentPrompt } from "../../../../lib/providers/CurrentPromptContext";
import { trackEvent } from "../../../../lib/utils/analytics";
+import { useDrag, useDrop } from "react-dnd";
interface Props {
index: number;
canDelete?: boolean;
onDelete: () => void;
+ isOver: boolean;
}
const StyledCard = styled(Card)`
@@ -21,68 +23,109 @@ const StyledCard = styled(Card)`
padding: 16px 20px 10px 20px;
}
`;
+interface DraggableChatMessageProps extends Props {
+ onMove: (dragIndex: number, hoverIndex: number) => void;
+}
-export const ChatMessage = ({ index, canDelete = true, onDelete }: Props) => {
+export const ChatMessage: React.FC
= ({
+ index,
+ canDelete = true,
+ onDelete,
+ onMove,
+ isOver,
+}: DraggableChatMessageProps) => {
const { promptId } = useCurrentPrompt();
+ const [{ opacity }, ref, preview] = useDrag({
+ type: "CHAT_MESSAGE",
+ item: { index },
+ collect: (monitor) => ({
+ opacity: monitor.isDragging() ? 0.4 : 1,
+ }),
+ });
+
+ const [, drop] = useDrop({
+ accept: "CHAT_MESSAGE",
+ drop: (draggedItem: { index: number }) => {
+ if (draggedItem.index !== index) {
+ onMove(draggedItem.index, index);
+ }
+ },
+ collect: (monitor) => ({
+ isOver: monitor.isOver(),
+ }),
+ });
+
return (
-
-
-
-
-
-
- {canDelete && (
- }
- onClick={onDelete}
+
+ {isOver ? null : (
+
+
+
ref(drop(node))} style={{ cursor: "move" }}>
+
+
+
+
+
+
+ {canDelete && (
+ }
+ onClick={onDelete}
+ />
+ )}
+
+ }
+ >
+
+
- )}
-
- }
- >
-
-
-
-
+
+
+ )}
+
);
};
diff --git a/package-lock.json b/package-lock.json
index 2e408662..99e39971 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -84,6 +84,8 @@
"react": "18.2.0",
"react-click-away-listener": "^2.2.3",
"react-countup": "^6.4.2",
+ "react-dnd": "^16.0.1",
+ "react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-hook-form": "^7.46.0",
"react-hotjar": "^6.1.0",
@@ -11268,6 +11270,21 @@
"react-dom": ">=16.9.0"
}
},
+ "node_modules/@react-dnd/asap": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
+ "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
+ },
+ "node_modules/@react-dnd/invariant": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
+ "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
+ },
+ "node_modules/@react-dnd/shallowequal": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
+ "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
+ },
"node_modules/@redis/bloom": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
@@ -19677,6 +19694,16 @@
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
+ "node_modules/dnd-core": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
+ "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
+ "dependencies": {
+ "@react-dnd/asap": "^5.0.1",
+ "@react-dnd/invariant": "^4.0.1",
+ "redux": "^4.2.0"
+ }
+ },
"node_modules/dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@@ -33979,6 +34006,43 @@
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
},
+ "node_modules/react-dnd": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
+ "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
+ "dependencies": {
+ "@react-dnd/invariant": "^4.0.1",
+ "@react-dnd/shallowequal": "^4.0.1",
+ "dnd-core": "^16.0.1",
+ "fast-deep-equal": "^3.1.3",
+ "hoist-non-react-statics": "^3.3.2"
+ },
+ "peerDependencies": {
+ "@types/hoist-non-react-statics": ">= 3.3.1",
+ "@types/node": ">= 12",
+ "@types/react": ">= 16",
+ "react": ">= 16.14"
+ },
+ "peerDependenciesMeta": {
+ "@types/hoist-non-react-statics": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-dnd-html5-backend": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
+ "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
+ "dependencies": {
+ "dnd-core": "^16.0.1"
+ }
+ },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -34283,6 +34347,14 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
},
+ "node_modules/redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
"node_modules/reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
@@ -48645,6 +48717,21 @@
"rc-util": "^5.33.0"
}
},
+ "@react-dnd/asap": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
+ "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
+ },
+ "@react-dnd/invariant": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
+ "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
+ },
+ "@react-dnd/shallowequal": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
+ "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
+ },
"@redis/bloom": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
@@ -54922,6 +55009,16 @@
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
+ "dnd-core": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
+ "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
+ "requires": {
+ "@react-dnd/asap": "^5.0.1",
+ "@react-dnd/invariant": "^4.0.1",
+ "redux": "^4.2.0"
+ }
+ },
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@@ -65202,6 +65299,26 @@
}
}
},
+ "react-dnd": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
+ "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
+ "requires": {
+ "@react-dnd/invariant": "^4.0.1",
+ "@react-dnd/shallowequal": "^4.0.1",
+ "dnd-core": "^16.0.1",
+ "fast-deep-equal": "^3.1.3",
+ "hoist-non-react-statics": "^3.3.2"
+ }
+ },
+ "react-dnd-html5-backend": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
+ "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
+ "requires": {
+ "dnd-core": "^16.0.1"
+ }
+ },
"react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -65436,6 +65553,14 @@
}
}
},
+ "redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "requires": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
"reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
diff --git a/package.json b/package.json
index 4b81001b..38e0484c 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,8 @@
"react": "18.2.0",
"react-click-away-listener": "^2.2.3",
"react-countup": "^6.4.2",
+ "react-dnd": "^16.0.1",
+ "react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-hook-form": "^7.46.0",
"react-hotjar": "^6.1.0",