import { useStore } from '@nanostores/react'; import classNames from 'classnames'; import { AnimatePresence, motion } from 'framer-motion'; import { computed } from 'nanostores'; import { memo, useEffect, useRef, useState } from 'react'; import { type BundledLanguage, type BundledTheme, createHighlighter, type HighlighterGeneric } from 'shiki'; import type { ActionState } from '~/lib/runtime/action-runner'; import { webBuilderStore } from '~/lib/stores/web-builder'; import { cubicEasingFn } from '~/utils/easings'; const highlighterOptions = { langs: ['shell'], themes: ['light-plus', 'dark-plus'], }; const shellHighlighter: HighlighterGeneric = import.meta.hot?.data?.shellHighlighter ?? (await createHighlighter(highlighterOptions)); if (import.meta.hot && import.meta.hot.data) { import.meta.hot.data.shellHighlighter = shellHighlighter; } interface ArtifactProps { messageId: string; } export const Artifact = memo(({ messageId }: ArtifactProps) => { const userToggledActions = useRef(false); const [showActions, setShowActions] = useState(false); const [allActionFinished, setAllActionFinished] = useState(false); const artifacts = useStore(webBuilderStore.chatStore.artifacts); const artifact = artifacts[messageId]; const actions = useStore( computed(artifact.runner.actions, (actions) => { return Object.values(actions); }), ); const toggleActions = () => { userToggledActions.current = true; setShowActions(!showActions); }; useEffect(() => { const actionsMap = artifact.runner.actions.get(); Object.entries(actionsMap).forEach(([actionId, action]) => { if (action.status === 'running' || action.status === 'pending') { artifact.runner.actions.setKey(actionId, { ...action, status: 'aborted', }); } }); }, []); useEffect(() => { if (actions.length && !showActions && !userToggledActions.current) { setShowActions(true); } if (actions.length !== 0 && artifact.type === 'bundled') { const finished = !actions.find((action) => action.status !== 'complete'); if (allActionFinished !== finished) { setAllActionFinished(finished); } } }, [actions]); return (
{actions.length && artifact.type !== 'bundled' && (
)}
{artifact.type !== 'bundled' && showActions && actions.length > 0 && (
)}
); }); interface ActionListProps { actions: ActionState[]; } const actionVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 }, }; function openArtifactInWebBuilder(pageName: string, rootDomId: string) { if (webBuilderStore.currentView.get() !== 'code') { webBuilderStore.currentView.set('code'); } webBuilderStore.setSelectedPage(pageName); webBuilderStore.editorStore.scrollToElement(rootDomId); } const ActionList = memo(({ actions }: ActionListProps) => { return (
    {actions.map((action, index) => { const { status } = action; return (
    {status === 'running' ? (
    ) : status === 'pending' ? (
    ) : status === 'complete' ? (
    ) : status === 'failed' || status === 'aborted' ? (
    ) : null}
    {action.action === 'add' ? 'Create' : action.action === 'update' ? 'Update' : 'Delete'}{' '} openArtifactInWebBuilder(action.pageName, action.rootDomId)} > {action.id}
    ); })}
); }); function getIconColor(status: ActionState['status']) { switch (status) { case 'pending': { return 'text-upage-elements-textTertiary'; } case 'running': { return 'text-upage-elements-loader-progress'; } case 'complete': { return 'text-upage-elements-icon-success'; } case 'aborted': { return 'text-upage-elements-textSecondary'; } case 'failed': { return 'text-upage-elements-icon-error'; } default: { return undefined; } } }