diff --git a/app-backend/config/workflow-info.json b/app-backend/config/workflow-info.json index e4d16fb..70d1887 100644 --- a/app-backend/config/workflow-info.json +++ b/app-backend/config/workflow-info.json @@ -21,7 +21,9 @@ "id": "chat_completion_0", "inMegaservice": false, "name": "chat_completion", - "params": {}, + "params": { + "ui_choice": "chat" + }, "version": 1 }, "chat_input_0": { diff --git a/app-frontend/react/.env b/app-frontend/react/.env index 71b04ce..26f897f 100644 --- a/app-frontend/react/.env +++ b/app-frontend/react/.env @@ -2,7 +2,4 @@ VITE_BACKEND_SERVICE_URL= VITE_DATAPREP_SERVICE_URL= VITE_CHAT_HISTORY_SERVICE_URL= VITE_UI_SELECTION= - -VITE_PROMPT_SERVICE_GET_ENDPOINT= -VITE_PROMPT_SERVICE_CREATE_ENDPOINT= -VITE_PROMPT_SERVICE_DELETE_ENDPOINT= \ No newline at end of file +VITE_DEFAULT_UI_TYPE= \ No newline at end of file diff --git a/app-frontend/react/.env.production b/app-frontend/react/.env.production index 26f274d..4729a14 100644 --- a/app-frontend/react/.env.production +++ b/app-frontend/react/.env.production @@ -2,7 +2,4 @@ VITE_BACKEND_SERVICE_URL=APP_BACKEND_SERVICE_URL VITE_DATAPREP_SERVICE_URL=APP_DATAPREP_SERVICE_URL VITE_CHAT_HISTORY_SERVICE_URL=APP_CHAT_HISTORY_SERVICE_URL VITE_UI_SELECTION=APP_UI_SELECTION - -VITE_PROMPT_SERVICE_GET_ENDPOINT=APP_PROMPT_SERVICE_GET_ENDPOINT -VITE_PROMPT_SERVICE_CREATE_ENDPOINT=APP_PROMPT_SERVICE_CREATE_ENDPOINT -VITE_PROMPT_SERVICE_DELETE_ENDPOINT=APP_PROMPT_SERVICE_DELETE_ENDPOINT +VITE_DEFAULT_UI_TYPE=APP_DEFAULT_UI_TYPE \ No newline at end of file diff --git a/app-frontend/react/env.sh b/app-frontend/react/env.sh index ce1372e..524b3e4 100644 --- a/app-frontend/react/env.sh +++ b/app-frontend/react/env.sh @@ -2,7 +2,7 @@ # Copyright (C) 2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -for i in $(env | grep APP_) #// Make sure to use the prefix MY_APP_ if you have any other prefix in env.production file variable name replace it with MY_APP_ +for i in $(env | grep APP_) #// Make sure to use the prefix APP_ if you have any other prefix in env.production file variable name replace it with APP_ do key=$(echo $i | cut -d '=' -f 1) value=$(echo $i | cut -d '=' -f 2-) diff --git a/app-frontend/react/src/App.tsx b/app-frontend/react/src/App.tsx index fd8a379..486afcf 100644 --- a/app-frontend/react/src/App.tsx +++ b/app-frontend/react/src/App.tsx @@ -12,7 +12,6 @@ import { getSupportedModels, getSupportedUseCases, } from "@redux/Conversation/ConversationSlice"; -import { getPrompts } from "@redux/Prompt/PromptSlice"; import MainLayout from "@layouts/Main/MainLayout"; import MinimalLayout from "@layouts/Minimal/MinimalLayout"; @@ -60,7 +59,6 @@ const App = () => { if (isAuthenticated) { dispatch(getSupportedUseCases()); dispatch(getSupportedModels()); - dispatch(getPrompts()); } }; @@ -74,7 +72,7 @@ const App = () => { // } dispatch(getAllConversations({ user: name})); - console.log ("on reload") + // console.log ("on reload") }, [useCase, name, isAuthenticated]); return ( diff --git a/app-frontend/react/src/components/Chat_Assistant/ChatAssistant.tsx b/app-frontend/react/src/components/Chat_Assistant/ChatAssistant.tsx index 9f00433..44a47b0 100644 --- a/app-frontend/react/src/components/Chat_Assistant/ChatAssistant.tsx +++ b/app-frontend/react/src/components/Chat_Assistant/ChatAssistant.tsx @@ -148,7 +148,10 @@ const ChatAssistant: React.FC = ({ } else { return ( - + ); } diff --git a/app-frontend/react/src/components/Chat_Markdown/ChatMarkdown.tsx b/app-frontend/react/src/components/Chat_Markdown/ChatMarkdown.tsx index 464320e..e3ef18e 100644 --- a/app-frontend/react/src/components/Chat_Markdown/ChatMarkdown.tsx +++ b/app-frontend/react/src/components/Chat_Markdown/ChatMarkdown.tsx @@ -5,7 +5,7 @@ import remarkGfm from "remark-gfm"; import remarkFrontmatter from "remark-frontmatter"; import remarkBreaks from "remark-breaks"; import ThinkCard from "./ThinkRender/ThinkCard"; -import { Button, Collapse, Box } from "@mui/material"; +import { Button, Collapse, Box, CircularProgress, Typography } from "@mui/material"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; @@ -14,24 +14,158 @@ import CodeRender from "./CodeRender/CodeRender"; type MarkdownProps = { content: string; + isStreaming?: boolean; }; const extractThinkBlocks = (markdown: string): { cleaned: string; thinks: string[] } => { - const thinkRegex = /([\s\S]*?)<\/think>/g; const thinks: string[] = []; - let cleaned = markdown; - let match; + let text = markdown; - while ((match = thinkRegex.exec(markdown)) !== null) { - thinks.push(match[1].trim()); + // Extract JSON tool responses first + const toolResponseRegex = /\{"tool_name":\s*"[^"]+",\s*"tool_content":\s*\[[\s\S]*?\]\}/g; + let toolMatch; + while ((toolMatch = toolResponseRegex.exec(text)) !== null) { + try { + const toolResponse = JSON.parse(toolMatch[0]); + if (toolResponse.tool_content && Array.isArray(toolResponse.tool_content)) { + const toolContent = toolResponse.tool_content.join('\n'); + thinks.push(`**Tool: ${toolResponse.tool_name}**\n\n${toolContent}`); + } + } catch (e) { + thinks.push(toolMatch[0]); + } } + text = text.replace(toolResponseRegex, ""); - cleaned = markdown.replace(thinkRegex, "").trim(); + // Handle edge case where content appears twice on the same line with artifacts + // Pattern: content"}content or similar + const duplicatePattern = /^(.+?)["}\]]*<\/think>(.+)$/; + const duplicateMatch = text.match(duplicatePattern); + + if (duplicateMatch) { + const [, beforeThink, afterThink] = duplicateMatch; + + // If the content before and after is similar/identical, just return the cleaner version + const cleanBefore = beforeThink.trim().replace(/[{}"\]]+$/, ''); + const cleanAfter = afterThink.trim(); + + // If they're the same or very similar, just return the after version + if (cleanBefore === cleanAfter || cleanAfter.includes(cleanBefore)) { + return { cleaned: cleanAfter, thinks: [] }; + } + } + + // More aggressive approach for specific patterns + const specificPatterns = [ + /The Chinook database contains a total of \d+ employees\.$/ + ]; + + for (const pattern of specificPatterns) { + const lastOccurrenceMatch = text.match(new RegExp(`.*${pattern.source}`)); + + if (lastOccurrenceMatch) { + const fullMatch = lastOccurrenceMatch[0]; + const finalSentenceMatch = fullMatch.match(pattern); + + if (finalSentenceMatch) { + const finalAnswer = finalSentenceMatch[0]; + const beforeFinalAnswer = text.substring(0, text.lastIndexOf(finalAnswer)); + + // Only add to thinks if there's meaningful content after cleaning + if (beforeFinalAnswer.trim().length > 0) { + let thinkContent = beforeFinalAnswer; + + // Extract complete think blocks + const completeThinkRegex = /([\s\S]*?)<\/think>/g; + let thinkMatch; + while ((thinkMatch = completeThinkRegex.exec(thinkContent)) !== null) { + thinks.push(thinkMatch[1].trim()); + } + thinkContent = thinkContent.replace(completeThinkRegex, ''); + + // Handle unclosed think blocks + const unClosedThinkMatch = thinkContent.match(/([\s\S]*)$/); + if (unClosedThinkMatch) { + thinks.push(unClosedThinkMatch[1].trim()); + thinkContent = thinkContent.replace(unClosedThinkMatch[0], ''); + } + + // Clean up any remaining content that might be leftover artifacts + let remaining = thinkContent + .replace(/<\/?think>/g, '') // Remove any remaining think tags + .replace(/[{}"\]]+/g, ' ') // Remove JSON artifacts + .replace(/\s*}\s*$/g, '') // Remove trailing } + .replace(/\s+/g, ' ') + .trim(); + + // If the remaining content is just a duplicate of the final answer, don't include it + if (remaining && remaining !== finalAnswer && remaining.length > 0) { + thinks.push(remaining); + } + } + + return { cleaned: finalAnswer, thinks }; + } + } + } + + // Fallback: use the previous logic if no final answer pattern is found + const finalAnswerPatterns = [ + /^([\s\S]*?)(\s*The .+ contains a total of \d+ .+\.\s*)$/, + /^([\s\S]*?)(\s*The .+ (is|are) .+\.\s*)$/, + /^([\s\S]*?)(\s*There (is|are) .+\.\s*)$/ + ]; + + for (const pattern of finalAnswerPatterns) { + const finalAnswerMatch = text.match(pattern); + + if (finalAnswerMatch) { + const beforeFinalAnswer = finalAnswerMatch[1]; + const finalAnswer = finalAnswerMatch[2].trim(); + + let thinkContent = beforeFinalAnswer; + + const completeThinkRegex = /([\s\S]*?)<\/think>/g; + let thinkMatch; + while ((thinkMatch = completeThinkRegex.exec(thinkContent)) !== null) { + thinks.push(thinkMatch[1].trim()); + } + thinkContent = thinkContent.replace(completeThinkRegex, ''); + + const unClosedThinkMatch = thinkContent.match(/([\s\S]*)$/); + if (unClosedThinkMatch) { + thinks.push(unClosedThinkMatch[1].trim()); + thinkContent = thinkContent.replace(unClosedThinkMatch[0], ''); + } + + const remaining = thinkContent.replace(/\s+/g, ' ').trim(); + if (remaining && remaining.length > 0) { + thinks.push(remaining); + } + + return { cleaned: finalAnswer, thinks }; + } + } + + // Final fallback: process normally + const completeThinkRegex = /([\s\S]*?)<\/think>/g; + let thinkMatch; + while ((thinkMatch = completeThinkRegex.exec(text)) !== null) { + thinks.push(thinkMatch[1].trim()); + } + text = text.replace(completeThinkRegex, ''); + + const unClosedThinkRegex = /([\s\S]*)$/; + const unClosedMatch = unClosedThinkRegex.exec(text); + if (unClosedMatch) { + thinks.push(unClosedMatch[1].trim()); + text = text.replace(unClosedMatch[0], ''); + } - return { cleaned, thinks }; + return { cleaned: text.trim(), thinks }; }; -const ChatMarkdown = ({ content }: MarkdownProps) => { +const ChatMarkdown = ({ content, isStreaming = false }: MarkdownProps) => { useEffect(() => { import("./CodeRender/CodeRender"); }, []); @@ -40,6 +174,105 @@ const ChatMarkdown = ({ content }: MarkdownProps) => { content.replace(/\\\\n/g, "\n").replace(/\\n/g, "\n") ); + // Safety net: if is leaked in the cleaned content, remove everything before it + const safeCleanedContent = (text: string): string => { + const thinkEndIndex = text.lastIndexOf(''); + if (thinkEndIndex !== -1) { + // Return everything after the last tag + return text.substring(thinkEndIndex + 8).trim(); + } + return text; + }; + + const finalCleanedContent = safeCleanedContent(cleaned); + + // Handle different display states based on streaming and content + const getDisplayComponent = () => { + const hasContent = finalCleanedContent.trim().length > 0; + + if (hasContent) { + // Show content if available + return ( + { + const hasBlockElement = React.Children.toArray(children).some( + (child) => + React.isValidElement(child) && + typeof child.type === "string" && + ["div", "h1", "h2", "h3", "ul", "ol", "table"].includes(child.type) + ); + return hasBlockElement ? ( + <>{children} + ) : ( +

+ {children} +

+ ); + }, + a: ({ children, ...props }) => ( + //@ts-ignore + + {children} + + ), + table: ({ children, ...props }) => ( +
+ {children}
+
+ ), + code({ inline, className, children }) { + const lang = /language-(\w+)/.exec(className || ""); + return ( + Loading Code Block...}> + {/*@ts-ignore*/} + + + ); + }, + }} + /> + ); + } else if (isStreaming) { + // Show spinner when streaming with no content + return ( + + + + Generating response... + + + ); + } else { + // Show fallback message when streaming ended with no content + return ( + + ); + } + }; + const [showThinks, setShowThinks] = useState(false); return ( @@ -64,63 +297,25 @@ const ChatMarkdown = ({ content }: MarkdownProps) => { - {thinks.map((block, idx) => ( - - ))} + {thinks + .filter((block) => { + // Filter out blocks that would be empty after ThinkCard's cleaning + const thinkEndIndex = block.lastIndexOf('
'); + const cleanedBlock = thinkEndIndex !== -1 + ? block.substring(thinkEndIndex + 8).trim() + : block.trim(); + return cleanedBlock.length > 0; + }) + .map((block, idx) => ( + + )) + } )} - { - const hasBlockElement = React.Children.toArray(children).some( - (child) => - React.isValidElement(child) && - typeof child.type === "string" && - ["div", "h1", "h2", "h3", "ul", "ol", "table"].includes(child.type) - ); - return hasBlockElement ? ( - <>{children} - ) : ( -

- {children} -

- ); - }, - a: ({ children, ...props }) => ( - //@ts-ignore - - {children} - - ), - table: ({ children, ...props }) => ( -
- {children}
-
- ), - code({ inline, className, children }) { - const lang = /language-(\w+)/.exec(className || ""); - return ( - Loading Code Block...}> - {/*@ts-ignore*/} - - - ); - }, - }} - /> + {getDisplayComponent()} ); }; diff --git a/app-frontend/react/src/components/Chat_Markdown/ThinkRender/ThinkCard.tsx b/app-frontend/react/src/components/Chat_Markdown/ThinkRender/ThinkCard.tsx index 74db261..198f4f8 100644 --- a/app-frontend/react/src/components/Chat_Markdown/ThinkRender/ThinkCard.tsx +++ b/app-frontend/react/src/components/Chat_Markdown/ThinkRender/ThinkCard.tsx @@ -6,6 +6,23 @@ type ThinkCardProps = { }; const ThinkCard = ({ content }: ThinkCardProps) => { + // Safety net: if
is leaked, do not display the text before it + const cleanContent = (text: string): string => { + const thinkEndIndex = text.lastIndexOf('
'); + if (thinkEndIndex !== -1) { + // Return everything after the last tag + return text.substring(thinkEndIndex + 8).trim(); + } + return text; + }; + + const safeContent = cleanContent(content); + + // Don't render the card if there's no content after filtering + if (!safeContent.trim()) { + return null; + } + return ( { > - {content} + {safeContent} diff --git a/app-frontend/react/src/components/Chat_User/ChatUser.tsx b/app-frontend/react/src/components/Chat_User/ChatUser.tsx index 8f08436..11a92a8 100644 --- a/app-frontend/react/src/components/Chat_User/ChatUser.tsx +++ b/app-frontend/react/src/components/Chat_User/ChatUser.tsx @@ -30,7 +30,7 @@ const ChatUser: React.FC = ({ content }) => { return (
- + {/* diff --git a/app-frontend/react/src/components/File_Input/FileInput.tsx b/app-frontend/react/src/components/File_Input/FileInput.tsx index a6213f0..be4d88d 100644 --- a/app-frontend/react/src/components/File_Input/FileInput.tsx +++ b/app-frontend/react/src/components/File_Input/FileInput.tsx @@ -47,6 +47,7 @@ interface FileInputProps { maxFileCount?: number; confirmationModal?: boolean; dataManagement?: boolean; + onSend?: (messageContent: string) => Promise; } const summaryFileExtensions = [ @@ -84,6 +85,7 @@ const FileInput: React.FC = ({ imageInput, summaryInput, dataManagement, + onSend, }) => { const { model, models, useCase, filesInDataSource, uploadInProgress, type } = useAppSelector(conversationSelector); @@ -348,6 +350,29 @@ const FileInput: React.FC = ({ Upload )} + + {summaryInput && ( + { + if (filesToUpload.length > 0) { + if (onSend) { + await onSend(`Summarizing "${filesToUpload[0].file?.name || filesToUpload[0].name}"..`); + setFilesToUpload([]); + dispatch(setSourceFiles([])); + } else { + notify( + "ERROR: onSend prop not provided to FileInput component. Check parent component implementation.", + NotificationSeverity.ERROR + ); + } + } + }} + disabled={filesToUpload.length === 0} + > + Summarize + + )}
{filesToUpload.length > 0 && ( diff --git a/app-frontend/react/src/components/PrimaryInput/PrimaryInput.module.scss b/app-frontend/react/src/components/PrimaryInput/PrimaryInput.module.scss index 184c4d7..5262040 100644 --- a/app-frontend/react/src/components/PrimaryInput/PrimaryInput.module.scss +++ b/app-frontend/react/src/components/PrimaryInput/PrimaryInput.module.scss @@ -42,3 +42,13 @@ } } } + +.orDivider { + display: flex; + justify-content: center; + align-items: center; + padding: 16px 0; + width: 100%; + font-weight: bold; + opacity: 0.8; +} \ No newline at end of file diff --git a/app-frontend/react/src/components/PrimaryInput/PrimaryInput.tsx b/app-frontend/react/src/components/PrimaryInput/PrimaryInput.tsx index 4f7e5ef..a737ae4 100644 --- a/app-frontend/react/src/components/PrimaryInput/PrimaryInput.tsx +++ b/app-frontend/react/src/components/PrimaryInput/PrimaryInput.tsx @@ -17,6 +17,7 @@ import { NotificationSeverity, notify, } from "@components/Notification/Notification"; +import FileInput from "@components/File_Input/FileInput"; const InputWrapper = styled(Box)(({ theme }) => ({ ...theme.customStyles.primaryInput.inputWrapper, @@ -132,7 +133,8 @@ const PrimaryInput: React.FC = ({ }; const placeHolderCopy = () => { - if (home && (isSummary || isFaq)) return "Enter text here or sources below"; + if (home && isSummary) return "Enter text here"; + else if (home && isFaq) return "Enter your text here or sources below"; else return "Enter your message"; }; @@ -186,6 +188,15 @@ const PrimaryInput: React.FC = ({ + {home && (isSummary) && ( + <> + + OR + + + + )} + {/* {home && !isSummary && !isFaq && ( )} */} diff --git a/app-frontend/react/src/components/PromptSettings/PromptSettings.tsx b/app-frontend/react/src/components/PromptSettings/PromptSettings.tsx index 50d4a2d..c6e1fb5 100644 --- a/app-frontend/react/src/components/PromptSettings/PromptSettings.tsx +++ b/app-frontend/react/src/components/PromptSettings/PromptSettings.tsx @@ -112,7 +112,7 @@ const PromptSettings: React.FC = ({ // if (sourceType === "web") input = ; // if (sourceType === "images" && type === "summary") // input = ; - input = ; + //input = ; return
{input} diff --git a/app-frontend/react/src/components/SideBar/SideBar.tsx b/app-frontend/react/src/components/SideBar/SideBar.tsx index b1405cd..ee356c9 100644 --- a/app-frontend/react/src/components/SideBar/SideBar.tsx +++ b/app-frontend/react/src/components/SideBar/SideBar.tsx @@ -27,6 +27,7 @@ import { Conversation } from "@redux/Conversation/Conversation"; import { KeyboardBackspace } from "@mui/icons-material"; import { useDispatch } from "react-redux"; import { useNavigateWithQuery, useToWithQuery } from "@utils/navigationAndAxiosWithQuery"; +import { DATA_PREP_URL } from "@root/config"; interface SideBarProps { asideOpen: boolean; @@ -97,6 +98,8 @@ const SideBar: React.FC = ({ // const { keycloak } = useKeycloak(); const { role } = useAppSelector(userSelector); const { conversations } = useAppSelector(conversationSelector); + + const isDataPrepEnabled = DATA_PREP_URL.trim() !== "NA"; const asideBackgroundColor = { backgroundColor: theme.customStyles.aside?.main, @@ -193,14 +196,30 @@ const SideBar: React.FC = ({ {role === "Admin" && ( <> - - - Data Management - + {isDataPrepEnabled ? ( + + + Data Management + + ) : ( + + + Data Management + + )} )} diff --git a/app-frontend/react/src/config.ts b/app-frontend/react/src/config.ts index ffae0bb..863556e 100644 --- a/app-frontend/react/src/config.ts +++ b/app-frontend/react/src/config.ts @@ -12,7 +12,11 @@ const config = { export default config; - +console.log ("BACKEND_SERVICE_URL", import.meta.env.VITE_BACKEND_SERVICE_URL); +console.log ("DATA_PREP_SERVICE_URL", import.meta.env.VITE_DATAPREP_SERVICE_URL); +console.log ("CHAT_HISTORY_SERVICE_URL", import.meta.env.VITE_CHAT_HISTORY_SERVICE_URL); +console.log ("UI_SELECTION", import.meta.env.VITE_UI_SELECTION); +console.log ("DEFAULT_UI_TYPE", import.meta.env.VITE_DEFAULT_UI_TYPE); // export const CHAT_QNA_URL = import.meta.env.VITE_BACKEND_SERVICE_ENDPOINT_CHATQNA; export const CHAT_QNA_URL = import.meta.env.VITE_BACKEND_SERVICE_URL @@ -21,11 +25,7 @@ export const CODE_GEN_URL = import.meta.env.VITE_BACKEND_SERVICE_URL // export const DOC_SUM_URL = import.meta.env.VITE_BACKEND_SERVICE_ENDPOINT_DOCSUM; export const DOC_SUM_URL = import.meta.env.VITE_BACKEND_SERVICE_URL export const UI_SELECTION = import.meta.env.VITE_UI_SELECTION; - -console.log ("BACKEND_SERVICE_URL", import.meta.env.VITE_BACKEND_SERVICE_URL); -console.log ("DATA_PREP_SERVICE_URL", import.meta.env.VITE_DATAPREP_SERVICE_URL); -console.log ("CHAT_HISTORY_SERVICE_URL", import.meta.env.VITE_CHAT_HISTORY_SERVICE_URL); -console.log ("UI_SELECTION", import.meta.env.VITE_UI_SELECTION); +export const DEFAULT_UI_TYPE = import.meta.env.VITE_DEFAULT_UI_TYPE; // export const FAQ_GEN_URL = import.meta.env.VITE_BACKEND_SERVICE_ENDPOINT_FAQGEN; export const DATA_PREP_URL = import.meta.env.VITE_DATAPREP_SERVICE_URL; @@ -34,22 +34,10 @@ export const DATA_PREP_INGEST_URL = DATA_PREP_URL + "/ingest"; export const DATA_PREP_GET_URL = DATA_PREP_URL + "/get"; export const DATA_PREP_DELETE_URL = DATA_PREP_URL + "/delete"; -console.log ("DATA_PREP_INGEST_URL", DATA_PREP_INGEST_URL); -console.log ("DATA_PREP_GET_URL", DATA_PREP_GET_URL); -console.log ("DATA_PREP_DELETE_URL", DATA_PREP_DELETE_URL); - export const CHAT_HISTORY_URL = import.meta.env.VITE_CHAT_HISTORY_SERVICE_URL; // export const CHAT_HISTORY_URL = "http://localhost:6012/v1/chathistory/"; export const CHAT_HISTORY_CREATE = CHAT_HISTORY_URL + "/create"; export const CHAT_HISTORY_GET = CHAT_HISTORY_URL + "/get"; export const CHAT_HISTORY_DELETE = CHAT_HISTORY_URL + "/delete"; -console.log ("CHAT_HISTORY_CREATE", CHAT_HISTORY_CREATE); -console.log ("CHAT_HISTORY_GET", CHAT_HISTORY_GET); -console.log ("CHAT_HISTORY_DELETE", CHAT_HISTORY_DELETE); - -export const PROMPT_MANAGER_GET = import.meta.env.VITE_PROMPT_SERVICE_GET_ENDPOINT; -export const PROMPT_MANAGER_CREATE = import.meta.env.VITE_PROMPT_SERVICE_CREATE_ENDPOINT; -export const PROMPT_MANAGER_DELETE = import.meta.env.VITE_PROMPT_SERVICE_DELETE_ENDPOINT; - diff --git a/app-frontend/react/src/pages/DataSource/DataSourceManagement.tsx b/app-frontend/react/src/pages/DataSource/DataSourceManagement.tsx index cb0fe9a..55b0fe0 100644 --- a/app-frontend/react/src/pages/DataSource/DataSourceManagement.tsx +++ b/app-frontend/react/src/pages/DataSource/DataSourceManagement.tsx @@ -74,9 +74,14 @@ const DataSourceManagement = () => { return dataList.map((file: file) => { const isChecked = !!checkedItems[file.id]; + // Remove .txt extension from web files for display + const displayName = activeSourceType === "web" && file.name.endsWith(".txt") + ? file.name.slice(0, -4) + : file.name; + const fileText = ( <> - {file.name} + {displayName} {/* TODO: timestamp for all conversations? */} {/* Last message {convertTime(conversation.updated_at)} */} diff --git a/app-frontend/react/src/pages/Home/Home.tsx b/app-frontend/react/src/pages/Home/Home.tsx index b02b8b6..ee14364 100644 --- a/app-frontend/react/src/pages/Home/Home.tsx +++ b/app-frontend/react/src/pages/Home/Home.tsx @@ -34,7 +34,7 @@ const Home = () => { ? UI_SELECTION.split(",").map((item) => item.trim()) : ["chat", "summary", "code"]; - console.log("Enabled UI:", enabledUI); + // console.log("Enabled UI:", enabledUI); const { type, types, token, model, temperature } = useAppSelector(conversationSelector); diff --git a/app-frontend/react/src/redux/Conversation/ConversationSlice.ts b/app-frontend/react/src/redux/Conversation/ConversationSlice.ts index 677fbff..9ded672 100644 --- a/app-frontend/react/src/redux/Conversation/ConversationSlice.ts +++ b/app-frontend/react/src/redux/Conversation/ConversationSlice.ts @@ -30,6 +30,7 @@ import config, { CHAT_HISTORY_DELETE, CODE_GEN_URL, DOC_SUM_URL, + DEFAULT_UI_TYPE, // FAQ_GEN_URL, } from "@root/config"; import { NotificationSeverity, notify } from "@components/Notification/Notification"; @@ -71,6 +72,14 @@ const interactionTypes = [ // }, ]; +// Determine default type from environment variable, fallback to chat +const getDefaultType = (): string => { + const envDefault = DEFAULT_UI_TYPE?.toLowerCase(); + const validTypes = ["chat", "summary", "code"]; + const result = validTypes.includes(envDefault) ? envDefault : "chat"; + return result; +}; + const initialState: ConversationReducer = { conversations: [], sharedConversations: [], @@ -85,7 +94,7 @@ const initialState: ConversationReducer = { useCases: [], model: "", models: [], - type: "chat", + type: getDefaultType(), types: interactionTypes, systemPrompt: config.defaultChatPrompt, minToken: 100, @@ -196,8 +205,13 @@ export const ConversationSlice = createSlice({ break; } + // Optimize model selection to avoid array searches let firstModel = state.models.find((model: Model) => model.types.includes(action.payload)); - state.model = firstModel?.model_name || state.models[0].model_name; + if (firstModel) { + state.model = firstModel.model_name; + } else if (state.models.length > 0) { + state.model = state.models[0].model_name; + } }, setUploadInProgress: (state, action: PayloadAction) => { state.uploadInProgress = action.payload; diff --git a/app-frontend/react/src/redux/store.ts b/app-frontend/react/src/redux/store.ts index 5de6ac7..96b970b 100644 --- a/app-frontend/react/src/redux/store.ts +++ b/app-frontend/react/src/redux/store.ts @@ -4,14 +4,12 @@ import { combineReducers, configureStore } from "@reduxjs/toolkit"; import userReducer from "@redux/User/userSlice"; import conversationReducer from "@redux/Conversation/ConversationSlice"; -import promptReducer from "@redux/Prompt/PromptSlice"; import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; export const store = configureStore({ reducer: combineReducers({ userReducer, conversationReducer, - promptReducer, }), devTools: import.meta.env.PROD || true, // preloadedState: loadFromLocalStorage(), diff --git a/sample-workflows/agentqna_openai.json b/sample-workflows/agentqna_openai.json index 948de34..251583c 100644 --- a/sample-workflows/agentqna_openai.json +++ b/sample-workflows/agentqna_openai.json @@ -1,1209 +1,1232 @@ { - "nodes": [ - { + "nodes": [ + { + "id": "chat_input_0", + "position": { + "x": -747.4396813508564, + "y": 168.71651616595648 + }, + "type": "customNode", + "data": { + "label": "Chat Input", + "name": "chat_input", + "version": 1, + "type": "ChatCompletionRequest", + "icon": "/usr/src/packages/server/src/nodes/controls.png", + "category": "Controls", + "description": "User Input from Chat Window", + "baseClasses": [ + "ChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "filePath": "/usr/src/packages/server/src/nodes/chat_input.js", + "inputAnchors": [], + "inputParams": [], + "inputs": {}, + "outputs": {}, + "outputAnchors": [ + { + "id": "chat_input_0-output-chat_input-ChatCompletionRequest", + "name": "chat_input", + "label": "ChatCompletionRequest", + "description": "User Input from Chat Window", + "type": "ChatCompletionRequest" + } + ], "id": "chat_input_0", - "position": { - "x": -747.4396813508564, - "y": 168.71651616595648 - }, - "type": "customNode", - "data": { - "label": "Chat Input", - "name": "chat_input", - "version": 1, - "type": "ChatCompletionRequest", - "icon": "/usr/src/packages/server/src/nodes/controls.png", - "category": "Controls", - "description": "User Input from Chat Window", - "baseClasses": [ - "ChatCompletionRequest" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": false, - "filePath": "/usr/src/packages/server/src/nodes/chat_input.js", - "inputAnchors": [], - "inputParams": [], - "inputs": {}, - "outputs": {}, - "outputAnchors": [ - { - "id": "chat_input_0-output-chat_input-ChatCompletionRequest", - "name": "chat_input", - "label": "ChatCompletionRequest", - "description": "User Input from Chat Window", - "type": "ChatCompletionRequest" - } - ], - "id": "chat_input_0", - "selected": false - }, - "width": 300, - "height": 143, - "positionAbsolute": { - "x": -747.4396813508564, - "y": 168.71651616595648 - }, "selected": false }, - { - "id": "chat_completion_0", - "position": { - "x": 46.92203056975487, - "y": 957.4841315237466 - }, - "type": "customNode", - "data": { - "label": "Chat Completion", - "name": "chat_completion", - "version": 1, - "type": "ChatCompletion", - "icon": "/usr/src/packages/server/src/nodes/controls.png", - "category": "Controls", - "description": "Send Chat Response to UI", - "baseClasses": [], - "tags": [ - "OPEA" - ], - "inMegaservice": false, - "inputs": { - "llm_response": "{{opea_service@supervisor_agent_0.data.instance}}" - }, - "hideOutput": true, - "filePath": "/usr/src/packages/server/src/nodes/chat_completion.js", - "inputAnchors": [ - { - "label": "LLM Response", - "name": "llm_response", - "type": "ChatCompletion", - "id": "chat_completion_0-input-llm_response-ChatCompletion" - } - ], - "inputParams": [], - "outputs": {}, - "outputAnchors": [], - "id": "chat_completion_0", - "selected": false - }, - "width": 300, - "height": 143, - "selected": false, - "positionAbsolute": { - "x": 46.92203056975487, - "y": 957.4841315237466 - }, - "dragging": false + "width": 300, + "height": 143, + "positionAbsolute": { + "x": -747.4396813508564, + "y": 168.71651616595648 }, - { + "selected": false + }, + { + "id": "doc_input_0", + "position": { + "x": -454.36258557190916, + "y": 1670.5564778929888 + }, + "type": "customNode", + "data": { + "label": "Doc Input", + "name": "doc_input", + "version": 1, + "type": "UploadFile", + "icon": "/usr/src/packages/server/src/nodes/controls.png", + "category": "Controls", + "description": "User Input from Document Upload", + "baseClasses": [ + "UploadFile" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "filePath": "/usr/src/packages/server/src/nodes/doc_input.js", + "inputAnchors": [], + "inputParams": [], + "inputs": {}, + "outputs": {}, + "outputAnchors": [ + { + "id": "doc_input_0-output-doc_input-UploadFile", + "name": "doc_input", + "label": "UploadFile", + "description": "User Input from Document Upload", + "type": "UploadFile" + } + ], "id": "doc_input_0", - "position": { - "x": -454.36258557190916, - "y": 1670.5564778929888 - }, - "type": "customNode", - "data": { - "label": "Doc Input", - "name": "doc_input", - "version": 1, - "type": "UploadFile", - "icon": "/usr/src/packages/server/src/nodes/controls.png", - "category": "Controls", - "description": "User Input from Document Upload", - "baseClasses": [ - "UploadFile" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": false, - "filePath": "/usr/src/packages/server/src/nodes/doc_input.js", - "inputAnchors": [], - "inputParams": [], - "inputs": {}, - "outputs": {}, - "outputAnchors": [ - { - "id": "doc_input_0-output-doc_input-UploadFile", - "name": "doc_input", - "label": "UploadFile", - "description": "User Input from Document Upload", - "type": "UploadFile" - } - ], - "id": "doc_input_0", - "selected": false - }, - "width": 300, - "height": 143, - "selected": false, - "positionAbsolute": { - "x": -454.36258557190916, - "y": 1670.5564778929888 - }, - "dragging": false + "selected": false }, - { - "id": "opea_service@prepare_doc_redis_prep_0", - "position": { - "x": 7.328997846805251, - "y": 1615.0882311218359 - }, - "type": "customNode", - "data": { - "label": "Data Preparation with Redis", - "name": "opea_service@prepare_doc_redis_prep", - "version": 1, - "type": "EmbedDoc", - "icon": "/usr/src/packages/server/src/nodes/data.png", - "category": "Data Preparation", - "description": "Data Preparation with redis using Langchain", - "baseClasses": [ - "EmbedDoc" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": false, - "dependent_services": { - "tei": { - "modelName": "", - "huggingFaceToken": "" - } - }, - "inputs": { - "doc_input": "{{doc_input_0.data.instance}}", - "modelName": "BAAI/bge-large-en-v1.5", + "width": 300, + "height": 143, + "selected": false, + "positionAbsolute": { + "x": -454.36258557190916, + "y": 1670.5564778929888 + }, + "dragging": false + }, + { + "id": "opea_service@prepare_doc_redis_prep_0", + "position": { + "x": 7.328997846805265, + "y": 1613.667948861028 + }, + "type": "customNode", + "data": { + "label": "Data Preparation with Redis", + "name": "opea_service@prepare_doc_redis_prep", + "version": 1, + "type": "EmbedDoc", + "icon": "/usr/src/packages/server/src/nodes/data.png", + "category": "Data Preparation", + "description": "Data Preparation with redis using Langchain", + "baseClasses": [ + "EmbedDoc" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "dependent_services": { + "tei": { + "modelName": "", "huggingFaceToken": "" - }, - "filePath": "/usr/src/packages/server/src/nodes/data_prep_redis.js", - "inputAnchors": [ - { - "label": "Documents", - "name": "doc_input", - "type": "UploadFile", - "id": "opea_service@prepare_doc_redis_prep_0-input-doc_input-UploadFile" - } - ], - "inputParams": [ - { - "label": "Model Name", - "name": "modelName", - "type": "string", - "default": "BAAI/bge-large-en-v1.5", - "id": "opea_service@prepare_doc_redis_prep_0-input-modelName-string" - }, - { - "label": "HuggingFace Token", - "name": "huggingFaceToken", - "type": "password", - "optional": true, - "id": "opea_service@prepare_doc_redis_prep_0-input-huggingFaceToken-password" - } - ], - "outputs": {}, - "outputAnchors": [ - { - "id": "opea_service@prepare_doc_redis_prep_0-output-opea_service@prepare_doc_redis_prep-EmbedDoc", - "name": "opea_service@prepare_doc_redis_prep", - "label": "EmbedDoc", - "description": "Data Preparation with redis using Langchain", - "type": "EmbedDoc" - } - ], - "id": "opea_service@prepare_doc_redis_prep_0", - "selected": false + } }, - "width": 300, - "height": 428, - "selected": false, - "positionAbsolute": { - "x": 7.328997846805251, - "y": 1615.0882311218359 + "inputs": { + "doc_input": "{{doc_input_0.data.instance}}", + "modelName": "BAAI/bge-large-en-v1.5", + "huggingFaceToken": "" }, - "dragging": false - }, - { - "id": "redis_vector_store_0", - "position": { - "x": 792.0415477563516, - "y": 1659.1365447342218 - }, - "type": "customNode", - "data": { - "label": "Redis Vector Store", - "name": "redis_vector_store", - "version": 1, - "type": "EmbedDoc", - "icon": "/usr/src/packages/server/src/nodes/vector stores.png", - "category": "VectorStores", - "description": "Redis Vector Store", - "baseClasses": [ - "EmbedDoc" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": true, - "inputs": { - "prepared_doc": "{{opea_service@prepare_doc_redis_prep_0.data.instance}}" + "filePath": "/usr/src/packages/server/src/nodes/data_prep_redis.js", + "inputAnchors": [ + { + "label": "Documents", + "name": "doc_input", + "type": "UploadFile", + "id": "opea_service@prepare_doc_redis_prep_0-input-doc_input-UploadFile" + } + ], + "inputParams": [ + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "BAAI/bge-large-en-v1.5", + "id": "opea_service@prepare_doc_redis_prep_0-input-modelName-string" }, - "filePath": "/usr/src/packages/server/src/nodes/redis_vector_store.js", - "inputAnchors": [ - { - "label": "Prepared Documents", - "name": "prepared_doc", - "type": "EmbedDoc", - "id": "redis_vector_store_0-input-prepared_doc-EmbedDoc" - } - ], - "inputParams": [], - "outputs": {}, - "outputAnchors": [ - { - "id": "redis_vector_store_0-output-redis_vector_store-EmbedDoc", - "name": "redis_vector_store", - "label": "EmbedDoc", - "description": "Redis Vector Store", - "type": "EmbedDoc" - } - ], - "id": "redis_vector_store_0", - "selected": false - }, - "width": 300, - "height": 231, - "positionAbsolute": { - "x": 792.0415477563516, - "y": 1659.1365447342218 - }, + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@prepare_doc_redis_prep_0-input-huggingFaceToken-password" + } + ], + "outputs": {}, + "outputAnchors": [ + { + "id": "opea_service@prepare_doc_redis_prep_0-output-opea_service@prepare_doc_redis_prep-EmbedDoc", + "name": "opea_service@prepare_doc_redis_prep", + "label": "EmbedDoc", + "description": "Data Preparation with redis using Langchain", + "type": "EmbedDoc" + } + ], + "id": "opea_service@prepare_doc_redis_prep_0", "selected": false }, - { - "id": "opea_service@embedding_tei_langchain_0", - "position": { - "x": 715.4584421600844, - "y": -197.98471823334398 + "width": 300, + "height": 428, + "selected": false, + "positionAbsolute": { + "x": 7.328997846805265, + "y": 1613.667948861028 + }, + "dragging": false + }, + { + "id": "redis_vector_store_0", + "position": { + "x": 792.0415477563516, + "y": 1659.1365447342218 + }, + "type": "customNode", + "data": { + "label": "Redis Vector Store", + "name": "redis_vector_store", + "version": 1, + "type": "EmbedDoc", + "icon": "/usr/src/packages/server/src/nodes/vector stores.png", + "category": "VectorStores", + "description": "Redis Vector Store", + "baseClasses": [ + "EmbedDoc" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": true, + "inputs": { + "prepared_doc": "{{opea_service@prepare_doc_redis_prep_0.data.instance}}" }, - "type": "customNode", - "data": { - "label": "TEI Embedding Langchain", - "name": "opea_service@embedding_tei_langchain", - "version": 1, - "type": "EmbedDoc", - "icon": "/usr/src/packages/server/src/nodes/embeddings.png", - "category": "Embeddings", - "description": "Text Embedding Inference using Langchain", - "baseClasses": [ - "EmbedDoc", - "EmbeddingResponse", - "ChatCompletionRequest" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": true, - "dependent_services": { - "tei": { - "modelName": "", - "huggingFaceToken": "" - } - }, - "inputs": { - "textToEmbed": "{{opea_service@rag_agent_0.data.instance}}", - "modelName": "BAAI/bge-large-en-v1.5", + "filePath": "/usr/src/packages/server/src/nodes/redis_vector_store.js", + "inputAnchors": [ + { + "label": "Prepared Documents", + "name": "prepared_doc", + "type": "EmbedDoc", + "id": "redis_vector_store_0-input-prepared_doc-EmbedDoc" + } + ], + "inputParams": [], + "outputs": {}, + "outputAnchors": [ + { + "id": "redis_vector_store_0-output-redis_vector_store-EmbedDoc", + "name": "redis_vector_store", + "label": "EmbedDoc", + "description": "Redis Vector Store", + "type": "EmbedDoc" + } + ], + "id": "redis_vector_store_0", + "selected": false + }, + "width": 300, + "height": 231, + "positionAbsolute": { + "x": 792.0415477563516, + "y": 1659.1365447342218 + }, + "selected": false + }, + { + "id": "opea_service@embedding_tei_langchain_0", + "position": { + "x": 717.0890343969285, + "y": -194.72176644010068 + }, + "type": "customNode", + "data": { + "label": "TEI Embedding Langchain", + "name": "opea_service@embedding_tei_langchain", + "version": 1, + "type": "EmbedDoc", + "icon": "/usr/src/packages/server/src/nodes/embeddings.png", + "category": "Embeddings", + "description": "Text Embedding Inference using Langchain", + "baseClasses": [ + "EmbedDoc", + "EmbeddingResponse", + "ChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": true, + "dependent_services": { + "tei": { + "modelName": "", "huggingFaceToken": "" - }, - "filePath": "/usr/src/packages/server/src/nodes/tei_embedding.js", - "inputAnchors": [ - { - "label": "Text To Embed", - "name": "textToEmbed", - "type": "TextDoc|EmbeddingRequest|ChatCompletionRequest", - "id": "opea_service@embedding_tei_langchain_0-input-textToEmbed-TextDoc|EmbeddingRequest|ChatCompletionRequest" - } - ], - "inputParams": [ - { - "label": "Model Name", - "name": "modelName", - "type": "string", - "default": "BAAI/bge-large-en-v1.5", - "id": "opea_service@embedding_tei_langchain_0-input-modelName-string" - }, - { - "label": "HuggingFace Token", - "name": "huggingFaceToken", - "type": "password", - "optional": true, - "id": "opea_service@embedding_tei_langchain_0-input-huggingFaceToken-password" - } - ], - "outputs": {}, - "outputAnchors": [ - { - "id": "opea_service@embedding_tei_langchain_0-output-opea_service@embedding_tei_langchain-EmbedDoc|EmbeddingResponse|ChatCompletionRequest", - "name": "opea_service@embedding_tei_langchain", - "label": "EmbedDoc", - "description": "Text Embedding Inference using Langchain", - "type": "EmbedDoc | EmbeddingResponse | ChatCompletionRequest" - } - ], - "id": "opea_service@embedding_tei_langchain_0", - "selected": false + } }, - "width": 300, - "height": 428, - "selected": false, - "positionAbsolute": { - "x": 715.4584421600844, - "y": -197.98471823334398 + "inputs": { + "textToEmbed": "{{opea_service@rag_agent_0.data.instance}}", + "modelName": "BAAI/bge-large-en-v1.5", + "huggingFaceToken": "" }, - "dragging": false + "filePath": "/usr/src/packages/server/src/nodes/tei_embedding.js", + "inputAnchors": [ + { + "label": "Text To Embed", + "name": "textToEmbed", + "type": "TextDoc|EmbeddingRequest|ChatCompletionRequest", + "id": "opea_service@embedding_tei_langchain_0-input-textToEmbed-TextDoc|EmbeddingRequest|ChatCompletionRequest" + } + ], + "inputParams": [ + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "BAAI/bge-large-en-v1.5", + "id": "opea_service@embedding_tei_langchain_0-input-modelName-string" + }, + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@embedding_tei_langchain_0-input-huggingFaceToken-password" + } + ], + "outputs": {}, + "outputAnchors": [ + { + "id": "opea_service@embedding_tei_langchain_0-output-opea_service@embedding_tei_langchain-EmbedDoc|EmbeddingResponse|ChatCompletionRequest", + "name": "opea_service@embedding_tei_langchain", + "label": "EmbedDoc", + "description": "Text Embedding Inference using Langchain", + "type": "EmbedDoc | EmbeddingResponse | ChatCompletionRequest" + } + ], + "id": "opea_service@embedding_tei_langchain_0", + "selected": false }, - { - "id": "opea_service@retriever_redis_0", - "position": { - "x": 1279.216157473191, - "y": -237.72484843730132 + "width": 300, + "height": 428, + "selected": false, + "positionAbsolute": { + "x": 717.0890343969285, + "y": -194.72176644010068 + }, + "dragging": false + }, + { + "id": "opea_service@retriever_redis_0", + "position": { + "x": 1279.216157473191, + "y": -237.72484843730132 + }, + "type": "customNode", + "data": { + "label": "Redis Retreiver", + "name": "opea_service@retriever_redis", + "version": 1, + "type": "SearchedDoc", + "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", + "category": "Retriever", + "description": "Redis Retreiver with Langchain", + "baseClasses": [ + "SearchedDoc", + "RetrievalResponse", + "ChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": true, + "dependent_services": { + "tei": { + "modelName": "", + "huggingFaceToken": "" + } + }, + "inputs": { + "text": "{{opea_service@embedding_tei_langchain_0.data.instance}}", + "vector_db": "{{redis_vector_store_0.data.instance}}", + "modelName": "BAAI/bge-base-en-v1.5", + "huggingFaceToken": "", + "search_type": "similarity" }, - "type": "customNode", - "data": { - "label": "Redis Retreiver", - "name": "opea_service@retriever_redis", - "version": 1, - "type": "SearchedDoc", - "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", - "category": "Retriever", - "description": "Redis Retreiver with Langchain", - "baseClasses": [ - "SearchedDoc", - "RetrievalResponse", - "ChatCompletionRequest" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": true, - "dependent_services": { - "tei": { - "modelName": "", - "huggingFaceToken": "" - } + "filePath": "/usr/src/packages/server/src/nodes/retriever_redis.js", + "inputAnchors": [ + { + "label": "Search Query", + "name": "text", + "type": "EmbedDoc|RetrievalRequest|ChatCompletionRequest", + "id": "opea_service@retriever_redis_0-input-text-EmbedDoc|RetrievalRequest|ChatCompletionRequest" }, - "inputs": { - "text": "{{opea_service@embedding_tei_langchain_0.data.instance}}", - "vector_db": "{{redis_vector_store_0.data.instance}}", - "modelName": "BAAI/bge-base-en-v1.5", - "huggingFaceToken": "", - "search_type": "similarity" + { + "label": "Redis Vector Store", + "name": "vector_db", + "type": "EmbedDoc", + "id": "opea_service@retriever_redis_0-input-vector_db-EmbedDoc" + } + ], + "inputParams": [ + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "BAAI/bge-base-en-v1.5", + "id": "opea_service@retriever_redis_0-input-modelName-string" }, - "filePath": "/usr/src/packages/server/src/nodes/retriever_redis.js", - "inputAnchors": [ - { - "label": "Search Query", - "name": "text", - "type": "EmbedDoc|RetrievalRequest|ChatCompletionRequest", - "id": "opea_service@retriever_redis_0-input-text-EmbedDoc|RetrievalRequest|ChatCompletionRequest" - }, - { - "label": "Redis Vector Store", - "name": "vector_db", - "type": "EmbedDoc", - "id": "opea_service@retriever_redis_0-input-vector_db-EmbedDoc" - } - ], - "inputParams": [ - { - "label": "Model Name", - "name": "modelName", - "type": "string", - "default": "BAAI/bge-base-en-v1.5", - "id": "opea_service@retriever_redis_0-input-modelName-string" - }, - { - "label": "HuggingFace Token", - "name": "huggingFaceToken", - "type": "password", - "optional": true, - "id": "opea_service@retriever_redis_0-input-huggingFaceToken-password" - }, - { - "label": "Search Type", - "name": "search_type", - "type": "options", - "default": "similarity", - "options": [ - { - "name": "similarity", - "label": "similarity" - }, - { - "name": "similarity_distance_threshold", - "label": "similarity_distance_threshold" - }, - { - "name": "similarity_score_threshold", - "label": "similarity_score_threshold" - }, - { - "name": "mmr", - "label": "mmr" - } - ], - "optional": true, - "additionalParams": true, - "inferenceParams": true, - "id": "opea_service@retriever_redis_0-input-search_type-options" - } - ], - "outputs": {}, - "outputAnchors": [ - { - "id": "opea_service@retriever_redis_0-output-opea_service@retriever_redis-SearchedDoc|RetrievalResponse|ChatCompletionRequest", - "name": "opea_service@retriever_redis", - "label": "SearchedDoc", - "description": "Redis Retreiver with Langchain", - "type": "SearchedDoc | RetrievalResponse | ChatCompletionRequest" - } - ], - "id": "opea_service@retriever_redis_0", - "selected": false - }, - "width": 300, - "height": 531, - "selected": false, - "positionAbsolute": { - "x": 1279.216157473191, - "y": -237.72484843730132 - }, - "dragging": false + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@retriever_redis_0-input-huggingFaceToken-password" + }, + { + "label": "Search Type", + "name": "search_type", + "type": "options", + "default": "similarity", + "options": [ + { + "name": "similarity", + "label": "similarity" + }, + { + "name": "similarity_distance_threshold", + "label": "similarity_distance_threshold" + }, + { + "name": "similarity_score_threshold", + "label": "similarity_score_threshold" + }, + { + "name": "mmr", + "label": "mmr" + } + ], + "optional": true, + "additionalParams": true, + "inferenceParams": true, + "id": "opea_service@retriever_redis_0-input-search_type-options" + } + ], + "outputs": {}, + "outputAnchors": [ + { + "id": "opea_service@retriever_redis_0-output-opea_service@retriever_redis-SearchedDoc|RetrievalResponse|ChatCompletionRequest", + "name": "opea_service@retriever_redis", + "label": "SearchedDoc", + "description": "Redis Retreiver with Langchain", + "type": "SearchedDoc | RetrievalResponse | ChatCompletionRequest" + } + ], + "id": "opea_service@retriever_redis_0", + "selected": false }, - { - "id": "opea_service@reranking_tei_0", - "position": { - "x": 1736.1385022512086, - "y": 66.18907138348126 + "width": 300, + "height": 531, + "selected": false, + "positionAbsolute": { + "x": 1279.216157473191, + "y": -237.72484843730132 + }, + "dragging": false + }, + { + "id": "opea_service@reranking_tei_0", + "position": { + "x": 1736.1385022512086, + "y": 67.82054728010291 + }, + "type": "customNode", + "data": { + "label": "TEI Reranking", + "name": "opea_service@reranking_tei", + "version": 1, + "type": "LLMParamsDoc", + "icon": "/usr/src/packages/server/src/nodes/reranking.png", + "category": "Reranking", + "description": "TEI Reranking", + "baseClasses": [ + "LLMParamsDoc", + "RerankingResponse", + "ChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": true, + "dependent_services": { + "tei": { + "modelName": "", + "huggingFaceToken": "" + } }, - "type": "customNode", - "data": { - "label": "TEI Reranking", - "name": "opea_service@reranking_tei", - "version": 1, - "type": "LLMParamsDoc", - "icon": "/usr/src/packages/server/src/nodes/reranking.png", - "category": "Reranking", - "description": "TEI Reranking", - "baseClasses": [ - "LLMParamsDoc", - "RerankingResponse", - "ChatCompletionRequest" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": true, - "dependent_services": { - "tei": { - "modelName": "", - "huggingFaceToken": "" - } + "inputs": { + "retreived_docs": "{{opea_service@retriever_redis_0.data.instance}}", + "modelName": "BAAI/bge-reranker-base", + "huggingFaceToken": "", + "top_n": 1 + }, + "filePath": "/usr/src/packages/server/src/nodes/tei_reranking.js", + "inputAnchors": [ + { + "label": "Documents", + "name": "retreived_docs", + "type": "SearchedDocs|RerankingRequest|ChatCompletionRequest", + "id": "opea_service@reranking_tei_0-input-retreived_docs-SearchedDocs|RerankingRequest|ChatCompletionRequest" + } + ], + "inputParams": [ + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "BAAI/bge-reranker-base", + "id": "opea_service@reranking_tei_0-input-modelName-string" }, - "inputs": { - "retreived_docs": "{{opea_service@retriever_redis_0.data.instance}}", - "modelName": "BAAI/bge-reranker-base", - "huggingFaceToken": "", - "top_n": 1 + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@reranking_tei_0-input-huggingFaceToken-password" }, - "filePath": "/usr/src/packages/server/src/nodes/tei_reranking.js", - "inputAnchors": [ - { - "label": "Documents", - "name": "retreived_docs", - "type": "SearchedDocs|RerankingRequest|ChatCompletionRequest", - "id": "opea_service@reranking_tei_0-input-retreived_docs-SearchedDocs|RerankingRequest|ChatCompletionRequest" - } - ], - "inputParams": [ - { - "label": "Model Name", - "name": "modelName", - "type": "string", - "default": "BAAI/bge-reranker-base", - "id": "opea_service@reranking_tei_0-input-modelName-string" - }, - { - "label": "HuggingFace Token", - "name": "huggingFaceToken", - "type": "password", - "optional": true, - "id": "opea_service@reranking_tei_0-input-huggingFaceToken-password" - }, - { - "label": "Top N", - "name": "top_n", - "type": "number", - "default": 1, - "optional": true, - "additionalParams": true, - "inferenceParams": true, - "id": "opea_service@reranking_tei_0-input-top_n-number" - } - ], - "outputs": {}, - "outputAnchors": [ - { - "id": "opea_service@reranking_tei_0-output-opea_service@reranking_tei-LLMParamsDoc|RerankingResponse|ChatCompletionRequest", - "name": "opea_service@reranking_tei", - "label": "LLMParamsDoc", - "description": "TEI Reranking", - "type": "LLMParamsDoc | RerankingResponse | ChatCompletionRequest" - } - ], - "id": "opea_service@reranking_tei_0", - "selected": false + { + "label": "Top N", + "name": "top_n", + "type": "number", + "default": 1, + "optional": true, + "additionalParams": true, + "inferenceParams": true, + "id": "opea_service@reranking_tei_0-input-top_n-number" + } + ], + "outputs": {}, + "outputAnchors": [ + { + "id": "opea_service@reranking_tei_0-output-opea_service@reranking_tei-LLMParamsDoc|RerankingResponse|ChatCompletionRequest", + "name": "opea_service@reranking_tei", + "label": "LLMParamsDoc", + "description": "TEI Reranking", + "type": "LLMParamsDoc | RerankingResponse | ChatCompletionRequest" + } + ], + "id": "opea_service@reranking_tei_0", + "selected": false + }, + "width": 300, + "height": 481, + "selected": false, + "positionAbsolute": { + "x": 1736.1385022512086, + "y": 67.82054728010291 + }, + "dragging": false + }, + { + "id": "opea_service@sql_agent_1", + "position": { + "x": 632.5012410561022, + "y": 574.0144914443133 + }, + "type": "customNode", + "data": { + "label": "SQL Agent", + "name": "opea_service@sql_agent", + "version": 1, + "type": "AgentTask", + "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", + "category": "Agent", + "description": "Agent specifically designed and optimized for answering questions aabout data in SQL databases.", + "baseClasses": [ + "AgentTask", + "ChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "dependent_services": { + "tgi": { + "modelName": "", + "huggingFaceToken": "" + } }, - "width": 300, - "height": 481, - "selected": false, - "positionAbsolute": { - "x": 1736.1385022512086, - "y": 66.18907138348126 + "outputs": { + "output": "ChatCompletionRequest" }, - "dragging": false - }, - { - "id": "opea_service@sql_agent_1", - "position": { - "x": 683.6037087065724, - "y": 579.6925434054767 + "inputs": { + "query": "{{opea_service@supervisor_agent_0.data.instance}}", + "llmEngine": "openai", + "modelName": "gpt-4o-mini", + "huggingFaceToken": "", + "openaiApiKey": "", + "db_path": "mysql+mysqlconnector://studio:studio@mysql.mysql.svc.cluster.local:3306/Chinook", + "db_name": "Chinook", + "strategy": "sql_agent", + "temperature": 0.1, + "maxNewToken": 8192, + "recursionLimit": "12" }, - "type": "customNode", - "data": { - "label": "SQL Agent", - "name": "opea_service@sql_agent", - "version": 1, - "type": "AgentTask", - "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", - "category": "Agent", - "description": "Agent specifically designed and optimized for answering questions aabout data in SQL databases.", - "baseClasses": [ - "AgentTask", - "ChatCompletionRequest" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": false, - "dependent_services": { - "tgi": { - "modelName": "", - "huggingFaceToken": "" - } + "filePath": "/usr/src/packages/server/src/nodes/sql_agent.js", + "inputAnchors": [ + { + "label": "Search Query", + "name": "query", + "type": "ChatCompletionRequest|SqlAgent", + "id": "opea_service@sql_agent_1-input-query-ChatCompletionRequest|SqlAgent" + } + ], + "inputParams": [ + { + "label": "LLM Engine", + "name": "llmEngine", + "type": "options", + "default": "tgi", + "options": [ + { + "name": "tgi", + "label": "TGI" + }, + { + "name": "openai", + "label": "OpenAI" + } + ], + "id": "opea_service@sql_agent_1-input-llmEngine-options" }, - "outputs": { - "output": "ChatCompletionRequest" + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "Intel/neural-chat-7b-v3-3", + "id": "opea_service@sql_agent_1-input-modelName-string" }, - "inputs": { - "query": "{{opea_service@supervisor_agent_0.data.instance}}", - "llmEngine": "openai", - "modelName": "gpt-4o-mini", - "huggingFaceToken": "", - "openaiApiKey": "to_be_filled", - "db_path": "mysql+mysqlconnector://studio:studio@mysql.mysql.svc.cluster.local:3306/Chinook", - "db_name": "Chinook", - "strategy": "sql_agent", - "temperature": 0.1, - "maxNewToken": 8192, - "recursionLimit": "12" + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@sql_agent_1-input-huggingFaceToken-password" }, - "filePath": "/usr/src/packages/server/src/nodes/sql_agent.js", - "inputAnchors": [ - { - "label": "Search Query", - "name": "query", - "type": "ChatCompletionRequest|SqlAgent", - "id": "opea_service@sql_agent_1-input-query-ChatCompletionRequest|SqlAgent" - } - ], - "inputParams": [ - { - "label": "LLM Engine", - "name": "llmEngine", - "type": "options", - "default": "tgi", - "options": [ - { - "name": "tgi", - "label": "TGI" - }, - { - "name": "openai", - "label": "OpenAI" - } - ], - "id": "opea_service@sql_agent_1-input-llmEngine-options" - }, - { - "label": "Model Name", - "name": "modelName", - "type": "string", - "default": "Intel/neural-chat-7b-v3-3", - "id": "opea_service@sql_agent_1-input-modelName-string" - }, - { - "label": "HuggingFace Token", - "name": "huggingFaceToken", - "type": "password", - "optional": true, - "id": "opea_service@sql_agent_1-input-huggingFaceToken-password" - }, - { - "label": "OpenAI API Key", - "name": "openaiApiKey", - "type": "password", - "optional": true, - "id": "opea_service@sql_agent_1-input-openaiApiKey-password" - }, - { - "label": "Database Path/URI", - "name": "db_path", - "type": "string", - "placeholder": "mysql://user:password@localhost:3306/mydatabase", - "id": "opea_service@sql_agent_1-input-db_path-string" - }, - { - "label": "Database Name", - "name": "db_name", - "type": "string", - "optional": true, - "id": "opea_service@sql_agent_1-input-db_name-string" - }, - { - "label": "Strategy", - "name": "strategy", - "type": "options", - "default": "sql_agent_llama", - "options": [ - { - "name": "sql_agent", - "label": "sql_agent" - }, - { - "name": "sql_agent_llama", - "label": "sql_agent_llama" - } - ], - "additionalParams": true, - "id": "opea_service@sql_agent_1-input-strategy-options" - }, - { - "label": "Temperature", - "name": "temperature", - "type": "number", - "default": 0.1, - "additionalParams": true, - "id": "opea_service@sql_agent_1-input-temperature-number" - }, - { - "label": "Max New Token", - "name": "maxNewToken", - "type": "number", - "default": 8192, - "additionalParams": true, - "id": "opea_service@sql_agent_1-input-maxNewToken-number" - }, - { - "label": "Recursion Limit", - "name": "recursionLimit", - "type": "number", - "default": 10, - "additionalParams": true, - "id": "opea_service@sql_agent_1-input-recursionLimit-number" - } - ], - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "description": "", - "options": [ - { - "id": "opea_service@sql_agent_1-output-ChatCompletionRequest-ChatCompletionRequest", - "name": "ChatCompletionRequest", - "label": "ChatCompletionRequest", - "description": "", - "type": "ChatCompletionRequest", - "isAnchor": true - } - ], - "default": "ChatCompletionRequest" - } - ], - "id": "opea_service@sql_agent_1", - "selected": false - }, - "width": 300, - "height": 873, - "selected": false, - "positionAbsolute": { - "x": 683.6037087065724, - "y": 579.6925434054767 - }, - "dragging": false - }, - { - "id": "opea_service@supervisor_agent_0", - "position": { - "x": -372.8818421270502, - "y": 181.09632356120426 - }, - "type": "customNode", - "data": { - "label": "Supervisor Agent", - "name": "opea_service@supervisor_agent", - "version": 1, - "type": "Agent", - "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", - "category": "Agent", - "description": "ReAct Supervisor Agent built on Langchain/Langgraph framework", - "baseClasses": [ - "Agent", - "ChatCompletionRequest" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": true, - "dependent_services": { - "tgi": { - "modelName": "", - "huggingFaceToken": "" - } + { + "label": "OpenAI API Key", + "name": "openaiApiKey", + "type": "password", + "optional": true, + "id": "opea_service@sql_agent_1-input-openaiApiKey-password" + }, + { + "label": "Database Path/URI", + "name": "db_path", + "type": "string", + "placeholder": "mysql://user:password@localhost:3306/mydatabase", + "id": "opea_service@sql_agent_1-input-db_path-string" + }, + { + "label": "Database Name", + "name": "db_name", + "type": "string", + "optional": true, + "id": "opea_service@sql_agent_1-input-db_name-string" + }, + { + "label": "Strategy", + "name": "strategy", + "type": "options", + "default": "sql_agent_llama", + "options": [ + { + "name": "sql_agent", + "label": "sql_agent" + }, + { + "name": "sql_agent_llama", + "label": "sql_agent_llama" + } + ], + "additionalParams": true, + "id": "opea_service@sql_agent_1-input-strategy-options" }, - "outputs": { - "output": "RagAgent" + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.1, + "additionalParams": true, + "id": "opea_service@sql_agent_1-input-temperature-number" }, - "inputs": { - "query": "{{chat_input_0.data.instance}}", - "llmEngine": "openai", - "modelName": "gpt-4o-mini", - "huggingFaceToken": "", - "openaiApiKey": "to_be_filled", - "strategy": "react_llama", - "temperature": 0.1, - "maxNewToken": 8192, - "recursionLimit": "12" + { + "label": "Max New Token", + "name": "maxNewToken", + "type": "number", + "default": 8192, + "additionalParams": true, + "id": "opea_service@sql_agent_1-input-maxNewToken-number" }, - "filePath": "/usr/src/packages/server/src/nodes/supervisor_agent.js", - "inputAnchors": [ - { - "label": "Search Query", - "name": "query", - "type": "ChatCompletionRequest", - "id": "opea_service@supervisor_agent_0-input-query-ChatCompletionRequest" - } - ], - "inputParams": [ - { - "label": "LLM Engine", - "name": "llmEngine", - "type": "options", - "default": "tgi", - "options": [ - { - "name": "tgi", - "label": "TGI" - }, - { - "name": "openai", - "label": "OpenAI" - } - ], - "id": "opea_service@supervisor_agent_0-input-llmEngine-options" - }, - { - "label": "Model Name", - "name": "modelName", - "type": "string", - "default": "Intel/neural-chat-7b-v3-3", - "id": "opea_service@supervisor_agent_0-input-modelName-string" - }, - { - "label": "HuggingFace Token", - "name": "huggingFaceToken", - "type": "password", - "optional": true, - "id": "opea_service@supervisor_agent_0-input-huggingFaceToken-password" - }, - { - "label": "OpenAI API Key", - "name": "openaiApiKey", - "type": "password", - "optional": true, - "id": "opea_service@supervisor_agent_0-input-openaiApiKey-password" - }, - { - "label": "Strategy", - "name": "strategy", - "type": "options", - "default": "react_llama", - "options": [ - { - "name": "react_langchain", - "label": "react_langchain" - }, - { - "name": "react_llama", - "label": "react_llama" - } - ], - "additionalParams": true, - "id": "opea_service@supervisor_agent_0-input-strategy-options" - }, - { - "label": "Temperature", - "name": "temperature", - "type": "number", - "default": 0.1, - "additionalParams": true, - "id": "opea_service@supervisor_agent_0-input-temperature-number" - }, - { - "label": "Max New Token", - "name": "maxNewToken", - "type": "number", - "default": 8192, - "additionalParams": true, - "id": "opea_service@supervisor_agent_0-input-maxNewToken-number" - }, - { - "label": "Recursion Limit", - "name": "recursionLimit", - "type": "number", - "default": 10, - "additionalParams": true, - "id": "opea_service@supervisor_agent_0-input-recursionLimit-number" - } - ], - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "description": "", - "options": [ - { - "id": "opea_service@supervisor_agent_0-output-RagAgent-RagAgent", - "name": "RagAgent", - "label": "RagAgent", - "description": "", - "type": "RagAgent", - "isAnchor": true - }, - { - "id": "opea_service@supervisor_agent_0-output-SqlAgent-SqlAgent", - "name": "SqlAgent", - "label": "SqlAgent", - "description": "", - "type": "SqlAgent", - "isAnchor": true - }, - { - "id": "opea_service@supervisor_agent_0-output-ChatCompletionRequest-ChatCompletion", - "name": "ChatCompletionRequest", - "label": "ChatCompletionRequest", - "description": "", - "type": "ChatCompletion", - "isAnchor": true - } - ], - "default": "RagAgent" - } - ], - "id": "opea_service@supervisor_agent_0", - "selected": false + { + "label": "Recursion Limit", + "name": "recursionLimit", + "type": "number", + "default": 10, + "additionalParams": true, + "id": "opea_service@sql_agent_1-input-recursionLimit-number" + } + ], + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "description": "", + "options": [ + { + "id": "opea_service@sql_agent_1-output-ChatCompletionRequest-ChatCompletionRequest", + "name": "ChatCompletionRequest", + "label": "ChatCompletionRequest", + "description": "", + "type": "ChatCompletionRequest", + "isAnchor": true + } + ], + "default": "ChatCompletionRequest" + } + ], + "id": "opea_service@sql_agent_1", + "selected": false + }, + "width": 300, + "height": 873, + "selected": false, + "positionAbsolute": { + "x": 632.5012410561022, + "y": 574.0144914443133 + }, + "dragging": false + }, + { + "id": "opea_service@supervisor_agent_0", + "position": { + "x": -355.8996109576659, + "y": 152.88908385598688 + }, + "type": "customNode", + "data": { + "label": "Supervisor Agent", + "name": "opea_service@supervisor_agent", + "version": 1, + "type": "Agent", + "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", + "category": "Agent", + "description": "ReAct Supervisor Agent built on Langchain/Langgraph framework", + "baseClasses": [ + "Agent", + "ChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": true, + "dependent_services": { + "tgi": { + "modelName": "", + "huggingFaceToken": "" + } }, - "width": 300, - "height": 777, - "selected": false, - "positionAbsolute": { - "x": -372.8818421270502, - "y": 181.09632356120426 + "outputs": { + "output": "RagAgent" }, - "dragging": false - }, - { - "id": "opea_service@rag_agent_0", - "position": { - "x": 152.0318692908072, - "y": -239.69271170416874 + "inputs": { + "query": "{{chat_input_0.data.instance}}", + "llmEngine": "openai", + "modelName": "gpt-4o-mini", + "huggingFaceToken": "", + "openaiApiKey": "", + "strategy": "react_llama", + "temperature": 0.1, + "maxNewToken": 8192, + "recursionLimit": "12" }, - "type": "customNode", - "data": { - "label": "Rag Agent", - "name": "opea_service@rag_agent", - "version": 1, - "type": "AgentTask", - "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", - "category": "Agent", - "description": "RAG Agent built on Langchain/Langgraph framework", - "baseClasses": [ - "AgentTask", - "ChatCompletionRequest" - ], - "tags": [ - "OPEA" - ], - "inMegaservice": false, - "megaserviceClient": true, - "dependent_services": { - "tgi": { - "modelName": "", - "huggingFaceToken": "" - } + "filePath": "/usr/src/packages/server/src/nodes/supervisor_agent.js", + "inputAnchors": [ + { + "label": "Search Query", + "name": "query", + "type": "ChatCompletionRequest", + "id": "opea_service@supervisor_agent_0-input-query-ChatCompletionRequest" + } + ], + "inputParams": [ + { + "label": "LLM Engine", + "name": "llmEngine", + "type": "options", + "default": "tgi", + "options": [ + { + "name": "tgi", + "label": "TGI" + }, + { + "name": "openai", + "label": "OpenAI" + } + ], + "id": "opea_service@supervisor_agent_0-input-llmEngine-options" }, - "outputs": { - "output": "retrivalRequest" + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "Intel/neural-chat-7b-v3-3", + "id": "opea_service@supervisor_agent_0-input-modelName-string" }, - "inputs": { - "query": "{{opea_service@supervisor_agent_0.data.instance}}", - "retrievalResponse": "{{opea_service@reranking_tei_0.data.instance}}", - "llmEngine": "openai", - "modelName": "gpt-4o-mini", - "huggingFaceToken": "", - "openaiApiKey": "to_be_filled", - "strategy": "rag_agent", - "temperature": 0.1, - "maxNewToken": 8192, - "recursionLimit": "12" + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@supervisor_agent_0-input-huggingFaceToken-password" }, - "filePath": "/usr/src/packages/server/src/nodes/rag_agent.js", - "inputAnchors": [ - { - "label": "Search Query", - "name": "query", - "type": "ChatCompletionRequest|RagAgent", - "id": "opea_service@rag_agent_0-input-query-ChatCompletionRequest|RagAgent" - }, - { - "label": "Retrieval Response", - "name": "retrievalResponse", - "type": "SearchedDoc|RetrievalResponse|ChatCompletionRequest", - "id": "opea_service@rag_agent_0-input-retrievalResponse-SearchedDoc|RetrievalResponse|ChatCompletionRequest" - } - ], - "inputParams": [ - { - "label": "LLM Engine", - "name": "llmEngine", - "type": "options", - "default": "tgi", - "options": [ - { - "name": "tgi", - "label": "TGI" - }, - { - "name": "openai", - "label": "OpenAI" - } - ], - "id": "opea_service@rag_agent_0-input-llmEngine-options" - }, - { - "label": "Model Name", - "name": "modelName", - "type": "string", - "default": "Intel/neural-chat-7b-v3-3", - "id": "opea_service@rag_agent_0-input-modelName-string" - }, - { - "label": "HuggingFace Token", - "name": "huggingFaceToken", - "type": "password", - "optional": true, - "id": "opea_service@rag_agent_0-input-huggingFaceToken-password" - }, - { - "label": "OpenAI API Key", - "name": "openaiApiKey", - "type": "password", - "optional": true, - "id": "opea_service@rag_agent_0-input-openaiApiKey-password" - }, - { - "label": "Strategy", - "name": "strategy", - "type": "options", - "default": "rag_agent_llama", - "options": [ - { - "name": "rag_agent", - "label": "rag_agent" - }, - { - "name": "rag_agent_llama", - "label": "rag_agent_llama" - } - ], - "additionalParams": true, - "id": "opea_service@rag_agent_0-input-strategy-options" - }, - { - "label": "Temperature", - "name": "temperature", - "type": "number", - "default": 0.1, - "additionalParams": true, - "id": "opea_service@rag_agent_0-input-temperature-number" - }, - { - "label": "Max New Token", - "name": "maxNewToken", - "type": "number", - "default": 8192, - "additionalParams": true, - "id": "opea_service@rag_agent_0-input-maxNewToken-number" - }, - { - "label": "Recursion Limit", - "name": "recursionLimit", - "type": "number", - "default": 10, - "additionalParams": true, - "id": "opea_service@rag_agent_0-input-recursionLimit-number" - } - ], - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "description": "", - "options": [ - { - "id": "opea_service@rag_agent_0-output-retrivalRequest-ChatCompletionRequest", - "name": "retrivalRequest", - "label": "Retrieval Request", - "description": "", - "type": "ChatCompletionRequest", - "isAnchor": true - }, - { - "id": "opea_service@rag_agent_0-output-ChatCompletionRequest-ChatCompletionRequest", - "name": "ChatCompletionRequest", - "label": "ChatCompletionRequest", - "description": "", - "type": "ChatCompletionRequest", - "isAnchor": true - } - ], - "default": "retrivalRequest" - } - ], - "id": "opea_service@rag_agent_0", - "selected": false - }, - "width": 300, - "height": 777, - "selected": false, - "positionAbsolute": { - "x": 152.0318692908072, - "y": -239.69271170416874 - }, - "dragging": false - } - ], - "edges": [ - { - "source": "doc_input_0", - "sourceHandle": "doc_input_0-output-doc_input-UploadFile", - "target": "opea_service@prepare_doc_redis_prep_0", - "targetHandle": "opea_service@prepare_doc_redis_prep_0-input-doc_input-UploadFile", - "type": "buttonedge", - "id": "doc_input_0-doc_input_0-output-doc_input-UploadFile-opea_service@prepare_doc_redis_prep_0-opea_service@prepare_doc_redis_prep_0-input-doc_input-UploadFile" - }, - { - "source": "opea_service@prepare_doc_redis_prep_0", - "sourceHandle": "opea_service@prepare_doc_redis_prep_0-output-opea_service@prepare_doc_redis_prep-EmbedDoc", - "target": "redis_vector_store_0", - "targetHandle": "redis_vector_store_0-input-prepared_doc-EmbedDoc", - "type": "buttonedge", - "id": "opea_service@prepare_doc_redis_prep_0-opea_service@prepare_doc_redis_prep_0-output-opea_service@prepare_doc_redis_prep-EmbedDoc-redis_vector_store_0-redis_vector_store_0-input-prepared_doc-EmbedDoc" - }, - { - "source": "opea_service@embedding_tei_langchain_0", - "sourceHandle": "opea_service@embedding_tei_langchain_0-output-opea_service@embedding_tei_langchain-EmbedDoc|EmbeddingResponse|ChatCompletionRequest", - "target": "opea_service@retriever_redis_0", - "targetHandle": "opea_service@retriever_redis_0-input-text-EmbedDoc|RetrievalRequest|ChatCompletionRequest", - "type": "buttonedge", - "id": "opea_service@embedding_tei_langchain_0-opea_service@embedding_tei_langchain_0-output-opea_service@embedding_tei_langchain-EmbedDoc|EmbeddingResponse|ChatCompletionRequest-opea_service@retriever_redis_0-opea_service@retriever_redis_0-input-text-EmbedDoc|RetrievalRequest|ChatCompletionRequest" + { + "label": "OpenAI API Key", + "name": "openaiApiKey", + "type": "password", + "optional": true, + "id": "opea_service@supervisor_agent_0-input-openaiApiKey-password" + }, + { + "label": "Strategy", + "name": "strategy", + "type": "options", + "default": "react_llama", + "options": [ + { + "name": "react_langchain", + "label": "react_langchain" + }, + { + "name": "react_llama", + "label": "react_llama" + } + ], + "additionalParams": true, + "id": "opea_service@supervisor_agent_0-input-strategy-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.1, + "additionalParams": true, + "id": "opea_service@supervisor_agent_0-input-temperature-number" + }, + { + "label": "Max New Token", + "name": "maxNewToken", + "type": "number", + "default": 8192, + "additionalParams": true, + "id": "opea_service@supervisor_agent_0-input-maxNewToken-number" + }, + { + "label": "Recursion Limit", + "name": "recursionLimit", + "type": "number", + "default": 10, + "additionalParams": true, + "id": "opea_service@supervisor_agent_0-input-recursionLimit-number" + } + ], + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "description": "", + "options": [ + { + "id": "opea_service@supervisor_agent_0-output-RagAgent-RagAgent", + "name": "RagAgent", + "label": "RagAgent", + "description": "", + "type": "RagAgent", + "isAnchor": true + }, + { + "id": "opea_service@supervisor_agent_0-output-SqlAgent-SqlAgent", + "name": "SqlAgent", + "label": "SqlAgent", + "description": "", + "type": "SqlAgent", + "isAnchor": true + }, + { + "id": "opea_service@supervisor_agent_0-output-ChatCompletionRequest-ChatCompletion", + "name": "ChatCompletionRequest", + "label": "ChatCompletionRequest", + "description": "", + "type": "ChatCompletion", + "isAnchor": true + } + ], + "default": "RagAgent" + } + ], + "id": "opea_service@supervisor_agent_0", + "selected": false }, - { - "source": "redis_vector_store_0", - "sourceHandle": "redis_vector_store_0-output-redis_vector_store-EmbedDoc", - "target": "opea_service@retriever_redis_0", - "targetHandle": "opea_service@retriever_redis_0-input-vector_db-EmbedDoc", - "type": "buttonedge", - "id": "redis_vector_store_0-redis_vector_store_0-output-redis_vector_store-EmbedDoc-opea_service@retriever_redis_0-opea_service@retriever_redis_0-input-vector_db-EmbedDoc" + "width": 300, + "height": 777, + "selected": false, + "positionAbsolute": { + "x": -355.8996109576659, + "y": 152.88908385598688 }, - { - "source": "opea_service@retriever_redis_0", - "sourceHandle": "opea_service@retriever_redis_0-output-opea_service@retriever_redis-SearchedDoc|RetrievalResponse|ChatCompletionRequest", - "target": "opea_service@reranking_tei_0", - "targetHandle": "opea_service@reranking_tei_0-input-retreived_docs-SearchedDocs|RerankingRequest|ChatCompletionRequest", - "type": "buttonedge", - "id": "opea_service@retriever_redis_0-opea_service@retriever_redis_0-output-opea_service@retriever_redis-SearchedDoc|RetrievalResponse|ChatCompletionRequest-opea_service@reranking_tei_0-opea_service@reranking_tei_0-input-retreived_docs-SearchedDocs|RerankingRequest|ChatCompletionRequest" + "dragging": false + }, + { + "id": "opea_service@rag_agent_0", + "position": { + "x": 150.6115870299992, + "y": -239.87656644279093 }, - { - "source": "chat_input_0", - "sourceHandle": "chat_input_0-output-chat_input-ChatCompletionRequest", - "target": "opea_service@supervisor_agent_0", - "targetHandle": "opea_service@supervisor_agent_0-input-query-ChatCompletionRequest", - "type": "buttonedge", - "id": "chat_input_0-chat_input_0-output-chat_input-ChatCompletionRequest-opea_service@supervisor_agent_0-opea_service@supervisor_agent_0-input-query-ChatCompletionRequest" + "type": "customNode", + "data": { + "label": "Rag Agent", + "name": "opea_service@rag_agent", + "version": 1, + "type": "AgentTask", + "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", + "category": "Agent", + "description": "RAG Agent built on Langchain/Langgraph framework", + "baseClasses": [ + "AgentTask", + "ChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "megaserviceClient": true, + "dependent_services": { + "tgi": { + "modelName": "", + "huggingFaceToken": "" + } + }, + "outputs": { + "output": "retrivalRequest" + }, + "inputs": { + "query": "{{opea_service@supervisor_agent_0.data.instance}}", + "retrievalResponse": "{{opea_service@reranking_tei_0.data.instance}}", + "llmEngine": "openai", + "modelName": "gpt-4o-mini", + "huggingFaceToken": "", + "openaiApiKey": "", + "strategy": "rag_agent", + "temperature": 0.1, + "maxNewToken": 8192, + "recursionLimit": "12" + }, + "filePath": "/usr/src/packages/server/src/nodes/rag_agent.js", + "inputAnchors": [ + { + "label": "Search Query", + "name": "query", + "type": "ChatCompletionRequest|RagAgent", + "id": "opea_service@rag_agent_0-input-query-ChatCompletionRequest|RagAgent" + }, + { + "label": "Retrieval Response", + "name": "retrievalResponse", + "type": "SearchedDoc|RetrievalResponse|ChatCompletionRequest", + "id": "opea_service@rag_agent_0-input-retrievalResponse-SearchedDoc|RetrievalResponse|ChatCompletionRequest" + } + ], + "inputParams": [ + { + "label": "LLM Engine", + "name": "llmEngine", + "type": "options", + "default": "tgi", + "options": [ + { + "name": "tgi", + "label": "TGI" + }, + { + "name": "openai", + "label": "OpenAI" + } + ], + "id": "opea_service@rag_agent_0-input-llmEngine-options" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "Intel/neural-chat-7b-v3-3", + "id": "opea_service@rag_agent_0-input-modelName-string" + }, + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@rag_agent_0-input-huggingFaceToken-password" + }, + { + "label": "OpenAI API Key", + "name": "openaiApiKey", + "type": "password", + "optional": true, + "id": "opea_service@rag_agent_0-input-openaiApiKey-password" + }, + { + "label": "Strategy", + "name": "strategy", + "type": "options", + "default": "rag_agent_llama", + "options": [ + { + "name": "rag_agent", + "label": "rag_agent" + }, + { + "name": "rag_agent_llama", + "label": "rag_agent_llama" + } + ], + "additionalParams": true, + "id": "opea_service@rag_agent_0-input-strategy-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.1, + "additionalParams": true, + "id": "opea_service@rag_agent_0-input-temperature-number" + }, + { + "label": "Max New Token", + "name": "maxNewToken", + "type": "number", + "default": 8192, + "additionalParams": true, + "id": "opea_service@rag_agent_0-input-maxNewToken-number" + }, + { + "label": "Recursion Limit", + "name": "recursionLimit", + "type": "number", + "default": 10, + "additionalParams": true, + "id": "opea_service@rag_agent_0-input-recursionLimit-number" + } + ], + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "description": "", + "options": [ + { + "id": "opea_service@rag_agent_0-output-retrivalRequest-ChatCompletionRequest", + "name": "retrivalRequest", + "label": "Retrieval Request", + "description": "", + "type": "ChatCompletionRequest", + "isAnchor": true + }, + { + "id": "opea_service@rag_agent_0-output-ChatCompletionRequest-ChatCompletionRequest", + "name": "ChatCompletionRequest", + "label": "ChatCompletionRequest", + "description": "", + "type": "ChatCompletionRequest", + "isAnchor": true + } + ], + "default": "retrivalRequest" + } + ], + "id": "opea_service@rag_agent_0", + "selected": false }, - { - "source": "opea_service@supervisor_agent_0", - "sourceHandle": "opea_service@supervisor_agent_0-output-RagAgent-RagAgent", - "target": "opea_service@rag_agent_0", - "targetHandle": "opea_service@rag_agent_0-input-query-ChatCompletionRequest|RagAgent", - "type": "buttonedge", - "id": "opea_service@supervisor_agent_0-opea_service@supervisor_agent_0-output-RagAgent-RagAgent-opea_service@rag_agent_0-opea_service@rag_agent_0-input-query-ChatCompletionRequest|RagAgent" + "width": 300, + "height": 777, + "selected": false, + "positionAbsolute": { + "x": 150.6115870299992, + "y": -239.87656644279093 }, - { - "source": "opea_service@supervisor_agent_0", - "sourceHandle": "opea_service@supervisor_agent_0-output-SqlAgent-SqlAgent", - "target": "opea_service@sql_agent_1", - "targetHandle": "opea_service@sql_agent_1-input-query-ChatCompletionRequest|SqlAgent", - "type": "buttonedge", - "id": "opea_service@supervisor_agent_0-opea_service@supervisor_agent_0-output-SqlAgent-SqlAgent-opea_service@sql_agent_1-opea_service@sql_agent_1-input-query-ChatCompletionRequest|SqlAgent" + "dragging": false + }, + { + "id": "chat_completion_0", + "position": { + "x": 38.6536493762799, + "y": 881.8909913709595 }, - { - "source": "opea_service@supervisor_agent_0", - "sourceHandle": "opea_service@supervisor_agent_0-output-ChatCompletionRequest-ChatCompletion", - "target": "chat_completion_0", - "targetHandle": "chat_completion_0-input-llm_response-ChatCompletion", - "type": "buttonedge", - "id": "opea_service@supervisor_agent_0-opea_service@supervisor_agent_0-output-ChatCompletionRequest-ChatCompletion-chat_completion_0-chat_completion_0-input-llm_response-ChatCompletion" + "type": "customNode", + "data": { + "label": "Chat Completion", + "name": "chat_completion", + "version": 1, + "type": "ChatCompletion", + "icon": "/usr/src/packages/server/src/nodes/assets/controls.png", + "category": "Controls", + "description": "Send Chat Response to UI", + "baseClasses": [], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "inputs": { + "llm_response": "{{opea_service@supervisor_agent_0.data.instance}}", + "ui_choice": "chat" + }, + "hideOutput": true, + "filePath": "/usr/src/packages/server/src/nodes/chat_completion.js", + "inputAnchors": [ + { + "label": "LLM Response", + "name": "llm_response", + "type": "StreamingResponse|ChatCompletion", + "id": "chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion" + } + ], + "inputParams": [ + { + "label": "UI Choice", + "name": "ui_choice", + "type": "options", + "default": "chat", + "options": [ + { + "name": "chat", + "label": "Chat Q&A" + }, + { + "name": "summary", + "label": "Summarize Content" + }, + { + "name": "code", + "label": "Generate Code" + } + ], + "id": "chat_completion_0-input-ui_choice-options" + } + ], + "outputs": {}, + "outputAnchors": [], + "id": "chat_completion_0", + "selected": false }, - { - "source": "opea_service@reranking_tei_0", - "sourceHandle": "opea_service@reranking_tei_0-output-opea_service@reranking_tei-LLMParamsDoc|RerankingResponse|ChatCompletionRequest", - "target": "opea_service@rag_agent_0", - "targetHandle": "opea_service@rag_agent_0-input-retrievalResponse-SearchedDoc|RetrievalResponse|ChatCompletionRequest", - "type": "buttonedge", - "id": "opea_service@reranking_tei_0-opea_service@reranking_tei_0-output-opea_service@reranking_tei-LLMParamsDoc|RerankingResponse|ChatCompletionRequest-opea_service@rag_agent_0-opea_service@rag_agent_0-input-retrievalResponse-SearchedDoc|RetrievalResponse|ChatCompletionRequest" + "width": 300, + "height": 239, + "selected": false, + "positionAbsolute": { + "x": 38.6536493762799, + "y": 881.8909913709595 }, - { - "source": "opea_service@rag_agent_0", - "sourceHandle": "opea_service@rag_agent_0-output-retrivalRequest-ChatCompletionRequest", - "target": "opea_service@embedding_tei_langchain_0", - "targetHandle": "opea_service@embedding_tei_langchain_0-input-textToEmbed-TextDoc|EmbeddingRequest|ChatCompletionRequest", - "type": "buttonedge", - "id": "opea_service@rag_agent_0-opea_service@rag_agent_0-output-retrivalRequest-ChatCompletionRequest-opea_service@embedding_tei_langchain_0-opea_service@embedding_tei_langchain_0-input-textToEmbed-TextDoc|EmbeddingRequest|ChatCompletionRequest" - } - ] - } \ No newline at end of file + "dragging": false + } + ], + "edges": [ + { + "source": "doc_input_0", + "sourceHandle": "doc_input_0-output-doc_input-UploadFile", + "target": "opea_service@prepare_doc_redis_prep_0", + "targetHandle": "opea_service@prepare_doc_redis_prep_0-input-doc_input-UploadFile", + "type": "buttonedge", + "id": "doc_input_0-doc_input_0-output-doc_input-UploadFile-opea_service@prepare_doc_redis_prep_0-opea_service@prepare_doc_redis_prep_0-input-doc_input-UploadFile" + }, + { + "source": "opea_service@prepare_doc_redis_prep_0", + "sourceHandle": "opea_service@prepare_doc_redis_prep_0-output-opea_service@prepare_doc_redis_prep-EmbedDoc", + "target": "redis_vector_store_0", + "targetHandle": "redis_vector_store_0-input-prepared_doc-EmbedDoc", + "type": "buttonedge", + "id": "opea_service@prepare_doc_redis_prep_0-opea_service@prepare_doc_redis_prep_0-output-opea_service@prepare_doc_redis_prep-EmbedDoc-redis_vector_store_0-redis_vector_store_0-input-prepared_doc-EmbedDoc" + }, + { + "source": "opea_service@embedding_tei_langchain_0", + "sourceHandle": "opea_service@embedding_tei_langchain_0-output-opea_service@embedding_tei_langchain-EmbedDoc|EmbeddingResponse|ChatCompletionRequest", + "target": "opea_service@retriever_redis_0", + "targetHandle": "opea_service@retriever_redis_0-input-text-EmbedDoc|RetrievalRequest|ChatCompletionRequest", + "type": "buttonedge", + "id": "opea_service@embedding_tei_langchain_0-opea_service@embedding_tei_langchain_0-output-opea_service@embedding_tei_langchain-EmbedDoc|EmbeddingResponse|ChatCompletionRequest-opea_service@retriever_redis_0-opea_service@retriever_redis_0-input-text-EmbedDoc|RetrievalRequest|ChatCompletionRequest" + }, + { + "source": "redis_vector_store_0", + "sourceHandle": "redis_vector_store_0-output-redis_vector_store-EmbedDoc", + "target": "opea_service@retriever_redis_0", + "targetHandle": "opea_service@retriever_redis_0-input-vector_db-EmbedDoc", + "type": "buttonedge", + "id": "redis_vector_store_0-redis_vector_store_0-output-redis_vector_store-EmbedDoc-opea_service@retriever_redis_0-opea_service@retriever_redis_0-input-vector_db-EmbedDoc" + }, + { + "source": "opea_service@retriever_redis_0", + "sourceHandle": "opea_service@retriever_redis_0-output-opea_service@retriever_redis-SearchedDoc|RetrievalResponse|ChatCompletionRequest", + "target": "opea_service@reranking_tei_0", + "targetHandle": "opea_service@reranking_tei_0-input-retreived_docs-SearchedDocs|RerankingRequest|ChatCompletionRequest", + "type": "buttonedge", + "id": "opea_service@retriever_redis_0-opea_service@retriever_redis_0-output-opea_service@retriever_redis-SearchedDoc|RetrievalResponse|ChatCompletionRequest-opea_service@reranking_tei_0-opea_service@reranking_tei_0-input-retreived_docs-SearchedDocs|RerankingRequest|ChatCompletionRequest" + }, + { + "source": "chat_input_0", + "sourceHandle": "chat_input_0-output-chat_input-ChatCompletionRequest", + "target": "opea_service@supervisor_agent_0", + "targetHandle": "opea_service@supervisor_agent_0-input-query-ChatCompletionRequest", + "type": "buttonedge", + "id": "chat_input_0-chat_input_0-output-chat_input-ChatCompletionRequest-opea_service@supervisor_agent_0-opea_service@supervisor_agent_0-input-query-ChatCompletionRequest" + }, + { + "source": "opea_service@supervisor_agent_0", + "sourceHandle": "opea_service@supervisor_agent_0-output-RagAgent-RagAgent", + "target": "opea_service@rag_agent_0", + "targetHandle": "opea_service@rag_agent_0-input-query-ChatCompletionRequest|RagAgent", + "type": "buttonedge", + "id": "opea_service@supervisor_agent_0-opea_service@supervisor_agent_0-output-RagAgent-RagAgent-opea_service@rag_agent_0-opea_service@rag_agent_0-input-query-ChatCompletionRequest|RagAgent" + }, + { + "source": "opea_service@supervisor_agent_0", + "sourceHandle": "opea_service@supervisor_agent_0-output-SqlAgent-SqlAgent", + "target": "opea_service@sql_agent_1", + "targetHandle": "opea_service@sql_agent_1-input-query-ChatCompletionRequest|SqlAgent", + "type": "buttonedge", + "id": "opea_service@supervisor_agent_0-opea_service@supervisor_agent_0-output-SqlAgent-SqlAgent-opea_service@sql_agent_1-opea_service@sql_agent_1-input-query-ChatCompletionRequest|SqlAgent" + }, + { + "source": "opea_service@reranking_tei_0", + "sourceHandle": "opea_service@reranking_tei_0-output-opea_service@reranking_tei-LLMParamsDoc|RerankingResponse|ChatCompletionRequest", + "target": "opea_service@rag_agent_0", + "targetHandle": "opea_service@rag_agent_0-input-retrievalResponse-SearchedDoc|RetrievalResponse|ChatCompletionRequest", + "type": "buttonedge", + "id": "opea_service@reranking_tei_0-opea_service@reranking_tei_0-output-opea_service@reranking_tei-LLMParamsDoc|RerankingResponse|ChatCompletionRequest-opea_service@rag_agent_0-opea_service@rag_agent_0-input-retrievalResponse-SearchedDoc|RetrievalResponse|ChatCompletionRequest" + }, + { + "source": "opea_service@rag_agent_0", + "sourceHandle": "opea_service@rag_agent_0-output-retrivalRequest-ChatCompletionRequest", + "target": "opea_service@embedding_tei_langchain_0", + "targetHandle": "opea_service@embedding_tei_langchain_0-input-textToEmbed-TextDoc|EmbeddingRequest|ChatCompletionRequest", + "type": "buttonedge", + "id": "opea_service@rag_agent_0-opea_service@rag_agent_0-output-retrivalRequest-ChatCompletionRequest-opea_service@embedding_tei_langchain_0-opea_service@embedding_tei_langchain_0-input-textToEmbed-TextDoc|EmbeddingRequest|ChatCompletionRequest" + }, + { + "source": "opea_service@supervisor_agent_0", + "sourceHandle": "opea_service@supervisor_agent_0-output-ChatCompletionRequest-ChatCompletion", + "target": "chat_completion_0", + "targetHandle": "chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion", + "type": "buttonedge", + "id": "opea_service@supervisor_agent_0-opea_service@supervisor_agent_0-output-ChatCompletionRequest-ChatCompletion-chat_completion_0-chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion" + } + ] +} \ No newline at end of file diff --git a/sample-workflows/basic_llm_workflow.json b/sample-workflows/basic_llm_workflow.json index d55e4c3..c87e3d5 100644 --- a/sample-workflows/basic_llm_workflow.json +++ b/sample-workflows/basic_llm_workflow.json @@ -213,7 +213,7 @@ "selected": false }, "width": 300, - "height": 478, + "height": 481, "selected": false, "positionAbsolute": { "x": 320.28599745224875, @@ -224,8 +224,8 @@ { "id": "chat_completion_0", "position": { - "x": 856.6184063184758, - "y": 137.45379050568326 + "x": 723.2775041173651, + "y": 161.79179483429388 }, "type": "customNode", "data": { @@ -233,7 +233,7 @@ "name": "chat_completion", "version": 1, "type": "ChatCompletion", - "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", + "icon": "/usr/src/packages/server/src/nodes/assets/controls.png", "category": "Controls", "description": "Send Chat Response to UI", "baseClasses": [], @@ -242,7 +242,8 @@ ], "inMegaservice": false, "inputs": { - "llm_response": "{{opea_service@llm_tgi_0.data.instance}}" + "llm_response": "{{opea_service@llm_tgi_0.data.instance}}", + "ui_choice": "chat" }, "hideOutput": true, "filePath": "/usr/src/packages/server/src/nodes/chat_completion.js", @@ -250,23 +251,46 @@ { "label": "LLM Response", "name": "llm_response", - "type": "ChatCompletion", - "id": "chat_completion_0-input-llm_response-ChatCompletion" + "type": "StreamingResponse|ChatCompletion", + "id": "chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion" + } + ], + "inputParams": [ + { + "label": "UI Choice", + "name": "ui_choice", + "type": "options", + "default": "chat", + "options": [ + { + "name": "chat", + "label": "Chat Q&A" + }, + { + "name": "summary", + "label": "Summarize Content" + }, + { + "name": "code", + "label": "Generate Code" + } + ], + "id": "chat_completion_0-input-ui_choice-options" } ], - "inputParams": [], "outputs": {}, "outputAnchors": [], "id": "chat_completion_0", "selected": false }, "width": 300, - "height": 143, + "height": 239, + "selected": false, "positionAbsolute": { - "x": 856.6184063184758, - "y": 137.45379050568326 + "x": 723.2775041173651, + "y": 161.79179483429388 }, - "selected": false + "dragging": false } ], "edges": [ @@ -282,9 +306,9 @@ "source": "opea_service@llm_tgi_0", "sourceHandle": "opea_service@llm_tgi_0-output-opea_service@llm_tgi-GeneratedDoc|StreamingResponse|ChatCompletion", "target": "chat_completion_0", - "targetHandle": "chat_completion_0-input-llm_response-ChatCompletion", + "targetHandle": "chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion", "type": "buttonedge", - "id": "opea_service@llm_tgi_0-opea_service@llm_tgi_0-output-opea_service@llm_tgi-GeneratedDoc|StreamingResponse|ChatCompletion-chat_completion_0-chat_completion_0-input-llm_response-ChatCompletion" + "id": "opea_service@llm_tgi_0-opea_service@llm_tgi_0-output-opea_service@llm_tgi-GeneratedDoc|StreamingResponse|ChatCompletion-chat_completion_0-chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion" } ] } \ No newline at end of file diff --git a/sample-workflows/sample_codegen.json b/sample-workflows/sample_codegen.json new file mode 100644 index 0000000..d06b910 --- /dev/null +++ b/sample-workflows/sample_codegen.json @@ -0,0 +1,313 @@ +{ + "nodes": [ + { + "id": "chat_input_0", + "position": { + "x": 526.4999999999999, + "y": 285.25 + }, + "type": "customNode", + "data": { + "label": "Chat Input", + "name": "chat_input", + "version": 1, + "type": "ChatCompletionRequest", + "icon": "/usr/src/packages/server/src/nodes/assets/controls.png", + "category": "Controls", + "description": "User Input from Chat Window", + "baseClasses": [ + "ChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "filePath": "/usr/src/packages/server/src/nodes/chat_input.js", + "inputAnchors": [], + "inputParams": [], + "inputs": {}, + "outputs": {}, + "outputAnchors": [ + { + "id": "chat_input_0-output-chat_input-ChatCompletionRequest", + "name": "chat_input", + "label": "ChatCompletionRequest", + "description": "User Input from Chat Window", + "type": "ChatCompletionRequest" + } + ], + "id": "chat_input_0", + "selected": false + }, + "width": 300, + "height": 143, + "selected": false, + "positionAbsolute": { + "x": 526.4999999999999, + "y": 285.25 + }, + "dragging": false + }, + { + "id": "opea_service@llm_codegen_0", + "position": { + "x": 888.5874400000002, + "y": 204.56385999999998 + }, + "type": "customNode", + "data": { + "label": "LLM Code Generation", + "name": "opea_service@llm_codegen", + "version": 1, + "type": "GeneratedDoc", + "icon": "/usr/src/packages/server/src/nodes/assets/llm.png", + "category": "LLM", + "description": "LLM Code Generation Inference", + "baseClasses": [ + "GeneratedDoc", + "StreamingResponse" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": true, + "dependent_services": { + "tgi": { + "modelName": "", + "huggingFaceToken": "" + } + }, + "inputs": { + "text": "{{chat_input_0.data.instance}}", + "modelName": "Qwen/Qwen2.5-Coder-7B-Instruct", + "huggingFaceToken": "", + "max_tokens": 17, + "top_k": 10, + "top_p": 0.95, + "typical_p": 0.95, + "temperature": 0.01, + "presence_penalty": 1.03, + "frequency_penalty": "", + "streaming": true, + "chat_template": "### You are a helpful, respectful and honest assistant to help the user with questions.\n### Context: {context}\n### Question: {question}\n### Answer:" + }, + "filePath": "/usr/src/packages/server/src/nodes/llm_codegen.js", + "inputAnchors": [ + { + "label": "LLM Params Document", + "name": "text", + "type": "LLMParamsDoc|ChatCompletionRequest", + "id": "opea_service@llm_codegen_0-input-text-LLMParamsDoc|ChatCompletionRequest" + } + ], + "inputParams": [ + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "Qwen/Qwen2.5-Coder-7B-Instruct", + "id": "opea_service@llm_codegen_0-input-modelName-string" + }, + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@llm_codegen_0-input-huggingFaceToken-password" + }, + { + "label": "Maximum Tokens", + "name": "max_tokens", + "type": "number", + "default": 17, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-max_tokens-number" + }, + { + "label": "Top K", + "name": "top_k", + "type": "number", + "default": 10, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-top_k-number" + }, + { + "label": "Top P", + "name": "top_p", + "type": "number", + "default": 0.95, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-top_p-number" + }, + { + "label": "Typical P", + "name": "typical_p", + "type": "number", + "default": 0.95, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-typical_p-number" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.01, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-temperature-number" + }, + { + "label": "Presence Penalty", + "name": "presence_penalty", + "type": "number", + "default": 1.03, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-presence_penalty-number" + }, + { + "label": "Frequency Penalty", + "name": "frequency_penalty", + "type": "number", + "default": 0, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-frequency_penalty-number" + }, + { + "label": "Streaming", + "name": "streaming", + "type": "boolean", + "default": true, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-streaming-boolean" + }, + { + "label": "Chat Template", + "name": "chat_template", + "type": "string", + "rows": true, + "default": "### You are a helpful, respectful and honest assistant to help the user with questions.\n### Context: {context}\n### Question: {question}\n### Answer:", + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_codegen_0-input-chat_template-string" + } + ], + "outputs": {}, + "outputAnchors": [ + { + "id": "opea_service@llm_codegen_0-output-opea_service@llm_codegen-GeneratedDoc|StreamingResponse", + "name": "opea_service@llm_codegen", + "label": "GeneratedDoc", + "description": "LLM Code Generation Inference", + "type": "GeneratedDoc | StreamingResponse" + } + ], + "id": "opea_service@llm_codegen_0", + "selected": false + }, + "width": 300, + "height": 481, + "selected": false, + "positionAbsolute": { + "x": 888.5874400000002, + "y": 204.56385999999998 + }, + "dragging": false + }, + { + "id": "chat_completion_1", + "position": { + "x": 1294.0750736940474, + "y": 314.4780045886494 + }, + "type": "customNode", + "data": { + "label": "Chat Completion", + "name": "chat_completion", + "version": 1, + "type": "ChatCompletion", + "icon": "/usr/src/packages/server/src/nodes/assets/controls.png", + "category": "Controls", + "description": "Send Chat Response to UI", + "baseClasses": [], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "inputs": { + "llm_response": "{{opea_service@llm_codegen_0.data.instance}}", + "ui_choice": "code" + }, + "hideOutput": true, + "filePath": "/usr/src/packages/server/src/nodes/chat_completion.js", + "inputAnchors": [ + { + "label": "LLM Response", + "name": "llm_response", + "type": "StreamingResponse|ChatCompletion", + "id": "chat_completion_1-input-llm_response-StreamingResponse|ChatCompletion" + } + ], + "inputParams": [ + { + "label": "UI Choice", + "name": "ui_choice", + "type": "options", + "default": "chat", + "options": [ + { + "name": "chat", + "label": "Chat Q&A" + }, + { + "name": "summary", + "label": "Summarize Content" + }, + { + "name": "code", + "label": "Generate Code" + } + ], + "id": "chat_completion_1-input-ui_choice-options" + } + ], + "outputs": {}, + "outputAnchors": [], + "id": "chat_completion_1", + "selected": false + }, + "width": 300, + "height": 239, + "positionAbsolute": { + "x": 1294.0750736940474, + "y": 314.4780045886494 + }, + "selected": false, + "dragging": false + } + ], + "edges": [ + { + "source": "chat_input_0", + "sourceHandle": "chat_input_0-output-chat_input-ChatCompletionRequest", + "target": "opea_service@llm_codegen_0", + "targetHandle": "opea_service@llm_codegen_0-input-text-LLMParamsDoc|ChatCompletionRequest", + "type": "buttonedge", + "id": "chat_input_0-chat_input_0-output-chat_input-ChatCompletionRequest-opea_service@llm_codegen_0-opea_service@llm_codegen_0-input-text-LLMParamsDoc|ChatCompletionRequest" + }, + { + "source": "opea_service@llm_codegen_0", + "sourceHandle": "opea_service@llm_codegen_0-output-opea_service@llm_codegen-GeneratedDoc|StreamingResponse", + "target": "chat_completion_1", + "targetHandle": "chat_completion_1-input-llm_response-StreamingResponse|ChatCompletion", + "type": "buttonedge", + "id": "opea_service@llm_codegen_0-opea_service@llm_codegen_0-output-opea_service@llm_codegen-GeneratedDoc|StreamingResponse-chat_completion_1-chat_completion_1-input-llm_response-StreamingResponse|ChatCompletion" + } + ] +} \ No newline at end of file diff --git a/sample-workflows/sample_docsum.json b/sample-workflows/sample_docsum.json new file mode 100644 index 0000000..4bb5df9 --- /dev/null +++ b/sample-workflows/sample_docsum.json @@ -0,0 +1,393 @@ +{ + "nodes": [ + { + "id": "file_input_0", + "position": { + "x": 311, + "y": 231 + }, + "type": "customNode", + "data": { + "label": "File Input", + "name": "file_input", + "version": 1, + "type": "UploadFile", + "icon": "/usr/src/packages/server/src/nodes/assets/controls.png", + "category": "Controls", + "description": "User Input from File Upload", + "baseClasses": [ + "UploadFile" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "filePath": "/usr/src/packages/server/src/nodes/file_input.js", + "inputAnchors": [], + "inputParams": [], + "inputs": {}, + "outputs": {}, + "outputAnchors": [ + { + "id": "file_input_0-output-file_input-UploadFile", + "name": "file_input", + "label": "UploadFile", + "description": "User Input from File Upload", + "type": "UploadFile" + } + ], + "id": "file_input_0", + "selected": false + }, + "width": 300, + "height": 143, + "selected": false, + "positionAbsolute": { + "x": 311, + "y": 231 + }, + "dragging": false + }, + { + "id": "opea_service@asr_0", + "position": { + "x": 682.5990163687969, + "y": 220.73967426705767 + }, + "type": "customNode", + "data": { + "label": "Audio and Speech Recognition Whisper", + "name": "opea_service@asr", + "version": 1, + "type": "AudioTranscriptionResponse", + "icon": "/usr/src/packages/server/src/nodes/assets/embeddings.png", + "category": "Audio and Speech Recognition", + "description": "Transcribe audio and video files using OpenAI Whisper model. Supports various audio formats including mp3, wav, and mp4.", + "baseClasses": [ + "AudioTranscriptionResponse", + "DocSumChatCompletionRequest" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": true, + "dependent_services": { + "whisper": { + "huggingFaceToken": "" + } + }, + "inputs": { + "file": "{{file_input_0.data.instance}}", + "huggingFaceToken": "" + }, + "filePath": "/usr/src/packages/server/src/nodes/asr.js", + "inputAnchors": [ + { + "label": "Audio or Video File", + "name": "file", + "type": "UploadFile", + "id": "opea_service@asr_0-input-file-UploadFile" + } + ], + "inputParams": [ + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@asr_0-input-huggingFaceToken-password" + } + ], + "outputs": {}, + "outputAnchors": [ + { + "id": "opea_service@asr_0-output-opea_service@asr-AudioTranscriptionResponse|DocSumChatCompletionRequest", + "name": "opea_service@asr", + "label": "AudioTranscriptionResponse", + "description": "Transcribe audio and video files using OpenAI Whisper model. Supports various audio formats including mp3, wav, and mp4.", + "type": "AudioTranscriptionResponse | DocSumChatCompletionRequest" + } + ], + "id": "opea_service@asr_0", + "selected": false + }, + "width": 300, + "height": 329, + "selected": false, + "positionAbsolute": { + "x": 682.5990163687969, + "y": 220.73967426705767 + }, + "dragging": false + }, + { + "id": "opea_service@llm_docsum_0", + "position": { + "x": 1108.64725623794, + "y": 119.44827891119263 + }, + "type": "customNode", + "data": { + "label": "LLM Document Summarization", + "name": "opea_service@llm_docsum", + "version": 1, + "type": "GeneratedDoc", + "icon": "/usr/src/packages/server/src/nodes/assets/llm.png", + "category": "LLM", + "description": "LLM Document Summarization Inference", + "baseClasses": [ + "GeneratedDoc", + "StreamingResponse" + ], + "tags": [ + "OPEA" + ], + "inMegaservice": true, + "dependent_services": { + "tgi": { + "modelName": "", + "huggingFaceToken": "" + } + }, + "inputs": { + "text": "{{opea_service@asr_0.data.instance}}", + "modelName": "Intel/neural-chat-7b-v3-3", + "huggingFaceToken": "", + "max_tokens": 17, + "top_k": 10, + "top_p": 0.95, + "typical_p": 0.95, + "temperature": 0.01, + "presence_penalty": 1.03, + "frequency_penalty": "", + "streaming": true, + "chat_template": "### You are a helpful, respectful and honest assistant to help the user with questions.\n### Context: {context}\n### Question: {question}\n### Answer:" + }, + "filePath": "/usr/src/packages/server/src/nodes/llm_docsum.js", + "inputAnchors": [ + { + "label": "LLM Params Document", + "name": "text", + "type": "LLMParamsDoc|DocSumChatCompletionRequest", + "id": "opea_service@llm_docsum_0-input-text-LLMParamsDoc|DocSumChatCompletionRequest" + } + ], + "inputParams": [ + { + "label": "Model Name", + "name": "modelName", + "type": "string", + "default": "Intel/neural-chat-7b-v3-3", + "id": "opea_service@llm_docsum_0-input-modelName-string" + }, + { + "label": "HuggingFace Token", + "name": "huggingFaceToken", + "type": "password", + "optional": true, + "id": "opea_service@llm_docsum_0-input-huggingFaceToken-password" + }, + { + "label": "Maximum Tokens", + "name": "max_tokens", + "type": "number", + "default": 17, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-max_tokens-number" + }, + { + "label": "Top K", + "name": "top_k", + "type": "number", + "default": 10, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-top_k-number" + }, + { + "label": "Top P", + "name": "top_p", + "type": "number", + "default": 0.95, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-top_p-number" + }, + { + "label": "Typical P", + "name": "typical_p", + "type": "number", + "default": 0.95, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-typical_p-number" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.01, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-temperature-number" + }, + { + "label": "Presence Penalty", + "name": "presence_penalty", + "type": "number", + "default": 1.03, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-presence_penalty-number" + }, + { + "label": "Frequency Penalty", + "name": "frequency_penalty", + "type": "number", + "default": 0, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-frequency_penalty-number" + }, + { + "label": "Streaming", + "name": "streaming", + "type": "boolean", + "default": true, + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-streaming-boolean" + }, + { + "label": "Chat Template", + "name": "chat_template", + "type": "string", + "rows": true, + "default": "### You are a helpful, respectful and honest assistant to help the user with questions.\n### Context: {context}\n### Question: {question}\n### Answer:", + "optional": true, + "additionalParams": true, + "id": "opea_service@llm_docsum_0-input-chat_template-string" + } + ], + "outputs": {}, + "outputAnchors": [ + { + "id": "opea_service@llm_docsum_0-output-opea_service@llm_docsum-GeneratedDoc|StreamingResponse", + "name": "opea_service@llm_docsum", + "label": "GeneratedDoc", + "description": "LLM Document Summarization Inference", + "type": "GeneratedDoc | StreamingResponse" + } + ], + "id": "opea_service@llm_docsum_0", + "selected": false + }, + "width": 300, + "height": 481, + "selected": false, + "positionAbsolute": { + "x": 1108.64725623794, + "y": 119.44827891119263 + }, + "dragging": false + }, + { + "id": "chat_completion_0", + "position": { + "x": 1496.5085721388793, + "y": 195.25842213672294 + }, + "type": "customNode", + "data": { + "label": "Chat Completion", + "name": "chat_completion", + "version": 1, + "type": "ChatCompletion", + "icon": "/usr/src/packages/server/src/nodes/assets/controls.png", + "category": "Controls", + "description": "Send Chat Response to UI", + "baseClasses": [], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "inputs": { + "llm_response": "{{opea_service@llm_docsum_0.data.instance}}", + "ui_choice": "summary" + }, + "hideOutput": true, + "filePath": "/usr/src/packages/server/src/nodes/chat_completion.js", + "inputAnchors": [ + { + "label": "LLM Response", + "name": "llm_response", + "type": "StreamingResponse|ChatCompletion", + "id": "chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion" + } + ], + "inputParams": [ + { + "label": "UI Choice", + "name": "ui_choice", + "type": "options", + "default": "chat", + "options": [ + { + "name": "chat", + "label": "Chat Q&A" + }, + { + "name": "summary", + "label": "Summarize Content" + }, + { + "name": "code", + "label": "Generate Code" + } + ], + "id": "chat_completion_0-input-ui_choice-options" + } + ], + "outputs": {}, + "outputAnchors": [], + "id": "chat_completion_0", + "selected": false + }, + "width": 300, + "height": 239, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 1496.5085721388793, + "y": 195.25842213672294 + } + } + ], + "edges": [ + { + "source": "file_input_0", + "sourceHandle": "file_input_0-output-file_input-UploadFile", + "target": "opea_service@asr_0", + "targetHandle": "opea_service@asr_0-input-file-UploadFile", + "type": "buttonedge", + "id": "file_input_0-file_input_0-output-file_input-UploadFile-opea_service@asr_0-opea_service@asr_0-input-file-UploadFile" + }, + { + "source": "opea_service@asr_0", + "sourceHandle": "opea_service@asr_0-output-opea_service@asr-AudioTranscriptionResponse|DocSumChatCompletionRequest", + "target": "opea_service@llm_docsum_0", + "targetHandle": "opea_service@llm_docsum_0-input-text-LLMParamsDoc|DocSumChatCompletionRequest", + "type": "buttonedge", + "id": "opea_service@asr_0-opea_service@asr_0-output-opea_service@asr-AudioTranscriptionResponse|DocSumChatCompletionRequest-opea_service@llm_docsum_0-opea_service@llm_docsum_0-input-text-LLMParamsDoc|DocSumChatCompletionRequest" + }, + { + "source": "opea_service@llm_docsum_0", + "sourceHandle": "opea_service@llm_docsum_0-output-opea_service@llm_docsum-GeneratedDoc|StreamingResponse", + "target": "chat_completion_0", + "targetHandle": "chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion", + "type": "buttonedge", + "id": "opea_service@llm_docsum_0-opea_service@llm_docsum_0-output-opea_service@llm_docsum-GeneratedDoc|StreamingResponse-chat_completion_0-chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion" + } + ] +} \ No newline at end of file diff --git a/sample-workflows/sample_workflow_chatqna.json b/sample-workflows/sample_workflow_chatqna.json index 7247488..6b16bad 100644 --- a/sample-workflows/sample_workflow_chatqna.json +++ b/sample-workflows/sample_workflow_chatqna.json @@ -96,52 +96,6 @@ }, "dragging": false }, - { - "id": "chat_completion_0", - "position": { - "x": 2189.1354963026597, - "y": 297.4088827779505 - }, - "type": "customNode", - "data": { - "label": "Chat Completion", - "name": "chat_completion", - "version": 1, - "type": "ChatCompletion", - "icon": "/usr/src/packages/server/src/nodes/opea-icon-color.svg", - "category": "Controls", - "description": "Send Chat Response to UI", - "baseClasses": [], - "tags": [ - "OPEA" - ], - "inMegaservice": false, - "inputs": { - "llm_response": "{{opea_service@llm_tgi_0.data.instance}}" - }, - "hideOutput": true, - "filePath": "/usr/src/packages/server/src/nodes/chat_completion.js", - "inputAnchors": [ - { - "label": "LLM Response", - "name": "llm_response", - "type": "ChatCompletion", - "id": "chat_completion_0-input-llm_response-ChatCompletion" - } - ], - "inputParams": [], - "outputs": {}, - "outputAnchors": [], - "id": "chat_completion_0", - "selected": false - }, - "width": 300, - "height": 143, - "positionAbsolute": { - "x": 2189.1354963026597, - "y": 297.4088827779505 - } - }, { "id": "opea_service@prepare_doc_redis_prep_0", "position": { @@ -214,7 +168,7 @@ "selected": false }, "width": 300, - "height": 427, + "height": 428, "selected": false, "positionAbsolute": { "x": 228.6922323555503, @@ -296,7 +250,7 @@ "selected": false }, "width": 300, - "height": 427, + "height": 428, "selected": false, "positionAbsolute": { "x": 296.4444521798094, @@ -469,7 +423,7 @@ "selected": false }, "width": 300, - "height": 476, + "height": 481, "selected": false, "positionAbsolute": { "x": 1811.03439857373, @@ -587,7 +541,7 @@ "selected": false }, "width": 300, - "height": 530, + "height": 531, "selected": false, "positionAbsolute": { "x": 917.1422079891504, @@ -680,7 +634,7 @@ "selected": false }, "width": 300, - "height": 479, + "height": 481, "selected": false, "positionAbsolute": { "x": 1380.4799693679547, @@ -737,13 +691,83 @@ "selected": false }, "width": 300, - "height": 230, + "height": 231, "selected": false, "positionAbsolute": { "x": 565.2677759986439, "y": 568.4177620749867 }, "dragging": false + }, + { + "id": "chat_completion_0", + "position": { + "x": 2185.13954097024, + "y": 201.18618923430142 + }, + "type": "customNode", + "data": { + "label": "Chat Completion", + "name": "chat_completion", + "version": 1, + "type": "ChatCompletion", + "icon": "/usr/src/packages/server/src/nodes/assets/controls.png", + "category": "Controls", + "description": "Send Chat Response to UI", + "baseClasses": [], + "tags": [ + "OPEA" + ], + "inMegaservice": false, + "inputs": { + "llm_response": "{{opea_service@llm_tgi_0.data.instance}}", + "ui_choice": "chat" + }, + "hideOutput": true, + "filePath": "/usr/src/packages/server/src/nodes/chat_completion.js", + "inputAnchors": [ + { + "label": "LLM Response", + "name": "llm_response", + "type": "StreamingResponse|ChatCompletion", + "id": "chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion" + } + ], + "inputParams": [ + { + "label": "UI Choice", + "name": "ui_choice", + "type": "options", + "default": "chat", + "options": [ + { + "name": "chat", + "label": "Chat Q&A" + }, + { + "name": "summary", + "label": "Summarize Content" + }, + { + "name": "code", + "label": "Generate Code" + } + ], + "id": "chat_completion_0-input-ui_choice-options" + } + ], + "outputs": {}, + "outputAnchors": [], + "id": "chat_completion_0", + "selected": false + }, + "width": 300, + "height": 239, + "positionAbsolute": { + "x": 2185.13954097024, + "y": 201.18618923430142 + }, + "selected": false } ], "edges": [ @@ -807,9 +831,9 @@ "source": "opea_service@llm_tgi_0", "sourceHandle": "opea_service@llm_tgi_0-output-opea_service@llm_tgi-GeneratedDoc|StreamingResponse|ChatCompletion", "target": "chat_completion_0", - "targetHandle": "chat_completion_0-input-llm_response-ChatCompletion", + "targetHandle": "chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion", "type": "buttonedge", - "id": "opea_service@llm_tgi_0-opea_service@llm_tgi_0-output-opea_service@llm_tgi-GeneratedDoc|StreamingResponse|ChatCompletion-chat_completion_0-chat_completion_0-input-llm_response-ChatCompletion" + "id": "opea_service@llm_tgi_0-opea_service@llm_tgi_0-output-opea_service@llm_tgi-GeneratedDoc|StreamingResponse|ChatCompletion-chat_completion_0-chat_completion_0-input-llm_response-StreamingResponse|ChatCompletion" } ] } \ No newline at end of file diff --git a/setup-scripts/build-image-to-registry/buildpush-genaicomps-images.yml b/setup-scripts/build-image-to-registry/buildpush-genaicomps-images.yml index 4a03d7a..55207b0 100755 --- a/setup-scripts/build-image-to-registry/buildpush-genaicomps-images.yml +++ b/setup-scripts/build-image-to-registry/buildpush-genaicomps-images.yml @@ -32,7 +32,7 @@ - { name: 'retriever', dockerfile: 'comps/retrievers/src/Dockerfile' } - { name: 'llm-textgen', dockerfile: 'comps/llms/src/text-generation/Dockerfile' } - { name: 'dataprep', dockerfile: 'comps/dataprep/src/Dockerfile' } - - { name: 'agent', dockerfile: 'comps/agent/src/Dockerfile' } + # - { name: 'agent', dockerfile: 'comps/agent/src/Dockerfile' } # temporary hardcoded in agent templates to v1.3 - { name: 'whisper', dockerfile: 'comps/third_parties/whisper/src/Dockerfile' } - { name: 'llm-docsum', dockerfile: 'comps/llms/src/doc-summarization/Dockerfile' } - { name: 'asr', dockerfile: 'comps/asr/src/Dockerfile' } @@ -45,4 +45,4 @@ - name: Push image command: docker push {{ container_registry }}/{{ item.name }}:{{ container_tag }} - loop: "{{ genaicomp_images }}" \ No newline at end of file + loop: "{{ genaicomp_images }}" diff --git a/setup-scripts/setup-genai-studio/manifests/studio-manifest.yaml b/setup-scripts/setup-genai-studio/manifests/studio-manifest.yaml index 3eca201..721f860 100644 --- a/setup-scripts/setup-genai-studio/manifests/studio-manifest.yaml +++ b/setup-scripts/setup-genai-studio/manifests/studio-manifest.yaml @@ -33,7 +33,7 @@ data: index index.html; } - # Location block for studio-appbuilder and app-frontend + # Location block for studio-frontend and app-frontend location / { # Initialize the default variable for namespace set $namespace ""; @@ -53,6 +53,13 @@ data: if ($namespace != "") { set $target_host http://${APP_FRONTEND_DNS}; } + + # Prevent caching when namespace is present + if ($namespace != "") { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } # Rewrite the request to include the namespace rewrite ^/(.*)$ /$1?ns=$namespace break; diff --git a/studio-backend/Dockerfile b/studio-backend/Dockerfile index b06463e..4884c5d 100644 --- a/studio-backend/Dockerfile +++ b/studio-backend/Dockerfile @@ -9,7 +9,7 @@ COPY app /usr/src/app # Upgrade libsqlite3 to a safe version RUN apt-get update -y && apt-get install -y --no-install-recommends --fix-missing \ - libsqlite3-0=3.40.1-2+deb12u1 openssh-client && \ + libsqlite3-0 openssh-client && \ rm -rf /var/lib/apt/lists/* # Upgrade setuptools to a safe version and install any needed packages specified in requirements.txt diff --git a/studio-backend/app/routers/clickdeploy_router.py b/studio-backend/app/routers/clickdeploy_router.py index efa13c3..24a7bea 100644 --- a/studio-backend/app/routers/clickdeploy_router.py +++ b/studio-backend/app/routers/clickdeploy_router.py @@ -1,63 +1,124 @@ from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect from fastapi.concurrency import run_in_threadpool import paramiko -import time +import asyncio import json from app.models.pipeline_model import DeployPipelineFlow -from app.services.clickdeploy_service import deploy_pipeline +from app.services.clickdeploy_service import upload_pipeline_zip router = APIRouter() -@router.post("/click-deploy") -async def deploy(request: DeployPipelineFlow): - print("[DEBUG] Entered /click-deploy endpoint") +@router.post("/upload-pipeline-files") +async def upload_pipeline_files(request: DeployPipelineFlow): + print("[DEBUG] Entered /upload-pipeline-files endpoint") remote_host = request.remoteHost remote_user = request.remoteUser pipeline_flow = request.pipelineFlow try: - print("[DEBUG] Calling deploy_pipeline...") - response = deploy_pipeline(remote_host, remote_user, pipeline_flow.dict()) - print("[DEBUG] deploy_pipeline returned") + print("[DEBUG] Calling upload_pipeline_zip...") + response = upload_pipeline_zip(remote_host, remote_user, pipeline_flow.dict()) + print("[DEBUG] upload_pipeline_zip returned") except Exception as e: - print(f"[ERROR] Exception in /click-deploy: {e}") + print(f"[ERROR] Exception in /upload-pipeline-files: {e}") raise HTTPException(status_code=500, detail=str(e)) return response -@router.websocket("/ws/clickdeploy-status") -async def check_clickdeploy_status(websocket: WebSocket): - print('checking clickdeploy status') +@router.websocket("/ws/deploy-and-monitor") +async def deploy_and_monitor_status(websocket: WebSocket): + print('deploying and monitoring status') await websocket.accept() + ssh_connection = None try: data = await websocket.receive_json() print("Received data: ", data) remote_host = data["hostname"] remote_user = data["username"] compose_dir = data["compose_dir"] + remote_zip_path = data.get("remote_zip_path", f"/home/{remote_user}/docker-compose.zip") - def check_status(): - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(remote_host, username=remote_user) - - # Get the number of services defined in compose.yaml - _, stdout_num, _ = ssh.exec_command(f"cd {compose_dir} && docker compose config --services | wc -l") - num_services_output = stdout_num.read().decode().strip().splitlines() - num_services_lines = [line for line in num_services_output if not line.startswith('WARN') and line.strip()] - num_services_str = num_services_lines[-1] if num_services_lines else '0' - - # Run docker compose ps to get service status - _, stdout_ps, _ = ssh.exec_command(f"cd {compose_dir} && docker compose ps --all --format json") - out = stdout_ps.read().decode() - json_lines = [line for line in out.strip().splitlines() if line.strip() and not line.strip().startswith('WARN')] - out_filtered = '\n'.join(json_lines) - - # Read nohup.out for progress logs (always fetch latest 10 lines) - _, stdout_nohup, _ = ssh.exec_command(f"cd {compose_dir} && tail -n 10 nohup.out") - nohup_out_lines = stdout_nohup.read().decode().splitlines() + # Step 1: Connect SSH + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(remote_host, username=remote_user) + ssh_connection = ssh # Store for cleanup + + # Step 2: Stop existing services if directory exists + await websocket.send_json({"status": "Preparing", "message": "Checking for existing services..."}) + print(f"[DEBUG] Checking if {compose_dir} directory exists...") + _, stdout, _ = ssh.exec_command(f"ls -d {compose_dir}", get_pty=True) + exit_status = stdout.channel.recv_exit_status() + if exit_status == 0: + _, stdout, _ = ssh.exec_command(f"cd {compose_dir} && docker compose down", get_pty=True) + while not stdout.channel.exit_status_ready(): + await websocket.send_json({"status": "Preparing", "message": "Stopping any existing services..."}) + await asyncio.sleep(2) # Non-blocking sleep + # Clean up old files + ssh.exec_command(f"cd {compose_dir}; rm -f .env nohup.out app.nginx.conf.template compose.yaml workflow-info.json", get_pty=True) + else: + pass + # Step 3: Extract files + await websocket.send_json({"status": "Preparing", "message": "Extracting files..."}) + extract_cmd = f"python3 -c \"import zipfile; zipfile.ZipFile('{remote_zip_path}').extractall('{compose_dir}')\"" + _, stdout, _ = ssh.exec_command(extract_cmd, get_pty=True) + if stdout.channel.recv_exit_status() != 0: + await websocket.send_json({"status": "Error", "error": "Failed to extract files using Python."}) ssh.close() - + await websocket.close() + return + await websocket.send_json({"status": "Preparing", "message": "Extraction complete."}) + # Clean up the uploaded docker-compose.zip file + _, stdout, _ = ssh.exec_command(f"rm -f {remote_zip_path}", get_pty=True) + + # Step 4: Start services + _, stdout, _ = ssh.exec_command(f"cd {compose_dir} && nohup docker compose up -d & sleep 0.1", get_pty=True) + ssh.close() + await websocket.send_json({"status": "Preparing", "message": "Deployment steps complete. Monitoring status..."}) + + def check_status(): + ssh_check = None + try: + ssh_check = paramiko.SSHClient() + ssh_check.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_check.connect(remote_host, username=remote_user) + + # Verify the directory exists and has content + print(f"[DEBUG] Checking directory: {compose_dir}") + _, stdout_ls, _ = ssh_check.exec_command(f"ls -la {compose_dir}") + # ls_output = stdout_ls.read().decode().strip() + # print(f"[DEBUG] Directory contents: {ls_output}") + + # Get the number of services defined in compose.yaml + _, stdout_num, _ = ssh_check.exec_command(f"cd {compose_dir} && docker compose config --services | wc -l") + num_services_output = stdout_num.read().decode().strip().splitlines() + num_services_lines = [line for line in num_services_output if not line.startswith('WARN') and line.strip()] + num_services_str = num_services_lines[-1] if num_services_lines else '0' + # print(f"[DEBUG] Number of services defined: {num_services_str}") + + # Run docker compose ps to get service status + _, stdout_ps, _ = ssh_check.exec_command(f"cd {compose_dir} && docker compose ps --all --format json") + out = stdout_ps.read().decode() + json_lines = [line for line in out.strip().splitlines() if line.strip() and not line.strip().startswith('WARN')] + out_filtered = '\n'.join(json_lines) + # print(f"[DEBUG] Docker compose ps output: {out_filtered}") + + # Read nohup.out for progress logs (always fetch latest 10 lines) + _, stdout_nohup, _ = ssh_check.exec_command(f"cd {compose_dir} && tail -n 10 nohup.out") + nohup_out_lines = stdout_nohup.read().decode().splitlines() + print(f"[DEBUG] Nohup output: {nohup_out_lines}") + + return _process_service_status(out_filtered, num_services_str, nohup_out_lines) + except Exception as e: + return {"error": f"Status check failed: {str(e)}"} + finally: + if ssh_check: + try: + ssh_check.close() + except: + pass + + def _process_service_status(out_filtered, num_services_str, nohup_out_lines): try: # If output contains multiple JSON objects, parse all and aggregate json_lines = [line for line in out_filtered.strip().splitlines() if line.strip()] @@ -97,9 +158,36 @@ def check_status(): "nohup_out": nohup_out_lines } - services_exited = sum(1 for s in all_services if isinstance(s, dict) and s.get("State", "") == "exited") - services_running = sum(1 for s in all_services if isinstance(s, dict) and s.get("State", "") == "running") - print(f"[DEBUG] Number of services deployed: {services_running + services_exited}/{num_services_str}") + # Define containers that are expected to complete and exit + expected_completed_containers = [ + "model-downloader", "downloader", "init", "setup", "migrate", "seed" + ] + + # Filter out expected completed containers from exited count + unexpected_exited_services = [] + expected_completed_services = [] + running_services = [] + + for s in all_services: + if isinstance(s, dict): + service_name = s.get("Name", "").lower() + state = s.get("State", "") + + if state == "running": + running_services.append(s) + elif state == "exited": + # Check if this is an expected completed container + is_expected_completed = any(keyword in service_name for keyword in expected_completed_containers) + if is_expected_completed: + expected_completed_services.append(s) + else: + unexpected_exited_services.append(s) + + services_exited = len(unexpected_exited_services) + services_running = len(running_services) + services_expected_completed = len(expected_completed_services) + + print(f"[DEBUG] Number of services deployed: {services_running} running, {services_exited} unexpectedly exited, {services_expected_completed} expected completed / {num_services_str} total") all_healthy = all((not isinstance(s, dict)) or (s.get("Health", "") in ("", "healthy")) for s in all_services) none_restarting = all(isinstance(s, dict) and s.get("State", "") != "restarting" for s in all_services) @@ -111,6 +199,7 @@ def check_status(): "none_restarting": none_restarting, "services_running": services_running, "services_exited": services_exited, + "services_expected_completed": services_expected_completed, "services_defined": int(num_services_str), "ps": all_services, "error": None, @@ -120,20 +209,22 @@ def check_status(): return {"error": str(e)} while True: - result = await run_in_threadpool(check_status) if result["error"]: await websocket.send_json({"status": "Error", "error": result["error"]}) break - if (int(result["services_running"]) + int(result["services_exited"])) == result["services_defined"]: - if result["all_healthy"] and result["services_running"] == result["services_defined"]: - # Wait 5 seconds and recheck none_restarting - time.sleep(5) + if (int(result["services_running"]) + int(result["services_exited"]) + int(result.get("services_expected_completed", 0))) == result["services_defined"]: + # Only consider actually running services + expected completed services as "successful" + expected_running_services = result["services_defined"] - result.get("services_expected_completed", 0) + + if result["all_healthy"] and result["services_running"] == expected_running_services and result["services_exited"] == 0: + await asyncio.sleep(5) recheck_result = await run_in_threadpool(check_status) if recheck_result["none_restarting"]: - await websocket.send_json({"status": "Done", "success": f"All {result['services_running']} services are running and healthy. Open http://localhost:8090 in your machine's browser to access the application."}) + total_successful = result["services_running"] + result.get("services_expected_completed", 0) + await websocket.send_json({"status": "Done", "success": f"All {total_successful} services completed successfully ({result['services_running']} running, {result.get('services_expected_completed', 0)} completed). Open http://localhost:8090 in your machine's browser to access the application."}) else: restarting_services = [ s.get("Name", "unknown") for s in recheck_result["ps"] @@ -141,16 +232,46 @@ def check_status(): ] await websocket.send_json({"status": "Error", "error": f"Services stuck in restarting status: [{', '.join(restarting_services)}]"}) else: - exited_services = [ - s.get("Name", "unknown") for s in result["ps"] - if isinstance(s, dict) and s.get("State", "") == "exited" - ] - await websocket.send_json({"status": "Error", "error": f"Services in exited state: [{', '.join(exited_services)}]"}) + # Only report unexpected exited services as errors + if result["services_exited"] > 0: + exited_services = [ + s.get("Name", "unknown") for s in result["ps"] + if isinstance(s, dict) and s.get("State", "") == "exited" and not any(keyword in s.get("Name", "").lower() for keyword in ["model-downloader", "downloader", "init", "setup", "migrate", "seed"]) + ] + if exited_services: + await websocket.send_json({"status": "Error", "error": f"Services in exited state: [{', '.join(exited_services)}]"}) + else: + # All exited services are expected completed ones, continue monitoring + await websocket.send_json({"status": "In Progress", "ps": result["ps"], "nohup_out": result.get("nohup_out", [])}) + await asyncio.sleep(2) + continue + else: + await websocket.send_json({"status": "In Progress", "ps": result["ps"], "nohup_out": result.get("nohup_out", [])}) + await asyncio.sleep(2) + continue break # Send nohup_out in progress status await websocket.send_json({"status": "In Progress", "ps": result["ps"], "nohup_out": result.get("nohup_out", [])}) - time.sleep(2) + await asyncio.sleep(2) except WebSocketDisconnect: - print("Client disconnected") + print("Client disconnected from deploy-and-monitor WebSocket") + except Exception as e: + print(f"Error in deploy-and-monitor WebSocket: {e}") + try: + await websocket.send_json({"status": "Error", "error": f"Unexpected error: {str(e)}"}) + except: + pass # WebSocket might already be closed finally: - await websocket.close() \ No newline at end of file + # Clean up SSH connection if it exists + if ssh_connection: + try: + ssh_connection.close() + print("SSH connection cleaned up") + except: + pass + # Ensure WebSocket is closed + try: + if not websocket.client_state.name == 'DISCONNECTED': + await websocket.close() + except: + pass \ No newline at end of file diff --git a/studio-backend/app/routers/debuglog_router.py b/studio-backend/app/routers/debuglog_router.py index 2b69dd9..c18c76e 100644 --- a/studio-backend/app/routers/debuglog_router.py +++ b/studio-backend/app/routers/debuglog_router.py @@ -232,7 +232,7 @@ async def get_all_pods_in_namespace(namespace: str): if services: try: dependencies = find_pod_dependencies(pod, pods, services, namespace, core_v1_api) - print(f"Pod {pod_name} dependencies: {dependencies}") + # print(f"Pod {pod_name} dependencies: {dependencies}") except Exception as e: print(f"Error analyzing dependencies for pod {pod_name}: {str(e)}") import traceback diff --git a/studio-backend/app/services/clickdeploy_service.py b/studio-backend/app/services/clickdeploy_service.py index ea665bb..955e52f 100644 --- a/studio-backend/app/services/clickdeploy_service.py +++ b/studio-backend/app/services/clickdeploy_service.py @@ -13,12 +13,11 @@ from app.services.workflow_info_service import WorkflowInfo from app.utils.exporter_utils import process_opea_services -def deploy_pipeline(hostname, username, pipeline_flow): +def upload_pipeline_zip(hostname, username, pipeline_flow): print("[INFO] Starting deployment to remote server...") remote_zip_path = f"/home/{username}/docker-compose.zip" temp_dir = None - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") - remote_compose_dir = f"docker-compose-{timestamp}" + remote_compose_dir = "genaistudio-compose" try: print("[INFO] Creating ZIP locally...") zip_path, temp_dir = create_zip_locally(pipeline_flow, hostname) @@ -38,28 +37,27 @@ def deploy_pipeline(hostname, username, pipeline_flow): sftp.close() print("[INFO] SFTP session closed.") - commands = [ - f"mkdir {remote_compose_dir}", - f"unzip -o {remote_zip_path} -d {remote_compose_dir}", - f"rm -f {remote_zip_path}", - f"cd {remote_compose_dir} && nohup docker compose up -d & sleep 0.1" - ] - for cmd in commands: - print(f"[INFO] Executing remote command: {cmd}") - _, stdout, stderr = ssh.exec_command(cmd, get_pty=True) + # Check if genaistudio-compose directory exists + print(f"[INFO] Checking if {remote_compose_dir} directory exists...") + _, stdout, stderr = ssh.exec_command(f"ls -d {remote_compose_dir}", get_pty=True) + exit_status = stdout.channel.recv_exit_status() + # Only upload and ensure directory, do not stop services here + if exit_status != 0: + print(f"[INFO] {remote_compose_dir} does not exist, will create new directory") + _, stdout, stderr = ssh.exec_command(f"mkdir -p {remote_compose_dir}", get_pty=True) exit_status = stdout.channel.recv_exit_status() stderr_str = stderr.read().decode().strip() print(f"[INFO] Command exit status: {exit_status}") - if stderr_str: - print(f"[ERROR] Stderr: {stderr_str}") - + if exit_status != 0: + print(f"[ERROR] Failed to create directory: {stderr_str}") + raise Exception(f"Failed to create remote directory: {stderr_str}") ssh.close() print("[INFO] SSH connection closed.") - return { "status": "success", - "message": "docker compose up -d has been started.", - "compose_dir": remote_compose_dir + "message": "docker-compose.zip uploaded and directory ensured.", + "compose_dir": remote_compose_dir, + "remote_zip_path": remote_zip_path } except Exception as e: print(f"[ERROR] An error: {e}") @@ -73,6 +71,7 @@ def create_zip_locally(request, hostname): env_file_path = os.path.join(temp_dir, ".env") compose_file_path = os.path.join(temp_dir, "compose.yaml") workflow_info_file_path = os.path.join(temp_dir, "workflow-info.json") + nginx_conf_path = os.path.join(temp_dir, "app.nginx.conf.template") zip_path = os.path.join(temp_dir, "docker-compose.zip") # Only keep large objects in memory as long as needed @@ -96,6 +95,16 @@ def create_zip_locally(request, hostname): with open(workflow_info_file_path, 'w') as f: f.write(json.dumps(workflow_info, indent=4)) + # Read app.nginx.conf.template template and copy to temp directory + nginx_template_path = os.path.join(os.path.dirname(__file__), '..', 'templates', 'app', 'app.nginx.conf.template') + if os.path.exists(nginx_template_path): + with open(nginx_template_path, 'r') as template_file: + nginx_conf_content = template_file.read() + with open(nginx_conf_path, 'w') as f: + f.write(nginx_conf_content) + else: + raise FileNotFoundError(f"app.nginx.conf.template template not found at {nginx_template_path}") + # Free up memory from large objects as soon as possible del workflow_info_raw, workflow_info_json, workflow_info, services_info, ports_info @@ -104,6 +113,7 @@ def create_zip_locally(request, hostname): zipf.write(env_file_path, arcname=".env") zipf.write(compose_file_path, arcname="compose.yaml") zipf.write(workflow_info_file_path, arcname="workflow-info.json") + zipf.write(nginx_conf_path, arcname="app.nginx.conf.template") for file_info in additional_files_info: source_path = file_info["source"] diff --git a/studio-backend/app/services/exporter_service.py b/studio-backend/app/services/exporter_service.py index ad2cc69..31d6911 100644 --- a/studio-backend/app/services/exporter_service.py +++ b/studio-backend/app/services/exporter_service.py @@ -45,10 +45,15 @@ def convert_proj_info_to_manifest(proj_info_json, output_file=None): continue print(f"Processing {metadata['name']} service") - # Remove HF_TOKEN if hugging_face_token is 'NA' + # Remove HF_TOKEN if it's 'NA' and TEI_EMBEDDING_ENDPOINT is not present if doc.get('kind') == 'ConfigMap' and 'data' in doc: - if 'HF_TOKEN' in doc['data'] and doc['data']['HF_TOKEN'] == 'NA': + if ( + 'HF_TOKEN' in doc['data'] and + doc['data']['HF_TOKEN'] == 'NA' and + 'TEI_EMBEDDING_ENDPOINT' not in doc['data'] + ): del doc['data']['HF_TOKEN'] + # Accumulate the YAML document in the manifest string manifest_string += '---\n' @@ -79,7 +84,7 @@ def convert_proj_info_to_compose(proj_info_json, output_file=None): service_file_path = os.path.join(TEMPLATES_DIR, compose_map[service_info["service_type"]]) with open(service_file_path, "r") as service_file: service_compose_read = service_file.read() - service_compose_raw = list(ordered_load_all(replace_dynamic_compose_placeholder(service_compose_read, service_info), yaml.SafeLoader)) + service_compose_raw = list(ordered_load_all(replace_dynamic_compose_placeholder(service_compose_read, service_info, proj_info_json), yaml.SafeLoader)) service_compose = [replace_compose_placeholders(doc, service_info) for doc in service_compose_raw] output_compose.extend((doc, service_name) for doc in service_compose) @@ -92,9 +97,11 @@ def convert_proj_info_to_compose(proj_info_json, output_file=None): combined_services = {} for doc, service_name in (output_compose): - # Remove HF_TOKEN if hugging_face_token is 'NA' + # Remove HF_TOKEN if hugging_face_token is 'NA' and TEI_EMBEDDING_ENDPOINT is not present for _, service_data in doc.items(): - if 'environment' in service_data and 'HF_TOKEN' in service_data['environment'] and service_data['environment']['HF_TOKEN'] == 'NA': + if ('environment' in service_data and 'HF_TOKEN' in service_data['environment'] and + service_data['environment']['HF_TOKEN'] == 'NA' and + 'TEI_EMBEDDING_ENDPOINT' not in service_data.get('environment', {})): del service_data['environment']['HF_TOKEN'] combined_services.update(doc) diff --git a/studio-backend/app/services/workflow_info_service.py b/studio-backend/app/services/workflow_info_service.py index 3c12b67..b48f17c 100644 --- a/studio-backend/app/services/workflow_info_service.py +++ b/studio-backend/app/services/workflow_info_service.py @@ -69,8 +69,9 @@ def generate_dag(self): node_data['connected_from'].append(connected_from_id) dag_nodes[connected_from_id]['connected_to'].append(id) continue - #skip ui_choice inputs + # Include ui_choice inputs in params for default UI type detection if input_key == 'ui_choice': + node_data['params'][input_key] = input_value continue if input_key == 'huggingFaceToken' and not input_value: @@ -110,7 +111,7 @@ def generate_dag(self): node_data['params'].pop(input_key, None) continue # Handle imageRepository specific logic - print(f"imageRepository: {node_data.get('imageRepository')}") + # print(f"imageRepository: {node_data.get('imageRepository')}") if node_data.get('imageRepository'): node_data['params']['IMAGE_REPOSITORY'] = node_data['imageRepository'] del node_data['inputParams'] diff --git a/studio-backend/app/templates/app/app.compose.yaml b/studio-backend/app/templates/app/app.compose.yaml index b62d21b..3ecfb4e 100644 --- a/studio-backend/app/templates/app/app.compose.yaml +++ b/studio-backend/app/templates/app/app.compose.yaml @@ -26,9 +26,11 @@ app-frontend: - no_proxy=${no_proxy} - https_proxy=${https_proxy} - http_proxy=${http_proxy} - - VITE_UI_SELECTION=${ui_selection} - - VITE_APP_BACKEND_SERVICE_URL=/v1/app-backend - __UI_CONFIG_INFO_ENV_PLACEHOLDER__ + - APP_BACKEND_SERVICE_URL=/v1/app-backend + - APP_DATAPREP_SERVICE_URL=__APP_DATAPREP_SERVICE_URL_PLACEHOLDER__ + - APP_CHAT_HISTORY_SERVICE_URL=/v1/chathistory + - APP_UI_SELECTION=chat,summary,code + - APP_DEFAULT_UI_TYPE=__DEFAULT_UI_TYPE_PLACEHOLDER__ ipc: host restart: always app-nginx: @@ -39,6 +41,8 @@ app-nginx: - app-backend ports: - 8090:80 + volumes: + - ./app.nginx.conf.template:/etc/nginx/nginx.conf.template:ro environment: - no_proxy=${no_proxy} - https_proxy=${https_proxy} diff --git a/studio-backend/app/templates/app/app.manifest.aws.ecr.yaml b/studio-backend/app/templates/app/app.manifest.aws.ecr.yaml index 971aeda..55fcaba 100644 --- a/studio-backend/app/templates/app/app.manifest.aws.ecr.yaml +++ b/studio-backend/app/templates/app/app.manifest.aws.ecr.yaml @@ -164,6 +164,9 @@ data: location / { proxy_pass http://app-frontend:5275; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/studio-backend/app/templates/app/app.manifest.yaml b/studio-backend/app/templates/app/app.manifest.yaml index 73a9ee3..9b43420 100644 --- a/studio-backend/app/templates/app/app.manifest.yaml +++ b/studio-backend/app/templates/app/app.manifest.yaml @@ -124,17 +124,16 @@ spec: containers: - name: app-frontend env: - # - name: VITE_APP_BACKEND_SERVICE_URL - # value: "/v1/app-backend" - name: APP_BACKEND_SERVICE_URL value: "/v1/app-backend" - name: APP_DATAPREP_SERVICE_URL - value: "/v1/dataprep" + value: "__APP_DATAPREP_SERVICE_URL_PLACEHOLDER__" - name: APP_CHAT_HISTORY_SERVICE_URL value: "/v1/chathistory" - name: APP_UI_SELECTION value: "chat,summary,code" - __UI_CONFIG_INFO_ENV_PLACEHOLDER__ + - name: APP_DEFAULT_UI_TYPE + value: __DEFAULT_UI_TYPE_PLACEHOLDER__ securityContext: {} image: __APP_FRONTEND_IMAGE__ imagePullPolicy: Always @@ -260,6 +259,9 @@ data: location / { proxy_pass http://app-frontend:5275; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/studio-backend/app/templates/app/app.nginx.conf.template b/studio-backend/app/templates/app/app.nginx.conf.template new file mode 100644 index 0000000..60bb8ed --- /dev/null +++ b/studio-backend/app/templates/app/app.nginx.conf.template @@ -0,0 +1,71 @@ +server { + listen 80; + listen [::]:80; + + location /home { + root /usr/share/nginx/html; + index index.html; + } + + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + send_timeout 600; + client_max_body_size 10G; + + location / { + proxy_pass http://${FRONTEND_SERVICE_IP}:${FRONTEND_SERVICE_PORT}; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /v1/${BACKEND_SERVICE_NAME} { + proxy_pass http://${BACKEND_SERVICE_IP}:${BACKEND_SERVICE_PORT}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # SSE essentials + proxy_buffering off; + proxy_cache off; + proxy_http_version 1.1; + proxy_set_header Connection ''; + chunked_transfer_encoding off; + proxy_request_buffering off; + gzip off; + } + + location /v1/dataprep/ingest { + proxy_pass http://${DATAPREP_SERVICE_IP}:${DATAPREP_SERVICE_PORT}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 6000; + proxy_send_timeout 6000; + proxy_read_timeout 6000; + send_timeout 6000; + } + + location /v1/dataprep/get { + proxy_pass http://${DATAPREP_SERVICE_IP}:${DATAPREP_SERVICE_PORT}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /v1/dataprep/delete { + proxy_pass http://${DATAPREP_SERVICE_IP}:${DATAPREP_SERVICE_PORT}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/studio-backend/app/templates/microsvc-composes/asr-usvc.yaml b/studio-backend/app/templates/microsvc-composes/asr-usvc.yaml new file mode 100644 index 0000000..81ee01c --- /dev/null +++ b/studio-backend/app/templates/microsvc-composes/asr-usvc.yaml @@ -0,0 +1,22 @@ +"{{endpoint}}": + image: "${REGISTRY}/asr:${TAG}" + container_name: "{{endpoint}}" + ports: + - "{{port_key}}:9099" + environment: + no_proxy: ${no_proxy} + http_proxy: ${http_proxy} + https_proxy: ${https_proxy} + HEALTHCHECK_ENDPOINT: "{{whisper_endpoint}}:{{whisper_port}}" + ASR_ENDPOINT: "http://{{whisper_endpoint}}:{{whisper_port}}" + LOGFLAG: "True" + depends_on: + "{{whisper_endpoint}}": + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9099/v1/health_check"] + interval: 30s + retries: 24 + timeout: 10s + start_period: 30s + restart: unless-stopped diff --git a/studio-backend/app/templates/microsvc-composes/data-prep.yaml b/studio-backend/app/templates/microsvc-composes/data-prep.yaml index df3eef5..5c3bc69 100644 --- a/studio-backend/app/templates/microsvc-composes/data-prep.yaml +++ b/studio-backend/app/templates/microsvc-composes/data-prep.yaml @@ -17,5 +17,5 @@ INDEX_NAME: "rag-redis" TEI_EMBEDDING_ENDPOINT: "http://${public_host_ip}:{{tei_port}}" DATAPREP_COMPONENT_NAME: "OPEA_DATAPREP_REDIS" - HUGGINGFACEHUB_API_TOKEN: "{{huggingFaceToken}}" + HF_TOKEN: "{{huggingFaceToken}}" LOGFLAG: "True" \ No newline at end of file diff --git a/studio-backend/app/templates/microsvc-composes/llm-uservice.yaml b/studio-backend/app/templates/microsvc-composes/llm-uservice.yaml index 93dd258..ed99468 100644 --- a/studio-backend/app/templates/microsvc-composes/llm-uservice.yaml +++ b/studio-backend/app/templates/microsvc-composes/llm-uservice.yaml @@ -1,5 +1,5 @@ "{{endpoint}}": - image: ${REGISTRY}/llm-textgen:${TAG} + image: "${REGISTRY}/{{IMAGE_REPOSITORY}}:${TAG}" container_name: "{{endpoint}}" depends_on: "{{llm_endpoint}}": @@ -12,7 +12,7 @@ http_proxy: ${http_proxy} https_proxy: ${https_proxy} LLM_ENDPOINT: "http://${public_host_ip}:{{llm_port}}" - HUGGINGFACEHUB_API_TOKEN: "{{huggingFaceToken}}" + HF_TOKEN: "{{huggingFaceToken}}" HF_HUB_DISABLE_PROGRESS_BARS: 1 HF_HUB_ENABLE_HF_TRANSFER: 0 LLM_MODEL_ID: "{{modelName}}" diff --git a/studio-backend/app/templates/microsvc-composes/rag-agent.yaml b/studio-backend/app/templates/microsvc-composes/rag-agent.yaml index 598744f..a9adc15 100644 --- a/studio-backend/app/templates/microsvc-composes/rag-agent.yaml +++ b/studio-backend/app/templates/microsvc-composes/rag-agent.yaml @@ -1,5 +1,5 @@ "{{endpoint}}": - image: ${REGISTRY}/agent:${TAG} + image: opea/agent:1.3 container_name: "{{endpoint}}" volumes: - ./agent-tools/:/home/user/tools/ diff --git a/studio-backend/app/templates/microsvc-composes/reranking-usvc.yaml b/studio-backend/app/templates/microsvc-composes/reranking-usvc.yaml index 6dc8701..8d53551 100644 --- a/studio-backend/app/templates/microsvc-composes/reranking-usvc.yaml +++ b/studio-backend/app/templates/microsvc-composes/reranking-usvc.yaml @@ -12,7 +12,7 @@ http_proxy: ${http_proxy} https_proxy: ${https_proxy} TEI_RERANKING_ENDPOINT: "http://${public_host_ip}:{{tei_port}}" - HUGGINGFACEHUB_API_TOKEN: "{{huggingFaceToken}}" + HF_TOKEN: "{{huggingFaceToken}}" HF_HUB_DISABLE_PROGRESS_BARS: 1 HF_HUB_ENABLE_HF_TRANSFER: 0 LOGFLAG: "True" diff --git a/studio-backend/app/templates/microsvc-composes/retriever-usvc.yaml b/studio-backend/app/templates/microsvc-composes/retriever-usvc.yaml index a62811b..eb2b3cf 100644 --- a/studio-backend/app/templates/microsvc-composes/retriever-usvc.yaml +++ b/studio-backend/app/templates/microsvc-composes/retriever-usvc.yaml @@ -17,7 +17,7 @@ REDIS_URL: "redis://${public_host_ip}:{{redis_vector_store_port}}" INDEX_NAME: "rag-redis" TEI_EMBEDDING_ENDPOINT: "http://${public_host_ip}:{{tei_port}}" - HUGGINGFACEHUB_API_TOKEN: "{{huggingFaceToken}}" + HF_TOKEN: "{{huggingFaceToken}}" LOGFLAG: "True" RETRIEVER_COMPONENT_NAME: "OPEA_RETRIEVER_REDIS" restart: unless-stopped \ No newline at end of file diff --git a/studio-backend/app/templates/microsvc-composes/sql-agent.yaml b/studio-backend/app/templates/microsvc-composes/sql-agent.yaml index 6e84a58..d84db20 100644 --- a/studio-backend/app/templates/microsvc-composes/sql-agent.yaml +++ b/studio-backend/app/templates/microsvc-composes/sql-agent.yaml @@ -1,5 +1,5 @@ "{{endpoint}}": - image: ${REGISTRY}/agent:${TAG} + image: opea/agent:1.3 container_name: "{{endpoint}}" ports: - "{{port_key}}:9096" diff --git a/studio-backend/app/templates/microsvc-composes/supervisor-agent.yaml b/studio-backend/app/templates/microsvc-composes/supervisor-agent.yaml index fbc7124..e946405 100644 --- a/studio-backend/app/templates/microsvc-composes/supervisor-agent.yaml +++ b/studio-backend/app/templates/microsvc-composes/supervisor-agent.yaml @@ -1,5 +1,5 @@ "{{endpoint}}": - image: ${REGISTRY}/agent:${TAG} + image: opea/agent:1.3 container_name: "{{endpoint}}" depends_on: __AGENT_ENDPOINTS__ diff --git a/studio-backend/app/templates/microsvc-composes/whisper.yaml b/studio-backend/app/templates/microsvc-composes/whisper.yaml new file mode 100644 index 0000000..77b3e37 --- /dev/null +++ b/studio-backend/app/templates/microsvc-composes/whisper.yaml @@ -0,0 +1,62 @@ +"{{endpoint}}-model-downloader": + image: "huggingface/downloader:0.17.3" + container_name: "{{endpoint}}-model-downloader" + user: "0:0" + volumes: + - "./data:/data" + environment: + no_proxy: ${no_proxy} + http_proxy: ${http_proxy} + https_proxy: ${https_proxy} + HF_HOME: "/tmp/.cache/huggingface" + HUGGINGFACE_HUB_CACHE: "/data" + HF_TOKEN: "{{huggingFaceToken}}" + command: + - sh + - -ec + - | + echo "Checking for existing model openai/whisper-small ..." + if [ -d "/data/models--openai--whisper-small" ] && [ -f "/data/models--openai--whisper-small/snapshots/*/config.json" ]; then + echo "Model already exists, skipping download" + chmod -R 777 /data/models--openai--whisper-small || true + chmod -R 777 /data/.locks || true + else + echo "Model not found, downloading openai/whisper-small ..." + rm -rf /data/.locks /data/models--openai--whisper-small || true + chmod -R 777 /data || true + huggingface-cli download --cache-dir /data openai/whisper-small + echo "Download completed, setting permissions ..." + chmod -R 777 /data/models--openai--whisper-small || true + chmod -R 777 /data/.locks || true + fi + echo "Model setup completed" + restart: "no" + +"{{endpoint}}": + image: "${REGISTRY}/whisper:${TAG}" + container_name: "{{endpoint}}" + depends_on: + "{{endpoint}}-model-downloader": + condition: service_completed_successfully + ports: + - "{{port_key}}:7066" + volumes: + - "/tmp/.cache/huggingface:/tmp/.cache/huggingface" + - "./data:/data" + environment: + no_proxy: ${no_proxy} + http_proxy: ${http_proxy} + https_proxy: ${https_proxy} + EASYOCR_MODULE_PATH: "/tmp/.EasyOCR" + ASR_MODEL_PATH: "openai/whisper-small" + HF_HOME: "/tmp/.cache/huggingface" + HUGGINGFACE_HUB_CACHE: "/data" + HF_TOKEN: "{{huggingFaceToken}}" + LOGFLAG: "True" + user: "0:0" + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:7066/health"] + interval: 30s + retries: 20 + timeout: 10s diff --git a/studio-backend/app/templates/microsvc-manifests/data-prep.yaml b/studio-backend/app/templates/microsvc-manifests/data-prep.yaml index c377c3d..734821a 100644 --- a/studio-backend/app/templates/microsvc-manifests/data-prep.yaml +++ b/studio-backend/app/templates/microsvc-manifests/data-prep.yaml @@ -15,7 +15,7 @@ data: INDEX_NAME: "rag-redis" KEY_INDEX_NAME: "file-keys" SEARCH_BATCH_SIZE: "10" - HUGGINGFACEHUB_API_TOKEN: "{huggingFaceToken}" + HF_TOKEN: "{huggingFaceToken}" HF_HOME: "/tmp/.cache/huggingface" http_proxy: "" https_proxy: "" diff --git a/studio-backend/app/templates/microsvc-manifests/llm-uservice.yaml b/studio-backend/app/templates/microsvc-manifests/llm-uservice.yaml index 0c831e4..53c84b9 100644 --- a/studio-backend/app/templates/microsvc-manifests/llm-uservice.yaml +++ b/studio-backend/app/templates/microsvc-manifests/llm-uservice.yaml @@ -10,7 +10,7 @@ metadata: data: HEALTHCHECK_ENDPOINT: "{llm_endpoint}:{llm_port}" LLM_ENDPOINT: "http://{llm_endpoint}:{llm_port}" - HUGGINGFACEHUB_API_TOKEN: "{huggingFaceToken}" + HF_TOKEN: "{huggingFaceToken}" HF_HOME: "/tmp/.cache/huggingface" http_proxy: "${HTTP_PROXY}" https_proxy: "${HTTP_PROXY}" diff --git a/studio-backend/app/templates/microsvc-manifests/rag-agent.yaml b/studio-backend/app/templates/microsvc-manifests/rag-agent.yaml index 64212fd..0086669 100644 --- a/studio-backend/app/templates/microsvc-manifests/rag-agent.yaml +++ b/studio-backend/app/templates/microsvc-manifests/rag-agent.yaml @@ -82,7 +82,7 @@ spec: runAsGroup: 0 containers: - name: rag-agent-container - image: ${REGISTRY}/agent:${TAG} + image: opea/agent:1.3 command: ["/bin/sh", "-c"] args: - | diff --git a/studio-backend/app/templates/microsvc-manifests/retriever-usvc.yaml b/studio-backend/app/templates/microsvc-manifests/retriever-usvc.yaml index 0d5275a..a508bb1 100644 --- a/studio-backend/app/templates/microsvc-manifests/retriever-usvc.yaml +++ b/studio-backend/app/templates/microsvc-manifests/retriever-usvc.yaml @@ -18,7 +18,7 @@ data: https_proxy: "" no_proxy: "" HF_HOME: "/tmp/.cache/huggingface" - HUGGINGFACEHUB_API_TOKEN: "{huggingFaceToken}" + HF_TOKEN: "{huggingFaceToken}" LOGFLAG: "True" RETRIEVER_COMPONENT_NAME: "OPEA_RETRIEVER_REDIS" --- diff --git a/studio-backend/app/templates/microsvc-manifests/sql-agent.yaml b/studio-backend/app/templates/microsvc-manifests/sql-agent.yaml index 21b3cf4..c0ed98a 100644 --- a/studio-backend/app/templates/microsvc-manifests/sql-agent.yaml +++ b/studio-backend/app/templates/microsvc-manifests/sql-agent.yaml @@ -47,40 +47,9 @@ spec: labels: app: "{endpoint}" spec: - # initContainers: - # - name: agentqna-tools - # image: curlimages/curl:latest - # command: ["/bin/sh", "-c"] - # args: - # - | - # TOOLS_GIT_URL="https://github.com/opea-project/GenAIStudio/tree/main/studio-backend/app/templates/tools" - # OWNER=$(echo ${TOOLS_GIT_URL} | sed -E 's|https://github.com/([^/]+)/([^/]+)/tree/([^/]+)/.*|\1|') - # REPO=$(echo ${TOOLS_GIT_URL} | sed -E 's|https://github.com/([^/]+)/([^/]+)/tree/([^/]+)/.*|\2|') - # BRANCH=$(echo ${TOOLS_GIT_URL} | sed -E 's|https://github.com/[^/]+/[^/]+/tree/([^/]+)/.*|\1|') - # TOOLS_DIR=$(echo ${TOOLS_GIT_URL} | sed -E 's|https://github.com/[^/]+/[^/]+/tree/[^/]+/(.*?)/?$|\1|') - # if [[ "${TOOLS_DIR: -1}" == "/" ]]; then TOOLS_DIR="${TOOLS_DIR%/}"; fi - # DOWNLOAD_URL="https://codeload.github.com/${OWNER}/${REPO}/tar.gz/${BRANCH}" - # curl "${DOWNLOAD_URL}" | tar -xz --strip-components=5 -C /home/user/tools/ "${REPO}-${BRANCH}/${TOOLS_DIR}" - - # # Conditional wait for remote service based on llm_engine - # if [ "$llm_engine" = "tgi" ]; then - # until nc -z -v -w30 ${llm_endpoint_url#http://} 80; do - # echo "Waiting for remote service..."; - # sleep 5; - # done - # fi - # envFrom: - # - configMapRef: - # name: config-{endpoint} - # volumeMounts: - # - name: agent-tools - # mountPath: /home/user/tools/ - # securityContext: - # runAsUser: 0 - # runAsGroup: 0 containers: - name: sql-agent-container - image: ${REGISTRY}/agent:${TAG} + image: opea/agent:1.3 command: ["/bin/sh", "-c"] args: - | diff --git a/studio-backend/app/templates/microsvc-manifests/supervisor-agent.yaml b/studio-backend/app/templates/microsvc-manifests/supervisor-agent.yaml index c7fc067..59ea8cf 100644 --- a/studio-backend/app/templates/microsvc-manifests/supervisor-agent.yaml +++ b/studio-backend/app/templates/microsvc-manifests/supervisor-agent.yaml @@ -82,7 +82,7 @@ spec: runAsGroup: 0 containers: - name: supervisor-agent-container - image: ${REGISTRY}/agent:${TAG} + image: opea/agent:1.3 command: ["/bin/sh", "-c"] args: - | diff --git a/studio-backend/app/utils/exporter_utils.py b/studio-backend/app/utils/exporter_utils.py index 22dc371..0fd6fe7 100644 --- a/studio-backend/app/utils/exporter_utils.py +++ b/studio-backend/app/utils/exporter_utils.py @@ -84,7 +84,7 @@ } def process_opea_services(proj_info_json): - print("exporter_utils.py: process_opea_services") + # print("exporter_utils.py: process_opea_services") base_port = 9000 # Create a deep copy of the proj_info_json to avoid modifying the original data proj_info_copy = copy.deepcopy(proj_info_json) diff --git a/studio-backend/app/utils/namespace_utils.py b/studio-backend/app/utils/namespace_utils.py index 46d0053..aa792dd 100644 --- a/studio-backend/app/utils/namespace_utils.py +++ b/studio-backend/app/utils/namespace_utils.py @@ -49,8 +49,7 @@ def wait_for_all_pods(namespace, core_v1_api): for pod in pods.items: pod_name = pod.metadata.name - - print(f"Pod {pod_name} - Phase: {pod.status.phase}") + # print(f"Pod {pod_name} - Phase: {pod.status.phase}") # Check for terminal failed states first if pod.status.phase in ["Failed", "Unknown"]: @@ -67,11 +66,12 @@ def wait_for_all_pods(namespace, core_v1_api): if pod.status.container_statuses: for i, container_status in enumerate(pod.status.container_statuses): container_name = container_status.name if container_status.name else f"container-{i}" - print(f"Pod {pod_name} container {container_name} - Ready: {container_status.ready}") + # print(f"Pod {pod_name} container {container_name} - Ready: {container_status.ready}") if container_status.state.waiting: waiting_reason = container_status.state.waiting.reason - print(f"Pod {pod_name} container {container_name} is waiting: {waiting_reason}") + # print(f"Pod {pod_name} container {container_name} is waiting: {waiting_reason}") + # Only fail on waiting states that indicate permanent failures if waiting_reason in [ "ErrImagePull", "ImagePullBackOff", "InvalidImageName", @@ -99,7 +99,8 @@ def wait_for_all_pods(namespace, core_v1_api): pod_failed = True break elif waiting_reason in ["PodInitializing", "ContainerCreating"]: - print(f"Pod {pod_name} container {container_name} is initializing") + pass + # print(f"Pod {pod_name} container {container_name} is initializing") elif container_status.state.terminated: terminated_reason = container_status.state.terminated.reason exit_code = container_status.state.terminated.exit_code @@ -116,7 +117,8 @@ def wait_for_all_pods(namespace, core_v1_api): pod_failed = True break elif container_status.state.running: - print(f"Pod {pod_name} container {container_name} is running") + pass + # print(f"Pod {pod_name} container {container_name} is running") else: print(f"Pod {pod_name} has no container statuses yet") @@ -126,7 +128,8 @@ def wait_for_all_pods(namespace, core_v1_api): # Check if pod is running and ready if pod.status.phase == "Running": if pod.status.container_statuses and all(container.ready for container in pod.status.container_statuses): - print(f"Pod {pod_name} is ready!") + pass + # print(f"Pod {pod_name} is ready!") else: pending_pods.append(pod_name) else: diff --git a/studio-backend/app/utils/placeholders_utils.py b/studio-backend/app/utils/placeholders_utils.py index b4c033e..6e686fd 100644 --- a/studio-backend/app/utils/placeholders_utils.py +++ b/studio-backend/app/utils/placeholders_utils.py @@ -1,6 +1,5 @@ import yaml -import textwrap import json import re import os @@ -34,6 +33,22 @@ def construct_mapping(loader, node): construct_mapping) return yaml.load_all(stream, OrderedLoader) +# Detect UI choice +def detect_default_ui_type(proj_info_json): + default_type = "chat" + nodes = proj_info_json.get('nodes', {}) + + # Look for chat_completion nodes + for node_name, node_data in nodes.items(): + if node_data.get('name') == 'chat_completion': + # Check if ui_choice parameter is set in params + params = node_data.get('params', {}) + ui_choice = params.get('ui_choice') + if ui_choice and ui_choice in ['chat', 'summary', 'code']: + return ui_choice + + return default_type + # Recursive function to replace placeholders in manifest templates def replace_manifest_placeholders(obj, variables): # print("placeholders_utils.py: replace_manifest_placeholders", obj, variables) @@ -65,38 +80,9 @@ def replace_manifest_placeholders(obj, variables): return obj def replace_dynamic_manifest_placeholder(value_str, service_info, proj_info_json): - # print("placeholders_utils.py: replace_dynamic_manifest_placeholder") indent_str = ' ' * 8 - + if service_info['service_type'] == 'app': - ui_env_config_info_str = "" - ui_nginx_config_info_str = "" - backend_workflow_info_str = "" - for key, value in service_info['ui_config_info'].items(): - if 'agent' in key: - continue - - # For __UI_CONFIG_INFO_ENV_PLACEHOLDER__ - url_name = value['url_name'] - endpoint_path = value['endpoint_path'] - env_block = f"{indent_str}- name: VITE_{url_name}\n{indent_str} value: {endpoint_path}\n" - ui_env_config_info_str += env_block - - # For __UI_CONFIG_INFO_ENV_PLACEHOLDER__ - endpoint_path = value['endpoint_path'] - port = value['port'] - location_block = textwrap.dedent(f""" - location {endpoint_path} {{ - proxy_pass http://{key}:{port}; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - }} - """) - indented_location_block = textwrap.indent(location_block, indent_str) - ui_nginx_config_info_str += indented_location_block - # For __PORTS_INFO_JSON_PLACEHOLDER__ ports_info_str = "\n ".join([f'{key}={value}' for key, value in service_info['ports_info'].items()]) @@ -110,14 +96,26 @@ def replace_dynamic_manifest_placeholder(value_str, service_info, proj_info_json # For __TELEMETRY_ENDPOINT_ENV_PLACEHOLDER__ telemetry_endpoint_env_str = f"- name: TELEMETRY_ENDPOINT\n{indent_str} value: {os.getenv('TELEMETRY_ENDPOINT', '')}\n" + # For __DEFAULT_UI_TYPE_PLACEHOLDER__ + default_ui_type = detect_default_ui_type(proj_info_json) + + # For __APP_DATAPREP_SERVICE_URL_PLACEHOLDER__ - Check if dataprep service exists + dataprep_service_url = "NA" + nodes = proj_info_json.get('nodes', {}) + for node_name, node_data in nodes.items(): + if node_data.get('name') == 'opea_service@prepare_doc_redis_prep': + dataprep_service_url = "/v1/dataprep" + break + # Replace the unique placeholders with the actual strings - final_config = value_str.replace("__UI_CONFIG_INFO_ENV_PLACEHOLDER__", ui_env_config_info_str.strip()).replace( - "__UI_CONFIG_INFO_NGINX_PLACEHOLDER__", ui_nginx_config_info_str.strip()).replace( + final_config = value_str.replace( "__PORTS_INFO_JSON_PLACEHOLDER__", ports_info_str.strip()).replace( "__BACKEND_PROJECT_INFO_JSON_PLACEHOLDER__", backend_workflow_info_str.replace(f"\n", f"\n{indent_str}")).replace( "__APP_FRONTEND_IMAGE__", app_frontend_image).replace( "__APP_BACKEND_IMAGE__", app_backend_image).replace( - "__TELEMETRY_ENDPOINT__", telemetry_endpoint_env_str) + "__TELEMETRY_ENDPOINT__", telemetry_endpoint_env_str).replace( + "__DEFAULT_UI_TYPE_PLACEHOLDER__", default_ui_type).replace( + "__APP_DATAPREP_SERVICE_URL_PLACEHOLDER__", dataprep_service_url) else: final_config = value_str @@ -143,7 +141,7 @@ def replace_compose_placeholders(obj, variables): return value return obj -def replace_dynamic_compose_placeholder(value_str, service_info): +def replace_dynamic_compose_placeholder(value_str, service_info, proj_info_json): indent_str = ' ' * 2 if 'supervisor_agent' in service_info['service_type']: @@ -158,31 +156,34 @@ def replace_dynamic_compose_placeholder(value_str, service_info): value_str = value_str.replace("__AGENT_ENDPOINTS__", dependent_endpoints_str.strip()) if service_info['service_type'] == 'app': - backend_endpoint_list_str = "" - ui_env_config_info_str = "" + # For __BACKEND_ENDPOINTS_LIST_PLACEHOLDER__ + backend_endpoint_list_str = "" for endpoint in service_info['endpoint_list']: endpoint_block = f"{indent_str}- {endpoint}\n" backend_endpoint_list_str += endpoint_block - - # For __UI_CONFIG_INFO_ENV_PLACEHOLDER__ - for key, value in service_info['ui_config_info'].items(): - if 'agent' in key: - continue - url_name = value['url_name'] - endpoint_path = value['endpoint_path'] - endpoint_block = f"{indent_str} - VITE_{url_name}={endpoint_path}\n" - ui_env_config_info_str += endpoint_block # Get app images from environment variables app_frontend_image = os.getenv("APP_FRONTEND_IMAGE", "opea/app-frontend:latest") app_backend_image = os.getenv("APP_BACKEND_IMAGE", "opea/app-backend:latest") + # For __DEFAULT_UI_TYPE_PLACEHOLDER__ + default_ui_type = detect_default_ui_type(proj_info_json) + + # For __APP_DATAPREP_SERVICE_URL_PLACEHOLDER__ - Check if dataprep service exists + dataprep_service_url = "NA" + nodes = proj_info_json.get('nodes', {}) + for node_name, node_data in nodes.items(): + if node_data.get('name') == 'opea_service@prepare_doc_redis_prep': + dataprep_service_url = "/v1/dataprep" + break + # Replace the unique placeholders with the actual strings final_config = value_str.replace("__BACKEND_ENDPOINTS_LIST_PLACEHOLDER__", backend_endpoint_list_str.strip()).replace( - "__UI_CONFIG_INFO_ENV_PLACEHOLDER__", ui_env_config_info_str.strip()).replace( "__APP_FRONTEND_IMAGE__", app_frontend_image).replace( - "__APP_BACKEND_IMAGE__", app_backend_image) + "__APP_BACKEND_IMAGE__", app_backend_image).replace( + "__DEFAULT_UI_TYPE_PLACEHOLDER__", default_ui_type).replace( + "__APP_DATAPREP_SERVICE_URL_PLACEHOLDER__", dataprep_service_url) else: final_config = value_str diff --git a/studio-backend/tests/exporter-groundtruth/gt_app-compose.yaml b/studio-backend/tests/exporter-groundtruth/gt_app-compose.yaml index 9437ad6..9c856d0 100644 --- a/studio-backend/tests/exporter-groundtruth/gt_app-compose.yaml +++ b/studio-backend/tests/exporter-groundtruth/gt_app-compose.yaml @@ -90,7 +90,7 @@ services: http_proxy: ${http_proxy} https_proxy: ${https_proxy} TGI_LLM_ENDPOINT: http://${public_host_ip}:3081 - HUGGINGFACEHUB_API_TOKEN: NA + HF_TOKEN: NA HF_HUB_DISABLE_PROGRESS_BARS: 1 HF_HUB_ENABLE_HF_TRANSFER: 0 restart: unless-stopped @@ -110,7 +110,7 @@ services: REDIS_HOST: ${public_host_ip} INDEX_NAME: rag-redis TEI_ENDPOINT: http://${public_host_ip}:2081 - HUGGINGFACEHUB_API_TOKEN: NA + HF_TOKEN: NA reranking-tei-0: image: opea/reranking:latest container_name: reranking-tei-0 @@ -124,7 +124,7 @@ services: http_proxy: ${http_proxy} https_proxy: ${https_proxy} TEI_RERANKING_ENDPOINT: http://${public_host_ip}:2082 - HUGGINGFACEHUB_API_TOKEN: NA + HF_TOKEN: NA HF_HUB_DISABLE_PROGRESS_BARS: 1 HF_HUB_ENABLE_HF_TRANSFER: 0 restart: unless-stopped @@ -144,7 +144,7 @@ services: REDIS_URL: redis://${public_host_ip}:6379 INDEX_NAME: rag-redis TEI_EMBEDDING_ENDPOINT: http://${public_host_ip}:2083 - HUGGINGFACEHUB_API_TOKEN: NA + HF_TOKEN: NA restart: unless-stopped app-backend: image: opea/app-backend:latest diff --git a/studio-backend/tests/exporter-groundtruth/gt_app-manifest-with-nginx.yaml b/studio-backend/tests/exporter-groundtruth/gt_app-manifest-with-nginx.yaml index b6a1550..342122e 100644 --- a/studio-backend/tests/exporter-groundtruth/gt_app-manifest-with-nginx.yaml +++ b/studio-backend/tests/exporter-groundtruth/gt_app-manifest-with-nginx.yaml @@ -634,7 +634,7 @@ data: INDEX_NAME: rag-redis KEY_INDEX_NAME: file-keys SEARCH_BATCH_SIZE: '10' - HUGGINGFACEHUB_API_TOKEN: NA + HF_TOKEN: NA HF_HOME: /tmp/.cache/huggingface http_proxy: '' https_proxy: '' @@ -973,7 +973,7 @@ data: https_proxy: '' no_proxy: '' HF_HOME: /tmp/.cache/huggingface - HUGGINGFACEHUB_API_TOKEN: NA + HF_TOKEN: NA LOGFLAG: '' RETRIEVER_COMPONENT_NAME: OPEA_RETRIEVER_REDIS --- diff --git a/studio-backend/tests/exporter-groundtruth/gt_app-manifest.yaml b/studio-backend/tests/exporter-groundtruth/gt_app-manifest.yaml index 3a0db4b..317a09e 100644 --- a/studio-backend/tests/exporter-groundtruth/gt_app-manifest.yaml +++ b/studio-backend/tests/exporter-groundtruth/gt_app-manifest.yaml @@ -634,7 +634,7 @@ data: INDEX_NAME: rag-redis KEY_INDEX_NAME: file-keys SEARCH_BATCH_SIZE: '10' - HUGGINGFACEHUB_API_TOKEN: NA + HF_TOKEN: NA HF_HOME: /tmp/.cache/huggingface http_proxy: '' https_proxy: '' @@ -973,7 +973,7 @@ data: https_proxy: '' no_proxy: '' HF_HOME: /tmp/.cache/huggingface - HUGGINGFACEHUB_API_TOKEN: NA + HF_TOKEN: NA LOGFLAG: '' RETRIEVER_COMPONENT_NAME: OPEA_RETRIEVER_REDIS --- diff --git a/studio-backend/tests/test_click-deploy.py b/studio-backend/tests/test_click-deploy.py index bdb273f..eb4aa20 100644 --- a/studio-backend/tests/test_click-deploy.py +++ b/studio-backend/tests/test_click-deploy.py @@ -8,7 +8,7 @@ import json from app.models.pipeline_model import PipelineFlow -from app.services.clickdeploy_service import deploy_pipeline +from app.services.clickdeploy_service import upload_pipeline_zip @pytest.fixture def setup_and_teardown(): @@ -36,9 +36,9 @@ def test_click_deploy(setup_and_teardown): assert False, "Payload validation failed" try: - response = deploy_pipeline(remote_host, remote_user, payload) - print("Response from deploy_pipeline:\n" + json.dumps(response, indent=2, ensure_ascii=False)) + response = upload_pipeline_zip(remote_host, remote_user, payload) + print("Response from upload_pipeline_zip:\n" + json.dumps(response, indent=2, ensure_ascii=False)) except Exception as e: - assert False, f"deploy_pipeline failed with exception: {e}" + assert False, f"upload_pipeline_zip failed with exception: {e}" - assert True, "deploy_pipeline executed successfully" \ No newline at end of file + assert True, "upload_pipeline_zip executed successfully" \ No newline at end of file diff --git a/studio-frontend/packages/server/src/nodes/chat_completion.js b/studio-frontend/packages/server/src/nodes/chat_completion.js index e95ae5d..e98b6ef 100644 --- a/studio-frontend/packages/server/src/nodes/chat_completion.js +++ b/studio-frontend/packages/server/src/nodes/chat_completion.js @@ -26,11 +26,15 @@ class OPEAChatCompletion { options: [ { name: 'chat', - label: 'Chat' + label: 'Chat Q&A' }, { - name: 'columns', - label: 'Two Columns (For Document Sumarization or Translation)' + name: 'summary', + label: 'Summarize Content' + }, + { + name: 'code', + label: 'Generate Code' } ] } diff --git a/studio-frontend/packages/server/src/services/chatflows/index.ts b/studio-frontend/packages/server/src/services/chatflows/index.ts index 55bbb83..2fcfac5 100644 --- a/studio-frontend/packages/server/src/services/chatflows/index.ts +++ b/studio-frontend/packages/server/src/services/chatflows/index.ts @@ -504,7 +504,7 @@ const oneClickDeploymentService = async (chatflowId: string, deploymentConfig: R try { const chatflow = await generatePipelineJson(chatflowId) const studioServerUrl = STUDIO_SERVER_URL - const endpoint = 'studio-backend/click-deploy' + const endpoint = 'studio-backend/upload-pipeline-files' console.log('chatflow', JSON.stringify(chatflow)) console.log('studioServerUrl', studioServerUrl) console.log('deploymentConfig', deploymentConfig) diff --git a/studio-frontend/packages/ui/src/ui-component/dialog/OneClickDeploymentDialog.jsx b/studio-frontend/packages/ui/src/ui-component/dialog/OneClickDeploymentDialog.jsx index 92b60f8..924160d 100644 --- a/studio-frontend/packages/ui/src/ui-component/dialog/OneClickDeploymentDialog.jsx +++ b/studio-frontend/packages/ui/src/ui-component/dialog/OneClickDeploymentDialog.jsx @@ -1,6 +1,6 @@ import { createPortal } from 'react-dom'; import { useDispatch } from 'react-redux'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import PropTypes from 'prop-types'; import { Box, @@ -26,17 +26,87 @@ import { } from '@/store/actions'; import chatflowsApi from '@/api/chatflows'; -const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, deployStatus, setDeployStatus, deploymentConfig, setDeploymentConfig }) => { +const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, deployStatus, setDeployStatus, deploymentConfig, setDeploymentConfig, deployWebSocket, setDeployWebSocket }) => { const portalElement = document.getElementById('portal'); const dispatch = useDispatch(); const [pubkey, setPubkey] = useState(''); const [copied, setCopied] = useState(false); const [deploying, setDeploying] = useState(false); - const [ws, setWs] = useState(null); + const [deploymentCompleted, setDeploymentCompleted] = useState(false); + // Remove local ws state - use the persistent one from parent + const wsRef = useRef(deployWebSocket); + const deploymentCompletedRef = useRef(deploymentCompleted); + + // Sync the ref when the parent WebSocket changes + useEffect(() => { + wsRef.current = deployWebSocket; + deploymentCompletedRef.current = deploymentCompleted; + if (deployWebSocket && deployWebSocket.readyState === WebSocket.OPEN) { + setDeploying(true); + // Set up event handlers for the existing WebSocket + deployWebSocket.onmessage = (event) => { + let data; + try { data = JSON.parse(event.data); } catch { return; } + console.log('WebSocket message:', data); + if (data.status === 'Done') { + setDeployStatus(['Success', ...(data.success || '').split(',').map(line => line.trim())]); + setDeploying(false); + setDeploymentCompleted(true); + deploymentCompletedRef.current = true; + // Clean up WebSocket on completion + if (wsRef.current) { + wsRef.current.close(); + wsRef.current = null; + setDeployWebSocket(null); + } + } else if (data.status === 'Error') { + let lines = []; + if (Array.isArray(data.error)) { + lines = data.error; + } else if (typeof data.error === 'string') { + lines = data.error.split(',').map(line => line.trim()); + } else { + lines = ['Unknown error']; + } + setDeployStatus(['Error', ...lines]); + setDeploying(false); + setDeploymentCompleted(true); + deploymentCompletedRef.current = true; + // Clean up WebSocket on error + if (wsRef.current) { + wsRef.current.close(); + wsRef.current = null; + setDeployWebSocket(null); + } + } else if (data.status === 'In Progress') { + setDeployStatus(['Info', data.nohup_out]); + } else if (data.status === 'Preparing') { + setDeployStatus(['Info', data.message]); + } + }; + deployWebSocket.onerror = (error) => { + console.error('WebSocket error:', error); + setDeployStatus(['Error', 'WebSocket connection error']); + setDeploying(false); + }; + deployWebSocket.onclose = (event) => { + console.log('WebSocket closed:', event.code, event.reason); + wsRef.current = null; + setDeployWebSocket(null); + // Only show error if deployment was still in progress and not completed successfully + if (deploying && !deploymentCompletedRef.current) { + setDeployStatus(['Error', 'Connection lost during deployment']); + setDeploying(false); + } + }; + } + }, [deployWebSocket, setDeployWebSocket, setDeployStatus, deploying, deploymentCompleted]); useEffect(() => { if (show) { dispatch({ type: SHOW_CANVAS_DIALOG }); + setDeploymentCompleted(false); // Reset completion flag when dialog opens + deploymentCompletedRef.current = false; // Reset ref too chatflowsApi.getPublicKey().then(response => { if (response.error) { dispatch(enqueueSnackbarAction({ @@ -47,12 +117,31 @@ const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, depl setPubkey(response.data.pubkey || ''); } }); + + // When modal reopens, check if there's a WebSocket still running + if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { + setDeploying(true); // Resume showing deploying state + // WebSocket event handlers are already set up in the other useEffect + } } else { dispatch({ type: HIDE_CANVAS_DIALOG }); + // Don't clean up WebSocket when modal is just hidden + // Let deployment continue in background } - return () => dispatch({ type: HIDE_CANVAS_DIALOG }); + return () => { + dispatch({ type: HIDE_CANVAS_DIALOG }); + // Only clean up on component unmount (when parent component unmounts) + // Parent component will handle WebSocket cleanup + }; }, [show, dispatch]); + const handleCancel = () => { + // Don't clean up WebSocket - let it continue monitoring in background + // Just close the modal while keeping the deployment running + setDeploying(false); // Reset local deploying state for UI + onCancel(); // Call the parent's onCancel to close modal + }; + const handleCopy = () => { navigator.clipboard.writeText(pubkey); setCopied(true); @@ -61,6 +150,8 @@ const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, depl const handleOneClickDeploy = async () => { setDeploying(true); + setDeploymentCompleted(false); // Reset completion flag + deploymentCompletedRef.current = false; // Reset ref too setDeployStatus(['Info', 'Connecting to machine...']); try { const result = await onConfirm(dialogProps.id, deploymentConfig); @@ -70,9 +161,13 @@ const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, depl return; } const compose_dir = result?.compose_dir; - const wsUrl = `${window.location.origin.replace(/^http/, 'ws')}/studio-backend/ws/clickdeploy-status`; + const wsUrl = `${window.location.origin.replace(/^http/, 'ws')}/studio-backend/ws/deploy-and-monitor`; const wsInstance = new window.WebSocket(wsUrl); - setWs(wsInstance); + + // Update parent with the WebSocket reference for persistence + wsRef.current = wsInstance; + setDeployWebSocket(wsInstance); + wsInstance.onopen = () => { wsInstance.send(JSON.stringify({ hostname: deploymentConfig.hostname, username: deploymentConfig.username, compose_dir: compose_dir })); }; @@ -83,6 +178,14 @@ const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, depl if (data.status === 'Done') { setDeployStatus(['Success', ...(data.success || '').split(',').map(line => line.trim())]); setDeploying(false); + setDeploymentCompleted(true); + deploymentCompletedRef.current = true; + // Clean up WebSocket on completion + if (wsRef.current) { + wsRef.current.close(); + wsRef.current = null; + setDeployWebSocket(null); + } } else if (data.status === 'Error') { let lines = []; if (Array.isArray(data.error)) { @@ -94,15 +197,35 @@ const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, depl } setDeployStatus(['Error', ...lines]); setDeploying(false); + setDeploymentCompleted(true); + deploymentCompletedRef.current = true; + // Clean up WebSocket on error + if (wsRef.current) { + wsRef.current.close(); + wsRef.current = null; + setDeployWebSocket(null); + } } else if (data.status === 'In Progress') { setDeployStatus(['Info', data.nohup_out]); + } else if (data.status === 'Preparing') { + setDeployStatus(['Info', data.message]); } }; - wsInstance.onerror = () => { - setDeployStatus(['Error', 'WebSocket error']); + wsInstance.onerror = (error) => { + console.error('WebSocket error:', error); + setDeployStatus(['Error', 'WebSocket connection error']); setDeploying(false); }; - wsInstance.onclose = () => setWs(null); + wsInstance.onclose = (event) => { + console.log('WebSocket closed:', event.code, event.reason); + wsRef.current = null; + setDeployWebSocket(null); + // Only show error if deployment was still in progress and not completed successfully + if (deploying && !deploymentCompletedRef.current) { + setDeployStatus(['Error', 'Connection lost during deployment']); + setDeploying(false); + } + }; } catch (err) { setDeployStatus(['Error', 'Deployment failed']); setDeploying(false); @@ -154,7 +277,7 @@ const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, depl }; const component = show ? ( - + {dialogProps.title || 'One Click Deployment'} @@ -181,7 +304,7 @@ const OneClickDeploymentDialog = ({ show, dialogProps, onCancel, onConfirm, depl {renderStatus()} - + {deploying ? 'Deploying...' : (dialogProps.confirmButtonName || 'Deploy')} @@ -200,7 +323,9 @@ OneClickDeploymentDialog.propTypes = { deployStatus: PropTypes.array, setDeployStatus: PropTypes.func, deploymentConfig: PropTypes.object, - setDeploymentConfig: PropTypes.func + setDeploymentConfig: PropTypes.func, + deployWebSocket: PropTypes.object, + setDeployWebSocket: PropTypes.func }; export default OneClickDeploymentDialog; diff --git a/studio-frontend/packages/ui/src/ui-component/table/FlowListTable.jsx b/studio-frontend/packages/ui/src/ui-component/table/FlowListTable.jsx index 3c696c9..a9b6603 100644 --- a/studio-frontend/packages/ui/src/ui-component/table/FlowListTable.jsx +++ b/studio-frontend/packages/ui/src/ui-component/table/FlowListTable.jsx @@ -268,6 +268,7 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF const [deployStatusById, setDeployStatusById] = useState({}); const [deployConfigById, setDeployConfigById] = useState({}); + const [deployWebSocketsById, setDeployWebSocketsById] = useState({}); // Store WebSocket references const setDeployStatusForId = (id, status) => { setDeployStatusById((prev) => ({ ...prev, [id]: status })); @@ -277,6 +278,22 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF setDeployConfigById((prev) => ({ ...prev, [id]: config })); }; + const setDeployWebSocketForId = (id, ws) => { + setDeployWebSocketsById((prev) => ({ ...prev, [id]: ws })); + }; + + // Cleanup deployment WebSockets when component unmounts + useEffect(() => { + return () => { + // Close all deployment WebSockets when component unmounts + Object.values(deployWebSocketsById).forEach(ws => { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.close(); + } + }); + }; + }, [deployWebSocketsById]); + useEffect(() => { setSortedData(handleSortData()); }, [data, order, orderBy]); // Run effect when any dependency changes @@ -655,17 +672,31 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF justifyContent='center' alignItems='center' > - - + {deployWebSocketsById[row.id] && deployWebSocketsById[row.id].readyState === WebSocket.OPEN ? ( + - - + + ) : ( + + + + + + )} @@ -711,6 +742,8 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF setDeployStatus={(status) => setDeployStatusForId(oneClickDeploymentDialogProps.id, status)} deploymentConfig={deployConfigById[oneClickDeploymentDialogProps.id] || { hostname: '', username: '' }} setDeploymentConfig={(config) => setDeployConfigForId(oneClickDeploymentDialogProps.id, config)} + deployWebSocket={deployWebSocketsById[oneClickDeploymentDialogProps.id]} + setDeployWebSocket={(ws) => setDeployWebSocketForId(oneClickDeploymentDialogProps.id, ws)} /> ) diff --git a/tests/playwright/studio-e2e/003_test_sandbox_docsum.spec.ts b/tests/playwright/studio-e2e/003_test_sandbox_docsum.spec.ts new file mode 100644 index 0000000..5064d9c --- /dev/null +++ b/tests/playwright/studio-e2e/003_test_sandbox_docsum.spec.ts @@ -0,0 +1,114 @@ +import { test, expect } from '@playwright/test'; +import { waitForStatusText } from '../utils'; +import path from 'path'; + +const sampleWorkflow = path.resolve(__dirname, '../../../sample-workflows/sample_workflow_docsum.json');//this workflow consists of Hugging Face token! cannot deploy as off now. +const uploadtxt1 = path.resolve(__dirname, '../../test-files/Little Red Riding Hood.txt'); + +const keywords = ["Little Red Riding Hood", "sick grandmother", "wolf", "woodcutter", "hunter", "closet", "food"]; // more keywords needed + +function containsAnyKeyword(response: string, keywords: string[]): boolean { + return keywords.some((keyword) => response.includes(keyword.replace(/\s+/g, ''))); +} + +async function setupResponseListener(page, apiResponse) { + page.on('response', async (response) => { + if (response.url().includes('/v1/app-backend') && response.request().method() === 'POST') { + const contentType = response.headers()['content-type']; + if (contentType.includes('text/event-stream')) { + const responseBody = await response.text(); + // Parse SSE stream + const events = responseBody.split('\n\n'); + for (const event of events) { + const lines = event.split('\n'); + for (const line of lines) { + if (line.startsWith('data: ')) { + const cleanedData = line.slice(6, -1).trim(); // Remove 'data: ' prefix + apiResponse.value += cleanedData + " "; + } + } + } + } else { + console.error('Response is not SSE'); + } + } + }); +} + +test('003_test_sandbox_docsum', async ({ browser, baseURL }) => { + test.setTimeout(1200000); + let apiResponse = { value: '' }; + const context = await browser.newContext({ + ignoreHTTPSErrors: true, + recordVideo: { + dir: './videos/', + size: { width: 1280, height: 720 } + } + }); + const page = await context.newPage(); + const IDC_URL = baseURL || "" + await page.goto(IDC_URL); + await page.getByLabel('Username or email').fill('test_automation@gmail.com'); + await page.getByLabel('Password', { exact: true }).click(); + await page.getByLabel('Password', { exact: true }).fill('test'); + await page.getByRole('button', { name: 'Sign In' }).click(); + await page.getByRole('button', { name: 'Create New Workflow' }).click(); + await page.getByRole('button', { name: 'Settings' }).click(); + let fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByRole('button', { name: 'Import Workflow' }).click(); + let fileChooser = await fileChooserPromise; + await fileChooser.setFiles(sampleWorkflow); + await page.getByRole('button', { name: 'Save Workflow' }).click(); + await page.getByPlaceholder('My New Chatflow').click(); + await page.getByPlaceholder('My New Chatflow').fill('test_003'); + await page.getByRole('button', { name: 'Save' }).click(); + await page.goto(IDC_URL); + await expect(page.locator('td.MuiTableCell-root div.MuiStack-root p.MuiTypography-root').first()).toHaveText('Not Running', { timeout: 60000 }); + await page.getByLabel('a dense table').locator('button').first().click(); + await waitForStatusText(page, 'td.MuiTableCell-root div.MuiStack-root p.MuiTypography-root', 'Getting Ready', 20, 60000); + await page.waitForTimeout(10000); + +//Open APP-UI + const page2Promise = page.waitForEvent('popup', { context }); + await page.getByLabel('Click to open Application UI').getByRole('button').nth(0).click(); + const page2 = await page2Promise; + await page2.bringToFront(); + await setupResponseListener(page2, apiResponse); + +//Entering DocSum + //await page2.waitForTimeout(2000); + await page2.getByRole('button', { name: 'Summarize Content' }).click(); + +//Upload the Story File + fileChooserPromise = page2.waitForEvent('filechooser'); + await page2.getByRole('button', { name: 'Browse Files' }).click(); + fileChooser = await fileChooserPromise; + await fileChooser.setFiles(uploadtxt1); + await page2.getByRole('button', { name: 'Summarize', exact: true }).click(); + + +//Summarization Generation and Response Validation + await page2.waitForTimeout(60000); + let responseContainsKeyword = apiResponse && containsAnyKeyword(apiResponse.value, keywords); + console.log ('response:', apiResponse.value); + await page2.screenshot({ path: 'screenshot_docsum_attempt1.png' }); + + if (!responseContainsKeyword) { + throw Error('Incorrect summarization!') + } + apiResponse.value = ""; + + +//Stop & Delete Sandbox + console.log ('Stop & Delete Sandbox-------------------'); + await page.bringToFront(); + await page.locator('button:has([data-testid="StopCircleOutlinedIcon"])').first().click(); + await waitForStatusText(page, 'td.MuiTableCell-root div.MuiStack-root p.MuiTypography-root', 'Not Running', 5, 60000); + await page.locator('#demo-customized-button').first().click(); + await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'Delete' }).click(); + await page2.close(); + await page.close(); + await context.close(); + console.log('Job done'); + }); \ No newline at end of file diff --git a/tests/playwright/studio-e2e/004_test_sandbox_codegen.spec.ts b/tests/playwright/studio-e2e/004_test_sandbox_codegen.spec.ts new file mode 100644 index 0000000..5e213ac --- /dev/null +++ b/tests/playwright/studio-e2e/004_test_sandbox_codegen.spec.ts @@ -0,0 +1,104 @@ +import { test, expect } from '@playwright/test'; +import { waitForStatusText } from '../utils'; +import path from 'path'; + +const sampleWorkflow = path.resolve(__dirname, '../../../sample-workflows/sample_workflow_codegen.json'); +const question = "write me a python function for fibonacci loop"; +const keywords = ["Python", "Fibonacci", "iterative", "if", "<=", "=", "(", ")", "[", "]"]; // more keywords needed + +function containsAnyKeyword(response: string, keywords: string[]): boolean { + return keywords.some((keyword) => response.includes(keyword.replace(/\s+/g, ''))); +} + +async function setupResponseListener(page, apiResponse) { + page.on('response', async (response) => { + if (response.url().includes('/v1/app-backend') && response.request().method() === 'POST') { + const contentType = response.headers()['content-type']; + if (contentType.includes('text/event-stream')) { + const responseBody = await response.text(); + // Parse SSE stream + const events = responseBody.split('\n\n'); + for (const event of events) { + const lines = event.split('\n'); + for (const line of lines) { + if (line.startsWith('data: ')) { + const cleanedData = line.slice(6, -1).trim(); // Remove 'data: ' prefix + apiResponse.value += cleanedData + " "; + } + } + } + } else { + console.error('Response is not SSE'); + } + } + }); +} + +test('004_test_sandbox_codegen', async ({ browser, baseURL }) => { + test.setTimeout(1200000); + let apiResponse = { value: '' }; + const context = await browser.newContext({ + ignoreHTTPSErrors: true, + recordVideo: { + dir: './videos/', + size: { width: 1280, height: 720 } + } + }); + const page = await context.newPage(); + const IDC_URL = baseURL || "" + await page.goto(IDC_URL); + await page.getByLabel('Username or email').fill('test_automation@gmail.com'); + await page.getByLabel('Password', { exact: true }).click(); + await page.getByLabel('Password', { exact: true }).fill('test'); + await page.getByRole('button', { name: 'Sign In' }).click(); + await page.getByRole('button', { name: 'Create New Workflow' }).click(); + await page.getByRole('button', { name: 'Settings' }).click(); + let fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByRole('button', { name: 'Import Workflow' }).click(); + let fileChooser = await fileChooserPromise; + await fileChooser.setFiles(sampleWorkflow); + await page.getByRole('button', { name: 'Save Workflow' }).click(); + await page.getByPlaceholder('My New Chatflow').click(); + await page.getByPlaceholder('My New Chatflow').fill('test_004'); + await page.getByRole('button', { name: 'Save' }).click(); + await page.goto(IDC_URL); + await expect(page.locator('td.MuiTableCell-root div.MuiStack-root p.MuiTypography-root').first()).toHaveText('Not Running', { timeout: 60000 }); + await page.getByLabel('a dense table').locator('button').first().click(); + await waitForStatusText(page, 'td.MuiTableCell-root div.MuiStack-root p.MuiTypography-root', 'Getting Ready', 20, 60000); + await page.waitForTimeout(10000); + +//Open APP-UI + const page2Promise = page.waitForEvent('popup', { context }); + await page.getByLabel('Click to open Application UI').getByRole('button').nth(0).click(); + const page2 = await page2Promise; + await page2.bringToFront(); + await setupResponseListener(page2, apiResponse); + +//Code Generation + await page2.waitForTimeout(2000); + await page2.getByRole('button', { name: 'Generate Code' }).click(); + await page2.getByRole('textbox', { name: 'Enter your message' }).fill(question); + await page2.getByRole('button').filter({ hasText: /^$/ }).nth(2).click(); //end here + await page2.waitForTimeout(60000); + let responseContainsKeyword = apiResponse && containsAnyKeyword(apiResponse.value, keywords); + console.log ('response:', apiResponse.value); + await page2.screenshot({ path: 'screenshot_codegen_attempt1.png' }); + + if (!responseContainsKeyword) { + throw Error('Code may not be generated!') + } + apiResponse.value = ""; + +// Stop & Delete Sandbox + console.log ('Stop & Delete Sandbox-------------------'); + await page.bringToFront(); + await page.locator('button:has([data-testid="StopCircleOutlinedIcon"])').first().click(); + await waitForStatusText(page, 'td.MuiTableCell-root div.MuiStack-root p.MuiTypography-root', 'Not Running', 5, 60000); + await page.locator('#demo-customized-button').first().click(); + await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'Delete' }).click(); + await page2.close(); + await page.close(); + await context.close(); + console.log('Job done'); + }); \ No newline at end of file diff --git a/tests/test-files/Little Red Riding Hood.txt b/tests/test-files/Little Red Riding Hood.txt new file mode 100644 index 0000000..ba1984e --- /dev/null +++ b/tests/test-files/Little Red Riding Hood.txt @@ -0,0 +1,10 @@ +The story centers around a girl named Little Red Riding Hood, named after her red hooded cape that she wears. The girl walks through the woods to deliver food to her sickly grandmother (wine and cake depending on the translation). + +A stalking wolf wants to eat the girl and the food in the basket. After he inquires as to where she is going, he suggests that she pick some flowers as a present for her grandmother. While she goes in search of flowers, he goes to the grandmothers house and gains entry by pretending to be Riding Hood. He swallows the grandmother whole, climbs into her bed, and waits for the girl, disguised as the grandmother. + +Gustave Doré engraving of the scene "She was astonished to see how her grandmother looked." +When Riding Hood arrives, she notices the strange appearance of her "grandmother". After some back and forth, Riding Hood comments on the wolfs teeth, at which point the wolf leaps out of bed and eats her as well. In Charles Perraults version of the story, the first to be published, the wolf falls asleep afterwards, whereupon the story ends. + +In later versions, the story continues. A woodcutter in the French version, or a hunter in the Brothers Grimm and traditional German versions, comes to the rescue with an axe, and cuts open the sleeping wolf. Little Red Riding Hood and her grandmother emerge shaken, but unharmed. Then they fill the wolfs body with heavy stones. The wolf awakens and attempts to flee, but the stones cause him to collapse and die. In the Grimms version, the wolf leaves the house and tries to drink out of a well, but the stones in his stomach cause him to fall in and drown (similarly to the story of "The Wolf and the Seven Little Kids"). + +Sanitized versions of the story have the grandmother locked in the closet rather than being eaten (and also having the wolf eat the food Little Red Riding Hood bought rather than her) and some have Little Red Riding Hood saved by the lumberjack as the wolf advances on her rather than after she is eaten, where the woodcutter kills or simply chases away the wolf with his axe. \ No newline at end of file