From e1f16fe78d4f4121f42ab00b49bc46b755a9af14 Mon Sep 17 00:00:00 2001 From: MXerFix Date: Tue, 23 Jan 2024 13:38:26 +0300 Subject: [PATCH 1/6] build gui v1 --- .../components/ShadTooltipComponent/index.tsx | 2 +- .../src/components/headerComponent/index.tsx | 48 ++++++- .../src/components/newChatComponent/index.tsx | 107 ++++++++++----- .../src/contexts/buildContext.tsx | 75 ++++++++++ .../src/contexts/chatContext.tsx | 36 +++++ df_designer_front/src/contexts/index.tsx | 16 ++- .../src/controllers/API/build.ts | 19 +++ df_designer_front/src/controllers/API/ws.tsx | 20 +++ .../src/icons/BuildLoaderIcon/index.tsx | 12 ++ .../BuildedCheckIcon/BuildedCheckIcon.tsx | 13 ++ df_designer_front/src/index.css | 63 +++++++++ .../components/PageComponent/index.tsx | 16 ++- .../src/pages/FlowPage/index.tsx | 1 + .../src/pages/PreviewPage/index.tsx | 129 +++++++++++++----- df_designer_front/src/routes.tsx | 2 +- df_designer_front/vite.config.ts | 2 +- flows.json | 2 +- 17 files changed, 478 insertions(+), 85 deletions(-) create mode 100644 df_designer_front/src/contexts/buildContext.tsx create mode 100644 df_designer_front/src/contexts/chatContext.tsx create mode 100644 df_designer_front/src/controllers/API/build.ts create mode 100644 df_designer_front/src/controllers/API/ws.tsx create mode 100644 df_designer_front/src/icons/BuildLoaderIcon/index.tsx create mode 100644 df_designer_front/src/icons/BuildedCheckIcon/BuildedCheckIcon.tsx diff --git a/df_designer_front/src/components/ShadTooltipComponent/index.tsx b/df_designer_front/src/components/ShadTooltipComponent/index.tsx index ae4bd65e..0c660708 100644 --- a/df_designer_front/src/components/ShadTooltipComponent/index.tsx +++ b/df_designer_front/src/components/ShadTooltipComponent/index.tsx @@ -17,7 +17,7 @@ const ShadTooltip = ({ {children} - + {content} diff --git a/df_designer_front/src/components/headerComponent/index.tsx b/df_designer_front/src/components/headerComponent/index.tsx index a2434875..a90f222a 100644 --- a/df_designer_front/src/components/headerComponent/index.tsx +++ b/df_designer_front/src/components/headerComponent/index.tsx @@ -1,4 +1,4 @@ -import { Building, Home, MessageCircle, MoonIcon, SettingsIcon, SunIcon, Users2, Wrench, XIcon } from "lucide-react"; +import { Building, Cross, Home, MessageCircle, MoonIcon, Plus, SettingsIcon, SunIcon, Users2, Wrench, XIcon } from "lucide-react"; import { useContext, useEffect, useState } from "react"; import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa"; import { Button } from "../ui/button"; @@ -25,6 +25,10 @@ import { DoubleButton } from "../ui/double-button"; import { WorkSpaceModeIcon } from "../../icons/CanvaModeIcon"; import { NodesPlacementIcon } from "../../icons/NodesPlacementIcon"; import ShadTooltip from "../ShadTooltipComponent"; +import { buildBotScript } from "../../controllers/API/build"; +import BuildLoaderIcon from "../../icons/BuildLoaderIcon"; +import BuildedCheckIcon from "../../icons/BuildedCheckIcon/BuildedCheckIcon"; +import { buildContext } from "../../contexts/buildContext"; export default function Header() { const { flows, addFlow, tabId, setTabId, removeFlow } = useContext(TabsContext); @@ -34,13 +38,41 @@ export default function Header() { const { id } = useParams(); const AlertWidth = 384; const { dark, setDark } = useContext(darkContext); - const { notificationCenter, setNotificationCenter, setErrorData } = + const { notificationCenter, setNotificationCenter, setErrorData, setSuccessData } = useContext(alertContext); const location = useLocation(); const [stars, setStars] = useState(null); const [workSpaceMode, setWorkSpaceMode] = useState(managerMode) const [nodesPlacement, setNodesPlacement] = useState(flowMode) + const { builded, setBuilded, connectionStatus, setConnectionStatus } = useContext(buildContext) + const [buildPending, setBuildPending] = useState(false) + + const buildBotHandler = async () => { + if (!builded) { + setBuildPending(true) + setBuilded(false) + try { + setTimeout(async () => { + const buildStatus = await buildBotScript() + if (!buildStatus) { + setErrorData({ title: 'Build failed!' }) + throw Error('Build failed') + } else { + setBuilded(true) + setSuccessData({ title: 'Bot was successfully builded!' }) + } + setBuildPending(false) + }, 2000); + } catch (error) { + console.log(error) + } + } else if (builded) { + setBuilded(false) + setConnectionStatus('broken') + } + } + const navigate = useNavigate() function handleAddFlow() { @@ -137,7 +169,7 @@ export default function Header() { openPopUp( <>
@@ -154,10 +186,14 @@ export default function Header() { - - +
diff --git a/df_designer_front/src/components/newChatComponent/index.tsx b/df_designer_front/src/components/newChatComponent/index.tsx index e6844216..93870286 100644 --- a/df_designer_front/src/components/newChatComponent/index.tsx +++ b/df_designer_front/src/components/newChatComponent/index.tsx @@ -1,31 +1,33 @@ import { ActivityLogIcon, Cross1Icon } from '@radix-ui/react-icons' import { useSpring, animated, a, useSprings, useTransition } from '@react-spring/web' -import { ArrowRight, MessageCircle, PanelRightClose, PanelRightOpen, Paperclip, RotateCcw } from 'lucide-react' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import { ArrowRight, Info, MessageCircle, PanelRightClose, PanelRightOpen, Paperclip, RotateCcw } from 'lucide-react' +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react' +import { Link } from 'react-router-dom' +import { chatContext } from '../../contexts/chatContext' +import { buildContext } from '../../contexts/buildContext' +import { alertContext } from '../../contexts/alertContext' +import ShadTooltip from '../ShadTooltipComponent' export type chatMessageType = { message: string - source: "user" | "bot" + source: "user" | "bot" | "warning" | "error" | "success" } -const NewChatComponent = () => { +const NewChatComponent = ({ className }: { className?: string }) => { + const { messages, setMessages } = useContext(chatContext) - - const [messages, setMessages] = useState([]) + // const [messages, setMessages] = useState([]) const [userMessage, setUserMessage] = useState('') const [userMessages, setUserMessages] = useState([]) const chatRef = useRef(null) + const { builded } = useContext(buildContext) + const { logs, setLogs, connectionStatus, setConnectionStatus, setLogsPage, logsPage } = useContext(buildContext) + const [isClosed, setIsClosed] = useState(false) + const statusSocket = useRef(null) + const { setNoticeData, setSuccessData, setErrorData } = useContext(alertContext) // const [botMessages, setBotMessages] = useState([]) - const [props, api] = useSpring( - () => ({ - from: { opacity: 0 }, - to: { opacity: 1 }, - }), - [messages.length] - ) - const transitions = useTransition(messages, { from: { opacity: 0, y: 50 }, enter: { opacity: 1, y: 0 }, @@ -35,30 +37,69 @@ const NewChatComponent = () => { } }) + // useEffect(() => { + // setTimeout(() => { + // setMessages(prev => [ + // ...prev, + // { + // message: 'Hi! I’m a bot. How can I help you?', + // source: 'bot' + // } + // ]) + // }, 2000); + // }, [userMessages]) + useEffect(() => { - setTimeout(() => { - setMessages(prev => [ - ...prev, - { - message: 'Hi! I’m a bot. How can I help you?', - source: 'bot' - } - ]) - }, 2000); - }, [userMessages]) + + if (builded && !isClosed) { + statusSocket.current = new WebSocket('ws://127.0.0.1:8000/socket') + setChatIsOpen(true) + + statusSocket.current.onopen = (e) => { + console.log(e) + setConnectionStatus('alive') + // setSuccessData({ title: 'Bot was successfully connected!' }) + setMessages(prev => [...prev, { message: 'Bot was successfully connected!', source: 'success' }]) + } + + + statusSocket.current.onmessage = (e) => { + setLogs((prev: string[]) => [...prev, e.data]) + } + + statusSocket.current.onclose = (e) => { + console.log(e) + setConnectionStatus('closed') + setNoticeData({ title: 'Connection was closed!' }) + setMessages(prev => [...prev, { message: 'Connection was closed!', source: 'warning' }]) + } + + statusSocket.current.onerror = (e) => { + console.log(e) + setConnectionStatus('broken') + setErrorData({ title: 'Connection broken!' }) + setMessages(prev => [...prev, { message: 'Connection was broken!', source: 'error' }]) + } + } + + if (statusSocket.current) { + return () => statusSocket.current.close() + } + + }, [builded]) useEffect(() => { chatRef.current.scrollTo({ behavior: 'smooth', top: chatRef.current.scrollHeight }) }, [messages]) - const [chatIsOpen, setChatIsOpen] = useState(true) + const [chatIsOpen, setChatIsOpen] = useState(window.location.pathname === '/preview') const closeChatHandler = useCallback(() => { setChatIsOpen(!chatIsOpen) }, [chatIsOpen]) return ( -
+
- @@ -78,7 +119,7 @@ const NewChatComponent = () => { */}
-
+
{/* {messages.map((m) => (

@@ -87,9 +128,13 @@ const NewChatComponent = () => { ))} */} {transitions((style, m) => ( - -

+ +

{m.message} + { + (m.source === 'warning' || m.source === 'error' || m.source === 'success') + && + }

))} @@ -119,7 +164,7 @@ const NewChatComponent = () => { >
- +
+
+
+

Logs

-
- {logs.map((log, idx) => ( -
- {idx+1} -

{log}

-
- ))} +
+
+ {logs?.map((log, idx) => ( +
+ {idx + 1} +

{log}

+
+ )) ?? <>No logs} +
+ {/* */}
- + {/* */}
) } diff --git a/df_designer_front/src/routes.tsx b/df_designer_front/src/routes.tsx index 0724745d..38385cc1 100644 --- a/df_designer_front/src/routes.tsx +++ b/df_designer_front/src/routes.tsx @@ -9,7 +9,7 @@ const Router = () => { } /> } /> - } /> + {/* } /> */} } /> diff --git a/df_designer_front/vite.config.ts b/df_designer_front/vite.config.ts index a558eee0..451af616 100644 --- a/df_designer_front/vite.config.ts +++ b/df_designer_front/vite.config.ts @@ -1,7 +1,7 @@ import { UserConfig, defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import svgr from "vite-plugin-svgr"; -const apiRoutes = ["/flows" , "/flows", "/health"]; +const apiRoutes = ["/flows" , "/flows", "/health", '/build', '/socket']; // Use environment variable to determine the target. const target = process.env.VITE_PROXY_TARGET || "http://127.0.0.1:8000"; diff --git a/flows.json b/flows.json index 3243460e..723921f2 100644 --- a/flows.json +++ b/flows.json @@ -1 +1 @@ -[{"name": "GLOBAL", "id": "GLOBAL", "description": "Global flow", "color": "#8338EC", "data": {"nodes": [{"dragging": false, "width": 384, "height": 229, "id": "GLOBAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "GLOBAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "GLOBAL_NODE", "display_name": "GLOBAL_NODE", "documentation": "GLOBAL_NODE", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}}]}}, {"description": "Graph Your Way to Great Conversations.", "name": "123", "data": {"nodes": [{"width": 384, "height": 153, "dragging": false, "id": "LOCAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "LOCAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "test default node", "display_name": "LOCAL_NODE", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}, "positionAbsolute": {"x": 0, "y": 0}, "selected": false}, {"width": 384, "height": 211, "id": "G7Fli", "type": "genericNode", "position": {"x": 607.5363389200626, "y": 63.12682904402632}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "def name(params):\n print(params)\n ", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "G7Fli", "value": null}, "selected": true, "positionAbsolute": {"x": 607.5363389200626, "y": 63.12682904402632}, "dragging": false}, {"width": 384, "height": 211, "id": "EzznH", "type": "genericNode", "position": {"x": 515.7776356958773, "y": 350.34055740665616}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "EzznH", "value": null}, "positionAbsolute": {"x": 515.7776356958773, "y": 350.34055740665616}}], "edges": [], "viewport": {"x": 59.27272727272725, "y": 48.43805980400043, "zoom": 1.1955734741357942}}, "color": "#8338EC", "id": "7da4b852-8e3a-4132-a7fa-937eef050744"}] \ No newline at end of file +[{"name": "GLOBAL", "id": "GLOBAL", "description": "Global flow", "color": "#8338EC", "data": {"nodes": [{"dragging": false, "width": 384, "height": 229, "id": "GLOBAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "GLOBAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "GLOBAL_NODE", "display_name": "GLOBAL_NODE", "documentation": "GLOBAL_NODE", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}}]}}, {"description": "Graph Your Way to Great Conversations.", "name": "123", "data": {"nodes": [{"width": 384, "height": 153, "dragging": false, "id": "LOCAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "LOCAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "test default node", "display_name": "LOCAL_NODE", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}, "positionAbsolute": {"x": 0, "y": 0}, "selected": false}, {"width": 384, "height": 211, "id": "G7Fli", "type": "genericNode", "position": {"x": 607.5363389200626, "y": 63.12682904402632}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "def name(params):\n print(params)\n ", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "G7Fli", "value": null}, "selected": false, "positionAbsolute": {"x": 607.5363389200626, "y": 63.12682904402632}, "dragging": false}, {"width": 384, "height": 211, "id": "EzznH", "type": "genericNode", "position": {"x": 515.7776356958773, "y": 350.34055740665616}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "EzznH", "value": null}, "positionAbsolute": {"x": 515.7776356958773, "y": 350.34055740665616}, "selected": true, "dragging": false}, {"width": 384, "height": 211, "id": "NWB3Y", "type": "genericNode", "position": {"x": 564.9624795631626, "y": 652.4522597751973}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "def name(params):\n print(params)\n ", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "NWB3Y", "value": null}, "selected": false, "dragging": false, "positionAbsolute": {"x": 564.9624795631626, "y": 652.4522597751973}}], "edges": [], "viewport": {"x": 740.270703235519, "y": 57.36363636363649, "zoom": 1.3287042963688849}}, "color": "#8338EC", "id": "7da4b852-8e3a-4132-a7fa-937eef050744"}, {"description": "Navigate the Networks of Conversation.", "name": "321", "data": {"nodes": [{"width": 384, "height": 153, "dragging": false, "id": "LOCAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "LOCAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "test default node", "display_name": "LOCAL_NODE", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}, "positionAbsolute": {"x": 0, "y": 0}, "selected": false}], "edges": [], "viewport": {"x": 1397.6957092225136, "y": 66.18678132306937, "zoom": 3.1489980269286932}}, "color": "#07DA35", "id": "eaff0646-cdef-425d-a98f-17cd7a0e0856"}] \ No newline at end of file From 17bb3db18632e3d09caf99be0b4eb3a142988638 Mon Sep 17 00:00:00 2001 From: MXerFix Date: Tue, 23 Jan 2024 13:44:30 +0300 Subject: [PATCH 2/6] fix 0.0.1 --- .../src/contexts/chatContext.tsx | 2 +- df_designer_front/src/controllers/API/ws.tsx | 20 ------------------- .../components/PageComponent/index.tsx | 2 +- flows.json | 2 +- 4 files changed, 3 insertions(+), 23 deletions(-) diff --git a/df_designer_front/src/contexts/chatContext.tsx b/df_designer_front/src/contexts/chatContext.tsx index f89acf34..1f85e1b2 100644 --- a/df_designer_front/src/contexts/chatContext.tsx +++ b/df_designer_front/src/contexts/chatContext.tsx @@ -18,7 +18,7 @@ export function ChatProvider({ children }) { const [messages, setMessages] = useState([ { message: 'Your bot is not started! Please, complete configuration, build and start your bot to see messages here! ', - source: 'bot' + source: 'warning' } ]) diff --git a/df_designer_front/src/controllers/API/ws.tsx b/df_designer_front/src/controllers/API/ws.tsx index 5e53b2f0..e69de29b 100644 --- a/df_designer_front/src/controllers/API/ws.tsx +++ b/df_designer_front/src/controllers/API/ws.tsx @@ -1,20 +0,0 @@ -import { useContext } from "react" -import { buildContext } from "../../contexts/buildContext" - - -export const statusSocketHandler = () => { - const statusSocket = new WebSocket('ws://127.0.0.1:8000/socket') - statusSocket.close() - statusSocket.send('open connection') - statusSocket.onopen = (e) => { - console.log(e) - alert('opened') - } - - statusSocket.onclose = (e) => { - console.log(e) - alert('closed') - } -} - - diff --git a/df_designer_front/src/pages/FlowPage/components/PageComponent/index.tsx b/df_designer_front/src/pages/FlowPage/components/PageComponent/index.tsx index ae9c8877..cc91ad8e 100644 --- a/df_designer_front/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/df_designer_front/src/pages/FlowPage/components/PageComponent/index.tsx @@ -756,7 +756,7 @@ export default function Page({ flow }: { flow: FlowType }) { {/* {preloader && } */} {preloader ? : ( <> - { } + {/* Main area */}
diff --git a/flows.json b/flows.json index 723921f2..17a96162 100644 --- a/flows.json +++ b/flows.json @@ -1 +1 @@ -[{"name": "GLOBAL", "id": "GLOBAL", "description": "Global flow", "color": "#8338EC", "data": {"nodes": [{"dragging": false, "width": 384, "height": 229, "id": "GLOBAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "GLOBAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "GLOBAL_NODE", "display_name": "GLOBAL_NODE", "documentation": "GLOBAL_NODE", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}}]}}, {"description": "Graph Your Way to Great Conversations.", "name": "123", "data": {"nodes": [{"width": 384, "height": 153, "dragging": false, "id": "LOCAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "LOCAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "test default node", "display_name": "LOCAL_NODE", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}, "positionAbsolute": {"x": 0, "y": 0}, "selected": false}, {"width": 384, "height": 211, "id": "G7Fli", "type": "genericNode", "position": {"x": 607.5363389200626, "y": 63.12682904402632}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "def name(params):\n print(params)\n ", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "G7Fli", "value": null}, "selected": false, "positionAbsolute": {"x": 607.5363389200626, "y": 63.12682904402632}, "dragging": false}, {"width": 384, "height": 211, "id": "EzznH", "type": "genericNode", "position": {"x": 515.7776356958773, "y": 350.34055740665616}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "EzznH", "value": null}, "positionAbsolute": {"x": 515.7776356958773, "y": 350.34055740665616}, "selected": true, "dragging": false}, {"width": 384, "height": 211, "id": "NWB3Y", "type": "genericNode", "position": {"x": 564.9624795631626, "y": 652.4522597751973}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "def name(params):\n print(params)\n ", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "NWB3Y", "value": null}, "selected": false, "dragging": false, "positionAbsolute": {"x": 564.9624795631626, "y": 652.4522597751973}}], "edges": [], "viewport": {"x": 740.270703235519, "y": 57.36363636363649, "zoom": 1.3287042963688849}}, "color": "#8338EC", "id": "7da4b852-8e3a-4132-a7fa-937eef050744"}, {"description": "Navigate the Networks of Conversation.", "name": "321", "data": {"nodes": [{"width": 384, "height": 153, "dragging": false, "id": "LOCAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "LOCAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "test default node", "display_name": "LOCAL_NODE", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}, "positionAbsolute": {"x": 0, "y": 0}, "selected": false}], "edges": [], "viewport": {"x": 1397.6957092225136, "y": 66.18678132306937, "zoom": 3.1489980269286932}}, "color": "#07DA35", "id": "eaff0646-cdef-425d-a98f-17cd7a0e0856"}] \ No newline at end of file +[{"name": "GLOBAL", "id": "GLOBAL", "description": "Global flow", "color": "#8338EC", "data": {"nodes": [{"dragging": false, "width": 384, "height": 229, "id": "GLOBAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "GLOBAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "GLOBAL_NODE", "display_name": "GLOBAL_NODE", "documentation": "GLOBAL_NODE", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}}]}}, {"description": "Graph Your Way to Great Conversations.", "name": "123", "data": {"nodes": [{"width": 384, "height": 153, "dragging": false, "id": "LOCAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "LOCAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "test default node", "display_name": "LOCAL_NODE", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}, "positionAbsolute": {"x": 0, "y": 0}, "selected": false}, {"width": 384, "height": 211, "id": "G7Fli", "type": "genericNode", "position": {"x": 607.5363389200626, "y": 63.12682904402632}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "def name(params):\n print(params)\n ", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "G7Fli", "value": null}, "selected": false, "positionAbsolute": {"x": 607.5363389200626, "y": 63.12682904402632}, "dragging": false}, {"width": 384, "height": 211, "id": "EzznH", "type": "genericNode", "position": {"x": 515.7776356958773, "y": 350.34055740665616}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "EzznH", "value": null}, "positionAbsolute": {"x": 515.7776356958773, "y": 350.34055740665616}, "selected": true, "dragging": false}, {"width": 384, "height": 211, "id": "NWB3Y", "type": "genericNode", "position": {"x": 564.9624795631626, "y": 652.4522597751973}, "data": {"type": "default_node", "node": {"base_classes": ["default_node"], "node_type": "default_node", "description": "test default node", "display_name": "Default Node", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "name": "default_node", "conditions": [{"conditionID": 0, "left": false, "name": "dft_cnd0", "priority": 1, "required": true, "type": "condition", "transitionType": "default", "intent": "", "action": "def name(params):\n print(params)\n ", "variables": "", "APIKey": "", "llm_model": "", "prompt": ""}], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}, "id": "NWB3Y", "value": null}, "selected": false, "dragging": false, "positionAbsolute": {"x": 564.9624795631626, "y": 652.4522597751973}}], "edges": [], "viewport": {"x": 740.270703235519, "y": 57.36363636363649, "zoom": 1.3287042963688849}}, "color": "#8338EC", "id": "7da4b852-8e3a-4132-a7fa-937eef050744"}, {"description": "Navigate the Networks of Conversation.", "name": "321", "data": {"nodes": [{"width": 384, "height": 153, "dragging": false, "id": "LOCAL_NODE", "position": {"x": 0, "y": 0}, "type": "genericNode", "data": {"id": "LOCAL_NODE", "type": "default_node", "value": null, "node": {"base_classes": ["default_node"], "description": "test default node", "display_name": "LOCAL_NODE", "documentation": "test field", "pre_responses": [], "pre_transitions": [], "conditions": [], "template": {"response": {"placeholder": "response", "name": "response", "list": false, "required": true, "show": true, "type": "str", "multiline": false, "value": "", "display_name": "Some response", "APIKey": "", "llm_model": "", "prompt": "", "quote": ""}}}}, "positionAbsolute": {"x": 0, "y": 0}, "selected": false}], "edges": [], "viewport": {"x": 59.27272727272725, "y": 147.83522727272728, "zoom": 3.087121212121212}}, "color": "#07DA35", "id": "eaff0646-cdef-425d-a98f-17cd7a0e0856"}] \ No newline at end of file From dfbe47396f32ae04058a1b37088f5c5d417f2bce Mon Sep 17 00:00:00 2001 From: MXerFix Date: Tue, 23 Jan 2024 14:23:33 +0300 Subject: [PATCH 3/6] fix gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d6bddeae..2dd22799 100644 --- a/.gitignore +++ b/.gitignore @@ -250,4 +250,6 @@ cython_debug/ /df_designer_front/.vscode ./flows.json -*.sqlite \ No newline at end of file +*.sqlite + +flows.json \ No newline at end of file From 921a1ad1b1a6f2ff55dadf6f05a5a5130d794a08 Mon Sep 17 00:00:00 2001 From: MXerFix Date: Mon, 19 Feb 2024 11:10:59 +0300 Subject: [PATCH 4/6] front-build-gui-UNSTABLE --- builds.json | 119 +++++++ df_designer/__init__.py | 53 +++ df_designer/db_requests.py | 47 +++ df_designer/logic.py | 15 + df_designer/main.py | 315 ++++++++++++++---- df_designer_front/package-lock.json | 57 ++++ df_designer_front/package.json | 5 + df_designer_front/prettier.config.js | 14 +- df_designer_front/src/App.tsx | 2 +- .../BuildLogItemComponent/BuildLogItem.tsx | 119 +++++++ .../BuildMenuComponent/BuildMenu.tsx | 43 +++ .../components/BuildButton.tsx | 145 ++++++++ .../components/ConnectionStatusComponent.tsx | 55 +++ .../components/RunButton.tsx | 151 +++++++++ .../ui/PresetDropdownItem.tsx | 21 ++ .../src/components/headerComponent/index.tsx | 63 ++-- .../src/components/newChatComponent/index.tsx | 284 ++++++++++++---- .../src/components/ui/emojiPicker.tsx | 110 ++++++ df_designer_front/src/constants.tsx | 31 ++ .../src/contexts/buildContext.tsx | 284 +++++++++++++--- .../src/contexts/chatContext.tsx | 2 +- .../src/contexts/darkContext.tsx | 4 +- .../src/controllers/API/build.ts | 120 ++++++- .../src/hooks/useLocalStorage.tsx | 14 + .../src/hooks/useOutsideClick.tsx | 21 ++ .../BuildedCheckIcon/BuildedCheckIcon.tsx | 2 +- .../src/icons/RunIcons/ConnectingIcon.tsx | 12 + .../src/icons/RunIcons/ConnectionGoodIcon.tsx | 12 + .../src/icons/RunIcons/ConnectionLostIcon.tsx | 13 + .../src/icons/RunIcons/NewChatIcon.tsx | 15 + .../src/icons/RunIcons/NewLogsIcon.tsx | 12 + .../src/icons/RunIcons/RunIcon.tsx | 12 + .../src/icons/RunIcons/StopIcon.tsx | 12 + df_designer_front/src/index.css | 54 ++- .../components/PageComponent/index.tsx | 2 +- .../src/pages/MainPage/index.tsx | 4 + .../src/pages/PreviewPage/index.tsx | 256 ++++++++++---- df_designer_front/src/types/entities/index.ts | 23 ++ df_designer_front/vite.config.ts | 4 +- 39 files changed, 2216 insertions(+), 311 deletions(-) create mode 100644 builds.json create mode 100644 df_designer/db_requests.py create mode 100644 df_designer_front/src/components/BuildLogItemComponent/BuildLogItem.tsx create mode 100644 df_designer_front/src/components/BuildMenuComponent/BuildMenu.tsx create mode 100644 df_designer_front/src/components/BuildMenuComponent/components/BuildButton.tsx create mode 100644 df_designer_front/src/components/BuildMenuComponent/components/ConnectionStatusComponent.tsx create mode 100644 df_designer_front/src/components/BuildMenuComponent/components/RunButton.tsx create mode 100644 df_designer_front/src/components/BuildMenuComponent/ui/PresetDropdownItem.tsx create mode 100644 df_designer_front/src/components/ui/emojiPicker.tsx create mode 100644 df_designer_front/src/hooks/useLocalStorage.tsx create mode 100644 df_designer_front/src/hooks/useOutsideClick.tsx create mode 100644 df_designer_front/src/icons/RunIcons/ConnectingIcon.tsx create mode 100644 df_designer_front/src/icons/RunIcons/ConnectionGoodIcon.tsx create mode 100644 df_designer_front/src/icons/RunIcons/ConnectionLostIcon.tsx create mode 100644 df_designer_front/src/icons/RunIcons/NewChatIcon.tsx create mode 100644 df_designer_front/src/icons/RunIcons/NewLogsIcon.tsx create mode 100644 df_designer_front/src/icons/RunIcons/RunIcon.tsx create mode 100644 df_designer_front/src/icons/RunIcons/StopIcon.tsx diff --git a/builds.json b/builds.json new file mode 100644 index 00000000..b2cb52b0 --- /dev/null +++ b/builds.json @@ -0,0 +1,119 @@ +[ + { + "FULL INFO BUILD && FULL IMFO RUNS": "для информации", + "id": "build1", + "timestamp": "12314235246", + "preset_name": "build_preset_1", + "status": "completed", + "logs": ["log_string_1", "log_string_2", "etc."], + "logs_path": "/something/...", + "runs": [ + { + "id": "run1", + "timestamp": "133254314", + "preset_name": "run_preset_1", + "status": "failed", + "logs": ["log_string_1", "log_string_2", "etc."], + "logs_path": "/something/...", + "build_id": "build1" + }, + { + "id": "run2", + "timestamp": "133254314", + "preset_name": "run_preset_2", + "status": "completed", + "logs": ["log_string_1", "log_string_2", "etc."], + "logs_path": "/something/...", + "build_id": "build1" + } + ] + }, + { + "BUILDS MINIFY && RUNS MINIFY": "по эндпоинту /bot/builds приходит список вот таких билдов и вот таких запусков", + "builds": [ + { + "id": "build1", + "timestamp": "12314235246", + "preset_name": "build_preset_1", + "status": "completed", + "runs": [ + { + "id": "run1", + "timestamp": "133254314", + "preset_name": "run_preset_1", + "status": "failed", + "build_id": "build1" + }, + { + "id": "run2", + "timestamp": "133254314", + "preset_name": "run_preset_2", + "status": "completed", + "build_id": "build1" + } + ] + }, + { + "id": "build2", + "timestamp": "12314235246", + "preset_name": "build_preset_1", + "status": "completed", + "runs": [ + { + "id": "run1", + "timestamp": "133254314", + "preset_name": "run_preset_1", + "status": "failed", + "build_id": "build2" + }, + { + "id": "run2", + "timestamp": "133254314", + "preset_name": "run_preset_2", + "status": "completed", + "build_id": "build2" + } + ] + } + ] + }, + { + "FULL INFO BUILD && RUNS MINIFY": "по эндпоинту /bot/builds/?id={id} ///потенциально задел на то, что можно будет любое поле передать параметром для поиска", + "build": { + "id": "build{id}", + "timestamp": "12314235246", + "preset_name": "build_preset_1", + "status": "completed", + "logs": ["log_string_1", "log_string_2", "etc."], + "logs_path": "/something/...", + "runs": [ + { + "id": "run1", + "timestamp": "133254314", + "preset_name": "run_preset_1", + "status": "failed", + "build_id": "build1" + }, + { + "id": "run2", + "timestamp": "133254314", + "preset_name": "run_preset_2", + "status": "completed", + "build_id": "build1" + } + ] + } + }, + { + "RUN FULL INFO": "по эндпоинту /bot/runs/?id={id} ///потенциально задел на то, что можно будет любое поле передать параметром для поиска", + "run": { + "id": "run{id}", + "timestamp": "133254314", + "preset_name": "run_preset_2", + "status": "completed", + "logs": ["log_string_1", "log_string_2", "etc."], + "logs_path": "/something/...", + "build_id": "build1" + } + } +] diff --git a/df_designer/__init__.py b/df_designer/__init__.py index e69de29b..b1ac1603 100644 --- a/df_designer/__init__.py +++ b/df_designer/__init__.py @@ -0,0 +1,53 @@ +import asyncio +import aiofiles + +from df_designer.db_requests import run_insert, run_update +from df_designer.logic import create_directory_to_log, log_file_name +from df_designer.settings import app + + +class Proc: + async def start(self): + """Start the process.""" + create_directory_to_log() + file_for_log = log_file_name() + self.id_record = await run_insert(file_for_log, "start") + async with aiofiles.open(file_for_log, "w", encoding="UTF-8") as file: + self.process = await asyncio.create_subprocess_exec( + # "ping", + # "localhost", + # "python", + # "correct_script.py", + # "error_script.py", + *app.cmd_to_run.split(" "), + stdout=file.fileno(), + stderr=file.fileno(), + ) + return self.process.pid + + async def check_status(self): + """Check status process and write fo database.""" + while True: + await asyncio.sleep(1) + if self.process.returncode is not None: + if self.process.returncode == 0: + await run_update(self.id_record, "stop") + break + elif self.process.returncode > 0: + await run_update(self.id_record, "error") + break + + async def stop(self): + """Stop the process.""" + self.process.terminate() + await run_update(self.id_record, "terminate") + + async def status(self): + """Return the status of the process.""" + return self.process.returncode + + async def pid(self): + return self.process.pid + + +process = Proc() diff --git a/df_designer/db_requests.py b/df_designer/db_requests.py new file mode 100644 index 00000000..2afe1df3 --- /dev/null +++ b/df_designer/db_requests.py @@ -0,0 +1,47 @@ +from typing import Any +from df_designer.db_connection import Logs, async_session +from sqlalchemy import insert, select, update +import time +from pathlib import Path + + +async def run_insert(file_for_log: Path, process_status: str): + """Insert process data.""" + async with async_session() as session: + stmt = ( + insert(Logs) + .values( + datetime=time.time(), + path=str(Path(file_for_log).absolute()), + status=process_status, + ) + .returning() + ) + id_record = await session.execute(stmt) + await session.commit() + return id_record + + +async def run_update(id_record: Any, process_status: str): + """Update process data.""" + async with async_session() as session: + stmt = ( + update(Logs) + .values(status=process_status) + .where(Logs.id == id_record.inserted_primary_key[0]) + ) + await session.execute(stmt) + await session.commit() + + +async def run_last(): + """Return last run path.""" + async with async_session() as session: + # stmt = select(Runs.path).where(Runs.status == "start").order_by(Runs.datetime) + stmt = ( + select(Logs.path) + .order_by(Logs.datetime.desc()) + .where(Logs.status == "start") + ) + result = await session.execute(stmt) + return result.scalar() diff --git a/df_designer/logic.py b/df_designer/logic.py index f06d92ba..d4f4fc7a 100644 --- a/df_designer/logic.py +++ b/df_designer/logic.py @@ -1,11 +1,14 @@ import json import os +from datetime import datetime from pathlib import Path from typing import Any import aiofiles from pydantic import Json +from df_designer.settings import app + async def save_data(path: Path, data: dict[str, str]): """Save the json config.""" @@ -20,3 +23,15 @@ async def get_data(path: Path) -> Json[Any]: return json.loads(await file.read()) else: return {} + + +def log_file_name() -> Path: + """Create title a log.""" + file_log_name = datetime.now().strftime("%Y_%m_%d_%H_%M_%s") + ".txt" + return Path(app.dir_logs, file_log_name) + + +def create_directory_to_log(): + """Create directory to log files.""" + if not Path(app.dir_logs).exists(): + Path(app.dir_logs).mkdir() diff --git a/df_designer/main.py b/df_designer/main.py index 2891a34e..75465189 100644 --- a/df_designer/main.py +++ b/df_designer/main.py @@ -1,18 +1,23 @@ import asyncio -import os +import copy +import time from datetime import datetime from pathlib import Path -import time +from typing import Any, Literal import aiofiles import dff -from fastapi import Request, WebSocket, WebSocketDisconnect +from pydantic import BaseModel +import starlette +from fastapi import BackgroundTasks, Request, WebSocket from fastapi.responses import HTMLResponse, JSONResponse from fastapi.staticfiles import StaticFiles from sqlalchemy import insert, select, update from websockets import ConnectionClosedOK -from df_designer.db_connection import async_session, Logs +from df_designer import process +from df_designer.db_connection import Logs, async_session +from df_designer.db_requests import run_last from df_designer.logic import get_data, save_data from df_designer.settings import app @@ -127,27 +132,9 @@ async def dff_tests_condition_post() -> dict[str, str]: return {"status": "ok"} -# TODO: compile (сериализация в питон), build, runtime -# /build -@app.post("/build") -async def build_post() -> dict[str, str]: - """(send flag to compile and connect user's bot ??)""" - return {"status": "ok"} - - -# TODO: другие эндпоинты (git, bot) - -""" -/git ?? - -/git/stars @get ?? -/git/forks @get ?? -""" - - @app.get("/run") -async def logs(): - """get logs""" +async def run(): + """get run""" async with async_session() as session: stmt = select(Logs) result = await session.execute(stmt) @@ -156,11 +143,11 @@ async def logs(): return {"status": "ok", "logs": logs_list} -@app.get("/run/{log_id}") -async def log_file(log_id: str): - """get log file""" +@app.get("/run/{run_id}") +async def run_file(run_id: str): + """get run file""" async with async_session() as session: - stmt = select(Logs).where(Logs.id == log_id) + stmt = select(Logs).where(Logs.id == run_id) result = await session.execute(stmt) log = result.scalar() log_file = Path(log.path) @@ -174,52 +161,240 @@ async def log_file(log_id: str): ) +@app.get("/process/start") +async def process_start(background_tasks: BackgroundTasks): + """start a process""" + result = await process.start() + background_tasks.add_task(process.check_status) + return {"status": "ok", "result": result} + + +@app.get("/process/status") +async def process_status(): + """status a process""" + status = await process.status() + return {"status": "ok", "status_process": status} + + +@app.get("/process/stop") +async def process_stop(): + """stop a process""" + await process.stop() + return {"status": "ok"} + + +@app.get("/process/pid") +async def process_pid(): + """pid a process""" + pid = await process.pid() + return {"status": "ok", "pid": pid} + + @app.websocket("/socket") -async def websocket(websocket: WebSocket): +async def run_to_websocket(websocket: WebSocket): + print(websocket.client_state.value) await websocket.accept() - cmd = app.cmd_to_run - proc = await asyncio.create_subprocess_shell( - cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - file_log_name = datetime.now().strftime("%Y_%m_%d_%H_%M_%s") + ".txt" - file_for_log = Path(app.dir_logs, file_log_name) + print(websocket.client_state.value) - async with async_session() as session: - stmt = ( - insert(Logs) - .values( - datetime=time.time(), - path=str(Path(file_for_log).absolute()), - status="start", - ) - .returning() - ) - id_record = await session.execute(stmt) - await session.commit() - if not Path(app.dir_logs).exists(): - Path(app.dir_logs).mkdir() + run_file = await run_last() + print(run_file) - async with aiofiles.open(file_for_log, "a") as file: + async with aiofiles.open(run_file, "r", encoding="UTF-8") as file: + # await websocket.send_text(file.read()) while True: - line = await proc.stdout.readline() - if line: - data = line.decode("utf-8") - await file.write(data) - await file.flush() - try: - await websocket.send_text(data) - except ConnectionClosedOK: - proc.terminate() - async with async_session() as session: - stmt = ( - update(Logs) - .values(status="stop") - .where(Logs.id == id_record.inserted_primary_key[0]) - ) - await session.execute(stmt) - await session.commit() + try: + data = await asyncio.wait_for((websocket.receive_text()), 0.01) + print(data) + except asyncio.exceptions.TimeoutError: + + line = await file.readline() + if not line: + await asyncio.sleep(1) + else: + await websocket.send_text(line) + + if await process.status() is not None: + await websocket.close() + print("websocket closed") break - else: + except starlette.websockets.WebSocketDisconnect: + print(websocket.client_state.value) break + print("== disconnected ==") + + +####################################################### +build_data: list[Any] = [] + + +class Preset(BaseModel): + name: str + duration: int + end_status: Literal["running", "completed", "failed", "null", "stopped"] + + +def imitation_build(id: int, duration: int, end_status: str): + time.sleep(duration) + if build_data[id]["status"] == "stopped": + return + build_data[id]["status"] = end_status + + +@app.post("/bot/build/start", tags=["bot build"]) +async def bot_build_start(preset: Preset, background_tasks: BackgroundTasks): + """Start a build. + + * name - build name + * duration - seconds + * end_status - "running", "completed", "failed", "null" + """ + build_data.append( + { + "id": len(build_data), + "timestamp": time.time(), + "preset_name": preset.dict()["name"], + "status": "running", + "logs": [], + "logs_path": "", + "runs": [], + } + ) + background_tasks.add_task( + imitation_build, + id=build_data[-1]["id"], + duration=preset.dict()["duration"], + end_status=preset.dict()["end_status"], + ) + return {"status": "ok", "build_info": build_data[-1]} + + +@app.get("/bot/build/status/", tags=["bot build"]) +async def bot_build_status(): + """Get build status.""" + try: + return build_data[-1]["status"] + except IndexError: + return {"build": "not found"} + + +@app.get("/bot/build/stop", tags=["bot build"]) +async def bot_build_stop(): + """Build stop.""" + build_data[-1]["status"] = "stopped" + return {"status": "ok"} + + +@app.get("/bot/builds", tags=["bot build"]) +async def bot_builds(): + """List builds.""" + # TODO: add filtering + build_join = copy.deepcopy(build_data) + runs_join = copy.deepcopy(runs_data) + for build in build_join: + build.pop("logs") + build.pop("logs_path") + for run in runs_join: + if run["build_id"] == build["id"]: + run.pop("logs") + run.pop("logs_path") + build["runs"].append(run) + return {"build": build_join} + + +@app.get("/bot/builds/{build_id}", tags=["bot build"]) +async def bot_builds_id(build_id: int): + """Specific builds.""" + try: + return {"build": build_data[build_id]} + except IndexError: + return {"build": "not found"} + + +########################################### +runs_data: list[Any] = [] + + +# dublicat +class Preset(BaseModel): + name: str + duration: int + end_status: Literal["running", "completed", "failed", "null", "stopped"] + + +def imitation_run(id: int, duration: int, end_status: str): + time.sleep(duration) + if runs_data[id]["status"] == "stopped": + return + runs_data[id]["status"] = end_status + + +@app.post("/bot/run/start", tags=["bot runs"]) +async def bot_runs_start(preset: Preset, background_tasks: BackgroundTasks): + """Start a runs. + + * name - build name + * duration - seconds + * end_status - "running", "completed", "failed", "null" + """ + try: + build_id = build_data[-1] + except IndexError: + return {"status": "error", "run_info": "build is not found"} + runs_data.append( + { + "id": len(runs_data), + "timestamp": time.time(), + "preset_name": preset.dict()["name"], + "status": "running", + "logs": [], + "logs_path": "", + "build_id": build_id["id"], + } + ) + background_tasks.add_task( + imitation_run, + id=runs_data[-1]["id"], + duration=preset.dict()["duration"], + end_status=preset.dict()["end_status"], + ) + return {"status": "ok", "run_info": runs_data[-1]} + + +@app.get("/bot/run/status/", tags=["bot runs"]) +async def bot_runs_status(): + """Get build runs.""" + try: + return runs_data[-1]["status"] + except IndexError: + return {"run": "not found"} + + +@app.get("/bot/run/stop", tags=["bot runs"]) +async def bot_runs_stop(): + """Runs stop.""" + runs_data[-1]["status"] = "stopped" + return {"status": "ok"} + + +@app.get("/bot/runs", tags=["bot runs"]) +async def bot_runs(): + """List runs.""" + # TODO: add filtering + mini_runs_data = copy.deepcopy(runs_data) + for run in mini_runs_data: + run.pop("logs") + run.pop("logs_path") + return {"run": mini_runs_data} + + +@app.get("/bot/runs/{runs_id}", tags=["bot runs"]) +async def bot_runs_id(runs_id: int): + """Specific runs.""" + try: + return {"run": runs_data[runs_id]} + except IndexError: + return {"run": "not found"} + + +# add logs +# add filtering diff --git a/df_designer_front/package-lock.json b/df_designer_front/package-lock.json index 96d2aacc..40ff4f34 100644 --- a/df_designer_front/package-lock.json +++ b/df_designer_front/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@codemirror/autocomplete": "^6.11.1", "@codemirror/lang-python": "^6.1.3", + "@emoji-mart/data": "^1.1.2", + "@emoji-mart/react": "^1.1.1", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@headlessui/react": "^1.7.10", @@ -50,6 +52,8 @@ "clsx": "^1.2.1", "dagre": "^0.8.5", "dompurify": "^3.0.3", + "emoji-mart": "^5.5.2", + "emoji-picker-react": "^4.7.12", "esbuild": "^0.17.18", "lodash": "^4.17.21", "lucide-react": "^0.233.0", @@ -59,6 +63,7 @@ "react-dom": "^18.2.0", "react-error-boundary": "^4.0.2", "react-icons": "^4.8.0", + "react-intersection-observer": "^9.7.0", "react-laag": "^2.0.5", "react-markdown": "^8.0.7", "react-router-dom": "^6.8.1", @@ -522,6 +527,20 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@emoji-mart/data": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.1.2.tgz", + "integrity": "sha512-1HP8BxD2azjqWJvxIaWAMyTySeZY0Osr83ukYjltPVkNXeJvTz7yDrPLBtnrD5uqJ3tg4CcLuuBW09wahqL/fg==" + }, + "node_modules/@emoji-mart/react": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz", + "integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==", + "peerDependencies": { + "emoji-mart": "^5.2", + "react": "^16.8 || ^17 || ^18" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -5852,6 +5871,25 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.546.tgz", "integrity": "sha512-cz9bBM26ZqoEmGHkdHXU3LP7OofVyEzRoMqfALQ9Au9WlB4rogAHzqj/NkNvw2JJjy4xuxS1me+pP2lbCD5Mfw==" }, + "node_modules/emoji-mart": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.5.2.tgz", + "integrity": "sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A==" + }, + "node_modules/emoji-picker-react": { + "version": "4.7.12", + "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.7.12.tgz", + "integrity": "sha512-SV625gwJHk3z2H62Iy03MISvP6jcSbYW6IqY23mWDX3rnfE0Mb9UfMdzf3pB2C9c1ruU9qe+L6S0deSZsab1cA==", + "dependencies": { + "flairup": "0.0.37" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6239,6 +6277,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flairup": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/flairup/-/flairup-0.0.37.tgz", + "integrity": "sha512-GY7coNGpJQczEIfBsgByKhkclQZC2OosDyyGTfFfm2rXEzwq7MbWQgGo/GwM7TdU53Ow4rPznKPMC8Q3iympjQ==" + }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -10330,6 +10373,20 @@ "react": "*" } }, + "node_modules/react-intersection-observer": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.7.0.tgz", + "integrity": "sha512-euleEjBVaMRwSOMNVcMX5WGn74GfZ9I78nx9SUb5a0eXd0IhegjJcUliSO9Jd+xiaZ5rgFvbGoVln66lpMyUUg==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/df_designer_front/package.json b/df_designer_front/package.json index 1a7d399f..25d8cb7d 100644 --- a/df_designer_front/package.json +++ b/df_designer_front/package.json @@ -5,6 +5,8 @@ "dependencies": { "@codemirror/autocomplete": "^6.11.1", "@codemirror/lang-python": "^6.1.3", + "@emoji-mart/data": "^1.1.2", + "@emoji-mart/react": "^1.1.1", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@headlessui/react": "^1.7.10", @@ -45,6 +47,8 @@ "clsx": "^1.2.1", "dagre": "^0.8.5", "dompurify": "^3.0.3", + "emoji-mart": "^5.5.2", + "emoji-picker-react": "^4.7.12", "esbuild": "^0.17.18", "lodash": "^4.17.21", "lucide-react": "^0.233.0", @@ -54,6 +58,7 @@ "react-dom": "^18.2.0", "react-error-boundary": "^4.0.2", "react-icons": "^4.8.0", + "react-intersection-observer": "^9.7.0", "react-laag": "^2.0.5", "react-markdown": "^8.0.7", "react-router-dom": "^6.8.1", diff --git a/df_designer_front/prettier.config.js b/df_designer_front/prettier.config.js index f01cc7a6..19ad2734 100644 --- a/df_designer_front/prettier.config.js +++ b/df_designer_front/prettier.config.js @@ -1,3 +1,13 @@ module.exports = { - plugins: [require("prettier-plugin-tailwindcss")], -}; + plugins: [require('prettier-plugin-tailwindcss')], + bracketLine: true, + printWidth: 100, + singleQuote: true, + jsxSingleQuote: false, + trailingComma: 'es5', + bracketSpacing: true, + bracketSameLine: true, + arrowParens: 'always', + singleAttributePerLine: true, + semi: false, +} diff --git a/df_designer_front/src/App.tsx b/df_designer_front/src/App.tsx index f0e31620..73f1467b 100644 --- a/df_designer_front/src/App.tsx +++ b/df_designer_front/src/App.tsx @@ -16,7 +16,6 @@ import { getVersion } from "./controllers/API"; import Router from "./routes"; import Header from "./components/headerComponent"; import { Preloader } from "./pages/Preloader/Preloader"; -import ContextMenuDemo from "./context_test"; import { PopUpContext } from "./contexts/popUpContext"; export default function App() { @@ -138,6 +137,7 @@ export default function App() { } useEffect(() => { + // @ts-ignore document.body.style = 'width: 100vw; height: 100vh;' }, [closePopUp]) diff --git a/df_designer_front/src/components/BuildLogItemComponent/BuildLogItem.tsx b/df_designer_front/src/components/BuildLogItemComponent/BuildLogItem.tsx new file mode 100644 index 00000000..8fae36f9 --- /dev/null +++ b/df_designer_front/src/components/BuildLogItemComponent/BuildLogItem.tsx @@ -0,0 +1,119 @@ +import React, { Dispatch, SetStateAction, useContext, useEffect, useRef, useState } from 'react' +import { savedBuildType, savedRunType } from '../../types/entities' +import { Check, ChevronLeft, Plus, Radio } from 'lucide-react' +import { classNames } from '../../utils' +import BuildLoaderIcon from '../../icons/BuildLoaderIcon' +import { buildApiType, buildContext, runApiType } from '../../contexts/buildContext' + +const BuildLogItem = ({ + build, + setCurrentItem, + currentItem, +}: { + build: buildApiType + setCurrentItem: Dispatch> + currentItem: runApiType | buildApiType +}) => { + const [isOpen, setIsOpen] = useState(false) + const { run, builded } = useContext(buildContext) + const is_build = currentItem?.['build_id'] ? true : false + useEffect(() => { + if (currentItem) { + if (build.runs && run && currentItem?.['build_id']) { + if (build.runs.some((run) => run.timestamp === currentItem.timestamp)) { + setIsOpen((prev) => true) + } + } + if (!currentItem?.['build_id'] && build && builded) { + if (build.timestamp === currentItem.timestamp) { + setIsOpen((prev) => true) + } + } + } + }, [run, builded]) + const accordionRef = useRef(null) + + console.log() + + return ( +
+
setCurrentItem(build)} + className={classNames( + 'mr-5 flex cursor-pointer flex-row items-center justify-between rounded px-2 py-2', + !currentItem?.['build_id'] && currentItem?.timestamp === build?.timestamp && 'bg-accent' + )}> +
+ {build.status === 'completed' ? ( + + {' '} + {' '} + + ) : ( + + {' '} + {' '} + + )} +
Build {build.id}
+
+
+ + {build['ago'] === 'now' ? 'now' : `${build['ago']} ago`} + + +
+
+
+ {build.runs + ?.map((run) => ( +
setCurrentItem(run)} + key={run.timestamp} + className={classNames( + 'mr-5 flex cursor-pointer flex-row items-center justify-between rounded px-4 py-1', + currentItem?.['build_id'] && + currentItem?.timestamp === run?.timestamp && + 'bg-accent' + )}> +
+ {run.status === 'completed' && ( + + {' '} + {' '} + + )} + {run.status === 'failed' && ( + + {' '} + {' '} + + )} + {run.status === 'running' && } +
Run {run.id}
+
+ + {run.status === 'running' ? ( + + Live {' '} + + ) : ( + <>{run['ago'] === 'now' ? 'now' : `${run['ago']} ago`} + )} + +
+ )) + .reverse()} +
+
+ ) +} + +export default BuildLogItem diff --git a/df_designer_front/src/components/BuildMenuComponent/BuildMenu.tsx b/df_designer_front/src/components/BuildMenuComponent/BuildMenu.tsx new file mode 100644 index 00000000..e1cb6259 --- /dev/null +++ b/df_designer_front/src/components/BuildMenuComponent/BuildMenu.tsx @@ -0,0 +1,43 @@ +import React, { Dispatch, MouseEventHandler, SetStateAction, useContext } from 'react' +import ConnectionStatusComponent from './components/ConnectionStatusComponent' +import RunButton from './components/RunButton' +import BuildButton from './components/BuildButton' +import NewChatIcon from '../../icons/RunIcons/NewChatIcon' +import NewLogsIcon from '../../icons/RunIcons/NewLogsIcon' +import { buildContext } from '../../contexts/buildContext' +import { ChevronLeft } from 'lucide-react' +import { classNames } from '../../utils' +import ShadTooltip from '../ShadTooltipComponent' + +export type buildMenuType = { + buildMenu: boolean + setBuildMenu: Dispatch> +} + +const BuildMenu = ({ buildMenu, setBuildMenu }: buildMenuType) => { + + const { setLogsPage, logsPage, setChat, chat } = useContext(buildContext) + + return ( +
+ + + + + + + + + + +
+ ) +} + +export default BuildMenu \ No newline at end of file diff --git a/df_designer_front/src/components/BuildMenuComponent/components/BuildButton.tsx b/df_designer_front/src/components/BuildMenuComponent/components/BuildButton.tsx new file mode 100644 index 00000000..048341ad --- /dev/null +++ b/df_designer_front/src/components/BuildMenuComponent/components/BuildButton.tsx @@ -0,0 +1,145 @@ +import { Dispatch, MouseEventHandler, SetStateAction, useContext, useRef, useState } from 'react' +import ShadTooltip from '../../ShadTooltipComponent' +import { buildContext } from '../../../contexts/buildContext' +import { Check, CheckIcon, ChevronDown, Plus } from 'lucide-react' +import BuildedCheckIcon from '../../../icons/BuildedCheckIcon/BuildedCheckIcon' +import BuildLoaderIcon from '../../../icons/BuildLoaderIcon' +import { Wrench } from 'lucide-react' +import { classNames } from '../../../utils' +import { useOutsideClick } from '../../../hooks/useOutsideClick' +import PresetDropdownItem from '../ui/PresetDropdownItem' +import { useTransition, a } from '@react-spring/web' +import { alertContext } from '../../../contexts/alertContext' +import { savedBuildType } from '../../../types/entities' +import { buildBotScript } from '../../../controllers/API/build' +import { mock_presets } from '../../../constants' + +export type BuildButtonType = { + setBuildMenu: Dispatch> + className?: string +} + +export type BuildRunPresetType = 'success' | 'error' | 'default' | 'deferred' + +const BuildButton = ({ className, setBuildMenu }: BuildButtonType) => { + const { builded, buildStatus, buildStart, buildPending } = useContext(buildContext) + const { setSuccessData, setErrorData } = useContext(alertContext) + const [isOpen, setIsOpen] = useState(false) + const [preset, setPreset] = useState('default') + // const [buildPending, setBuildPending] = useState(false) + + const presetHandler = (preset_name: BuildRunPresetType) => { + if (preset_name !== preset) { + setPreset(preset_name) + } else { + setPreset('default') + } + setIsOpen(false) + } + + const buildBotHandler = async () => { + buildStart({ + name: mock_presets[preset].name, + duration: mock_presets[preset].duration, + end_status: mock_presets[preset].end_status, + }) + } + + const presetsRef = useOutsideClick(() => { + setIsOpen(false) + }) + + const presetMenuTransition = useTransition(isOpen, { + from: { opacity: 0, transform: 'translateY(-10px)' }, + enter: { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 0, transform: 'translateY(-10px)' }, + config: { duration: 100 }, + }) + + return ( +
+
+ + + + + + +
+ {presetMenuTransition((style, flag) => ( + <> + {flag && ( + + Build as{':'} + + + + + + )} + + ))} +
+ ) +} + +export default BuildButton diff --git a/df_designer_front/src/components/BuildMenuComponent/components/ConnectionStatusComponent.tsx b/df_designer_front/src/components/BuildMenuComponent/components/ConnectionStatusComponent.tsx new file mode 100644 index 00000000..c02f14b8 --- /dev/null +++ b/df_designer_front/src/components/BuildMenuComponent/components/ConnectionStatusComponent.tsx @@ -0,0 +1,55 @@ +import React, { useCallback, useContext } from 'react' +import { buildContext } from '../../../contexts/buildContext' +import ConnectionGoodIcon from '../../../icons/RunIcons/ConnectionGoodIcon' +import ConnectionLostIcon from '../../../icons/RunIcons/ConnectionLostIcon' +import ConnectingIcon from '../../../icons/RunIcons/ConnectingIcon' +import { classNames } from '../../../utils' +import ShadTooltip from '../../ShadTooltipComponent' + +type connectionStatusComponentType = { + className?: string +} + +const ConnectionStatusComponent = ({ className }: connectionStatusComponentType) => { + + const { connectionStatus } = useContext(buildContext) + + const connectionStatusHandler = useCallback(() => { + switch (connectionStatus) { + case 'alive': return { + icon: , + status: 'Stable connection' + } + case 'broken': return { + icon: , + status: 'Connection broken' + } + case 'closed': return { + icon: , + status: 'Connection closed' + } + case 'not open': return { + icon: , + status: 'Connection not opened' + } + case 'pending': return { + icon: , + status: 'Trying to connect...' + } + } + return { + icon: <>, + status: 'error' + } + }, [connectionStatus]) + + return ( + +
+ {connectionStatusHandler().icon} +
+
+ ) +} + +export default ConnectionStatusComponent \ No newline at end of file diff --git a/df_designer_front/src/components/BuildMenuComponent/components/RunButton.tsx b/df_designer_front/src/components/BuildMenuComponent/components/RunButton.tsx new file mode 100644 index 00000000..6e6efe3c --- /dev/null +++ b/df_designer_front/src/components/BuildMenuComponent/components/RunButton.tsx @@ -0,0 +1,151 @@ +import React, { MouseEventHandler, useContext, useState } from 'react' +import ShadTooltip from '../../ShadTooltipComponent' +import { buildContext } from '../../../contexts/buildContext' +import { Check, CheckIcon, ChevronDown, Plus } from 'lucide-react' +import BuildedCheckIcon from '../../../icons/BuildedCheckIcon/BuildedCheckIcon' +import BuildLoaderIcon from '../../../icons/BuildLoaderIcon' +import { Wrench } from 'lucide-react' +import RunIcon from '../../../icons/RunIcons/RunIcon' +import { classNames } from '../../../utils' +import StopIcon from '../../../icons/RunIcons/StopIcon' +import PresetDropdownItem from '../ui/PresetDropdownItem' +import { useOutsideClick } from '../../../hooks/useOutsideClick' +import { useTransition, a } from '@react-spring/web' +import { savedBuildType, savedRunType } from '../../../types/entities' +import { alertContext } from '../../../contexts/alertContext' +import { BuildRunPresetType } from './BuildButton' +import { mock_presets } from '../../../constants' + +export type RunButtonType = { + className?: string +} + +const RunButton = ({ className }: RunButtonType) => { + const { run, connectionStatus, runPending, runStart, builded } = + useContext(buildContext) + const { setErrorData } = useContext(alertContext) + const [isOpen, setIsOpen] = useState(false) + const [preset, setPreset] = useState('default') + + const presetHandler = (preset_name: BuildRunPresetType) => { + if (preset_name !== preset) { + setPreset(preset_name) + } else { + setPreset('default') + } + setIsOpen(false) + } + + const presetsRef = useOutsideClick(() => { + setIsOpen(false) + }) + + const presetMenuTransition = useTransition(isOpen, { + from: { opacity: 0, transform: 'translateY(-10px)' }, + enter: { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 0, transform: 'translateY(-10px)' }, + config: { duration: 100 }, + }) + + + const runBotHandler = () => { + if (!builded) { + return setErrorData({title: 'Build first'}) + } + runStart({ + name: mock_presets[preset].name, + duration: mock_presets[preset].duration, + end_status: mock_presets[preset].end_status, + }) + } + + return ( +
+
+ + + + + + +
+ {presetMenuTransition((style, flag) => ( + <> + {flag && ( + + Run as{':'} + + + + + + )} + + ))} +
+ ) +} + +export default RunButton diff --git a/df_designer_front/src/components/BuildMenuComponent/ui/PresetDropdownItem.tsx b/df_designer_front/src/components/BuildMenuComponent/ui/PresetDropdownItem.tsx new file mode 100644 index 00000000..0a8ff257 --- /dev/null +++ b/df_designer_front/src/components/BuildMenuComponent/ui/PresetDropdownItem.tsx @@ -0,0 +1,21 @@ +import { CheckIcon } from 'lucide-react'; +import React from 'react'; + +interface PresetDropdownItemProps { + preset: string; + presetHandler: (presetName: string) => void; + presetName: string; +} + +const PresetDropdownItem: React.FC = ({ preset, presetHandler, presetName }) => { + return ( + + ); +}; + +export default PresetDropdownItem; \ No newline at end of file diff --git a/df_designer_front/src/components/headerComponent/index.tsx b/df_designer_front/src/components/headerComponent/index.tsx index a90f222a..17840dec 100644 --- a/df_designer_front/src/components/headerComponent/index.tsx +++ b/df_designer_front/src/components/headerComponent/index.tsx @@ -29,6 +29,13 @@ import { buildBotScript } from "../../controllers/API/build"; import BuildLoaderIcon from "../../icons/BuildLoaderIcon"; import BuildedCheckIcon from "../../icons/BuildedCheckIcon/BuildedCheckIcon"; import { buildContext } from "../../contexts/buildContext"; +import { savedBuildType, savedRunType } from "../../types/entities"; +import BuildButton from "../BuildMenuComponent/components/BuildButton"; +import RunButton from "../BuildMenuComponent/components/RunButton"; +import ConnectionStatusComponent from "../BuildMenuComponent/components/ConnectionStatusComponent"; +import NewChatIcon from "../../icons/RunIcons/NewChatIcon"; +import NewLogsIcon from "../../icons/RunIcons/NewLogsIcon"; +import BuildMenu from "../BuildMenuComponent/BuildMenu"; export default function Header() { const { flows, addFlow, tabId, setTabId, removeFlow } = useContext(TabsContext); @@ -45,33 +52,9 @@ export default function Header() { const [stars, setStars] = useState(null); const [workSpaceMode, setWorkSpaceMode] = useState(managerMode) const [nodesPlacement, setNodesPlacement] = useState(flowMode) - const { builded, setBuilded, connectionStatus, setConnectionStatus } = useContext(buildContext) - const [buildPending, setBuildPending] = useState(false) - - const buildBotHandler = async () => { - if (!builded) { - setBuildPending(true) - setBuilded(false) - try { - setTimeout(async () => { - const buildStatus = await buildBotScript() - if (!buildStatus) { - setErrorData({ title: 'Build failed!' }) - throw Error('Build failed') - } else { - setBuilded(true) - setSuccessData({ title: 'Bot was successfully builded!' }) - } - setBuildPending(false) - }, 2000); - } catch (error) { - console.log(error) - } - } else if (builded) { - setBuilded(false) - setConnectionStatus('broken') - } - } + + const [buildMenu, setBuildMenu] = useState(false) + const navigate = useNavigate() @@ -95,12 +78,22 @@ export default function Header() { }, [flowMode]) - + /** + * + * @param bool => + * Nodes placement mode handler (Canva Mode or List Mode) + */ const nodesPlacementHandler = (bool: boolean) => { setNodesPlacement(bool) setFlowMode(bool) } + + /** + * + * @param bool => + * WorkSpace mode handler (Edit Mode or Manager (no edit) Mode) + */ const workSpaceModeHandler = (bool: boolean) => { setWorkSpaceMode(bool) setManagerMode(bool) @@ -139,6 +132,10 @@ export default function Header() {
+ - - -
diff --git a/df_designer_front/src/components/newChatComponent/index.tsx b/df_designer_front/src/components/newChatComponent/index.tsx index 93870286..ffb7a533 100644 --- a/df_designer_front/src/components/newChatComponent/index.tsx +++ b/df_designer_front/src/components/newChatComponent/index.tsx @@ -1,20 +1,36 @@ import { ActivityLogIcon, Cross1Icon } from '@radix-ui/react-icons' import { useSpring, animated, a, useSprings, useTransition } from '@react-spring/web' -import { ArrowRight, Info, MessageCircle, PanelRightClose, PanelRightOpen, Paperclip, RotateCcw } from 'lucide-react' +import { + ArrowBigUp, + ArrowRight, + Info, + MessageCircle, + PanelRightClose, + PanelRightOpen, + Paperclip, + RotateCcw, + Send, + Smile, +} from 'lucide-react' import React, { useCallback, useContext, useEffect, useRef, useState } from 'react' import { Link } from 'react-router-dom' import { chatContext } from '../../contexts/chatContext' import { buildContext } from '../../contexts/buildContext' import { alertContext } from '../../contexts/alertContext' import ShadTooltip from '../ShadTooltipComponent' +import NewChatIcon from '../../icons/RunIcons/NewChatIcon' +import { darkContext } from '../../contexts/darkContext' +import axios from 'axios' +import EmojiPicker, { EmojiType } from '../ui/emojiPicker' +import Picker from 'emoji-picker-react' +import { Transition } from 'react-transition-group' export type chatMessageType = { message: string - source: "user" | "bot" | "warning" | "error" | "success" + source: 'user' | 'bot' | 'warning' | 'error' | 'success' } const NewChatComponent = ({ className }: { className?: string }) => { - const { messages, setMessages } = useContext(chatContext) // const [messages, setMessages] = useState([]) @@ -22,19 +38,60 @@ const NewChatComponent = ({ className }: { className?: string }) => { const [userMessages, setUserMessages] = useState([]) const chatRef = useRef(null) const { builded } = useContext(buildContext) - const { logs, setLogs, connectionStatus, setConnectionStatus, setLogsPage, logsPage } = useContext(buildContext) + const { + logs, + setLogs, + connectionStatus, + setConnectionStatus, + setLogsPage, + logsPage, + chat, + setChat, + run, + setRun, + } = useContext(buildContext) const [isClosed, setIsClosed] = useState(false) const statusSocket = useRef(null) const { setNoticeData, setSuccessData, setErrorData } = useContext(alertContext) - // const [botMessages, setBotMessages] = useState([]) + const { dark } = useContext(darkContext) + const [isEmoji, setIsEmoji] = useState(false) + + const [emojis, setEmojis] = useState([]) + const [emojisPending, setEmojisPending] = useState(false) + useEffect(() => { + const getEmojis = async () => { + setEmojisPending((prev) => true) + const emojis_data = await axios + .get('https://emoji-api.com/emojis?access_key=4dd2f9e45b38e17c21b432caf8ac12206775bfef') + .finally(() => setEmojisPending((prev) => false)) + return emojis_data + } + + getEmojis() + .then(({ data }) => { + setEmojis((prev) => data) + }) + .catch(() => { + console.log('emojis load error') + }) + }, []) const transitions = useTransition(messages, { from: { opacity: 0, y: 50 }, enter: { opacity: 1, y: 0 }, leave: { opacity: 0 }, config: { - duration: 100 - } + duration: 100, + }, + }) + + const emoji_transition = useTransition(isEmoji, { + from: { opacity: 0, transform: 'scale(0.5)' }, + enter: { opacity: 1, transform: 'scale(1)' }, + leave: { opacity: 0, transform: 'scale(0.5)' }, + config: { + duration: 100, + }, }) // useEffect(() => { @@ -50,18 +107,24 @@ const NewChatComponent = ({ className }: { className?: string }) => { // }, [userMessages]) useEffect(() => { + setIsEmoji(false) + }, [chat]) - if (builded && !isClosed) { + useEffect(() => { + if (builded && !isClosed && run) { statusSocket.current = new WebSocket('ws://127.0.0.1:8000/socket') - setChatIsOpen(true) + setChat(true) statusSocket.current.onopen = (e) => { console.log(e) setConnectionStatus('alive') // setSuccessData({ title: 'Bot was successfully connected!' }) - setMessages(prev => [...prev, { message: 'Bot was successfully connected!', source: 'success' }]) + setMessages((prev) => [ + ...prev, + { message: 'Bot was successfully connected!', source: 'success' }, + ]) } - + statusSocket.current.onmessage = (e) => { setLogs((prev: string[]) => [...prev, e.data]) @@ -69,110 +132,189 @@ const NewChatComponent = ({ className }: { className?: string }) => { statusSocket.current.onclose = (e) => { console.log(e) - setConnectionStatus('closed') + // setConnectionStatus('broken') setNoticeData({ title: 'Connection was closed!' }) - setMessages(prev => [...prev, { message: 'Connection was closed!', source: 'warning' }]) + setMessages((prev) => [...prev, { message: 'Connection was closed!', source: 'warning' }]) } statusSocket.current.onerror = (e) => { console.log(e) + setRun((prev) => false) setConnectionStatus('broken') setErrorData({ title: 'Connection broken!' }) - setMessages(prev => [...prev, { message: 'Connection was broken!', source: 'error' }]) + setMessages((prev) => [...prev, { message: 'Connection was broken!', source: 'error' }]) } } if (statusSocket.current) { - return () => statusSocket.current.close() + return () => { + statusSocket.current.close() + setConnectionStatus('closed') + } } - - }, [builded]) + }, [run]) useEffect(() => { chatRef.current.scrollTo({ behavior: 'smooth', top: chatRef.current.scrollHeight }) }, [messages]) - const [chatIsOpen, setChatIsOpen] = useState(window.location.pathname === '/preview') + const toggleChatHandler = useCallback(() => { + setChat(!chat) + }, [chat]) - const closeChatHandler = useCallback(() => { - setChatIsOpen(!chatIsOpen) - }, [chatIsOpen]) + const onMessage = (mouseClick: boolean, e?: React.KeyboardEvent) => { + setIsEmoji((prev) => false) + if (e && e.key === 'Enter' && !mouseClick) { + e.preventDefault() + if (userMessage) { + setMessages((prev) => [...prev, { message: userMessage, source: 'user' }]) + setUserMessages((prev) => [...prev, { message: userMessage, source: 'user' }]) + setUserMessage((prev) => '') + } + } else if (mouseClick && userMessage) { + setMessages((prev) => [...prev, { message: userMessage, source: 'user' }]) + setUserMessages((prev) => [...prev, { message: userMessage, source: 'user' }]) + setUserMessage((prev) => '') + } + } + + // const emojiAnimStyle = useSpring({ + // from: { + // opacity: (isEmoji && chat) ? 0 : 1, + // scale: (isEmoji && chat) ? '25%' : '100%', + // }, + // to: { + // opacity: (isEmoji && chat) ? 1 : 0, + // scale: (isEmoji && chat) ? '100%' : '25%', + // }, + // config: { + // duration: 200 + // } + // }) return ( -
-
- +
+
- +

Chat

- - {/* */} +
-
- {/* {messages.map((m) => ( - -

- {m.message} -

-
- ))} */} +
{transitions((style, m) => ( - -

+ +

{m.message} - { - (m.source === 'warning' || m.source === 'error' || m.source === 'success') - && - } + {(m.source === 'warning' || m.source === 'error' || m.source === 'success') && ( + + + + )}

))}
-
- +
+
+ +
+
+
+ +
+ {emoji_transition((style, flag) => ( + <> + {chat && flag && ( + + { + setUserMessage((prev) => prev + emoji) + }} + lazy={false} + theme="auto" + /> + + )} + + ))} +
+
+ +
- + className="h-full w-full resize-none p-6 outline-none" + placeholder="Type...">
-
- - + */}
) } -export default NewChatComponent \ No newline at end of file +export default NewChatComponent diff --git a/df_designer_front/src/components/ui/emojiPicker.tsx b/df_designer_front/src/components/ui/emojiPicker.tsx new file mode 100644 index 00000000..8675f941 --- /dev/null +++ b/df_designer_front/src/components/ui/emojiPicker.tsx @@ -0,0 +1,110 @@ +import React, { useDeferredValue, useEffect, useState } from 'react' +import { useInView } from 'react-intersection-observer' +import useLocalStorage from '../../hooks/useLocalStorage' + +export type EmojiType = { + slug: string + character: string + unicodeName: string + codePoint: string + group: string + subGroup: string +} + +type onEmojiClickType = ( + emoji: string, + event: React.MouseEvent +) => void + +export type emojiPickerType = { + data: EmojiType[] + lazy?: boolean + theme?: 'dark' | 'light' | 'auto' + onEmojiClick?: onEmojiClickType +} + +const EmojiPicker = ({ data, lazy = false, theme = 'auto', onEmojiClick }: emojiPickerType) => { + const [visibleData, setVisibleData] = useState(useDeferredValue(data.slice(0, 84))) + const [limit, setLimit] = useState<{ + up: number + down: number + }>({ up: 0, down: 84 }) + const [recently, setRecently] = useLocalStorage('recently', []) + + useEffect(() => { + setVisibleData((prev) => data.slice(0, 84)) + }, [data]) + + const observerElement_1 = useInView() + const observerElement_2 = useInView() + + const recentlyHandler = (emoji: EmojiType) => { + if (recently.some((e) => e.character === emoji.character)) { + setRecently((prev) => [emoji, ...prev.filter((e) => e.character !== emoji.character)]) + } else { + setRecently((prev) => [visibleData.find((e) => e.character === emoji.character), ...prev].slice(0, 10)) + } + } + + useEffect(() => { + if (observerElement_1.inView) { + setLimit((prevLimit) => { + return { + up: prevLimit.up + 25, + down: prevLimit.down + 40, + } + }) + setVisibleData((prev) => [...prev, ...data.slice(prev.length, prev.length + 40)]) + } + }, [observerElement_1.inView]) + + useEffect(() => { + if (observerElement_2.inView && visibleData.length > 100 && !observerElement_1.inView) { + setVisibleData((prev) => data.slice(0, prev.length - 35)) + } + }, [observerElement_2.inView]) + + return ( +
+
+

Recently used

+
+ {recently?.map((emoji, index) => ( + + ))} +
+
+
+

Emoji

+
+ {visibleData?.map((emoji, index) => ( + + ))} +
+
+
+ ) +} + +export default EmojiPicker diff --git a/df_designer_front/src/constants.tsx b/df_designer_front/src/constants.tsx index 76bd8507..f56fed63 100644 --- a/df_designer_front/src/constants.tsx +++ b/df_designer_front/src/constants.tsx @@ -1,5 +1,6 @@ // src/constants.tsx +import { apiPresetType } from "./contexts/buildContext"; import { FlowType } from "./types/flow"; import { buildTweaks } from "./utils"; @@ -185,3 +186,33 @@ export const NAV_DISPLAY_STYLE = * */ export const USER_PROJECTS_HEADER = "My Collection"; + +type mock_presetsType = { + default: apiPresetType + success: apiPresetType + error: apiPresetType + deferred: apiPresetType +} + +export const mock_presets: mock_presetsType = { + default: { + name: "Default", + duration: 3, + end_status: "completed", + }, + success: { + name: "Success", + duration: 4, + end_status: "completed", + }, + error: { + name: "Error", + duration: 4, + end_status: "failed", + }, + deferred: { + name: "Deferred", + duration: 20, + end_status: "failed", + } +} diff --git a/df_designer_front/src/contexts/buildContext.tsx b/df_designer_front/src/contexts/buildContext.tsx index 193450ba..d4a037cc 100644 --- a/df_designer_front/src/contexts/buildContext.tsx +++ b/df_designer_front/src/contexts/buildContext.tsx @@ -1,75 +1,277 @@ -import { createContext, useEffect, useState } from "react"; -import { chatMessageType } from "../components/newChatComponent"; +import { Dispatch, SetStateAction, createContext, useContext, useEffect, useState } from 'react' +import { chatMessageType } from '../components/newChatComponent' +import { savedBuildType } from '../types/entities' +import { + getBuildStatus, + getBuilds, + getRunStatus, + getRuns, + startBuild, + startRun, + stopBuild, + stopRun, +} from '../controllers/API/build' +import { alertContext } from './alertContext' + +export type connectionStatusType = 'alive' | 'broken' | 'closed' | 'not open' | 'pending' +export type buildStatusType = 'success' | 'failed' | 'not builded' | 'pending' +export type buildApiStatusType = 'completed' | 'failed' | 'running' | 'stopped' +export type apiPresetType = { + name?: string + duration?: number + end_status?: buildApiStatusType +} + +export type buildApiTypeMinify = { + id: number + status: buildApiStatusType + timestamp: number + preset_name: string + runs: any[] +} + +export type buildApiType = { + id: number + status: buildApiStatusType + log_path: string + logs: string[] + preset_name: string + timestamp: number + runs: runApiType[] +} + +export interface buildApiTypeRuns extends buildApiType { + runs: runApiType[] +} + +export type runApiType = { + id: number + status: buildApiStatusType + timestamp: number + preset_name: string + build_id: number + logs?: string[] + logs_path?: string +} type buildContextType = { - logs: string[], - setLogs: (newState: any) => void; - builded: boolean; - setBuilded: (newState: boolean) => void, - connectionStatus: 'alive' | 'broken' | 'closed' | 'not open' - setConnectionStatus: (newState: 'alive' | 'broken' | 'closed') => void + chat: boolean + setChat: Dispatch> + logs: string[] + setLogs: Dispatch> + builds: buildApiType[] + setBuilds: Dispatch> + buildPending: boolean + setBuildPending: Dispatch> + builded: boolean + setBuilded: Dispatch> + buildStart: (options?: apiPresetType) => void + connectionStatus: connectionStatusType + setConnectionStatus: Dispatch> + buildStatus: buildStatusType + setBuildStatus: Dispatch> logsPage: boolean - setLogsPage: (newState: boolean) => void + setLogsPage: Dispatch> + run: boolean + setRun: Dispatch> + runs: runApiType[] + setRuns: Dispatch> + runPending: boolean + setRunPending: Dispatch> + runStart: (options?: apiPresetType) => void // statusSocketHandler: () => void -}; +} const initialValue: buildContextType = { + chat: false, + setChat: () => {}, logs: [], - setLogs: () => { }, + setLogs: () => {}, + builds: [], + setBuilds: () => {}, + buildPending: false, + setBuildPending: () => {}, builded: false, - setBuilded: () => { }, + setBuilded: () => {}, + buildStart: () => {}, connectionStatus: 'not open', - setConnectionStatus: () => { }, + setConnectionStatus: () => {}, + buildStatus: 'not builded', + setBuildStatus: () => {}, logsPage: false, - setLogsPage: () => { }, + setLogsPage: () => {}, + run: false, + setRun: () => {}, + runs: [], + setRuns: () => {}, + runPending: false, + setRunPending: () => {}, + runStart: () => {}, // statusSocketHandler: () => { } -}; +} -export const buildContext = createContext(initialValue); +export const buildContext = createContext(initialValue) export function BuildProvider({ children }) { - const [builded, setBuilded] = useState() + const [run, setRun] = useState(false) const [logsPage, setLogsPage] = useState(false) - const [logs, setLogs] = useState([]) - const [connectionStatus, setConnectionStatus] = useState<'alive' | 'broken' | 'closed' | 'not open'>('not open') + const [chat, setChat] = useState(false) + const [logs, setLogs] = useState(['init']) + const [buildStatus, setBuildStatus] = useState('not builded') + const [buildPending, setBuildPending] = useState(false) + const [connectionStatus, setConnectionStatus] = useState('not open') + const [builds, setBuilds] = useState([]) + const [runs, setRuns] = useState([]) + const [runPending, setRunPending] = useState(false) - // const statusSocketHandler = () => { - // const statusSocket = new WebSocket('ws://127.0.0.1:8000/socket') - // statusSocket.close() - // statusSocket.send('open connection') - // statusSocket.onopen = (e) => { - // console.log(e) - // alert('opened') - // } + const { setErrorData, setSuccessData, setNoticeData } = useContext(alertContext) - // statusSocket.onmessage = (e) => { - // console.log(e) - // setLogs(e.data) - // } + useEffect(() => { + const fetchBuilds = async () => { + const b = await getBuilds() + setBuilds(prev => b.build) + if (b.build.length) { + if (b.build.pop().status === 'completed') { + setBuilded(true) + setBuildStatus('success') + } + } + return b + } + fetchBuilds() + }, []) - // statusSocket.onclose = (e) => { - // console.log(e) - // alert('closed') - // } - // } + const buildStart = async ({ + duration = 3, + end_status = 'completed', + name = 'default', + }: apiPresetType) => { + setBuildPending((prev) => true) + setBuildStatus('pending') + try { + const start_res = await startBuild({ duration, end_status, name }) + setBuilds((prev) => [...prev, start_res.build_info]) + let flag = true + let timer = 0 + let timerId = setInterval(() => timer++, 1000) + while (flag) { + if (timer > 15) { + setBuilded((prev) => false) + setBuildStatus('failed') + setErrorData({ title: 'Build timeout error!' }) + await stopBuild() + return (flag = false) + } + const status = await getBuildStatus() + if (status !== 'running') { + flag = false + const { build } = await getBuilds() + setBuilds((prev) => build) + if (status === 'completed') { + setBuildStatus('success') + setBuilded((prev) => true) + setSuccessData({ title: 'Build successfully!' }) + } else if (status === 'failed') { + setBuildStatus('failed') + setBuilded((prev) => false) + setErrorData({ title: 'Build failed!' }) + } + } + await new Promise((resolve) => setTimeout(resolve, 1000)) + } + clearInterval(timerId) + } catch (error) { + } finally { + setBuildPending((prev) => false) + } + } + const runStart = async ({ + duration = 3, + end_status = 'completed', + name = 'default', + }: apiPresetType) => { + setRunPending((prev) => true) + setConnectionStatus('pending') + try { + const start_res = await startRun({ duration, end_status, name }) + setRuns((prev) => [...prev, start_res.run_info]) + const b = await getBuilds() + setBuilds((prev) => b.build) + let flag = true + let timer = 0 + let timerId = setInterval(() => timer++, 1000) + while (flag) { + if (timer > 15) { + setRun((prev) => false) + setConnectionStatus('broken') + setErrorData({ title: 'Run timeout error!' }) + await stopRun() + return (flag = false) + } + const status = await getRunStatus() + if (status !== 'running') { + flag = false + const { run } = await getRuns() + setRuns((prev) => run) + const b = await getBuilds() + setBuilds((prev) => b.build) + if (status === 'completed') { + setConnectionStatus('closed') + setRun((prev) => false) + setSuccessData({ title: 'Run successfully closed!' }) + } else if (status === 'failed') { + setConnectionStatus('broken') + setRun((prev) => false) + setErrorData({ title: 'Run failed!' }) + } + } else { + const { run } = await getRuns() + setRuns((prev) => run) + const b = await getBuilds() + setBuilds((prev) => b.build) + setRunPending((prev) => false) + setConnectionStatus('alive') + setRun((prev) => true) + } + await new Promise((resolve) => setTimeout(resolve, 1000)) + } + clearInterval(timerId) + } catch (error) { + } finally { + } + } return ( + setLogsPage, + chat, + setChat, + run, + setRun, + runs, + setRuns, + runPending, + setRunPending, + runStart, + }}> {children} - ); + ) } diff --git a/df_designer_front/src/contexts/chatContext.tsx b/df_designer_front/src/contexts/chatContext.tsx index 1f85e1b2..f89acf34 100644 --- a/df_designer_front/src/contexts/chatContext.tsx +++ b/df_designer_front/src/contexts/chatContext.tsx @@ -18,7 +18,7 @@ export function ChatProvider({ children }) { const [messages, setMessages] = useState([ { message: 'Your bot is not started! Please, complete configuration, build and start your bot to see messages here! ', - source: 'warning' + source: 'bot' } ]) diff --git a/df_designer_front/src/contexts/darkContext.tsx b/df_designer_front/src/contexts/darkContext.tsx index 8607963f..6b50e79d 100644 --- a/df_designer_front/src/contexts/darkContext.tsx +++ b/df_designer_front/src/contexts/darkContext.tsx @@ -1,14 +1,14 @@ import { createContext, useEffect, useState } from "react"; type darkContextType = { - dark: {}; + dark: boolean; setDark: (newState: {}) => void; grid: boolean; setGrid: (newState: {}) => void; }; const initialValue = { - dark: {}, + dark: false, setDark: () => {}, grid: false, setGrid: () => {}, diff --git a/df_designer_front/src/controllers/API/build.ts b/df_designer_front/src/controllers/API/build.ts index 20640a9b..02dc5bf7 100644 --- a/df_designer_front/src/controllers/API/build.ts +++ b/df_designer_front/src/controllers/API/build.ts @@ -1,13 +1,37 @@ import axios, { AxiosResponse } from "axios" +import { uniqueId } from "lodash" +import { apiPresetType, buildApiType, buildApiTypeMinify, runApiType } from "../../contexts/buildContext" +import { savedBuildType } from "../../types/entities" + +type buildResponseTypeMinify = { + status?: string + build_info: buildApiTypeMinify +} type buildResponseType = { - status: string + status?: string + build_info: buildApiType +} + +type buildsResponseType = { + status?: string + build: buildApiType[] +} + +type runResponseType = { + status?: string + run_info: runApiType +} + +type runsResponseType = { + status?: string + run: runApiType[] } export const buildBotScript = async () => { try { const response: AxiosResponse = await axios.post('/build') - const data: buildResponseType = response.data + const data: buildResponseTypeMinify = response.data if (data.status !== 'ok') { throw Error('data.status is not ok') } else { @@ -16,4 +40,96 @@ export const buildBotScript = async () => { } catch (error) { throw error } +} + +export const getBuilds = async () => { + try { + const response: AxiosResponse = await axios.get('/bot/builds') + const data: buildsResponseType = response.data + console.log(data) + return data + } catch (error) { + throw error + } +} + +export const startBuild = async (preset?: apiPresetType) => { + try { + const response: AxiosResponse = await axios.post('/bot/build/start', preset ?? { + name: uniqueId('build-'), + duration: 4, + end_status: 'completed' + }) + const data = response.data + console.log(data) + return data + } catch (error) { + throw error + } +} + +export const getBuildStatus = async () => { + try { + const response: AxiosResponse = await axios.get(`/bot/build/status`) + const data = response.data + console.log(data) + return data + } catch (error) { + throw error + } +} + +export const stopBuild = async () => { + try { + const response: AxiosResponse<{status: string}> = await axios.get('/bot/build/stop') + const data = response.data + console.log(data) + return data + } catch (error) { + throw error + } +} + +export const startRun = async (preset?: apiPresetType) => { + try { + const response: AxiosResponse = await axios.post('/bot/run/start', preset) + const data = response.data + console.log(data) + return data + } catch (error) { + throw error + } +} + +export const getRunStatus = async () => { + try { + const response: AxiosResponse = await axios.get('/bot/run/status') + const data = response.data + console.log(data) + return data + } catch (error) { + throw error + } +} + +export const getRuns = async () => { + try { + const response: AxiosResponse = await axios.get('/bot/runs') + const data = response.data + console.log(data) + return data + } catch (error) { + throw error + } +} + +export const stopRun = async () => { + try { + const response: AxiosResponse<{status: string}> = await axios.get('/bot/run/stop') + const data = response.data + console.log(data) + return data + } catch (error) { + throw error + } } \ No newline at end of file diff --git a/df_designer_front/src/hooks/useLocalStorage.tsx b/df_designer_front/src/hooks/useLocalStorage.tsx new file mode 100644 index 00000000..926b5768 --- /dev/null +++ b/df_designer_front/src/hooks/useLocalStorage.tsx @@ -0,0 +1,14 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; + +function useLocalStorage(key: string, initialState: S | (() => S)): [S, Dispatch>] { + const [state, setState] = useState(JSON.parse(localStorage.getItem(key)) ?? initialState) + + useEffect(() => { + const rawValue = JSON.stringify(state); + localStorage.setItem(key, rawValue); + }, [key, state]); + + return [state, setState] +} + +export default useLocalStorage \ No newline at end of file diff --git a/df_designer_front/src/hooks/useOutsideClick.tsx b/df_designer_front/src/hooks/useOutsideClick.tsx new file mode 100644 index 00000000..990f5f29 --- /dev/null +++ b/df_designer_front/src/hooks/useOutsideClick.tsx @@ -0,0 +1,21 @@ +import { useEffect, useRef } from 'react'; + +export const useOutsideClick = (callback: () => void) => { + const ref = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + callback(); + } + }; + + document.addEventListener('mouseup', handleClickOutside); + + return () => { + document.removeEventListener('mouseup', handleClickOutside); + }; + }, [callback]); + + return ref; +}; \ No newline at end of file diff --git a/df_designer_front/src/icons/BuildedCheckIcon/BuildedCheckIcon.tsx b/df_designer_front/src/icons/BuildedCheckIcon/BuildedCheckIcon.tsx index 69ae79fc..46776073 100644 --- a/df_designer_front/src/icons/BuildedCheckIcon/BuildedCheckIcon.tsx +++ b/df_designer_front/src/icons/BuildedCheckIcon/BuildedCheckIcon.tsx @@ -4,7 +4,7 @@ import { SVGElementInterface } from '../../types/components' const BuildedCheckIcon = ({ className }: SVGElementInterface) => { return ( - + ) diff --git a/df_designer_front/src/icons/RunIcons/ConnectingIcon.tsx b/df_designer_front/src/icons/RunIcons/ConnectingIcon.tsx new file mode 100644 index 00000000..e329bcef --- /dev/null +++ b/df_designer_front/src/icons/RunIcons/ConnectingIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { SVGElementInterface } from '../../types/components' + +const ConnectingIcon = ({ className }: SVGElementInterface) => { + return ( + + + + ) +} + +export default ConnectingIcon \ No newline at end of file diff --git a/df_designer_front/src/icons/RunIcons/ConnectionGoodIcon.tsx b/df_designer_front/src/icons/RunIcons/ConnectionGoodIcon.tsx new file mode 100644 index 00000000..4fd65407 --- /dev/null +++ b/df_designer_front/src/icons/RunIcons/ConnectionGoodIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { SVGElementInterface } from '../../types/components' + +const ConnectionGoodIcon = ({ className }:SVGElementInterface) => { + return ( + + + + ) +} + +export default ConnectionGoodIcon \ No newline at end of file diff --git a/df_designer_front/src/icons/RunIcons/ConnectionLostIcon.tsx b/df_designer_front/src/icons/RunIcons/ConnectionLostIcon.tsx new file mode 100644 index 00000000..a030b038 --- /dev/null +++ b/df_designer_front/src/icons/RunIcons/ConnectionLostIcon.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { SVGElementInterface } from '../../types/components' + +const ConnectionLostIcon = ({ className }: SVGElementInterface) => { + return ( + + + + + ) +} + +export default ConnectionLostIcon \ No newline at end of file diff --git a/df_designer_front/src/icons/RunIcons/NewChatIcon.tsx b/df_designer_front/src/icons/RunIcons/NewChatIcon.tsx new file mode 100644 index 00000000..4866cfa0 --- /dev/null +++ b/df_designer_front/src/icons/RunIcons/NewChatIcon.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import { SVGElementInterface } from '../../types/components' + +const NewChatIcon = ({ className }: SVGElementInterface) => { + return ( + + + + + + + ) +} + +export default NewChatIcon \ No newline at end of file diff --git a/df_designer_front/src/icons/RunIcons/NewLogsIcon.tsx b/df_designer_front/src/icons/RunIcons/NewLogsIcon.tsx new file mode 100644 index 00000000..570b16ae --- /dev/null +++ b/df_designer_front/src/icons/RunIcons/NewLogsIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { SVGElementInterface } from '../../types/components' + +const NewLogsIcon = ({ className }: SVGElementInterface) => { + return ( + + + + ) +} + +export default NewLogsIcon \ No newline at end of file diff --git a/df_designer_front/src/icons/RunIcons/RunIcon.tsx b/df_designer_front/src/icons/RunIcons/RunIcon.tsx new file mode 100644 index 00000000..282bb126 --- /dev/null +++ b/df_designer_front/src/icons/RunIcons/RunIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { SVGElementInterface } from '../../types/components' + +const RunIcon = ({ className }: SVGElementInterface) => { + return ( + + + + ) +} + +export default RunIcon \ No newline at end of file diff --git a/df_designer_front/src/icons/RunIcons/StopIcon.tsx b/df_designer_front/src/icons/RunIcons/StopIcon.tsx new file mode 100644 index 00000000..81d1dd40 --- /dev/null +++ b/df_designer_front/src/icons/RunIcons/StopIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { SVGElementInterface } from '../../types/components' + +const StopIcon = ({ className }:SVGElementInterface) => { + return ( + + + + ) +} + +export default StopIcon \ No newline at end of file diff --git a/df_designer_front/src/index.css b/df_designer_front/src/index.css index efef3c3b..d3e4cd10 100644 --- a/df_designer_front/src/index.css +++ b/df_designer_front/src/index.css @@ -75,7 +75,7 @@ --card: 0 1% 21%; /* hsl(0 1% 21%) */ --card-foreground: 0 0% 100%; /* hsl(0 0% 100%) */ - --border: 0 1% 21%; /* hsl(0 1% 21%) */ + --border: 0 1% 23%; /* hsl(0 1% 23%) */ --input: 0 1% 21%; /* hsla(0, 0%, 7%, 1)*/ --primary: 0 1% 21%; /* hsla(0, 1%, 21%, 1) */ @@ -84,7 +84,7 @@ --secondary: 240 1% 13%; /* hsl(240 1% 13%) */ --secondary-foreground: 0 0% 100%; /* hsl(0 0% 100%) */ - --accent: 0 1% 21%; /* hsl(0 1% 21%) */ + --accent: 0 1% 18%; /* hsl(0 1% 18%) */ --accent-foreground: 0 0% 100%; /* hsl(0 0% 100%) */ --destructive: 0 63% 31%; /* hsl(0 63% 31%) */ @@ -196,10 +196,10 @@ The cursor: default; property value restores the browser's default cursor style @apply absolute inset-y-0 flex items-center py-1.5 pl-3; } .extra-side-bar-save-disable { - @apply border border-transparent p-1.5 text-muted-foreground transition-all duration-300; + @apply border border-transparent rounded p-1.5 text-muted-foreground transition-all duration-300; } .extra-side-bar-save-disable:hover { - @apply rounded border-border hover:bg-accent hover:text-accent-foreground; + @apply border-border hover:bg-accent hover:text-accent-foreground; } .side-bar-button-size { @apply h-5 w-5; @@ -994,8 +994,9 @@ The cursor: default; property value restores the browser's default cursor style } .chat-wrapper { - height: calc(100vh - 60px); - max-height: calc(100vh - 60px); + min-height: calc(100vh - 54px); + height: calc(100vh - 54px); + max-height: calc(100vh - 54px); box-shadow: 0px 8px 15px 0px rgba(14, 1, 66, 0.15); } @@ -1004,8 +1005,8 @@ The cursor: default; property value restores the browser's default cursor style } .builded-check { - animation: builded-check 6s linear normal; - @apply absolute w-4 h-4 -top-2 -right-2 opacity-0 + /* animation: builded-check 6s linear normal; */ + @apply absolute w-4/5 h-[2px] rounded-t-xl bottom-0 } @keyframes builded-check { @@ -1062,6 +1063,43 @@ The cursor: default; property value restores the browser's default cursor style } } + .build-menu { + @apply w-max flex flex-row items-center justify-start gap-0.5 py-0.5 px-0.5 border border-border rounded-md transition-all duration-300 + } + + .build-menu-closed { + @apply pr-0 gap-0 + } + + .build-menu-item { + width: 34px; + } + + .build-menu-item-long { + width: 52px; + } + + .build-menu-item-disabled { + width: 0 !important; + @apply w-0 p-0 pointer-events-none opacity-0 scale-x-0 + } + + .preset-select { + @apply absolute -left-1/2 top-[125%] z-50 bg-background rounded-lg border border-border flex flex-col items-center justify-start gap-1 p-1 + } + + .preset-select-title { + @apply block ml-2 text-sm w-full text-start text-neutral-500 + } + + .run-button-option { + @apply hover:bg-accent transition px-3 py-0.5 cursor-pointer flex items-center justify-between gap-2 min-w-[116px] rounded-lg + } + + .preset-open-btn { + @apply h-[34px] rounded border border-[transparent] transition hover:border-border hover:bg-accent + } + } .border-tab { diff --git a/df_designer_front/src/pages/FlowPage/components/PageComponent/index.tsx b/df_designer_front/src/pages/FlowPage/components/PageComponent/index.tsx index 4d3bb03a..b3ca6e3c 100644 --- a/df_designer_front/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/df_designer_front/src/pages/FlowPage/components/PageComponent/index.tsx @@ -790,7 +790,7 @@ export default function Page({ flow }: { flow: FlowType }) { {/* Main area */}
- + {/* Primary column */} {transitions((style, item) => ( diff --git a/df_designer_front/src/pages/MainPage/index.tsx b/df_designer_front/src/pages/MainPage/index.tsx index c97a979c..81f57489 100644 --- a/df_designer_front/src/pages/MainPage/index.tsx +++ b/df_designer_front/src/pages/MainPage/index.tsx @@ -12,10 +12,14 @@ export default function HomePage() { const { openPopUp } = useContext(PopUpContext) const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow } = useContext(TabsContext); + useEffect(() => { setTabId(""); }, []); const navigate = useNavigate(); + + console.log(flows) + return (
diff --git a/df_designer_front/src/pages/PreviewPage/index.tsx b/df_designer_front/src/pages/PreviewPage/index.tsx index 39d94d48..711d1b9f 100644 --- a/df_designer_front/src/pages/PreviewPage/index.tsx +++ b/df_designer_front/src/pages/PreviewPage/index.tsx @@ -1,89 +1,104 @@ import { ActivityLogIcon, Cross1Icon } from '@radix-ui/react-icons' -import { ArrowLeft, Cross, MessageCircle, Paperclip, RotateCcw } from 'lucide-react' +import { ArrowLeft, Check, ChevronDown, Cross, MessageCircle, Paperclip, Plus, Radio, RotateCcw } from 'lucide-react' import React, { useContext, useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import NewChatComponent from '../../components/newChatComponent' -import { buildContext } from '../../contexts/buildContext' +import { buildApiType, buildContext, runApiType } from '../../contexts/buildContext' import { alertContext } from '../../contexts/alertContext' import { classNames } from '../../utils' +import { savedBuildType, savedRunType } from '../../types/entities' +import BuildLogItem from '../../components/BuildLogItemComponent/BuildLogItem' -const PreviewPage = ({className}: {className?: string}) => { +const PreviewPage = ({ className }: { className?: string }) => { const wrapperRef = useRef(null) const titleRef = useRef(null) const logsRef = useRef(null) + + const { logs, runs, setLogsPage, run, builds } = useContext(buildContext) + const { setSuccessData } = useContext(alertContext) - // const [mockLogs, setMockLogs] = useState([ - // 'Current runner version: "2.311.0"', - // 'Secret source: Dependabot', - // 'Prepare workflow directory', - // 'Prepare all required actions', - // 'Getting action download info', - // 'Download action repository "actions/checkout@v4" (SHA:b4ffde65f46336ab88eb53be808477a3936bae11)', - // 'Download action repository "actions/setup-python@v4" (SHA:65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236)', - // 'Complete job name: lint', - // 'Bot successfully started at localhost:5000' - // ]) + // const [builds, setBuilds] = useState([]) + const [isOpen, setIsOpen] = useState(run) + const [localBuilds, setLocalBuilds] = useState([]) - // const { builded } = useContext(buildContext) - const { logs, setLogs, connectionStatus, setConnectionStatus, setLogsPage } = useContext(buildContext) - // const [isClosed, setIsClosed] = useState(false) - // const statusSocket = useRef(null) - // const { setNoticeData, setSuccessData, setErrorData } = useContext(alertContext) + useEffect(() => { + const builds_with_ago_timestamp = builds?.map((build, idx) => { + console.log(build.timestamp, Date.now()) + const agoTimestamp = Math.round((Date.now() - (build.timestamp * 1000)) / 60000) + const ago = agoTimestamp < 60 ? (agoTimestamp > 1 ? `${agoTimestamp} minutes` : (agoTimestamp < 0.5 ? `now` : `1 minute`)) : (agoTimestamp > 120 ? `${Math.floor(agoTimestamp / 60)} hours` : `1 hour`) + return { + ...build, + ago: ago, + title: `Build ${build.id}`, + runs: build.runs?.map((run: runApiType, idx) => { + const agoTimestamp = Math.round((Date.now() - (run.timestamp * 1000 ?? Date.now())) / 60000) + const ago = agoTimestamp < 60 ? (agoTimestamp > 1 ? `${agoTimestamp} minutes` : (agoTimestamp < 0.5 ? `now` : `1 minute`)) : (agoTimestamp > 120 ? `${Math.floor(agoTimestamp / 60)} hours` : `1 hour`) + return { + ...run, + ago: ago, + title: `Run ${run.id}` + } + }) ?? [] + } + }) ?? [] + setLocalBuilds(prev => builds_with_ago_timestamp.reverse()) + }, [builds, runs, run]) + - // useEffect(() => { - - // if (builded && !isClosed) { - // statusSocket.current = new WebSocket('ws://127.0.0.1:8000/socket') - - // statusSocket.current.onopen = (e) => { - // console.log(e) - // setConnectionStatus('alive') - // setSuccessData({title: 'Connection was successfully opened!'}) - // } - - - // statusSocket.current.onmessage = (e) => { - // setLogs((prev: string[]) => [...prev, e.data]) - // } - - // statusSocket.current.onclose = (e) => { - // console.log(e) - // setConnectionStatus('closed') - // setNoticeData({title: 'Connection was closed!'}) - // } - - // statusSocket.current.onerror = (e) => { - // console.log(e) - // setConnectionStatus('broken') - // setErrorData({title: 'Connection broken!'}) - // } - // } - - // if (statusSocket.current) { - // return () => statusSocket.current.close() - // } - - // }, [builded, isClosed]) + useEffect(() => { + logsRef.current?.scrollTo({ behavior: 'smooth', top: logsRef.current?.scrollHeight }) + }, [logs]) - // useEffect(() => { + const [currentItem, setCurrentItem] = useState() + - // mockLogs.forEach((log, idx) => { - // setTimeout(() => { - // setLogs(prev => [...prev, log]) - // }, (idx+1)*150); - // }) + useEffect(() => { + const lastRun = builds[builds.length - 1].runs[builds[builds.length - 1].runs.length - 1] + if (lastRun) { + setCurrentItem(lastRun) + } + if (run) { + setIsOpen(run) + } + }, [run]) - // }, []) + const [pageLogs, setPageLogs] = useState([]) useEffect(() => { - logsRef.current?.scrollTo({behavior: 'smooth', top: logsRef.current?.scrollHeight}) - }, [logs]) + if (builds) { + const lastBuild: buildApiType = builds[builds.length - 1] + const lastRun: runApiType = lastBuild.runs[lastBuild.runs?.length - 1] + if (lastRun && currentItem) { + if (currentItem?.['build_id'] && currentItem?.timestamp === lastRun.timestamp) { + if (run) { + setPageLogs(prev => logs) + } else { + setPageLogs(prev => currentItem.logs) + } + } else { + if (currentItem) { + setPageLogs(prev => currentItem.logs) + } else { + setPageLogs(prev => []) + } + } + } + if (lastBuild && currentItem && !currentItem?.['build_id']) { + setPageLogs(prev => currentItem.logs ?? []) + } + } + }, [run, logs, currentItem]) + + const copyHandler = (text: string) => { + navigator.clipboard.writeText(text) + setSuccessData({ title: "Log was successfully copied!" }) + } return ( -
+

Logs

-
+
- {logs?.map((log, idx) => ( -
- {idx + 1} -

{log}

-
- )) ?? <>No logs} + }} className='flex flex-col items-start justify-start gap-y-0.5 min-w-[17rem] border-r border-border overflow-y-scroll '> + {localBuilds.map((build) => ( + + ))}
+ {currentItem && currentItem?.['build_id'] && ( +
+
+
+ {currentItem?.status === 'completed' && } + {currentItem?.status === 'failed' && } + {currentItem?.status === 'running' && Live } + Run {currentItem?.id} +
+

+ Status: + {currentItem?.status === 'completed' && Success} + {currentItem?.status === 'failed' && Error} + {currentItem?.status === 'running' && Live} +

+

+ Start time: {new Date(currentItem?.timestamp).toLocaleString()} +

+ {/*

+ End time: {currentItem.timestamp.end ? new Date(currentItem?.timestamp?.end).toLocaleString() : Live} +

*/} + {/*

+ Duration: + {currentItem.status === 'running' ? ( + <>{Math.round((Date.now() - currentItem?.timestamp?.start) / 1000)} seconds + ) : ( + <>{Math.round((currentItem?.timestamp?.end - currentItem?.timestamp?.start) / 1000)} seconds + )} +

*/} +

+ Preset: default +

+

+ Logs path: none +

+
+
+
setIsOpen(!isOpen)} className='font-bold text-lg flex flex-row items-center gap-1 mb-1 cursor-pointer'>Logs
+
+ {pageLogs?.map((log, idx) => ( +
copyHandler(log)} className='flex flex-row items-center justify-start gap-2 hover:bg-accent transition w-full cursor-pointer'> + {idx + 1} +

{log}

+
+ )) ?? <>No logs} +
+
+
+ )} + {currentItem && !currentItem?.['build_id'] && ( +
+
+
+ {currentItem?.status === 'completed' && } + {currentItem?.status === 'failed' && } + Build {currentItem?.id} +
+

+ Status: + {currentItem?.status === 'completed' && Success} + {currentItem?.status === 'failed' && Error} +

+

+ Compilation time: {new Date(currentItem?.timestamp*1000).toLocaleString()} +

+

+ Duration: + + <> {3} seconds + +

+

+ Preset: { currentItem?.preset_name } +

+

+ Logs path: +

+
+
+
setIsOpen(!isOpen)} className='font-bold text-lg flex flex-row items-center gap-1 mb-1'>Logs
+
+ {pageLogs?.map((log, idx) => ( +
copyHandler(log)} className='flex flex-row items-center justify-start gap-2 hover:bg-accent transition w-full cursor-pointer'> + {idx + 1} +

{log}

+
+ )) ?? <>No logs} +
+
+
+ )} {/* -

Logs

+
+
+
+ +

Logs

-
-
+
+
{localBuilds.map((build) => ( - + ))}
{currentItem && currentItem?.['build_id'] && (
-
-
- {currentItem?.status === 'completed' && } - {currentItem?.status === 'failed' && } - {currentItem?.status === 'running' && Live } +
+
+ {currentItem?.status === 'completed' && ( + + {' '} + {' '} + + )} + {currentItem?.status === 'failed' && ( + + {' '} + {' '} + + )} + {currentItem?.status === 'running' && ( + + Live {' '} + + )} Run {currentItem?.id}
-

- Status: - {currentItem?.status === 'completed' && Success} - {currentItem?.status === 'failed' && Error} - {currentItem?.status === 'running' && Live} +

+ Status: + {currentItem?.status === 'completed' && ( + Success + )} + {currentItem?.status === 'failed' && Error} + {currentItem?.status === 'running' && Live}

-

- Start time: {new Date(currentItem?.timestamp).toLocaleString()} +

+ Start time:{' '} + {new Date(currentItem?.timestamp).toLocaleString()}

{/*

End time: {currentItem.timestamp.end ? new Date(currentItem?.timestamp?.end).toLocaleString() : Live} @@ -144,24 +213,44 @@ const PreviewPage = ({ className }: { className?: string }) => { <>{Math.round((currentItem?.timestamp?.end - currentItem?.timestamp?.start) / 1000)} seconds )}

*/} -

- Preset: default +

+ Preset:{' '} + default

-

- Logs path: none +

+ Logs path: none

-
setIsOpen(!isOpen)} className='font-bold text-lg flex flex-row items-center gap-1 mb-1 cursor-pointer'>Logs
-
+
setIsOpen(!isOpen)} + className="mb-1 flex cursor-pointer flex-row items-center gap-1 text-lg font-bold"> + Logs {' '} +
+
{pageLogs?.map((log, idx) => ( -
copyHandler(log)} className='flex flex-row items-center justify-start gap-2 hover:bg-accent transition w-full cursor-pointer'> - {idx + 1} +
copyHandler(log)} + className="flex w-full cursor-pointer flex-row items-center justify-start gap-2 transition hover:bg-accent"> + + {' '} + {idx + 1}{' '} +

{log}

)) ?? <>No logs} @@ -171,44 +260,77 @@ const PreviewPage = ({ className }: { className?: string }) => { )} {currentItem && !currentItem?.['build_id'] && (
-
-
- {currentItem?.status === 'completed' && } - {currentItem?.status === 'failed' && } +
+
+ {currentItem?.status === 'completed' && ( + + {' '} + {' '} + + )} + {currentItem?.status === 'failed' && ( + + {' '} + {' '} + + )} Build {currentItem?.id}
-

- Status: - {currentItem?.status === 'completed' && Success} - {currentItem?.status === 'failed' && Error} +

+ Status: + {currentItem?.status === 'completed' && ( + Success + )} + {currentItem?.status === 'failed' && Error}

-

- Compilation time: {new Date(currentItem?.timestamp*1000).toLocaleString()} +

+ Compilation time:{' '} + {new Date(currentItem?.timestamp * 1000).toLocaleString()}

-

- Duration: +

+ Duration: <> {3} seconds

-

- Preset: { currentItem?.preset_name } +

+ Preset:{' '} + {currentItem?.preset_name}

-

- Logs path: +

+ Logs path:

-
setIsOpen(!isOpen)} className='font-bold text-lg flex flex-row items-center gap-1 mb-1'>Logs
-
+
setIsOpen(!isOpen)} + className="mb-1 flex flex-row items-center gap-1 text-lg font-bold"> + Logs {' '} +
+
{pageLogs?.map((log, idx) => ( -
copyHandler(log)} className='flex flex-row items-center justify-start gap-2 hover:bg-accent transition w-full cursor-pointer'> - {idx + 1} +
copyHandler(log)} + className="flex w-full cursor-pointer flex-row items-center justify-start gap-2 transition hover:bg-accent"> + + {' '} + {idx + 1}{' '} +

{log}

)) ?? <>No logs} @@ -231,4 +353,4 @@ const PreviewPage = ({ className }: { className?: string }) => { ) } -export default PreviewPage \ No newline at end of file +export default PreviewPage diff --git a/df_designer_front/vite.config.ts b/df_designer_front/vite.config.ts index 5e4185ef..e4f5ad47 100644 --- a/df_designer_front/vite.config.ts +++ b/df_designer_front/vite.config.ts @@ -1,15 +1,17 @@ import { UserConfig, defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import svgr from "vite-plugin-svgr"; -const apiRoutes = ["/flows" , "/flows", "/health", '/build', '/socket', '/builds', '/run', '/runs', '/bot', '/bot/build/start', '/bot/build/status']; +const apiRoutes = ["/flows" , "/flows", "/health", '/build', '/socket', '/builds', '/run', '/runs', '/bot', '/bot/build/start', '/bot/build/status', '/bot/build/stop', '/bot/run/start', '/bot/run/status', '/bot/run/stop']; // Use environment variable to determine the target. -const target = process.env.VITE_PROXY_TARGET || "http://127.0.0.1:8000"; +const target = process.env.VITE_PROXY_TARGET || "http://0.0.0.0:8000"; + +console.log(process.env.VITE_PROXY_TARGET) const proxyTargets = apiRoutes.reduce((proxyObj, route) => { proxyObj[route] = { target: target, - changeOrigin: false, + changeOrigin: true, secure: false, ws: true, }; @@ -22,6 +24,7 @@ export default defineConfig((): UserConfig => { }, plugins: [react(), svgr()], server: { + host: "0.0.0.0", port: 3000, proxy: { ...proxyTargets, diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..e464cccb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: "1" +services: + backend: + build: + context: . + dockerfile: ./dockerfile + ports: + - 8000:8000 + volumes: + - ./:/app + command: bash -c "poetry install && poetry run python -m df_designer run-app --ip-address 0.0.0.0 --port 8000" + frontend: + build: + context: . + dockerfile: ./dockerfile + args: + - REACT_APP_BACKEND_URL=http://backend:8000 + - REACT_APP_BACKEND_WS_URL=ws://backend:8000 + ports: + - 3000:3000 + environment: + - VITE_PROXY_TARGET=http://backend:8000 + volumes: + - ./df_designer_front:/app/df_designer_front + command: bash -c "cd ./df_designer_front && npm install && npm start" \ No newline at end of file diff --git a/dockerfile b/dockerfile new file mode 100644 index 00000000..1731c889 --- /dev/null +++ b/dockerfile @@ -0,0 +1,30 @@ +FROM python:3.12 + +# Configure Poetry +ENV POETRY_VERSION=1.7.1 +ENV POETRY_HOME=/opt/poetry +ENV POETRY_VENV=/opt/poetry-venv +ENV POETRY_CACHE_DIR=/opt/.cache + +# Install poetry separated from system interpreter +RUN python3 -m venv $POETRY_VENV \ + && $POETRY_VENV/bin/pip install -U pip setuptools \ + && $POETRY_VENV/bin/pip install poetry==${POETRY_VERSION} + +# Add `poetry` to PATH +ENV PATH="${PATH}:${POETRY_VENV}/bin" + +WORKDIR /app + +# Install dependencies +COPY poetry.lock ./ +COPY pyproject.toml ./ +RUN poetry install + +RUN apt-get update && apt-get upgrade -y && \ + apt-get install -y nodejs \ + npm +# COPY ./df_designer_front/package.json ./df_designer_front/package-lock.json ./df_designer_front/ +# RUN cd df_designer_front && npm install + +COPY ./ ./ From aa04a26b59fe0efb6a771fb821ec2adb12696796 Mon Sep 17 00:00:00 2001 From: MXerFix Date: Tue, 27 Feb 2024 02:52:54 +0300 Subject: [PATCH 6/6] docker fix --- df_designer_front/vite.config.ts | 41 ++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/df_designer_front/vite.config.ts b/df_designer_front/vite.config.ts index e4f5ad47..f409d0d9 100644 --- a/df_designer_front/vite.config.ts +++ b/df_designer_front/vite.config.ts @@ -1,34 +1,49 @@ -import { UserConfig, defineConfig } from "vite"; -import react from "@vitejs/plugin-react-swc"; -import svgr from "vite-plugin-svgr"; -const apiRoutes = ["/flows" , "/flows", "/health", '/build', '/socket', '/builds', '/run', '/runs', '/bot', '/bot/build/start', '/bot/build/status', '/bot/build/stop', '/bot/run/start', '/bot/run/status', '/bot/run/stop']; +import { UserConfig, defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' +import svgr from 'vite-plugin-svgr' +const apiRoutes = [ + '/flows', + '/health', + '/build', + '/socket', + '/builds', + '/run', + '/runs', + '/bot', + '/bot/build/start', + '/bot/build/status/', + '/bot/build/stop', + '/bot/run/start', + '/bot/run/status', + '/bot/run/stop', +] // Use environment variable to determine the target. -const target = process.env.VITE_PROXY_TARGET || "http://0.0.0.0:8000"; +const target = process.env.VITE_PROXY_TARGET || 'http://0.0.0.0:8000' console.log(process.env.VITE_PROXY_TARGET) const proxyTargets = apiRoutes.reduce((proxyObj, route) => { proxyObj[route] = { target: target, - changeOrigin: true, + changeOrigin: false, secure: false, ws: true, - }; - return proxyObj; -}, {}); + } + return proxyObj +}, {}) export default defineConfig((): UserConfig => { return { build: { - outDir: "../df_designer/static", + outDir: '../df_designer/static', }, plugins: [react(), svgr()], server: { - host: "0.0.0.0", + host: '0.0.0.0', port: 3000, proxy: { ...proxyTargets, }, }, - }; -}); + } +})