diff --git a/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx b/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx index fba6d662..1101f643 100644 --- a/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx +++ b/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx @@ -933,7 +933,7 @@ function StopGeneratingButton({ if (!inboxId) return; const decodedInboxId = decodeURIComponent(inboxId); const jobId = extractJobIdFromInbox(decodedInboxId); - stopGenerating({ + await stopGenerating({ nodeAddress: auth?.node_address ?? '', token: auth?.api_v2_key ?? '', jobId: jobId, diff --git a/apps/shinkai-desktop/src/components/playground/baml-editor.tsx b/apps/shinkai-desktop/src/components/playground/baml-editor.tsx new file mode 100644 index 00000000..deb60842 --- /dev/null +++ b/apps/shinkai-desktop/src/components/playground/baml-editor.tsx @@ -0,0 +1,678 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { DialogClose } from '@radix-ui/react-dialog'; +import { StopIcon } from '@radix-ui/react-icons'; +import { useTranslation } from '@shinkai_network/shinkai-i18n'; +import { getTool } from '@shinkai_network/shinkai-message-ts/api/tools/index'; +import { ShinkaiTool } from '@shinkai_network/shinkai-message-ts/api/tools/types'; +import { + buildInboxIdFromJobId, + extractJobIdFromInbox, +} from '@shinkai_network/shinkai-message-ts/utils/inbox_name_handler'; +import { DEFAULT_CHAT_CONFIG } from '@shinkai_network/shinkai-node-state/v2/constants'; +import { useCreateJob } from '@shinkai_network/shinkai-node-state/v2/mutations/createJob/useCreateJob'; +import { useCreateWorkflow } from '@shinkai_network/shinkai-node-state/v2/mutations/createWorkflow/useCreateWorkflow'; +import { useStopGeneratingLLM } from '@shinkai_network/shinkai-node-state/v2/mutations/stopGeneratingLLM/useStopGeneratingLLM'; +import { useGetWorkflowList } from '@shinkai_network/shinkai-node-state/v2/queries/getWorkflowList/useGetWorkflowList'; +import { + Button, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + MarkdownPreview, + Popover, + PopoverContent, + PopoverTrigger, + Textarea, + TextField, + Tooltip, + TooltipContent, + TooltipPortal, + TooltipTrigger, +} from '@shinkai_network/shinkai-ui'; +import { formatText } from '@shinkai_network/shinkai-ui/helpers'; +import { + CirclePlayIcon, + GalleryHorizontal, + GalleryVertical, +} from 'lucide-react'; +import { InfoCircleIcon } from 'primereact/icons/infocircle'; +import { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useNavigate, useParams } from 'react-router-dom'; +import { toast } from 'sonner'; +import { z } from 'zod'; + +import { + CreateWorkflowFormSchema, + createWorkflowFormSchema, + useStopGenerationPlayground, +} from '../../pages/workflow-playground'; +import { useAuth } from '../../store/auth'; +import { useSettings } from '../../store/settings'; +import { isWorkflowShinkaiTool } from '../tools/tool-details'; +import { BAML_EXAMPLES } from './constants'; + +const escapeContent = (content: string) => { + return content.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); +}; + +const bamlFormSchema = z.object({ + bamlInput: z.string().min(1), + dslFile: z.string().min(1), + functionName: z.string().min(1), + paramName: z.string().min(1), + bamlScriptName: z.string().min(1), +}); + +type BamlFormSchema = z.infer; + +function BamlEditor() { + const { t } = useTranslation(); + const auth = useAuth((state) => state.auth); + const navigate = useNavigate(); + const defaulAgentId = useSettings((state) => state.defaultAgentId); + const { inboxId: encodedInboxId = '' } = useParams(); + const inboxId = decodeURIComponent(encodedInboxId); + + const [workflowDialogOpen, setWorkflowDialogOpen] = useState(false); + const { mutateAsync: stopGenerating } = useStopGeneratingLLM(); + + const createWorkflowForm = useForm({ + resolver: zodResolver(createWorkflowFormSchema), + }); + + const { mutateAsync: createJob } = useCreateJob({ + onSuccess: (data) => { + navigate( + `/workflow-playground/${encodeURIComponent(buildInboxIdFromJobId(data.jobId))}`, + ); + }, + }); + + const { mutateAsync: createWorkflow, isPending: isCreateWorkflowPending } = + useCreateWorkflow({ + onSuccess: () => { + toast.success('BAML saved successfully'); + setWorkflowDialogOpen(false); + }, + onError: (error) => { + toast.error('Failed to save BAML', { + description: error?.response?.data?.message ?? error.message, + }); + }, + }); + + const { isLoadingMessage } = useStopGenerationPlayground(); + + const { data: workflowList } = useGetWorkflowList( + { + nodeAddress: auth?.node_address ?? '', + token: auth?.api_v2_key ?? '', + }, + { + select: (data) => + data.filter((workflow) => workflow.name.includes('baml_')), + }, + ); + + const [openWorkflowList, setOpenWorkflowList] = useState(false); + + const [isTwoColumnLayout, setIsTwoColumnLayout] = useState(true); + + const handleUseTemplate = async (toolRouterKey: string) => { + if (!auth) return; + const workflowInfo = await getTool( + auth?.node_address, + auth?.api_v2_key, + toolRouterKey, + ); + const tool = workflowInfo.content?.[0] as ShinkaiTool; + + if (isWorkflowShinkaiTool(tool)) { + console.log(tool.workflow.steps, 'tool.workflow.steps'); + // @ts-expect-error types + const workflowSteps = tool.workflow.steps?.[0]?.body?.[0]?.value; + const dslFile = workflowSteps?.find( + // @ts-expect-error types + (step) => step.value.register === '$DSL', + )?.value?.value; + const bamlInput = workflowSteps?.find( + // @ts-expect-error types + (step) => step.value.register === '$INPUT', + )?.value?.value; + const paramName = workflowSteps?.find( + // @ts-expect-error types + (step) => step.value.register === '$PARAM', + )?.value?.value; + const functionName = workflowSteps?.find( + // @ts-expect-error types + (step) => step.value.register === '$FUNCTION', + )?.value?.value; + + bamlForm.setValue('dslFile', dslFile); + bamlForm.setValue('bamlInput', bamlInput); + bamlForm.setValue('paramName', paramName); + bamlForm.setValue('functionName', functionName); + bamlForm.setValue('bamlScriptName', tool.workflow.name); + setOpenWorkflowList(false); + } + }; + + const bamlForm = useForm({ + resolver: zodResolver(bamlFormSchema), + defaultValues: { + bamlInput: '', + dslFile: '', + functionName: '', + paramName: '', + bamlScriptName: '', + }, + }); + + const handleBamlScriptChange = (script: string) => { + bamlForm.setValue('dslFile', BAML_EXAMPLES[script].dslFile); + bamlForm.setValue('functionName', BAML_EXAMPLES[script].functionName); + bamlForm.setValue('paramName', BAML_EXAMPLES[script].paramName); + bamlForm.setValue('bamlScriptName', BAML_EXAMPLES[script].name); + bamlForm.setValue('bamlInput', BAML_EXAMPLES[script].bamlInput); + }; + + const currentBamlScriptName = bamlForm.watch('bamlScriptName'); + const currentBamlInput = bamlForm.watch('bamlInput'); + const currentDslFile = bamlForm.watch('dslFile'); + const currentFunctionName = bamlForm.watch('functionName'); + const currentParamName = bamlForm.watch('paramName'); + + useEffect(() => { + const escapedBamlInput = escapeContent(currentBamlInput); + const escapedDslFile = escapeContent(currentDslFile); + const workflowRaw = getWorkflowFromBaml( + currentBamlScriptName, + escapedDslFile, + escapedBamlInput, + currentParamName, + currentFunctionName, + ); + createWorkflowForm.setValue('workflowRaw', workflowRaw); + }, [ + currentBamlScriptName, + currentBamlInput, + currentDslFile, + currentFunctionName, + currentParamName, + createWorkflowForm, + ]); + + const onBamlSubmit = async (data: BamlFormSchema) => { + const { bamlInput, dslFile, functionName, paramName, bamlScriptName } = + data; + const escapedBamlInput = escapeContent(bamlInput); + const escapedDslFile = escapeContent(dslFile); + const workflowCode = getWorkflowFromBaml( + bamlScriptName, + escapedDslFile, + escapedBamlInput, + paramName, + functionName, + ); + + if (!auth) return; + + await createJob({ + nodeAddress: auth?.node_address ?? '', + token: auth?.api_v2_key ?? '', + llmProvider: defaulAgentId, + content: escapedBamlInput, + workflowCode, + isHidden: true, + chatConfig: { + stream: false, + custom_prompt: '', + temperature: DEFAULT_CHAT_CONFIG.temperature, + top_p: DEFAULT_CHAT_CONFIG.top_p, + top_k: DEFAULT_CHAT_CONFIG.top_k, + }, + }); + }; + const handleWorkflowSave = async (data: CreateWorkflowFormSchema) => { + await createWorkflow({ + token: auth?.api_v2_key ?? '', + nodeAddress: auth?.node_address ?? '', + raw: data.workflowRaw ?? '', + description: data.workflowDescription, + }); + }; + + const onStopGenerating = async () => { + if (!inboxId) return; + const decodedInboxId = decodeURIComponent(inboxId); + const jobId = extractJobIdFromInbox(decodedInboxId); + await stopGenerating({ + nodeAddress: auth?.node_address ?? '', + token: auth?.api_v2_key ?? '', + jobId: jobId, + }); + }; + + return ( +
+
+
+ + BAML Template + +
+ + + + + + +

Switch Layout

+
+
+
+ + + + + + + + + + No results found. + + {Object.keys(BAML_EXAMPLES).map((key) => ( + { + handleBamlScriptChange(key); + setOpenWorkflowList(false); + }} + > + {BAML_EXAMPLES?.[key]?.name} + + ))} + + + + + {workflowList + ?.filter( + (workflow) => + workflow.author !== '@@official.shinkai', + ) + ?.map((workflow) => ( + { + handleUseTemplate(workflow.tool_router_key); + }} + > + {formatText(workflow.name)} + + ))} + + + + {workflowList + ?.filter( + (workflow) => + workflow.author === '@@official.shinkai', + ) + ?.map((workflow) => ( + { + handleUseTemplate(workflow.tool_router_key); + }} + > + {formatText(workflow.name)} + + ))} + + + + + + + + + + + +
+ + + Save Workflow + + +
+
+
+ Code + + + + + + +

+ The name and version of the workflow is + specified in the workflow code. +

+
+
+
+
+ +
+
+ + Description + + ( + + +