diff --git a/app/components/artifact.module.scss b/app/components/artifact.module.scss new file mode 100644 index 000000000..2909008b0 --- /dev/null +++ b/app/components/artifact.module.scss @@ -0,0 +1,30 @@ +.artifact { + display: flex; + width: 100%; + height: 100%; + flex-direction: column; + &-header { + display: flex; + align-items: center; + height: 36px; + padding: 20px; + background: var(--second); + } + &-title { + flex: 1; + text-align: center; + font-weight: bold; + font-size: 24px; + } + &-content { + flex-grow: 1; + padding: 0 20px 20px 20px; + background-color: var(--second); + } +} + +.artifact-iframe { + width: 100%; + border: var(--border-in-light); + border-radius: 6px; +} diff --git a/app/components/artifact.tsx b/app/components/artifact.tsx index bab68ee51..3006fe012 100644 --- a/app/components/artifact.tsx +++ b/app/components/artifact.tsx @@ -13,11 +13,12 @@ import { Modal, showToast } from "./ui-lib"; import { copyToClipboard, downloadAs } from "../utils"; import { Path, ApiPath, REPO_URL } from "@/app/constant"; import { Loading } from "./home"; +import styles from "./artifact.module.scss"; export function HTMLPreview(props: { code: string; autoHeight?: boolean; - height?: number; + height?: number | string; onLoad?: (title?: string) => void; }) { const ref = useRef(null); @@ -65,17 +66,22 @@ export function HTMLPreview(props: { return props.code + script; }, [props.code]); + const handleOnLoad = () => { + if (props?.onLoad) { + props.onLoad(title); + } + }; + return ( + onLoad={handleOnLoad} + /> ); } @@ -179,7 +185,6 @@ export function Artifact() { const [code, setCode] = useState(""); const [loading, setLoading] = useState(true); const [fileName, setFileName] = useState(""); - const { height } = useWindowSize(); useEffect(() => { if (id) { @@ -199,40 +204,28 @@ export function Artifact() { }, [id]); return ( -
-
+
+
} shadow /> -
NextChat Artifact
+
NextChat Artifact
code} fileName={fileName} />
- {loading && } - {code && ( - { - setFileName(title as string); - setLoading(false); - }} - /> - )} +
+ {loading && } + {code && ( + { + setFileName(title as string); + setLoading(false); + }} + /> + )} +
); } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 6bfd99b53..33956e6bc 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -641,12 +641,13 @@ export function ChatActions(props: { ]} onClose={() => setShowPluginSelector(false)} onSelection={(s) => { - if (s.length === 0) return; const plugin = s[0]; chatStore.updateCurrentSession((session) => { session.mask.plugin = s; }); - showToast(plugin); + if (plugin) { + showToast(plugin); + } }} /> )} diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index 0918e7c5b..36c742902 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -14,7 +14,8 @@ import React from "react"; import { useDebouncedCallback } from "use-debounce"; import { showImageModal, FullScreen } from "./ui-lib"; import { ArtifactShareButton, HTMLPreview } from "./artifact"; - +import { Plugin } from "../constant"; +import { useChatStore } from "../store"; export function Mermaid(props: { code: string }) { const ref = useRef(null); const [hasError, setHasError] = useState(false); @@ -67,6 +68,9 @@ export function PreCode(props: { children: any }) { const [mermaidCode, setMermaidCode] = useState(""); const [htmlCode, setHtmlCode] = useState(""); const { height } = useWindowSize(); + const chatStore = useChatStore(); + const session = chatStore.currentSession(); + const plugins = session.mask?.plugin; const renderArtifacts = useDebouncedCallback(() => { if (!ref.current) return; @@ -87,6 +91,11 @@ export function PreCode(props: { children: any }) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [refText]); + const enableArtifacts = useMemo( + () => plugins?.includes(Plugin.Artifact), + [plugins], + ); + return ( <>
@@ -104,10 +113,10 @@ export function PreCode(props: { children: any }) {
       {mermaidCode.length > 0 && (
         
       )}
-      {htmlCode.length > 0 && (
-        
+      {htmlCode.length > 0 && enableArtifacts && (
+        
            htmlCode}
           />
            void;
+  onClick?: (e: MouseEvent) => void;
   vertical?: boolean;
 }) {
   return (
@@ -470,15 +470,35 @@ export function Selector(props: {
   onClose?: () => void;
   multiple?: boolean;
 }) {
+  const [selectedValues, setSelectedValues] = useState(
+    Array.isArray(props.defaultSelectedValue)
+      ? props.defaultSelectedValue
+      : props.defaultSelectedValue !== undefined
+      ? [props.defaultSelectedValue]
+      : [],
+  );
+
+  const handleSelection = (e: MouseEvent, value: T) => {
+    if (props.multiple) {
+      e.stopPropagation();
+      const newSelectedValues = selectedValues.includes(value)
+        ? selectedValues.filter((v) => v !== value)
+        : [...selectedValues, value];
+      setSelectedValues(newSelectedValues);
+      props.onSelection?.(newSelectedValues);
+    } else {
+      setSelectedValues([value]);
+      props.onSelection?.([value]);
+      props.onClose?.();
+    }
+  };
+
   return (
     
props.onClose?.()}>
{props.items.map((item, i) => { - const selected = props.multiple - ? // @ts-ignore - props.defaultSelectedValue?.includes(item.value) - : props.defaultSelectedValue === item.value; + const selected = selectedValues.includes(item.value); return ( (props: { key={i} title={item.title} subTitle={item.subTitle} - onClick={(event) => { - event.stopPropagation(); - if (!item.disable) { - props.onSelection?.([item.value]); - props.onClose?.(); + onClick={(e) => { + if (item.disable) { + e.stopPropagation(); + } else { + handleSelection(e, item.value); } }} > @@ -515,7 +535,6 @@ export function Selector(props: {
); } - export function FullScreen(props: any) { const { children, right = 10, top = 10, ...rest } = props; const ref = useRef();