refactor: replacing mobx with jotai (#160)

* refactor: replacing mobx with jotai

Signed-off-by: James <james@jan.ai>
Co-authored-by: James <james@jan.ai>
Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
NamH 2023-09-13 21:33:53 -07:00 committed by GitHub
parent cc39664ce4
commit d55a83888b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
115 changed files with 3749 additions and 7716 deletions

View File

@ -5,8 +5,7 @@ NEXT_PUBLIC_DOWNLOAD_APP_IOS=#
NEXT_PUBLIC_DOWNLOAD_APP_ANDROID=# NEXT_PUBLIC_DOWNLOAD_APP_ANDROID=#
NEXT_PUBLIC_GRAPHQL_ENGINE_URL=http://localhost:8080/v1/graphql NEXT_PUBLIC_GRAPHQL_ENGINE_URL=http://localhost:8080/v1/graphql
NEXT_PUBLIC_GRAPHQL_ENGINE_WEB_SOCKET_URL=ws://localhost:8080/v1/graphql NEXT_PUBLIC_GRAPHQL_ENGINE_WEB_SOCKET_URL=ws://localhost:8080/v1/graphql
OPENAPI_ENDPOINT=http://host.docker.internal:8000/v1 NEXT_PUBLIC_OPENAPI_ENDPOINT=http://localhost:8000/v1/chat/completions
OPENAPI_KEY=openapikey
KEYCLOAK_CLIENT_ID=hasura KEYCLOAK_CLIENT_ID=hasura
KEYCLOAK_CLIENT_SECRET=oMtCPAV7diKpE564SBspgKj4HqlKM4Hy KEYCLOAK_CLIENT_SECRET=oMtCPAV7diKpE564SBspgKj4HqlKM4Hy
AUTH_ISSUER=http://localhost:8088/realms/$KEYCLOAK_CLIENT_ID AUTH_ISSUER=http://localhost:8088/realms/$KEYCLOAK_CLIENT_ID

View File

@ -77,9 +77,7 @@ Replace above configuration with your actual infrastructure.
| [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin) | UI | ^0.5.9 | | [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin) | UI | ^0.5.9 |
| [embla-carousel](https://www.embla-carousel.com/) | UI | ^8.0.0-rc11 | | [embla-carousel](https://www.embla-carousel.com/) | UI | ^8.0.0-rc11 |
| [@apollo/client](https://www.apollographql.com/docs/react/) | State management | ^3.8.1 | | [@apollo/client](https://www.apollographql.com/docs/react/) | State management | ^3.8.1 |
| [mobx](https://mobx.js.org/README.html) | State management | ^6.10.0 | | [jotai](https://jotai.org/) | State management | ^2.4.0 |
| [mobx-react-lite](https://www.npmjs.com/package/mobx-react-lite) | State management | ^4.0.3 |
| [mobx-state-tree](https://mobx-state-tree.js.org/) | State management | ^5.1.8 |
## Deploy to Netlify ## Deploy to Netlify

View File

@ -1,69 +1,29 @@
"use client";
import { useCallback } from "react";
import Image from "next/image";
import { useStore } from "@/_models/RootStore";
import { observer } from "mobx-react-lite";
import { MenuAdvancedPrompt } from "../MenuAdvancedPrompt"; import { MenuAdvancedPrompt } from "../MenuAdvancedPrompt";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useMutation } from "@apollo/client"; import BasicPromptButton from "../BasicPromptButton";
import { CreateMessageDocument, CreateMessageMutation } from "@/graphql"; import PrimaryButton from "../PrimaryButton";
export const AdvancedPrompt: React.FC = observer(() => { const AdvancedPrompt: React.FC = () => {
const { register, handleSubmit } = useForm(); const { register, handleSubmit } = useForm();
const { historyStore } = useStore();
const onAdvancedPrompt = useCallback(() => { const onSubmit = (data: any) => {};
historyStore.toggleAdvancedPrompt();
}, []);
const [createMessageMutation] = useMutation<CreateMessageMutation>(
CreateMessageDocument
);
const onSubmit = (data: any) => {
historyStore.sendControlNetPrompt(
createMessageMutation,
data.prompt,
data.negativePrompt,
data.fileInput[0]
);
};
return ( return (
<form <form
className={`${ className="w-[288px] h-screen flex flex-col border-r border-gray-200"
historyStore.showAdvancedPrompt ? "w-[288px]" : "hidden"
} h-screen flex flex-col border-r border-gray-200`}
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
> >
<button <BasicPromptButton />
onClick={onAdvancedPrompt}
className="flex items-center mx-2 mt-3 mb-[10px] flex-none gap-1 text-xs leading-[18px] text-[#6B7280]"
>
<Image src={"/icons/chevron-left.svg"} width={20} height={20} alt="" />
<span className="font-semibold text-gray-500 text-xs">
BASIC PROMPT
</span>
</button>
<div className="flex flex-col justify-start flex-1 p-3 gap-[10px] overflow-x-hidden scroll">
<MenuAdvancedPrompt register={register} /> <MenuAdvancedPrompt register={register} />
</div>
<div className="py-3 px-2 flex flex-none gap-3 items-center justify-between border-t border-gray-200"> <div className="py-3 px-2 flex flex-none gap-3 items-center justify-between border-t border-gray-200">
<button className="w-1/2 flex items-center text-gray-900 py-2 px-3 rounded-lg gap-1 justify-center bg-gray-100 text-sm leading-5"> <PrimaryButton
<Image fullWidth={true}
src={"/icons/unicorn_arrow-random.svg"} title="Generate"
width={16} onClick={() => handleSubmit(onSubmit)}
height={16}
alt=""
/> />
Random
</button>
<button
className="w-1/2 flex items-center text-gray-900 justify-center py-2 px-3 rounded-lg gap-1 bg-yellow-300 text-sm leading-5"
onClick={(e) => handleSubmit(onSubmit)(e)}
>
Generate
</button>
</div> </div>
</form> </form>
); );
}); };
export default AdvancedPrompt;

View File

@ -0,0 +1,40 @@
"use client";
import {
currentConversationAtom,
showingAdvancedPromptAtom,
} from "@/_helpers/JotaiWrapper";
import { useAtomValue, useSetAtom } from "jotai";
import SecondaryButton from "../SecondaryButton";
import SendButton from "../SendButton";
import { ProductType } from "@/_models/Product";
const BasicPromptAccessories: React.FC = () => {
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
const currentConversation = useAtomValue(currentConversationAtom);
const shouldShowAdvancedPrompt =
currentConversation?.product.type === ProductType.ControlNet;
return (
<div
style={{
backgroundColor: "#F8F8F8",
borderWidth: 1,
borderColor: "#D1D5DB",
}}
className="flex justify-between py-2 pl-3 pr-2 rounded-b-lg"
>
{shouldShowAdvancedPrompt && (
<SecondaryButton
title="Advanced"
onClick={() => setShowingAdvancedPrompt(true)}
/>
)}
<div className="flex justify-end items-center space-x-1 w-full pr-3" />
{!shouldShowAdvancedPrompt && <SendButton />}
</div>
);
};
export default BasicPromptAccessories;

View File

@ -0,0 +1,20 @@
import React from "react";
import { useSetAtom } from "jotai";
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
import { showingAdvancedPromptAtom } from "@/_helpers/JotaiWrapper";
const BasicPromptButton: React.FC = () => {
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
return (
<button
onClick={() => setShowingAdvancedPrompt(false)}
className="flex items-center mx-2 mt-3 mb-[10px] flex-none gap-1 text-xs leading-[18px] text-[#6B7280]"
>
<ChevronLeftIcon width={20} height={20} />
<span className="font-semibold text-gray-500 text-xs">BASIC PROMPT</span>
</button>
);
};
export default React.memo(BasicPromptButton);

View File

@ -0,0 +1,38 @@
"use client";
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
import useSendChatMessage from "@/_hooks/useSendChatMessage";
import { useAtom } from "jotai";
const BasicPromptInput: React.FC = () => {
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom);
const { sendChatMessage } = useSendChatMessage();
const handleMessageChange = (event: any) => {
setCurrentPrompt(event.target.value);
};
const handleKeyDown = (event: any) => {
if (event.key === "Enter") {
if (!event.shiftKey) {
event.preventDefault();
sendChatMessage();
}
}
};
return (
<textarea
onKeyDown={handleKeyDown}
value={currentPrompt}
onChange={handleMessageChange}
rows={2}
name="comment"
id="comment"
className="overflow-hidden block w-full scroll resize-none border-0 bg-transparent py-1.5 text-gray-900 transition-height duration-200 placeholder:text-gray-400 sm:text-sm sm:leading-6 dark:text-white"
placeholder="Add your comment..."
/>
);
};
export default BasicPromptInput;

View File

@ -1,4 +1,4 @@
import Image from "next/image"; import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
import React, { PropsWithChildren } from "react"; import React, { PropsWithChildren } from "react";
type PropType = PropsWithChildren< type PropType = PropsWithChildren<
@ -17,13 +17,7 @@ export const PrevButton: React.FC<PropType> = (props) => {
type="button" type="button"
{...restProps} {...restProps}
> >
<Image <ChevronLeftIcon width={20} height={20} />
className="rotate-180"
src={"/icons/chevron-right.svg"}
width={20}
height={20}
alt=""
/>
{children} {children}
</button> </button>
); );
@ -38,7 +32,7 @@ export const NextButton: React.FC<PropType> = (props) => {
type="button" type="button"
{...restProps} {...restProps}
> >
<Image src={"/icons/chevron-right.svg"} width={20} height={20} alt="" /> <ChevronRightIcon width={20} height={20} />
{children} {children}
</button> </button>
); );

View File

@ -1,186 +1,47 @@
import React, { useEffect, useLayoutEffect, useRef, useState } from "react"; "use client";
import { useStore } from "@/_models/RootStore";
import { observer } from "mobx-react-lite";
import { ChatMessage, MessageStatus, MessageType } from "@/_models/ChatMessage";
import SimpleImageMessage from "../SimpleImageMessage";
import SimpleTextMessage from "../SimpleTextMessage";
import { Instance } from "mobx-state-tree";
import { GenerativeSampleContainer } from "../GenerativeSampleContainer";
import { AiModelType } from "@/_models/Product";
import SampleLlmContainer from "@/_components/SampleLlmContainer";
import SimpleControlNetMessage from "../SimpleControlNetMessage";
import {
GetConversationMessagesQuery,
GetConversationMessagesDocument,
} from "@/graphql";
import { useLazyQuery } from "@apollo/client";
import LoadingIndicator from "../LoadingIndicator";
import StreamTextMessage from "../StreamTextMessage";
type Props = { import React, { useCallback, useRef, useState } from "react";
onPromptSelected: (prompt: string) => void; import ChatItem from "../ChatItem";
}; import { ChatMessage } from "@/_models/ChatMessage";
import useChatMessages from "@/_hooks/useChatMessages";
import { currentChatMessagesAtom } from "@/_helpers/JotaiWrapper";
import { useAtomValue } from "jotai";
export const ChatBody: React.FC<Props> = observer(({ onPromptSelected }) => { const ChatBody: React.FC = () => {
const ref = useRef<HTMLDivElement>(null); const messages = useAtomValue(currentChatMessagesAtom);
const scrollRef = useRef<HTMLDivElement>(null); const [offset, setOffset] = useState(0);
const [height, setHeight] = useState(0); const { loading, hasMore } = useChatMessages(offset);
const { historyStore } = useStore(); const intersectObs = useRef<any>(null);
const refSmooth = useRef<HTMLDivElement>(null);
const [heightContent, setHeightContent] = useState(0);
const refContent = useRef<HTMLDivElement>(null); const lastPostRef = useCallback(
const convo = historyStore.getActiveConversation(); (message: ChatMessage) => {
const [getConversationMessages] = useLazyQuery<GetConversationMessagesQuery>( if (loading) return;
GetConversationMessagesDocument
);
useEffect(() => { if (intersectObs.current) intersectObs.current.disconnect();
refSmooth.current?.scrollIntoView({ behavior: "instant" });
}, [heightContent]);
useLayoutEffect(() => { intersectObs.current = new IntersectionObserver((entries) => {
if (refContent.current) { if (entries[0].isIntersecting && hasMore) {
setHeightContent(refContent.current?.offsetHeight); setOffset((prevOffset) => prevOffset + 5);
} }
}); });
useLayoutEffect(() => { if (message) intersectObs.current.observe(message);
if (!ref.current) return; },
setHeight(ref.current?.offsetHeight); [loading, hasMore]
}, []);
const loadFunc = () => {
historyStore.fetchMoreMessages(getConversationMessages);
};
const messages = historyStore.getActiveMessages();
const shouldShowSampleContainer = messages.length === 0;
const shouldShowImageSampleContainer =
shouldShowSampleContainer &&
convo &&
convo.product.type === AiModelType.GenerativeArt;
const model = convo?.product;
const handleScroll = () => {
if (!scrollRef.current) return;
if (
scrollRef.current?.clientHeight - scrollRef.current?.scrollTop + 1 >=
scrollRef.current?.scrollHeight
) {
loadFunc();
}
};
useEffect(() => {
loadFunc();
scrollRef.current?.addEventListener("scroll", handleScroll);
return () => {
scrollRef.current?.removeEventListener("scroll", handleScroll);
};
}, [scrollRef.current]);
return (
<div className="flex-grow flex flex-col h-fit" ref={ref}>
{shouldShowSampleContainer && model ? (
shouldShowImageSampleContainer ? (
<GenerativeSampleContainer
model={convo?.product}
onPromptSelected={onPromptSelected}
/>
) : (
<SampleLlmContainer
model={convo?.product}
onPromptSelected={onPromptSelected}
/>
)
) : (
<div
className="flex flex-col-reverse scroll"
style={{
height: height + "px",
overflowX: "hidden",
}}
ref={scrollRef}
>
<div
className="flex flex-col justify-end gap-8 py-2"
ref={refContent}
>
{messages.map((message, index) => renderItem(index, message))}
<div ref={refSmooth}>
{convo?.isWaitingForModelResponse && (
<div className="w-[50px] h-[50px] px-2 flex flex-row items-start justify-start">
<LoadingIndicator />
</div>
)}
</div>
</div>
</div>
)}
</div>
); );
const content = messages.map((message, index) => {
if (messages.length === index + 1) {
return <ChatItem ref={lastPostRef} message={message} key={message.id} />;
}
return <ChatItem message={message} key={message.id} />;
}); });
const renderItem = (
index: number,
{
id,
messageType,
senderAvatarUrl,
senderName,
createdAt,
imageUrls,
text,
status,
}: Instance<typeof ChatMessage>
) => {
switch (messageType) {
case MessageType.ImageWithText:
return ( return (
<SimpleControlNetMessage <div className="flex flex-col-reverse flex-1 py-4 overflow-y-auto scroll">
key={index} {content}
avatarUrl={senderAvatarUrl ?? "/icons/app_icon.svg"} </div>
senderName={senderName}
createdAt={createdAt}
imageUrls={imageUrls ?? []}
text={text ?? ""}
/>
); );
case MessageType.Image:
return (
<SimpleImageMessage
key={index}
avatarUrl={senderAvatarUrl ?? "/icons/app_icon.svg"}
senderName={senderName}
createdAt={createdAt}
imageUrls={imageUrls ?? []}
text={text}
/>
);
case MessageType.Text:
return status === MessageStatus.Ready ? (
<SimpleTextMessage
key={index}
avatarUrl={senderAvatarUrl ?? "/icons/app_icon.svg"}
senderName={senderName}
createdAt={createdAt}
text={text}
/>
) : (
<StreamTextMessage
key={index}
id={id}
avatarUrl={senderAvatarUrl ?? "/icons/app_icon.svg"}
senderName={senderName}
createdAt={createdAt}
text={text}
/>
);
default:
return null;
}
}; };
export default ChatBody;

View File

@ -0,0 +1,66 @@
import SimpleControlNetMessage from "../SimpleControlNetMessage";
import SimpleImageMessage from "../SimpleImageMessage";
import SimpleTextMessage from "../SimpleTextMessage";
import { ChatMessage, MessageType } from "@/_models/ChatMessage";
import StreamTextMessage from "../StreamTextMessage";
import { useAtom } from "jotai";
import { currentStreamingMessageAtom } from "@/_helpers/JotaiWrapper";
export default function renderChatMessage({
id,
messageType,
senderAvatarUrl,
senderName,
createdAt,
imageUrls,
text,
status,
}: ChatMessage): React.ReactNode {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [message, _] = useAtom(currentStreamingMessageAtom);
switch (messageType) {
case MessageType.ImageWithText:
return (
<SimpleControlNetMessage
key={id}
avatarUrl={senderAvatarUrl}
senderName={senderName}
createdAt={createdAt}
imageUrls={imageUrls ?? []}
text={text ?? ""}
/>
);
case MessageType.Image:
return (
<SimpleImageMessage
key={id}
avatarUrl={senderAvatarUrl}
senderName={senderName}
createdAt={createdAt}
imageUrls={imageUrls ?? []}
text={text}
/>
);
case MessageType.Text:
return id !== message?.id ? (
<SimpleTextMessage
key={id}
avatarUrl={senderAvatarUrl}
senderName={senderName}
createdAt={createdAt}
text={text}
/>
) : (
<StreamTextMessage
key={id}
id={id}
avatarUrl={senderAvatarUrl}
senderName={senderName}
createdAt={createdAt}
text={text}
/>
);
default:
return null;
}
}

View File

@ -1,91 +1,30 @@
"use client"; "use client";
import { useState, useEffect } from "react";
import { ChatBody } from "../ChatBody"; import ModelDetailSideBar from "../ModelDetailSideBar";
import { InputToolbar } from "../InputToolbar"; import ProductOverview from "../ProductOverview";
import { UserToolbar } from "../UserToolbar"; import { useAtomValue } from "jotai";
import ModelMenu from "../ModelMenu";
import { useStore } from "@/_models/RootStore";
import { observer } from "mobx-react-lite";
import ConfirmDeleteConversationModal from "../ConfirmDeleteConversationModal";
import { ModelDetailSideBar } from "../ModelDetailSideBar";
import NewChatBlankState from "../NewChatBlankState";
import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import { import {
DeleteConversationMutation, getActiveConvoIdAtom,
DeleteConversationDocument, showingProductDetailAtom,
} from "@/graphql"; } from "@/_helpers/JotaiWrapper";
import { useMutation } from "@apollo/client"; import { ReactNode } from "react";
const ChatContainer: React.FC = observer(() => { type Props = {
const [prefillPrompt, setPrefillPrompt] = useState(""); children: ReactNode;
const { historyStore } = useStore();
const { user } = useGetCurrentUser();
const showBodyChat = historyStore.activeConversationId != null;
const conversation = historyStore.getActiveConversation();
const [deleteConversation] = useMutation<DeleteConversationMutation>(
DeleteConversationDocument
);
useEffect(() => {
if (!user) {
historyStore.clearAllConversations();
}
}, [user]);
const [open, setOpen] = useState(false);
const onConfirmDelete = () => {
setPrefillPrompt("");
historyStore.closeModelDetail();
if (conversation?.id) {
deleteConversation({ variables: { id: conversation.id } }).then(() =>
historyStore.deleteConversationById(conversation.id)
);
}
setOpen(false);
}; };
const onSuggestPromptClick = (prompt: string) => { export default function ChatContainer({ children }: Props) {
if (prompt !== prefillPrompt) { const activeConvoId = useAtomValue(getActiveConvoIdAtom);
setPrefillPrompt(prompt); const showingProductDetail = useAtomValue(showingProductDetailAtom);
if (!activeConvoId) {
return <ProductOverview />;
} }
};
return ( return (
<div className="flex flex-1 h-full overflow-y-hidden"> <div className="flex flex-1 overflow-hidden">
<ConfirmDeleteConversationModal {children}
open={open} {showingProductDetail ? <ModelDetailSideBar /> : null}
setOpen={setOpen}
onConfirmDelete={onConfirmDelete}
/>
{showBodyChat ? (
<div className="flex-1 flex flex-col w-full">
<div className="flex w-full overflow-hidden flex-shrink-0 px-3 py-1 border-b dark:bg-gray-950 border-gray-200 bg-white shadow-sm sm:px-3 lg:px-3">
{/* Separator */}
<div
className="h-full w-px bg-gray-200 lg:hidden"
aria-hidden="true"
/>
<div className="flex justify-between self-stretch flex-1">
<UserToolbar />
<ModelMenu
onDeleteClick={() => setOpen(true)}
onCreateConvClick={() => {}}
/>
</div>
</div>
<div className="flex flex-col h-full px-1 sm:px-2 lg:px-3 overflow-hidden">
<ChatBody onPromptSelected={onSuggestPromptClick} />
<InputToolbar prefillPrompt={prefillPrompt} />
</div>
</div>
) : (
<NewChatBlankState />
)}
<ModelDetailSideBar onPromptClick={onSuggestPromptClick} />
</div> </div>
); );
}); }
export default ChatContainer;

View File

@ -0,0 +1,19 @@
import React, { forwardRef } from "react";
import renderChatMessage from "../ChatBody/renderChatMessage";
import { ChatMessage } from "@/_models/ChatMessage";
type Props = {
message: ChatMessage;
};
type Ref = HTMLDivElement;
const ChatItem = forwardRef<Ref, Props>(({ message }, ref) => {
const item = renderChatMessage(message);
const content = ref ? <div ref={ref}>{item}</div> : item;
return content;
});
export default ChatItem;

View File

@ -1,32 +1,30 @@
import { useStore } from "@/_models/RootStore"; import {
getActiveConvoIdAtom,
setActiveConvoIdAtom,
} from "@/_helpers/JotaiWrapper";
import { useAtomValue, useSetAtom } from "jotai";
import Image from "next/image"; import Image from "next/image";
import React from "react";
type Props = { type Props = {
imageUrl: string; imageUrl: string;
isSelected: boolean;
conversationId: string; conversationId: string;
}; };
const CompactHistoryItem: React.FC<Props> = ({ const CompactHistoryItem: React.FC<Props> = ({ imageUrl, conversationId }) => {
imageUrl, const activeConvoId = useAtomValue(getActiveConvoIdAtom);
isSelected, const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
conversationId,
}) => { const isSelected = activeConvoId === conversationId;
const { historyStore } = useStore();
const onClick = () => {
historyStore.setActiveConversationId(conversationId);
};
return ( return (
<button <button
onClick={onClick} onClick={() => setActiveConvoId(conversationId)}
className={`${ className={`${
isSelected ? "bg-gray-100" : "bg-transparent" isSelected ? "bg-gray-100" : "bg-transparent"
} p-2 rounded-lg`} } w-14 h-14 rounded-lg`}
> >
<Image <Image
className="rounded-full" className="rounded-full mx-auto"
src={imageUrl} src={imageUrl}
width={36} width={36}
height={36} height={36}
@ -36,4 +34,4 @@ const CompactHistoryItem: React.FC<Props> = ({
); );
}; };
export default React.memo(CompactHistoryItem); export default CompactHistoryItem;

View File

@ -0,0 +1,21 @@
import { useAtomValue } from "jotai";
import CompactHistoryItem from "../CompactHistoryItem";
import { userConversationsAtom } from "@/_helpers/JotaiWrapper";
const CompactHistoryList: React.FC = () => {
const conversations = useAtomValue(userConversationsAtom);
return (
<div className="flex flex-col flex-1 gap-1 mt-3">
{conversations.map(({ id, product }) => (
<CompactHistoryItem
key={id}
conversationId={id}
imageUrl={product.avatarUrl ?? ""}
/>
))}
</div>
);
};
export default CompactHistoryList;

View File

@ -1,13 +1,13 @@
import React from "react"; import React from "react";
import JanImage from "../JanImage"; import JanImage from "../JanImage";
import { setActiveConvoIdAtom } from "@/_helpers/JotaiWrapper";
import { useSetAtom } from "jotai";
type Props = { const CompactLogo: React.FC = () => {
onClick: () => void; const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
};
const CompactLogo: React.FC<Props> = ({ onClick }) => {
return ( return (
<button onClick={onClick}> <button onClick={() => setActiveConvoId(undefined)}>
<JanImage imageUrl="/icons/app_icon.svg" width={28} height={28} /> <JanImage imageUrl="/icons/app_icon.svg" width={28} height={28} />
</button> </button>
); );

View File

@ -1,33 +1,11 @@
"use client" import CompactHistoryList from "../CompactHistoryList";
import { observer } from "mobx-react-lite";
import CompactLogo from "../CompactLogo"; import CompactLogo from "../CompactLogo";
import CompactHistoryItem from "../CompactHistoryItem";
import { useStore } from "@/_models/RootStore";
export const CompactSideBar: React.FC = observer(() => { const CompactSideBar: React.FC = () => (
const { historyStore } = useStore(); <div className="h-screen w-16 border-r border-gray-300 flex flex-col items-center pt-3 gap-3">
<CompactLogo />
const onLogoClick = () => { <CompactHistoryList />
historyStore.clearActiveConversationId();
};
return (
<div
className={`${
!historyStore.showAdvancedPrompt ? "hidden" : "block"
} h-screen border-r border-gray-300 flex flex-col items-center pt-3 gap-3`}
>
<CompactLogo onClick={onLogoClick} />
<div className="flex flex-col gap-1 mx-1 mt-3 overflow-x-hidden">
{historyStore.conversations.map(({ id, product: aiModel }) => (
<CompactHistoryItem
key={id}
conversationId={id}
imageUrl={aiModel.avatarUrl ?? ""}
isSelected={historyStore.activeConversationId === id}
/>
))}
</div>
</div> </div>
); );
});
export default CompactSideBar;

View File

@ -1,27 +1,26 @@
import { showConfirmDeleteConversationModalAtom } from "@/_helpers/JotaiWrapper";
import useDeleteConversation from "@/_hooks/useDeleteConversation";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import React, { Fragment, useRef, useState } from "react"; import { useAtom } from "jotai";
import React, { Fragment, useRef } from "react";
type Props = { const ConfirmDeleteConversationModal: React.FC = () => {
open: boolean; const [show, setShow] = useAtom(showConfirmDeleteConversationModalAtom);
setOpen: (open: boolean) => void; const cancelButtonRef = useRef(null);
onConfirmDelete: () => void; const { deleteConvo } = useDeleteConversation();
const onConfirmDelete = () => {
deleteConvo().then(() => setShow(false));
}; };
const ConfirmDeleteConversationModal: React.FC<Props> = ({
open,
setOpen,
onConfirmDelete,
}) => {
const cancelButtonRef = useRef(null);
return ( return (
<Transition.Root show={open} as={Fragment}> <Transition.Root show={show} as={Fragment}>
<Dialog <Dialog
as="div" as="div"
className="relative z-10" className="relative z-10"
initialFocus={cancelButtonRef} initialFocus={cancelButtonRef}
onClose={setOpen} onClose={setShow}
> >
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
@ -81,7 +80,7 @@ const ConfirmDeleteConversationModal: React.FC<Props> = ({
<button <button
type="button" type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto" className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => setOpen(false)} onClick={() => setShow(false)}
ref={cancelButtonRef} ref={cancelButtonRef}
> >
Cancel Cancel

View File

@ -1,22 +1,21 @@
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
import { showConfirmSignOutModalAtom } from "@/_helpers/JotaiWrapper";
import { useAtom } from "jotai";
import useSignOut from "@/_hooks/useSignOut";
type Props = { const ConfirmSignOutModal: React.FC = () => {
open: boolean; const [show, setShow] = useAtom(showConfirmSignOutModalAtom);
setOpen: (open: boolean) => void; const { signOut } = useSignOut();
onConfirm: () => void;
};
const ConfirmSignOutModal: React.FC<Props> = ({ open, setOpen, onConfirm }) => {
const onLogOutClick = () => { const onLogOutClick = () => {
onConfirm(); signOut().then(() => setShow(false));
setOpen(false);
}; };
return ( return (
<Transition.Root show={open} as={Fragment}> <Transition.Root show={show} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setOpen}> <Dialog as="div" className="relative z-10" onClose={setShow}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
@ -73,7 +72,7 @@ const ConfirmSignOutModal: React.FC<Props> = ({ open, setOpen, onConfirm }) => {
<button <button
type="button" type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto" className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => setOpen(false)} onClick={() => setShow(false)}
> >
Cancel Cancel
</button> </button>

View File

@ -1,18 +1,16 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/image";
import {
ProductDetailFragment,
} from "@/graphql";
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from "@/_hooks/useCreateConversation";
import { Product } from "@/_models/Product";
type Props = { type Props = {
product: ProductDetailFragment; product: Product;
}; };
const ConversationalCard: React.FC<Props> = ({ product }) => { const ConversationalCard: React.FC<Props> = ({ product }) => {
const { requestCreateConvo } = useCreateConversation(); const { requestCreateConvo } = useCreateConversation();
const { name, image_url, description } = product; const { name, avatarUrl, description } = product;
return ( return (
<button <button
@ -25,7 +23,7 @@ const ConversationalCard: React.FC<Props> = ({ product }) => {
<Image <Image
width={32} width={32}
height={32} height={32}
src={image_url ?? ""} src={avatarUrl ?? ""}
className="rounded-full" className="rounded-full"
alt="" alt=""
/> />

View File

@ -1,22 +1,22 @@
import { Product } from "@/_models/Product";
import ConversationalCard from "../ConversationalCard"; import ConversationalCard from "../ConversationalCard";
import Image from "next/image"; import { ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline";
import { ProductDetailFragment } from "@/graphql";
type Props = { type Props = {
products: ProductDetailFragment[]; products: Product[];
}; };
const ConversationalList: React.FC<Props> = ({ products }) => ( const ConversationalList: React.FC<Props> = ({ products }) => (
<> <>
<div className="flex items-center gap-3 mt-8 mb-2"> <div className="flex items-center gap-3 mt-8 mb-2">
<Image src={"/icons/messicon.svg"} width={24} height={24} alt="" /> <ChatBubbleBottomCenterTextIcon width={24} height={24} className="ml-6" />
<span className="font-semibold text-gray-900 dark:text-white"> <span className="font-semibold text-gray-900 dark:text-white">
Conversational Conversational
</span> </span>
</div> </div>
<div className="mt-2 flex w-full gap-2 overflow-x-scroll scroll overflow-hidden"> <div className="mt-2 pl-6 flex w-full gap-2 overflow-x-scroll scroll overflow-hidden">
{products.map((item) => ( {products.map((item) => (
<ConversationalCard key={item.name} product={item} /> <ConversationalCard key={item.slug} product={item} />
))} ))}
</div> </div>
</> </>

View File

@ -1,37 +0,0 @@
import React, { useState } from "react";
import { useStore } from "@/_models/RootStore";
type Props = {
targetRef: React.RefObject<HTMLDivElement>;
};
export const Draggable: React.FC<Props> = ({ targetRef }) => {
const { historyStore } = useStore();
const [initialPos, setInitialPos] = useState<number | null>(null);
const [initialSize, setInitialSize] = useState<number | null>(null);
const [width, setWidth] = useState<number>(0);
const initial = (e: React.DragEvent<HTMLDivElement>) => {
setInitialPos(e.clientX);
setInitialSize(targetRef.current?.offsetWidth ?? 0);
};
const resize = (e: React.DragEvent<HTMLDivElement>) => {
if (initialPos !== null && initialSize !== null) {
setWidth(initialSize - (e.clientX - initialPos));
targetRef.current!.style.width = `${width}px`;
}
if (width <= 270) {
historyStore.closeModelDetail();
}
};
return (
<div
className="absolute left-0 top-0 w-1 h-full cursor-ew-resize"
draggable={true}
onDrag={resize}
onDragStart={initial}
></div>
);
};

View File

@ -0,0 +1,24 @@
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/outline";
type Props = {
title: string;
expanded: boolean;
onClick: () => void;
};
const ExpandableHeader: React.FC<Props> = ({ title, expanded, onClick }) => (
<button onClick={onClick} className="flex items-center justify-between px-2">
<h2 className="text-gray-400 font-bold text-[12px] leading-[12px] pl-1">
{title}
</h2>
<div className="mr-2">
{expanded ? (
<ChevronDownIcon width={12} height={12} color="#6B7280" />
) : (
<ChevronUpIcon width={12} height={12} color="#6B7280" />
)}
</div>
</button>
);
export default ExpandableHeader;

View File

@ -1,23 +1,21 @@
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from "@/_hooks/useCreateConversation";
import { ProductDetailFragment } from "@/graphql"; import { Product } from "@/_models/Product";
import { useCallback } from "react";
type Props = { type Props = {
product: ProductDetailFragment; product: Product;
}; };
const GenerateImageCard: React.FC<Props> = ({ product }) => { const GenerateImageCard: React.FC<Props> = ({ product }) => {
const { name, image_url } = product; const { name, avatarUrl } = product;
const { requestCreateConvo } = useCreateConversation(); const { requestCreateConvo } = useCreateConversation();
const onClick = useCallback(() => {
requestCreateConvo(product);
}, [product]);
return ( return (
<button onClick={onClick} className="relative active:opacity-50 text-left"> <button
onClick={() => requestCreateConvo(product)}
className="relative active:opacity-50 text-left"
>
<img <img
src={image_url ?? ""} src={avatarUrl}
alt="" alt=""
className="w-full h-full rounded-[8px] bg-gray-200 group-hover:opacity-75 object-cover object-center" className="w-full h-full rounded-[8px] bg-gray-200 group-hover:opacity-75 object-cover object-center"
/> />

View File

@ -1,33 +1,27 @@
import Image from "next/image"; import { Product } from "@/_models/Product";
import GenerateImageCard from "../GenerateImageCard"; import GenerateImageCard from "../GenerateImageCard";
import { ProductDetailFragment } from "@/graphql"; import { PhotoIcon } from "@heroicons/react/24/outline";
type Props = { type Props = {
products: ProductDetailFragment[]; products: Product[];
}; };
const GenerateImageList: React.FC<Props> = ({ products }) => { const GenerateImageList: React.FC<Props> = ({ products }) => (
if (products.length === 0) { <>
return <div></div>; {products.length === 0 ? null : (
} <div className="flex items-center gap-3 mt-8 mb-2">
<PhotoIcon width={24} height={24} className="ml-6" />
return ( <span className="font-semibold text-gray-900 dark:text-white">
<div className="pb-4">
<div className="flex mt-4 justify-between">
<div className="gap-4 flex items-center">
<Image src={"icons/ic_image.svg"} width={20} height={20} alt="" />
<h2 className="text-gray-900 font-bold dark:text-white">
Generate Images Generate Images
</h2> </span>
</div> </div>
</div> )}
<div className="mt-2 grid grid-cols-2 gap-6 sm:gap-x-6 md:grid-cols-4 md:gap-8"> <div className="mt-2 mx-6 mb-6 grid grid-cols-2 gap-6 sm:gap-x-6 md:grid-cols-4 md:gap-8">
{products.map((item) => ( {products.map((item) => (
<GenerateImageCard key={item.name} product={item} /> <GenerateImageCard key={item.name} product={item} />
))} ))}
</div> </div>
</div> </>
); );
};
export default GenerateImageList; export default GenerateImageList;

View File

@ -1,30 +1,25 @@
import JanWelcomeTitle from "../JanWelcomeTitle"; import JanWelcomeTitle from "../JanWelcomeTitle";
import { Product } from "@/_models/Product";
import { Instance } from "mobx-state-tree";
import { GetProductPromptsQuery, GetProductPromptsDocument } from "@/graphql"; import { GetProductPromptsQuery, GetProductPromptsDocument } from "@/graphql";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Product } from "@/_models/Product";
import { useSetAtom } from "jotai";
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
type Props = { type Props = {
model: Instance<typeof Product>; product: Product;
onPromptSelected: (prompt: string) => void;
}; };
export const GenerativeSampleContainer: React.FC<Props> = ({ const GenerativeSampleContainer: React.FC<Props> = ({ product }) => {
model, const setCurrentPrompt = useSetAtom(currentPromptAtom);
onPromptSelected, const { data } = useQuery<GetProductPromptsQuery>(GetProductPromptsDocument, {
}) => { variables: { productSlug: product.slug },
const { loading, error, data } = useQuery<GetProductPromptsQuery>( });
GetProductPromptsDocument,
{
variables: { productSlug: model.id },
}
);
return ( return (
<div className="flex flex-col max-w-2xl flex-shrink-0 mx-auto mt-6"> <div className="flex flex-col max-w-2xl flex-shrink-0 mx-auto mt-6">
<JanWelcomeTitle <JanWelcomeTitle
title={model.name} title={product.name}
description={model.modelDescription ?? ""} description={product.longDescription}
/> />
<div className="flex flex-col"> <div className="flex flex-col">
<h2 className="font-semibold text-xl leading-6 tracking-[-0.4px] mb-5"> <h2 className="font-semibold text-xl leading-6 tracking-[-0.4px] mb-5">
@ -34,7 +29,7 @@ export const GenerativeSampleContainer: React.FC<Props> = ({
{data?.prompts.map((item) => ( {data?.prompts.map((item) => (
<button <button
key={item.slug} key={item.slug}
onClick={() => onPromptSelected(item.content ?? "")} onClick={() => setCurrentPrompt(item.content ?? "")}
className="w-full h-full" className="w-full h-full"
> >
<img <img
@ -50,3 +45,5 @@ export const GenerativeSampleContainer: React.FC<Props> = ({
</div> </div>
); );
}; };
export default GenerativeSampleContainer;

View File

@ -0,0 +1,22 @@
"use client";
import { showingMobilePaneAtom } from "@/_helpers/JotaiWrapper";
import { Bars3Icon } from "@heroicons/react/24/outline";
import { useSetAtom } from "jotai";
import React from "react";
const HamburgerButton: React.FC = () => {
const setShowingMobilePane = useSetAtom(showingMobilePaneAtom);
return (
<button
type="button"
className="self-end inline-flex items-center justify-center rounded-md p-1 text-gray-700 lg:hidden"
onClick={() => setShowingMobilePane(true)}
>
<span className="sr-only">Open main menu</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button>
);
};
export default React.memo(HamburgerButton);

View File

@ -1,61 +1,16 @@
"use client"; import React from "react";
import React, { useState } from "react";
import { Bars3Icon } from "@heroicons/react/24/outline";
import MobileMenuPane from "../MobileMenuPane";
import ConfirmSignOutModal from "../ConfirmSignOutModal";
import useSignOut from "@/_hooks/useSignOut";
import { ThemeChanger } from "../ChangeTheme";
import UserProfileDropDown from "../UserProfileDropDown"; import UserProfileDropDown from "../UserProfileDropDown";
import useSignIn from "@/_hooks/useSignIn"; import LoginButton from "../LoginButton";
import useGetCurrentUser from "@/_hooks/useGetCurrentUser"; import HamburgerButton from "../HamburgerButton";
const Header: React.FC = () => { const Header: React.FC = () => (
const { signInWithKeyCloak } = useSignIn(); <header className="flex border-b-[1px] border-gray-200 p-3 dark:bg-gray-800">
const { user, loading } = useGetCurrentUser(); <nav className="flex-1 justify-center">
const { signOut } = useSignOut(); <HamburgerButton />
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [showLogOutModal, setShowLogOutModal] = useState(false);
return (
<header
id="header"
className="text-sm bg-white border-b-[1px] border-gray-200 relative w-full py-3 px-6 dark:bg-gray-800"
>
<nav className="mx-auto flex items-center" aria-label="Global">
<div className="flex items-center flex-1 justify-center" />
<div className="hidden md:flex items-center gap-2">
<ThemeChanger />
{loading ? (
<div></div>
) : user ? (
<UserProfileDropDown
onLogOutClick={() => setShowLogOutModal(true)}
/>
) : (
<button onClick={signInWithKeyCloak}>Login</button>
)}
</div>
<div className="flex lg:hidden">
<button
type="button"
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700"
onClick={() => setMobileMenuOpen(true)}
>
<span className="sr-only">Open main menu</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</nav> </nav>
<ConfirmSignOutModal <LoginButton />
open={showLogOutModal} <UserProfileDropDown />
setOpen={setShowLogOutModal}
onConfirm={signOut}
/>
<MobileMenuPane open={mobileMenuOpen} setOpen={setMobileMenuOpen} />
</header> </header>
); );
};
export default Header; export default Header;

View File

@ -1,38 +1,50 @@
import { AiModelType } from "@/_models/Product";
import { useStore } from "@/_models/RootStore";
import { observer } from "mobx-react-lite";
import React from "react"; import React from "react";
import JanImage from "../JanImage"; import JanImage from "../JanImage";
import { displayDate } from "@/_utils/datetime"; import { displayDate } from "@/_utils/datetime";
import {
conversationStatesAtom,
getActiveConvoIdAtom,
setActiveConvoIdAtom,
} from "@/_helpers/JotaiWrapper";
import { useAtomValue, useSetAtom } from "jotai";
import { ProductType } from "@/_models/Product";
import Image from "next/image"; import Image from "next/image";
import { Conversation } from "@/_models/Conversation";
type Props = { type Props = {
conversationId: string; conversation: Conversation;
avatarUrl: string; avatarUrl: string;
name: string; name: string;
updatedAt?: number; updatedAt?: number;
}; };
const HistoryItem: React.FC<Props> = observer( const HistoryItem: React.FC<Props> = ({
({ conversationId, avatarUrl, name, updatedAt }) => { conversation,
const { historyStore } = useStore(); avatarUrl,
const send = true; // TODO store this in mobx name,
updatedAt,
}) => {
const conversationStates = useAtomValue(conversationStatesAtom);
const activeConvoId = useAtomValue(getActiveConvoIdAtom);
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
const isSelected = activeConvoId === conversation.id;
const onClick = () => { const onClick = () => {
historyStore.setActiveConversationId(conversationId); if (activeConvoId !== conversation.id) {
setActiveConvoId(conversation.id);
}
}; };
const conversation = historyStore.getConversationById(conversationId);
const isSelected = historyStore.activeConversationId === conversationId;
const backgroundColor = isSelected const backgroundColor = isSelected
? "bg-gray-100 dark:bg-gray-700" ? "bg-gray-100 dark:bg-gray-700"
: "bg-white dark:bg-gray-500"; : "bg-white dark:bg-gray-500";
let rightImageUrl: string | undefined; let rightImageUrl: string | undefined;
if (conversation && conversation.isWaitingForModelResponse) { if (conversationStates[conversation.id]?.waitingForResponse === true) {
rightImageUrl = "/icons/loading.svg"; rightImageUrl = "/icons/loading.svg";
} else if ( } else if (
conversation && conversation &&
conversation.product.type === AiModelType.GenerativeArt && conversation.product.type === ProductType.GenerativeArt &&
conversation.lastImageUrl && conversation.lastImageUrl &&
conversation.lastImageUrl.trim().startsWith("https://") conversation.lastImageUrl.trim().startsWith("https://")
) { ) {
@ -41,13 +53,14 @@ const HistoryItem: React.FC<Props> = observer(
return ( return (
<button <button
className={`flex flex-row items-center gap-[10px] rounded-lg p-2 ${backgroundColor}`} className={`flex flex-row mx-1 items-center gap-[10px] rounded-lg p-2 ${backgroundColor} hover:bg-hover-light`}
onClick={onClick} onClick={onClick}
> >
<img <Image
className="rounded-full aspect-square object-cover"
src={avatarUrl}
width={36} width={36}
height={36}
src={avatarUrl}
className="w-9 aspect-square rounded-full"
alt="" alt=""
/> />
<div className="flex flex-col justify-between text-sm leading-[20px] w-full"> <div className="flex flex-col justify-between text-sm leading-[20px] w-full">
@ -63,7 +76,6 @@ const HistoryItem: React.FC<Props> = observer(
{conversation?.lastTextMessage || <br className="h-5 block" />} {conversation?.lastTextMessage || <br className="h-5 block" />}
</span> </span>
</div> </div>
{send ? (
<> <>
{rightImageUrl != null ? ( {rightImageUrl != null ? (
<JanImage <JanImage
@ -74,14 +86,10 @@ const HistoryItem: React.FC<Props> = observer(
/> />
) : undefined} ) : undefined}
</> </>
) : (
<div className="w-3 h-3 rounded-full bg-blue-500"></div>
)}
</div> </div>
</div> </div>
</button> </button>
); );
} };
);
export default HistoryItem; export default HistoryItem;

View File

@ -1,58 +1,41 @@
import HistoryItem from "../HistoryItem"; import HistoryItem from "../HistoryItem";
import { observer } from "mobx-react-lite"; import { useEffect, useState } from "react";
import { useStore } from "@/_models/RootStore"; import ExpandableHeader from "../ExpandableHeader";
import Image from "next/image"; import { useAtomValue } from "jotai";
import { useState } from "react"; import { userConversationsAtom } from "@/_helpers/JotaiWrapper";
import useGetUserConversations from "@/_hooks/useGetUserConversations";
interface IHistoryListProps { const HistoryList: React.FC = () => {
searchText: string; const conversations = useAtomValue(userConversationsAtom);
} const [expand, setExpand] = useState<boolean>(true);
const HistoryList: React.FC<IHistoryListProps> = observer((props) => { const { getUserConversations } = useGetUserConversations();
const { historyStore } = useStore();
const [showHistory, setShowHistory] = useState(true); useEffect(() => {
getUserConversations();
}, []);
return ( return (
<div className="flex flex-col w-full pl-1 pt-3"> <div className="flex flex-col flex-grow pt-3 gap-2">
<button <ExpandableHeader
onClick={() => setShowHistory(!showHistory)} title="CHAT HISTORY"
className="flex items-center justify-between px-2" expanded={expand}
> onClick={() => setExpand(!expand)}
<h2 className="text-[#9CA3AF] font-bold text-[12px] leading-[12px]">
HISTORY
</h2>
<Image
className={`${showHistory ? "" : "rotate-180"}`}
src={"/icons/unicorn_angle-up.svg"}
width={24}
height={24}
alt=""
/> />
</button> <div
<div className={`flex-col gap-1 ${showHistory ? "flex" : "hidden"}`}> className={`flex flex-col gap-1 mt-1 ${!expand ? "hidden " : "block"}`}
{historyStore.conversations >
.filter( {conversations.map((convo) => (
(e) =>
props.searchText === "" ||
e.product.name
.toLowerCase()
.includes(props.searchText.toLowerCase()) ||
e.product.description
?.toLowerCase()
.includes(props.searchText.toLowerCase())
)
.sort((n1, n2) => (n2.updatedAt || 0) - (n1.updatedAt || 0))
.map(({ id, product: aiModel, updatedAt }) => (
<HistoryItem <HistoryItem
key={id} key={convo.id}
conversationId={id} conversation={convo}
avatarUrl={aiModel.avatarUrl ?? ""} avatarUrl={convo.product.avatarUrl}
name={aiModel.name} name={convo.product.name}
updatedAt={updatedAt} updatedAt={convo.updatedAt}
/> />
))} ))}
</div> </div>
</div> </div>
); );
}); };
export default HistoryList; export default HistoryList;

View File

@ -1,146 +1,23 @@
import SendButton from "../SendButton"; "use client";
import { ChangeEvent, useEffect, useState } from "react";
import { useStore } from "@/_models/RootStore";
import { AiModelType } from "@/_models/Product";
import Image from "next/image";
import { observer } from "mobx-react-lite";
import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import useSignIn from "@/_hooks/useSignIn";
import { useMutation } from "@apollo/client";
import {
CreateMessageDocument,
CreateMessageMutation,
GenerateImageMutation,
GenerateImageDocument,
} from "@/graphql";
type Props = { import BasicPromptInput from "../BasicPromptInput";
prefillPrompt: string; import BasicPromptAccessories from "../BasicPromptAccessories";
}; import { showingAdvancedPromptAtom } from "@/_helpers/JotaiWrapper";
import { useAtomValue } from "jotai";
export const InputToolbar: React.FC<Props> = observer(({ prefillPrompt }) => { const InputToolbar: React.FC = () => {
const { historyStore } = useStore(); const showingAdvancedPrompt = useAtomValue(showingAdvancedPromptAtom);
const [text, setText] = useState(prefillPrompt);
const { user } = useGetCurrentUser();
const { signInWithKeyCloak } = useSignIn();
const [createMessageMutation] = useMutation<CreateMessageMutation>( if (showingAdvancedPrompt) {
CreateMessageDocument return <div />;
);
const [imageGenerationMutation] = useMutation<GenerateImageMutation>(
GenerateImageDocument
);
useEffect(() => {
setText(prefillPrompt);
}, [prefillPrompt]);
const handleMessageChange = (event: any) => {
setText(event.target.value);
};
const onSubmitClick = () => {
if (!user) {
signInWithKeyCloak();
return;
} }
if (text.trim().length === 0) return;
historyStore.sendMessage(
createMessageMutation,
imageGenerationMutation,
text,
user.id,
user.displayName,
user.avatarUrl
);
setText("");
};
const handleKeyDown = (event: any) => {
if (event.key === "Enter") {
if (!event.shiftKey) {
event.preventDefault();
onSubmitClick();
}
}
};
let shouldDisableSubmitButton = false;
if (historyStore.getActiveConversation()?.isWaitingForModelResponse) {
shouldDisableSubmitButton = true;
}
if (text.length === 0) {
shouldDisableSubmitButton = true;
}
const onAdvancedPrompt = () => {
historyStore.toggleAdvancedPrompt();
};
const handleResize = (event: ChangeEvent<HTMLTextAreaElement>) => {
event.target.style.height = "auto";
event.target.style.height = event.target.scrollHeight + "px";
};
const shouldShowAdvancedPrompt =
historyStore.getActiveConversation()?.product?.type ===
AiModelType.ControlNet ?? false;
return ( return (
<div <div className="mx-3 mb-3 flex-none overflow-hidden shadow-sm ring-1 ring-inset ring-gray-300 rounded-lg dark:bg-gray-800">
className={`${ <BasicPromptInput />
historyStore.showAdvancedPrompt ? "hidden" : "block" <BasicPromptAccessories />
} mb-3 flex-none overflow-hidden w-full shadow-sm ring-1 ring-inset ring-gray-300 rounded-lg dark:bg-gray-800`}
>
<div className="overflow-hidden">
<label htmlFor="comment" className="sr-only">
Add your comment
</label>
<textarea
onKeyDown={handleKeyDown}
value={text}
onChange={handleMessageChange}
onInput={handleResize}
rows={2}
name="comment"
id="comment"
className="block w-full scroll resize-none border-0 bg-transparent py-1.5 text-gray-900 transition-height duration-200 placeholder:text-gray-400 sm:text-sm sm:leading-6 dark:text-white"
placeholder="Add your comment..."
/>
</div>
<div
style={{
backgroundColor: "#F8F8F8",
borderWidth: 1,
borderColor: "#D1D5DB",
}}
className="flex justify-between py-2 pl-3 pr-2 rounded-b-lg"
>
{shouldShowAdvancedPrompt && (
<button
onClick={onAdvancedPrompt}
className="flex items-center gap-1 py-[1px]"
>
<Image
src={"/icons/ic_setting.svg"}
width={20}
height={20}
alt=""
/>
<span className="text-sm leading-5 text-gray-600">Advanced</span>
</button>
)}
<div className="flex justify-end items-center space-x-1 w-full pr-3" />
<div className="flex-shrink-0">
{!shouldShowAdvancedPrompt && (
<SendButton
onClick={onSubmitClick}
disabled={shouldDisableSubmitButton}
/>
)}
</div>
</div>
</div> </div>
); );
}); };
export default InputToolbar;

View File

@ -0,0 +1,19 @@
import { setActiveConvoIdAtom } from "@/_helpers/JotaiWrapper";
import { useSetAtom } from "jotai";
import Image from "next/image";
import React from "react";
const JanLogo: React.FC = () => {
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
return (
<button
className="p-3 flex gap-[2px] items-center"
onClick={() => setActiveConvoId(undefined)}
>
<Image src={"/icons/app_icon.svg"} width={28} height={28} alt="" />
<Image src={"/icons/Jan.svg"} width={27} height={12} alt="" />
</button>
);
};
export default React.memo(JanLogo);

View File

@ -0,0 +1,24 @@
"use client";
import { useAtomValue } from "jotai";
import AdvancedPrompt from "../AdvancedPrompt";
import CompactSideBar from "../CompactSideBar";
import LeftSidebar from "../LeftSidebar";
import { showingAdvancedPromptAtom } from "@/_helpers/JotaiWrapper";
const LeftContainer: React.FC = () => {
const isShowingAdvPrompt = useAtomValue(showingAdvancedPromptAtom);
if (isShowingAdvPrompt) {
return (
<div className="flex h-screen">
<CompactSideBar />
<AdvancedPrompt />
</div>
);
}
return <LeftSidebar />;
};
export default LeftContainer;

View File

@ -0,0 +1,20 @@
import React from "react";
import SearchBar from "../SearchBar";
import ShortcutList from "../ShortcutList";
import HistoryList from "../HistoryList";
import DiscordContainer from "../DiscordContainer";
import JanLogo from "../JanLogo";
const LeftSidebar: React.FC = () => (
<div className="hidden h-screen lg:flex flex-col lg:inset-y-0 lg:w-72 lg:flex-col flex-shrink-0 overflow-hidden border-r border-gray-200 dark:bg-gray-800">
<JanLogo />
<div className="flex flex-col flex-1 gap-3 overflow-x-hidden">
<SearchBar />
<ShortcutList />
<HistoryList />
</div>
<DiscordContainer />
</div>
);
export default LeftSidebar;

View File

@ -0,0 +1,27 @@
"use client";
import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import useSignIn from "@/_hooks/useSignIn";
const LoginButton: React.FC = () => {
const { signInWithKeyCloak } = useSignIn();
const { user, loading } = useGetCurrentUser();
if (loading || user) {
return <div />;
}
return (
<div className="hidden lg:block">
<button
onClick={signInWithKeyCloak}
type="button"
className="rounded-md bg-indigo-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Login
</button>
</div>
);
};
export default LoginButton;

View File

@ -0,0 +1,13 @@
import ChatBody from "../ChatBody";
import InputToolbar from "../InputToolbar";
import MainChatHeader from "../MainChatHeader";
const MainChat: React.FC = () => (
<div className="flex flex-col h-full w-full">
<MainChatHeader />
<ChatBody />
<InputToolbar />
</div>
);
export default MainChat;

View File

@ -0,0 +1,11 @@
import ModelMenu from "../ModelMenu";
import UserToolbar from "../UserToolbar";
const MainChatHeader: React.FC = () => (
<div className="flex w-full px-3 justify-between py-1 border-b border-gray-200 shadow-sm dark:bg-gray-950">
<UserToolbar />
<ModelMenu />
</div>
);
export default MainChatHeader;

View File

@ -8,9 +8,8 @@ type Props = {
register: UseFormRegister<FieldValues>; register: UseFormRegister<FieldValues>;
}; };
export const MenuAdvancedPrompt: React.FC<Props> = ({ register }) => { export const MenuAdvancedPrompt: React.FC<Props> = ({ register }) => (
return ( <div className="flex flex-col flex-1 p-3 gap-[10px] overflow-x-hidden scroll">
<div className="flex flex-col">
<AdvancedPromptText register={register} /> <AdvancedPromptText register={register} />
<hr className="my-5" /> <hr className="my-5" />
<AdvancedPromptImageUpload register={register} /> <AdvancedPromptImageUpload register={register} />
@ -20,4 +19,3 @@ export const MenuAdvancedPrompt: React.FC<Props> = ({ register }) => {
<AdvancedPromptGenerationParams /> <AdvancedPromptGenerationParams />
</div> </div>
); );
};

View File

@ -2,12 +2,11 @@ import Link from "next/link";
import { Popover, Transition } from "@headlessui/react"; import { Popover, Transition } from "@headlessui/react";
import { Fragment } from "react"; import { Fragment } from "react";
import useGetCurrentUser from "@/_hooks/useGetCurrentUser"; import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import { useSetAtom } from "jotai";
import { showConfirmSignOutModalAtom } from "@/_helpers/JotaiWrapper";
type Props = { export const MenuHeader: React.FC = () => {
onLogOutClick: () => void; const setShowConfirmSignOutModal = useSetAtom(showConfirmSignOutModalAtom);
};
export const MenuHeader: React.FC<Props> = ({ onLogOutClick }) => {
const { user } = useGetCurrentUser(); const { user } = useGetCurrentUser();
if (!user) { if (!user) {
@ -35,7 +34,7 @@ export const MenuHeader: React.FC<Props> = ({ onLogOutClick }) => {
</div> </div>
<hr /> <hr />
<button <button
onClick={onLogOutClick} onClick={() => setShowConfirmSignOutModal(true)}
className="px-4 py-3 text-sm w-full text-left text-gray-700" className="px-4 py-3 text-sm w-full text-left text-gray-700"
> >
Sign Out Sign Out

View File

@ -21,7 +21,7 @@ const MobileDownload = () => {
{/** Buttons */} {/** Buttons */}
<div className="flex w-full mt-4 justify-between"> <div className="flex w-full mt-4 justify-between">
<a <a
href={process.env.NEXT_PUBLIC_DOWNLOAD_APP_IOS || "#"} href={process.env.NEXT_PUBLIC_DOWNLOAD_APP_IOS || ""}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="w-[48%]" className="w-[48%]"
@ -42,7 +42,7 @@ const MobileDownload = () => {
</a> </a>
<a <a
href={process.env.NEXT_PUBLIC_DOWNLOAD_APP_ANDROID || "#"} href={process.env.NEXT_PUBLIC_DOWNLOAD_APP_ANDROID || ""}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="w-[48%]" className="w-[48%]"

View File

@ -1,15 +1,21 @@
import React from "react"; import React, { useRef } from "react";
import { Dialog } from "@headlessui/react"; import { Dialog } from "@headlessui/react";
import { XMarkIcon } from "@heroicons/react/24/outline"; import { XMarkIcon } from "@heroicons/react/24/outline";
import Image from "next/image"; import Image from "next/image";
import { showingMobilePaneAtom } from "@/_helpers/JotaiWrapper";
import { useAtom } from "jotai";
type Props = { const MobileMenuPane: React.FC = () => {
open: boolean; const [show, setShow] = useAtom(showingMobilePaneAtom);
setOpen: (open: boolean) => void; let loginRef = useRef(null);
};
const MobileMenuPane: React.FC<Props> = ({ open, setOpen }) => ( return (
<Dialog as="div" className="md:hidden" open={open} onClose={setOpen}> <Dialog
as="div"
open={show}
initialFocus={loginRef}
onClose={() => setShow(false)}
>
<div className="fixed inset-0 z-10" /> <div className="fixed inset-0 z-10" />
<Dialog.Panel className="fixed inset-y-0 right-0 z-10 w-full overflow-y-auto bg-white px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10"> <Dialog.Panel className="fixed inset-y-0 right-0 z-10 w-full overflow-y-auto bg-white px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -26,7 +32,7 @@ const MobileMenuPane: React.FC<Props> = ({ open, setOpen }) => (
<button <button
type="button" type="button"
className="-m-2.5 rounded-md p-2.5 text-gray-700" className="-m-2.5 rounded-md p-2.5 text-gray-700"
onClick={() => setOpen(false)} onClick={() => setShow(false)}
> >
<span className="sr-only">Close menu</span> <span className="sr-only">Close menu</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" /> <XMarkIcon className="h-6 w-6" aria-hidden="true" />
@ -37,6 +43,7 @@ const MobileMenuPane: React.FC<Props> = ({ open, setOpen }) => (
<div className="space-y-2 py-6" /> <div className="space-y-2 py-6" />
<div className="py-6"> <div className="py-6">
<a <a
ref={loginRef}
href="#" href="#"
className="-mx-3 block rounded-lg px-3 py-2.5 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50" className="-mx-3 block rounded-lg px-3 py-2.5 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
> >
@ -48,5 +55,6 @@ const MobileMenuPane: React.FC<Props> = ({ open, setOpen }) => (
</Dialog.Panel> </Dialog.Panel>
</Dialog> </Dialog>
); );
};
export default MobileMenuPane; export default MobileMenuPane;

View File

@ -1,36 +1,9 @@
import { FC, useRef } from "react";
import OverviewPane from "../OverviewPane"; import OverviewPane from "../OverviewPane";
import { observer } from "mobx-react-lite";
import { useStore } from "@/_models/RootStore";
import { Draggable } from "../Draggable";
type Props = { const ModelDetailSideBar: React.FC = () => (
onPromptClick?: (prompt: string) => void; <div className="flex w-[473px] h-full border-l-[1px] border-[#E5E7EB]">
}; <OverviewPane />
export const ModelDetailSideBar: FC<Props> = observer(({ onPromptClick }) => {
const ref = useRef<HTMLDivElement>(null);
const { historyStore } = useStore();
const conversation = useStore().historyStore.getActiveConversation();
return (
<div
style={historyStore.showModelDetail ? { width: "473px" } : {}}
ref={ref}
className={`${
historyStore.showModelDetail ? "w-[473px]" : "hidden"
} flex flex-col gap-3 h-full p-3 relative pb-3 border-l-[1px] border-[#E5E7EB]`}
>
<Draggable targetRef={ref} />
<div className="flex-col h-full gap-3 flex flex-1">
<OverviewPane
slug={conversation?.product.id ?? ""}
onPromptClick={onPromptClick}
description={conversation?.product.description}
technicalURL={conversation?.product.modelUrl}
technicalVersion={conversation?.product.modelVersion}
/>
</div>
</div> </div>
); );
});
export default ModelDetailSideBar;

View File

@ -1,36 +1,39 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import { useStore } from "@/_models/RootStore"; import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { useCallback } from "react"; import {
import { observer } from "mobx-react-lite"; currentProductAtom,
showConfirmDeleteConversationModalAtom,
showingProductDetailAtom,
} from "@/_helpers/JotaiWrapper";
import { PlusIcon, TrashIcon } from "@heroicons/react/24/outline";
import useCreateConversation from "@/_hooks/useCreateConversation";
type Props = { const ModelMenu: React.FC = () => {
onDeleteClick: () => void; const currentProduct = useAtomValue(currentProductAtom);
onCreateConvClick: () => void; const [active, setActive] = useAtom(showingProductDetailAtom);
const { requestCreateConvo } = useCreateConversation();
const setShowConfirmDeleteConversationModal = useSetAtom(
showConfirmDeleteConversationModalAtom
);
const onCreateConvoClick = () => {
if (!currentProduct) return;
requestCreateConvo(currentProduct, true);
}; };
const ModelMenu: React.FC<Props> = observer(
({ onDeleteClick, onCreateConvClick }) => {
const { historyStore } = useStore();
const onModelInfoClick = useCallback(() => {
historyStore.toggleModelDetail();
}, []);
return ( return (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button onClick={onCreateConvClick}> <button onClick={() => onCreateConvoClick()}>
<Image src="/icons/unicorn_plus.svg" width={24} height={24} alt="" /> <PlusIcon width={24} height={24} color="#9CA3AF" />
</button> </button>
<button onClick={onDeleteClick}> <button onClick={() => setShowConfirmDeleteConversationModal(true)}>
<Image src="/icons/unicorn_trash.svg" width={24} height={24} alt="" /> <TrashIcon width={24} height={24} color="#9CA3AF" />
</button> </button>
<button onClick={onModelInfoClick}> <button onClick={() => setActive(!active)}>
<Image <Image
src={ src={active ? "/icons/ic_sidebar_fill.svg" : "/icons/ic_sidebar.svg"}
historyStore.showModelDetail
? "/icons/ic_sidebar_fill.svg"
: "/icons/ic_sidebar.svg"
}
width={24} width={24}
height={24} height={24}
alt="" alt=""
@ -38,7 +41,6 @@ const ModelMenu: React.FC<Props> = observer(
</button> </button>
</div> </div>
); );
} };
);
export default ModelMenu; export default ModelMenu;

View File

@ -1,54 +0,0 @@
import React from "react";
import Slider from "../Slider";
import ConversationalList from "../ConversationalList";
import GenerateImageList from "../GenerateImageList";
import { GetProductsQuery, GetProductsDocument } from "@/graphql";
import { useQuery } from "@apollo/client";
import Image from "next/image";
const NewChatBlankState: React.FC = () => {
// This can be achieved by separating queries using GetProductsByCollectionSlugQuery
const { loading, data } = useQuery<GetProductsQuery>(GetProductsDocument, {
variables: { slug: "conversational" },
});
const featured = [...(data?.products ?? [])]
.sort(() => 0.5 - Math.random())
.slice(0, 3);
const conversational =
data?.products.filter((e) =>
e.product_collections.some((c) =>
c.collections.some((s) => s.slug == "conversational")
)
) ?? [];
const generativeArts =
data?.products.filter((e) =>
e.product_collections.some((c) =>
c.collections.some((s) => s.slug == "text-to-image")
)
) ?? [];
if (loading) {
return (
<div className="w-full flex flex-row justify-center items-center">
<Image src="/icons/loading.svg" width={32} height={32} alt="loading" />
</div>
);
}
if (!data || data.products.length === 0) {
return <div></div>;
}
return (
<div className="bg-gray-100 px-6 pt-8 w-full h-full overflow-y-scroll scroll">
<Slider products={featured} />
<ConversationalList products={conversational} />
<GenerateImageList products={generativeArts} />
</div>
);
};
export default NewChatBlankState;

View File

@ -1,94 +1,58 @@
import { GetProductPromptsDocument, GetProductPromptsQuery } from "@/graphql"; "use client";
import { useQuery } from "@apollo/client";
import { useLayoutEffect, useRef, useState } from "react";
type Props = { import { useAtomValue } from "jotai";
slug: string; import TryItYourself from "./TryItYourself";
description?: string | null; import React from "react";
technicalVersion?: string | null; import { currentProductAtom } from "@/_helpers/JotaiWrapper";
technicalURL?: string | null;
onPromptClick?: (prompt: string) => void;
inAIModel?: number;
};
const OverviewPane: React.FC<Props> = ({ const OverviewPane: React.FC = () => {
slug, const product = useAtomValue(currentProductAtom);
description,
technicalVersion,
technicalURL,
onPromptClick,
inAIModel,
}) => {
const ref = useRef<HTMLDivElement>(null);
const [read, setRead] = useState<boolean>(true);
const [height, setHeight] = useState<number>(0);
const { loading, error, data } = useQuery<GetProductPromptsQuery>(
GetProductPromptsDocument,
{
variables: { productSlug: slug },
}
);
useLayoutEffect(() => {
if (!ref.current) return;
setHeight(ref.current?.offsetHeight);
}, [read]);
return ( return (
<div <div className="scroll overflow-y-auto">
className="w-full flex flex-auto flex-col gap-6 overflow-x-hidden scroll" <div className="flex flex-col flex-grow gap-6 m-3">
ref={ref} <AboutProductItem
style={!inAIModel ? { height: `${height}px` } : { height: "100%" }} title={"About this AI"}
> value={product?.description ?? ""}
<div className="flex flex-col gap-2 items-start"> />
<h2 className="text-black font-bold">About this AI</h2> <SmallItem title={"Model Version"} value={product?.version ?? ""} />
<p className={`text-[#6B7280] ${read ? "hidden-text-model" : ""}`}> <div className="flex flex-col">
{description}
</p>
<button
onClick={() => setRead(!read)}
className="text-[#1F2A37] font-bold"
>
{read ? "read more" : "read less"}
</button>
</div>
<div className="flex flex-col gap-4 tracking-[-0.4px] leading-[22px] text-base">
<div className="flex flex-col gap-1">
<span className="text-[#6B7280] ">Model Version</span>
<span className="font-semibold">{technicalVersion}</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-[#6B7280]">Model URL</span> <span className="text-[#6B7280]">Model URL</span>
<a <a
className="text-[#1C64F2] break-all pr-10" className="text-[#1C64F2]"
href={technicalURL || "#"} href={product?.modelUrl ?? "#"}
target="_blank_" target="_blank_"
> >
{technicalURL} {product?.modelUrl}
</a> </a>
</div> </div>
</div> <TryItYourself />
<div className="flex flex-col gap-4 tracking-[-0.4px] leading-[22px] text-base">
<h2 className="font-bold">Try it yourself</h2>
<ul className="border-[1px] border-[#D1D5DB] rounded-[12px]">
{data?.prompts.map((prompt, index) => {
const showBorder = index !== data?.prompts.length - 1;
return (
<button
onClick={() => onPromptClick?.(prompt.content ?? "")}
key={prompt.slug}
className={`text-sm text-gray-500 leading-[20px] flex gap-[10px] border-b-[${
showBorder ? "1" : "0"
}px] border-[#E5E7EB] hover:text-blue-400 text-left p-3 w-full`}
>
{prompt.content}
</button>
);
})}
</ul>
</div> </div>
</div> </div>
); );
}; };
export default OverviewPane; export default OverviewPane;
type Props = {
title: string;
value: string;
};
const AboutProductItem: React.FC<Props> = ({ title, value }) => {
return (
<div className="flex flex-col items-start">
<h2 className="text-black font-bold">{title}</h2>
<p className="text-[#6B7280]">{value}</p>
</div>
);
};
const SmallItem: React.FC<Props> = ({ title, value }) => {
return (
<div className="flex flex-col">
<span className="text-[#6B7280] ">{title}</span>
<span className="font-semibold">{value}</span>
</div>
);
};

View File

@ -0,0 +1,25 @@
import React from "react";
type Props = {
title: string;
onClick: () => void;
fullWidth?: boolean;
};
const PrimaryButton: React.FC<Props> = ({
title,
onClick,
fullWidth = false,
}) => (
<button
onClick={onClick}
type="button"
className={`rounded-md bg-indigo-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-50 ${
fullWidth ? "flex-1 " : ""
}}`}
>
{title}
</button>
);
export default PrimaryButton;

View File

@ -0,0 +1,29 @@
import React from "react";
import Slider from "../Slider";
import ConversationalList from "../ConversationalList";
import GenerateImageList from "../GenerateImageList";
import Image from "next/image";
import useGetProducts from "@/_hooks/useGetProducts";
const ProductOverview: React.FC = () => {
const { loading, featured, conversational, generativeArts } =
useGetProducts();
if (loading) {
return (
<div className="w-full flex flex-grow flex-row justify-center items-center">
<Image src="/icons/loading.svg" width={32} height={32} alt="loading" />
</div>
);
}
return (
<div className="bg-gray-100 overflow-y-auto flex-grow scroll">
<Slider products={featured} />
<ConversationalList products={conversational} />
<GenerateImageList products={generativeArts} />
</div>
);
};
export default ProductOverview;

View File

@ -0,0 +1,14 @@
import ChatContainer from "../ChatContainer";
import Header from "../Header";
import MainChat from "../MainChat";
const RightContainer = () => (
<div className="flex flex-col flex-1 h-screen">
<Header />
<ChatContainer>
<MainChat />
</ChatContainer>
</div>
);
export default RightContainer;

View File

@ -1,27 +1,25 @@
import { Instance } from "mobx-state-tree";
import { Product } from "@/_models/Product";
import JanWelcomeTitle from "../JanWelcomeTitle"; import JanWelcomeTitle from "../JanWelcomeTitle";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { GetProductPromptsDocument, GetProductPromptsQuery } from "@/graphql"; import { GetProductPromptsDocument, GetProductPromptsQuery } from "@/graphql";
import { Product } from "@/_models/Product";
import { useSetAtom } from "jotai";
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
type Props = { type Props = {
model: Instance<typeof Product>; product: Product;
onPromptSelected: (prompt: string) => void;
}; };
const SampleLlmContainer: React.FC<Props> = ({ model, onPromptSelected }) => { const SampleLlmContainer: React.FC<Props> = ({ product }) => {
const { loading, error, data } = useQuery<GetProductPromptsQuery>( const setCurrentPrompt = useSetAtom(currentPromptAtom);
GetProductPromptsDocument, const { data } = useQuery<GetProductPromptsQuery>(GetProductPromptsDocument, {
{ variables: { productSlug: product.slug },
variables: { productSlug: model.id }, });
}
);
return ( return (
<div className="flex flex-col max-w-sm flex-shrink-0 gap-9 items-center pt-6 mx-auto"> <div className="flex flex-col max-w-sm flex-shrink-0 gap-9 items-center pt-6 mx-auto">
<JanWelcomeTitle <JanWelcomeTitle
title={model.name} title={product.name}
description={model.description ?? ""} description={product.description ?? ""}
/> />
<div className="flex flex-col"> <div className="flex flex-col">
<h2 className="font-semibold text-xl leading-6 tracking-[-0.4px] mb-5"> <h2 className="font-semibold text-xl leading-6 tracking-[-0.4px] mb-5">
@ -30,7 +28,7 @@ const SampleLlmContainer: React.FC<Props> = ({ model, onPromptSelected }) => {
<div className="flex flex-col"> <div className="flex flex-col">
{data?.prompts.map((item) => ( {data?.prompts.map((item) => (
<button <button
onClick={() => onPromptSelected(item.content ?? "")} onClick={() => setCurrentPrompt(item.content ?? "")}
key={item.slug} key={item.slug}
className="rounded p-2 hover:bg-[#0000000F] text-xs leading-[18px] text-gray-500 text-left" className="rounded p-2 hover:bg-[#0000000F] text-xs leading-[18px] text-gray-500 text-left"
> >

View File

@ -1,20 +1,26 @@
import Image from "next/image"; import { searchAtom } from "@/_helpers/JotaiWrapper";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { useSetAtom } from "jotai";
const SearchBar: React.FC = () => {
const setText = useSetAtom(searchAtom);
interface ISearchBarProps {
onTextChanged: (text: string) => void;
}
const SearchBar: React.FC<ISearchBarProps> = (props) => {
return ( return (
<div className="relative mt-3 flex items-center w-full"> <div className="relative mx-3 mt-3 flex items-center">
<div className="absolute top-0 left-2 h-full flex items-center"> <div className="absolute top-0 left-2 h-full flex items-center">
<Image src={"/icons/search.svg"} width={16} height={16} alt="" /> <MagnifyingGlassIcon
width={16}
height={16}
color="#3C3C43"
opacity={0.6}
/>
</div> </div>
<input <input
type="text" type="text"
name="search" name="search"
id="search" id="search"
placeholder="Search (⌘K)" placeholder="Search (⌘K)"
onChange={(e) => props.onTextChanged(e.target.value)} onChange={(e) => setText(e.target.value)}
className="block w-full rounded-md border-0 py-1.5 pl-8 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" className="block w-full rounded-md border-0 py-1.5 pl-8 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/> />
</div> </div>

View File

@ -0,0 +1,18 @@
type Props = {
title: string;
onClick: () => void;
disabled?: boolean;
};
const SecondaryButton: React.FC<Props> = ({ title, onClick, disabled }) => (
<button
disabled={disabled}
type="button"
onClick={onClick}
className="rounded-full bg-white px-2.5 py-1 text-xs font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
{title}
</button>
);
export default SecondaryButton;

View File

@ -1,11 +1,19 @@
import {
currentConvoStateAtom,
currentPromptAtom,
} from "@/_helpers/JotaiWrapper";
import useSendChatMessage from "@/_hooks/useSendChatMessage";
import { useAtomValue } from "jotai";
import Image from "next/image"; import Image from "next/image";
type Props = { const SendButton: React.FC = () => {
onClick: () => void; const currentPrompt = useAtomValue(currentPromptAtom);
disabled?: boolean; const currentConvoState = useAtomValue(currentConvoStateAtom);
}; const { sendChatMessage } = useSendChatMessage();
const isWaitingForResponse = currentConvoState?.waitingForResponse ?? false;
const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse;
const SendButton: React.FC<Props> = ({ onClick, disabled = false }) => {
const enabledStyle = { const enabledStyle = {
backgroundColor: "#FACA15", backgroundColor: "#FACA15",
}; };
@ -16,7 +24,7 @@ const SendButton: React.FC<Props> = ({ onClick, disabled = false }) => {
return ( return (
<button <button
onClick={onClick} onClick={sendChatMessage}
style={disabled ? disabledStyle : enabledStyle} style={disabled ? disabledStyle : enabledStyle}
type="submit" type="submit"
className="p-2 gap-[10px] inline-flex items-center rounded-[12px] text-sm font-semibold shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" className="p-2 gap-[10px] inline-flex items-center rounded-[12px] text-sm font-semibold shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"

View File

@ -1,9 +1,10 @@
import React from "react"; import React from "react";
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from "@/_hooks/useCreateConversation";
import { ProductDetailFragment } from "@/graphql"; import Image from "next/image";
import { Product } from "@/_models/Product";
type Props = { type Props = {
product: ProductDetailFragment; product: Product;
}; };
const ShortcutItem: React.FC<Props> = ({ product }) => { const ShortcutItem: React.FC<Props> = ({ product }) => {
@ -14,17 +15,22 @@ const ShortcutItem: React.FC<Props> = ({ product }) => {
}; };
return ( return (
<button className="flex items-center gap-2" onClick={onClickHandler}> <button
{product.image_url && ( className="flex items-center gap-2 mx-1 p-2"
<img onClick={onClickHandler}
src={product.image_url} >
{product.avatarUrl && (
<Image
width={36}
height={36}
src={product.avatarUrl}
className="w-9 aspect-square rounded-full" className="w-9 aspect-square rounded-full"
alt="" alt=""
/> />
)} )}
<div className="flex flex-col text-sm leading-[20px]"> <span className="text-gray-900 dark:text-white font-normal text-sm">
<span className="text-[#111928] dark:text-white">{product.name}</span> {product.name}
</div> </span>
</button> </button>
); );
}; };

View File

@ -1,42 +1,52 @@
import Image from "next/image"; "use client";
import React from "react";
import React, { useEffect, useState } from "react";
import ShortcutItem from "../ShortcutItem"; import ShortcutItem from "../ShortcutItem";
import { observer } from "mobx-react-lite"; import { GetProductsDocument, GetProductsQuery } from "@/graphql";
import { ProductDetailFragment } from "@/graphql"; import ExpandableHeader from "../ExpandableHeader";
import { useQuery } from "@apollo/client";
import { useAtomValue } from "jotai";
import { searchAtom } from "@/_helpers/JotaiWrapper";
import { Product, toProduct } from "@/_models/Product";
type Props = { const ShortcutList: React.FC = () => {
products: ProductDetailFragment[]; const searchText = useAtomValue(searchAtom);
}; const { data } = useQuery<GetProductsQuery>(GetProductsDocument);
const [expand, setExpand] = useState<boolean>(true);
const [featuredProducts, setFeaturedProducts] = useState<Product[]>([]);
const ShortcutList: React.FC<Props> = observer(({ products }) => { useEffect(() => {
const [expand, setExpand] = React.useState<boolean>(true); if (data?.products) {
const products: Product[] = data.products.map((p) => toProduct(p));
setFeaturedProducts(
[...(products || [])]
.sort(() => 0.5 - Math.random())
.slice(0, 3)
.filter(
(e) =>
searchText === "" ||
e.name.toLowerCase().includes(searchText.toLowerCase())
) || []
);
}
}, [data?.products, searchText]);
return ( return (
<div className="flex flex-col w-full px-3 pt-3"> <div className="flex flex-col mt-6 gap-2">
<button <ExpandableHeader
title="START A NEW CHAT"
expanded={expand}
onClick={() => setExpand(!expand)} onClick={() => setExpand(!expand)}
className="flex justify-between items-center"
>
<h2 className="text-[#9CA3AF] font-bold text-xs leading-[12px]">
SHORTCUTS
</h2>
<Image
className={`${expand ? "" : "rotate-180"}`}
src={"/icons/unicorn_angle-up.svg"}
width={24}
height={24}
alt=""
/> />
</button> {expand ? (
<div <div className="flex flex-row mx-1 items-center rounded-lg hover:bg-hover-light">
className={`flex flex-col gap-3 py-2 ${!expand ? "hidden " : "block"}`} {featuredProducts.map((product) => (
>
{products.map((product) => (
<ShortcutItem key={product.slug} product={product} /> <ShortcutItem key={product.slug} product={product} />
))} ))}
</div> </div>
) : null}
</div> </div>
); );
}); };
export default ShortcutList; export default ShortcutList;

View File

@ -1,109 +0,0 @@
"use client";
import React, { useEffect, useState } from "react";
import SearchBar from "../SearchBar";
import ShortcutList from "../ShortcutList";
import HistoryList from "../HistoryList";
import { observer } from "mobx-react-lite";
import { useStore } from "@/_models/RootStore";
import Image from "next/image";
import { usePathname } from "next/navigation";
import useGetUserConversations from "@/_hooks/useGetUserConversations";
import DiscordContainer from "../DiscordContainer";
import { useQuery } from "@apollo/client";
import {
GetProductsQuery,
GetProductsDocument,
ProductDetailFragment,
} from "@/graphql";
import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
export const SidebarLeft: React.FC = observer(() => {
const router = usePathname();
const [searchText, setSearchText] = useState("");
const { user } = useGetCurrentUser();
const { getUserConversations } = useGetUserConversations();
const [featuredProducts, setFeaturedProducts] = useState<
ProductDetailFragment[]
>([]);
const { historyStore } = useStore();
const navigation = ["pricing", "docs", "about"];
const { loading, error, data } =
useQuery<GetProductsQuery>(GetProductsDocument);
const checkRouter = () =>
navigation.map((item) => router?.includes(item)).includes(true);
useEffect(() => {
if (user) {
const createConversationAndActive = async () => {
await getUserConversations(user);
};
createConversationAndActive();
}
}, [user]);
useEffect(() => {
if (data?.products) {
setFeaturedProducts(
[...(data.products || [])]
.sort(() => 0.5 - Math.random())
.slice(0, 3)
.filter(
(e) =>
searchText === "" ||
e.name.toLowerCase().includes(searchText.toLowerCase())
) || []
);
}
}, [data?.products, searchText]);
const onLogoClick = () => {
historyStore.clearActiveConversationId();
};
const onSearching = (text: string) => {
setSearchText(text);
};
return (
<div
className={`${
historyStore.showAdvancedPrompt ? "lg:hidden" : "lg:flex"
} ${
checkRouter() ? "lg:hidden" : "lg:block"
} hidden lg:inset-y-0 lg:w-72 lg:flex-col flex-shrink-0 overflow-hidden border-r border-gray-200 dark:bg-gray-800`}
>
<div className="h-full flex grow flex-col overflow-hidden">
<button className="p-3 flex gap-3" onClick={onLogoClick}>
<div className="flex gap-[2px] items-center">
<Image src={"/icons/app_icon.svg"} width={28} height={28} alt="" />
<Image src={"/icons/Jan.svg"} width={27} height={12} alt="" />
</div>
</button>
<div className="flex flex-col gap-3 overflow-x-hidden h-full">
<div className="flex items-center px-3">
<SearchBar onTextChanged={onSearching} />
</div>
<div className="flex flex-col h-full overflow-x-hidden scroll gap-3">
{data && <ShortcutList products={featuredProducts} />}
{loading && (
<div className="w-full flex flex-row justify-center items-center">
<Image
src="/icons/loading.svg"
width={32}
height={32}
alt="loading"
/>
</div>
)}
<HistoryList searchText={searchText} />
</div>
</div>
</div>
<div className="flex-grow">
<DiscordContainer />
</div>
</div>
);
});

View File

@ -2,15 +2,6 @@ import Image from "next/image";
import JanImage from "../JanImage"; import JanImage from "../JanImage";
import { displayDate } from "@/_utils/datetime"; import { displayDate } from "@/_utils/datetime";
import Link from "next/link"; import Link from "next/link";
import { useStore } from "@/_models/RootStore";
import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import {
CreateMessageMutation,
CreateMessageDocument,
GenerateImageMutation,
GenerateImageDocument,
} from "@/graphql";
import { useMutation } from "@apollo/client";
type Props = { type Props = {
avatarUrl?: string; avatarUrl?: string;
@ -27,31 +18,7 @@ const SimpleImageMessage: React.FC<Props> = ({
text, text,
createdAt, createdAt,
}) => { }) => {
const { historyStore } = useStore(); // TODO handle regenerate image case
const { user } = useGetCurrentUser();
const [createMessageMutation] = useMutation<CreateMessageMutation>(
CreateMessageDocument
);
const [imageGenerationMutation] = useMutation<GenerateImageMutation>(
GenerateImageDocument
);
const onRegenerate = () => {
if (!user) {
// TODO: we should show an error here
return;
}
historyStore.sendMessage(
createMessageMutation,
imageGenerationMutation,
text ?? "",
user.id,
senderName,
avatarUrl
);
};
return ( return (
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<img <img
@ -88,7 +55,7 @@ const SimpleImageMessage: React.FC<Props> = ({
</Link> </Link>
<button <button
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-[12px]" className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-[12px]"
onClick={onRegenerate} // onClick={() => sendChatMessage()}
> >
<Image src="/icons/refresh.svg" width={16} height={16} alt="" /> <Image src="/icons/refresh.svg" width={16} height={16} alt="" />
<span className="leading-[20px] text-[14px] text-[#111928]"> <span className="leading-[20px] text-[14px] text-[#111928]">

View File

@ -2,9 +2,10 @@ import React from "react";
import { displayDate } from "@/_utils/datetime"; import { displayDate } from "@/_utils/datetime";
import { TextCode } from "../TextCode"; import { TextCode } from "../TextCode";
import { getMessageCode } from "@/_utils/message"; import { getMessageCode } from "@/_utils/message";
import Image from "next/image";
type Props = { type Props = {
avatarUrl?: string; avatarUrl: string;
senderName: string; senderName: string;
createdAt: number; createdAt: number;
text?: string; text?: string;
@ -15,10 +16,9 @@ const SimpleTextMessage: React.FC<Props> = ({
createdAt, createdAt,
avatarUrl = "", avatarUrl = "",
text = "", text = "",
}) => { }) => (
return ( <div className="flex items-start gap-2 ml-3">
<div className="flex items-start gap-2"> <Image
<img
className="rounded-full" className="rounded-full"
src={avatarUrl} src={avatarUrl}
width={32} width={32}
@ -44,13 +44,13 @@ const SimpleTextMessage: React.FC<Props> = ({
</div> </div>
)) ))
) : ( ) : (
<p className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]"> <p
{text} className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]"
</p> dangerouslySetInnerHTML={{ __html: text }}
/>
)} )}
</div> </div>
</div> </div>
); );
};
export default React.memo(SimpleTextMessage); export default React.memo(SimpleTextMessage);

View File

@ -1,13 +1,13 @@
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from "@/_hooks/useCreateConversation";
import { ProductDetailFragment } from "@/graphql"; import { Product } from "@/_models/Product";
import Image from "next/image"; import Image from "next/image";
type Props = { type Props = {
product: ProductDetailFragment; product: Product;
}; };
const Slide: React.FC<Props> = ({ product }) => { const Slide: React.FC<Props> = ({ product }) => {
const { name, image_url, description } = product; const { name, avatarUrl, description } = product;
const { requestCreateConvo } = useCreateConversation(); const { requestCreateConvo } = useCreateConversation();
const onClick = () => { const onClick = () => {
@ -17,9 +17,10 @@ const Slide: React.FC<Props> = ({ product }) => {
return ( return (
<div className="w-full embla__slide h-[435px] relative"> <div className="w-full embla__slide h-[435px] relative">
<Image <Image
className="object-cover w-full h-full embla__slide__img" className="w-full h-auto embla__slide__img"
src={image_url ?? ""} src={avatarUrl}
layout="fill" fill
priority
alt="" alt=""
/> />
<div className="absolute bg-[rgba(0,0,0,0.7)] w-full text-white bottom-0 right-0"> <div className="absolute bg-[rgba(0,0,0,0.7)] w-full text-white bottom-0 right-0">

View File

@ -2,10 +2,10 @@ import { FC, useCallback, useEffect, useState } from "react";
import Slide from "../Slide"; import Slide from "../Slide";
import useEmblaCarousel, { EmblaCarouselType } from "embla-carousel-react"; import useEmblaCarousel, { EmblaCarouselType } from "embla-carousel-react";
import { NextButton, PrevButton } from "../ButtonSlider"; import { NextButton, PrevButton } from "../ButtonSlider";
import { ProductDetailFragment } from "@/graphql"; import { Product } from "@/_models/Product";
type Props = { type Props = {
products: ProductDetailFragment[]; products: Product[];
}; };
const Slider: FC<Props> = ({ products }) => { const Slider: FC<Props> = ({ products }) => {
@ -35,12 +35,12 @@ const Slider: FC<Props> = ({ products }) => {
}, [emblaApi, onSelect]); }, [emblaApi, onSelect]);
return ( return (
<div className="embla rounded-lg overflow-hidden relative"> <div className="embla rounded-lg overflow-hidden relative mt-6 mx-6">
<div className="embla__viewport" ref={emblaRef}> <div className="embla__viewport" ref={emblaRef}>
<div className="embla__container"> <div className="embla__container">
{products.map((product) => { {products.map((product) => (
return <Slide key={product.slug} product={product} />; <Slide key={product.slug} product={product} />
})} ))}
</div> </div>
</div> </div>
<div className="embla__buttons"> <div className="embla__buttons">

View File

@ -1,19 +1,13 @@
import React, { useEffect } from "react"; import React from "react";
import { displayDate } from "@/_utils/datetime"; import { displayDate } from "@/_utils/datetime";
import { useStore } from "@/_models/RootStore"; import { TextCode } from "../TextCode";
import { StreamingText, useTextBuffer } from "nextjs-openai"; import { getMessageCode } from "@/_utils/message";
import { MessageSenderType, MessageStatus } from "@/_models/ChatMessage"; import Image from "next/image";
import { Role } from "@/_models/History"; import { useAtom } from "jotai";
import { useMutation } from "@apollo/client"; import { currentStreamingMessageAtom } from "@/_helpers/JotaiWrapper";
import { OpenAI } from "openai-streams";
import {
UpdateMessageDocument,
UpdateMessageMutation,
UpdateMessageMutationVariables,
} from "@/graphql";
type Props = { type Props = {
id?: string; id: string;
avatarUrl?: string; avatarUrl?: string;
senderName: string; senderName: string;
createdAt: number; createdAt: number;
@ -25,67 +19,13 @@ const StreamTextMessage: React.FC<Props> = ({
senderName, senderName,
createdAt, createdAt,
avatarUrl = "", avatarUrl = "",
text = "",
}) => { }) => {
const [data, setData] = React.useState<any | undefined>(); const [message, _] = useAtom(currentStreamingMessageAtom);
const { historyStore } = useStore();
const conversation = historyStore?.getActiveConversation();
const [updateMessage] = useMutation<UpdateMessageMutation>(
UpdateMessageDocument
);
React.useEffect(() => { return message?.text && message?.text?.length > 0 ? (
if ( <div className="flex items-start gap-2 ml-3">
!conversation || <Image
conversation.chatMessages.findIndex((e) => e.id === id) !==
conversation.chatMessages.length - 1
) {
return;
}
const messages = conversation?.chatMessages
.slice(-10)
.filter((e) => e.id !== id)
.map((e) => ({
role:
e.messageSenderType === MessageSenderType.User
? Role.User
: Role.Assistant,
content: e.text,
}));
setData({
messages,
});
}, [conversation]);
const { buffer, done } = useTextBuffer({
url: `api/openai`,
data,
});
useEffect(() => {
if (done) {
// mutate result
const variables: UpdateMessageMutationVariables = {
id: id,
data: {
content: buffer.join(""),
status: MessageStatus.Ready,
},
};
updateMessage({
variables,
});
}
}, [done]);
useEffect(() => {
if (buffer.length > 0 && conversation?.isWaitingForModelResponse) {
historyStore.finishActiveConversationWaiting();
}
}, [buffer]);
return data ? (
<div className="flex items-start gap-2">
<img
className="rounded-full" className="rounded-full"
src={avatarUrl} src={avatarUrl}
width={32} width={32}
@ -101,9 +41,21 @@ const StreamTextMessage: React.FC<Props> = ({
{displayDate(createdAt)} {displayDate(createdAt)}
</div> </div>
</div> </div>
<div className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]"> {message.text.includes("```") ? (
<StreamingText buffer={buffer} fade={100} /> getMessageCode(message.text).map((item, i) => (
<div className="flex gap-1 flex-col" key={i}>
<p className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]">
{item.text}
</p>
{item.code.trim().length > 0 && <TextCode text={item.code} />}
</div> </div>
))
) : (
<p
className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]"
dangerouslySetInnerHTML={{ __html: message.text }}
/>
)}
</div> </div>
</div> </div>
) : ( ) : (

View File

@ -0,0 +1,39 @@
import { currentProductAtom, currentPromptAtom } from "@/_helpers/JotaiWrapper";
import { GetProductPromptsQuery, GetProductPromptsDocument } from "@/graphql";
import { useQuery } from "@apollo/client";
import { useAtomValue, useSetAtom } from "jotai";
const TryItYourself = () => {
const setCurrentPrompt = useSetAtom(currentPromptAtom);
const product = useAtomValue(currentProductAtom);
const { data } = useQuery<GetProductPromptsQuery>(GetProductPromptsDocument, {
variables: { productSlug: product?.slug ?? "" },
});
if (!data || data.prompts.length === 0) {
return <div />;
}
const promps = data.prompts;
return (
<div className="flex flex-col gap-4 tracking-[-0.4px] leading-[22px] text-base">
<h2 className="font-bold">Try it yourself</h2>
<ul className="border-[1px] border-[#D1D5DB] rounded-[12px]">
{promps.map((prompt, index) => (
<button
onClick={() => setCurrentPrompt(prompt.content ?? "")}
key={prompt.slug}
className={`text-sm text-gray-500 leading-[20px] flex gap-[10px] border-b-[${
index !== promps.length - 1 ? "1" : "0"
}px] border-[#E5E7EB] hover:text-blue-400 text-left p-3 w-full`}
>
{prompt.content}
</button>
))}
</ul>
</div>
);
};
export default TryItYourself;

View File

@ -1,13 +1,11 @@
"use client";
import React from "react"; import React from "react";
import { Popover } from "@headlessui/react"; import { Popover } from "@headlessui/react";
import { MenuHeader } from "../MenuHeader"; import { MenuHeader } from "../MenuHeader";
import useGetCurrentUser from "@/_hooks/useGetCurrentUser"; import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
type Props = { const UserProfileDropDown: React.FC = () => {
onLogOutClick: () => void;
};
const UserProfileDropDown: React.FC<Props> = ({ onLogOutClick }) => {
const { loading, user } = useGetCurrentUser(); const { loading, user } = useGetCurrentUser();
if (loading || !user) { if (loading || !user) {
@ -29,7 +27,7 @@ const UserProfileDropDown: React.FC<Props> = ({ onLogOutClick }) => {
</h2> </h2>
</div> </div>
</Popover.Button> </Popover.Button>
<MenuHeader onLogOutClick={onLogOutClick} /> <MenuHeader />
</Popover> </Popover>
</Popover.Group> </Popover.Group>
); );

View File

@ -1,23 +1,29 @@
import { observer } from "mobx-react-lite"; "use client";
import { useStore } from "@/_models/RootStore";
export const UserToolbar: React.FC = observer(() => { import { currentConversationAtom } from "@/_helpers/JotaiWrapper";
const { historyStore } = useStore(); import { useAtomValue } from "jotai";
const conversation = historyStore.getActiveConversation(); import Image from "next/image";
const avatarUrl = conversation?.product.avatarUrl ?? ""; const UserToolbar: React.FC = () => {
const title = conversation?.product.name ?? ""; const currentConvo = useAtomValue(currentConversationAtom);
const avatarUrl = currentConvo?.product.avatarUrl ?? "";
const title = currentConvo?.product.name ?? "";
return ( return (
<div className="flex items-center gap-3 p-1"> <div className="flex items-center gap-3 p-1">
<img <Image
className="rounded-full aspect-square w-8 h-8" className="rounded-full aspect-square w-8 h-8"
src={avatarUrl} src={avatarUrl}
alt="" alt=""
width={36}
height={36}
/> />
<span className="flex gap-[2px] leading-6 text-base font-semibold"> <span className="flex gap-[2px] leading-6 text-base font-semibold">
{title} {title}
</span> </span>
</div> </div>
); );
}); };
export default UserToolbar;

View File

@ -0,0 +1,215 @@
"use client";
import { ChatMessage, MessageStatus } from "@/_models/ChatMessage";
import { Conversation, ConversationState } from "@/_models/Conversation";
import { Product } from "@/_models/Product";
import { Provider, atom } from "jotai";
import { ReactNode } from "react";
type Props = {
children: ReactNode;
};
export default function JotaiWrapper({ children }: Props) {
return <Provider>{children}</Provider>;
}
const activeConversationIdAtom = atom<string | undefined>(undefined);
export const getActiveConvoIdAtom = atom((get) =>
get(activeConversationIdAtom)
);
export const setActiveConvoIdAtom = atom(
null,
(_get, set, convoId: string | undefined) => {
set(activeConversationIdAtom, convoId);
}
);
export const currentPromptAtom = atom<string>("");
export const showingAdvancedPromptAtom = atom<boolean>(false);
export const showingProductDetailAtom = atom<boolean>(false);
export const showingMobilePaneAtom = atom<boolean>(false);
/**
* Stores all conversations for the current user
*/
export const userConversationsAtom = atom<Conversation[]>([]);
export const currentConversationAtom = atom<Conversation | undefined>((get) =>
get(userConversationsAtom).find((c) => c.id === get(activeConversationIdAtom))
);
export const setConvoUpdatedAtAtom = atom(null, (get, set, convoId: string) => {
const convo = get(userConversationsAtom).find((c) => c.id === convoId);
if (!convo) return;
const newConvo: Conversation = { ...convo, updatedAt: Date.now() };
const newConversations: Conversation[] = get(userConversationsAtom).map((c) =>
c.id === convoId ? newConvo : c
);
set(userConversationsAtom, newConversations);
});
export const currentStreamingMessageAtom = atom<ChatMessage | undefined>(undefined);
export const setConvoLastImageAtom = atom(
null,
(get, set, convoId: string, lastImageUrl: string) => {
const convo = get(userConversationsAtom).find((c) => c.id === convoId);
if (!convo) return;
const newConvo: Conversation = { ...convo, lastImageUrl };
const newConversations: Conversation[] = get(userConversationsAtom).map(
(c) => (c.id === convoId ? newConvo : c)
);
set(userConversationsAtom, newConversations);
}
);
/**
* Stores all conversation states for the current user
*/
export const conversationStatesAtom = atom<Record<string, ConversationState>>(
{}
);
export const currentConvoStateAtom = atom<ConversationState | undefined>(
(get) => {
const activeConvoId = get(activeConversationIdAtom);
if (!activeConvoId) {
console.log("active convo id is undefined");
return undefined;
}
return get(conversationStatesAtom)[activeConvoId];
}
);
export const addNewConversationStateAtom = atom(
null,
(get, set, conversationId: string, state: ConversationState) => {
const currentState = { ...get(conversationStatesAtom) };
currentState[conversationId] = state;
set(conversationStatesAtom, currentState);
}
);
export const updateConversationWaitingForResponseAtom = atom(
null,
(get, set, conversationId: string, waitingForResponse: boolean) => {
const currentState = { ...get(conversationStatesAtom) };
currentState[conversationId] = {
...currentState[conversationId],
waitingForResponse,
};
set(conversationStatesAtom, currentState);
}
);
export const updateConversationHasMoreAtom = atom(
null,
(get, set, conversationId: string, hasMore: boolean) => {
const currentState = { ...get(conversationStatesAtom) };
currentState[conversationId] = { ...currentState[conversationId], hasMore };
set(conversationStatesAtom, currentState);
}
);
/**
* Stores all chat messages for all conversations
*/
export const chatMessages = atom<Record<string, ChatMessage[]>>({});
export const currentChatMessagesAtom = atom<ChatMessage[]>((get) => {
const activeConversationId = get(activeConversationIdAtom);
if (!activeConversationId) return [];
return get(chatMessages)[activeConversationId] ?? [];
});
export const addOldMessagesAtom = atom(
null,
(get, set, newMessages: ChatMessage[]) => {
const currentConvoId = get(activeConversationIdAtom);
if (!currentConvoId) return;
const currentMessages = get(chatMessages)[currentConvoId] ?? [];
const updatedMessages = [...currentMessages, ...newMessages];
const newData: Record<string, ChatMessage[]> = {
...get(chatMessages),
};
newData[currentConvoId] = updatedMessages;
set(chatMessages, newData);
}
);
export const addNewMessageAtom = atom(
null,
(get, set, newMessage: ChatMessage) => {
const currentConvoId = get(activeConversationIdAtom);
if (!currentConvoId) return;
const currentMessages = get(chatMessages)[currentConvoId] ?? [];
const updatedMessages = [newMessage, ...currentMessages];
const newData: Record<string, ChatMessage[]> = {
...get(chatMessages),
};
newData[currentConvoId] = updatedMessages;
set(chatMessages, newData);
}
);
export const updateMessageAtom = atom(
null,
(get, set, id: string, conversationId: string, text: string) => {
const messages = get(chatMessages)[conversationId] ?? [];
const message = messages.find((e) => e.id === id);
if (message) {
message.text = text;
const updatedMessages = [...messages];
const newData: Record<string, ChatMessage[]> = {
...get(chatMessages),
};
newData[conversationId] = updatedMessages;
set(chatMessages, newData);
}
}
);
/**
* For updating the status of the last AI message that is pending
*/
export const updateLastMessageAsReadyAtom = atom(
null,
(get, set, id, text: string) => {
const currentConvoId = get(activeConversationIdAtom);
if (!currentConvoId) return;
const currentMessages = get(chatMessages)[currentConvoId] ?? [];
const messageToUpdate = currentMessages.find((e) => e.id === id);
// if message is not found, do nothing
if (!messageToUpdate) return;
const index = currentMessages.indexOf(messageToUpdate);
const updatedMsg: ChatMessage = {
...messageToUpdate,
status: MessageStatus.Ready,
text: text,
};
currentMessages[index] = updatedMsg;
const newData: Record<string, ChatMessage[]> = {
...get(chatMessages),
};
newData[currentConvoId] = currentMessages;
set(chatMessages, newData);
}
);
export const currentProductAtom = atom<Product | undefined>(
(get) =>
get(userConversationsAtom).find(
(c) => c.id === get(activeConversationIdAtom)
)?.product
);
export const searchAtom = atom<string>("");
// modal atoms
export const showConfirmDeleteConversationModalAtom = atom(false);
export const showConfirmSignOutModalAtom = atom(false);

View File

@ -1,13 +0,0 @@
"use client";
import { Provider, initializeStore } from "@/_models/RootStore";
import { ReactNode } from "react";
type Props = {
children: ReactNode;
};
export const MobxWrapper: React.FC<Props> = ({ children }) => {
const store = initializeStore();
return <Provider value={store}>{children}</Provider>;
};

View File

@ -0,0 +1,19 @@
"use client";
import ConfirmDeleteConversationModal from "@/_components/ConfirmDeleteConversationModal";
import ConfirmSignOutModal from "@/_components/ConfirmSignOutModal";
import MobileMenuPane from "@/_components/MobileMenuPane";
import { ReactNode } from "react";
type Props = {
children: ReactNode;
};
export const ModalWrapper: React.FC<Props> = ({ children }) => (
<>
<MobileMenuPane />
<ConfirmDeleteConversationModal />
<ConfirmSignOutModal />
{children}
</>
);

View File

@ -7,10 +7,9 @@ type Props = {
children: ReactNode; children: ReactNode;
}; };
export const ThemeWrapper: React.FC<Props> = ({ children }) => { // consider to use next-themes or not. This caused the error Warning: Extra attributes from the server: class,style at html after hydration
return ( export const ThemeWrapper: React.FC<Props> = ({ children }) => (
<ThemeProvider enableSystem={false} attribute="class"> <ThemeProvider enableSystem={false} attribute="class">
{children} {children}
</ThemeProvider> </ThemeProvider>
); );
};

View File

@ -1,30 +0,0 @@
import { IStateTreeNode, SnapshotIn } from "mobx-state-tree"
/**
* If you include this in your model in an action() block just under your props,
* it'll allow you to set property values directly while retaining type safety
* and also is executed in an action. This is useful because often you find yourself
* making a lot of repetitive setter actions that only update one prop.
*
* E.g.:
*
* const UserModel = types.model("User")
* .props({
* name: types.string,
* age: types.number
* })
* .actions(withSetPropAction)
*
* const user = UserModel.create({ name: "Jamon", age: 40 })
*
* user.setProp("name", "John") // no type error
* user.setProp("age", 30) // no type error
* user.setProp("age", "30") // type error -- must be number
*/
export const withSetPropAction = <T extends IStateTreeNode>(mstInstance: T) => ({
// generic setter for all properties
setProp<K extends keyof SnapshotIn<T>, V extends SnapshotIn<T>[K]>(field: K, newValue: V) {
// @ts-ignore - for some reason TS complains about this, but it still works fine
mstInstance[field] = newValue
},
})

View File

@ -0,0 +1,16 @@
import {
SubscribeMessageSubscription,
SubscribeMessageDocument,
} from "@/graphql";
import { useSubscription } from "@apollo/client";
const useChatMessageSubscription = (messageId: string) => {
const { data, loading, error } =
useSubscription<SubscribeMessageSubscription>(SubscribeMessageDocument, {
variables: { id: messageId },
});
return { data, loading, error };
};
export default useChatMessageSubscription;

View File

@ -0,0 +1,73 @@
import {
addOldMessagesAtom,
conversationStatesAtom,
currentConversationAtom,
updateConversationHasMoreAtom,
} from "@/_helpers/JotaiWrapper";
import { ChatMessage, toChatMessage } from "@/_models/ChatMessage";
import { MESSAGE_PER_PAGE } from "@/_utils/const";
import {
GetConversationMessagesQuery,
GetConversationMessagesDocument,
GetConversationMessagesQueryVariables,
MessageDetailFragment,
} from "@/graphql";
import { useLazyQuery } from "@apollo/client";
import { useAtomValue, useSetAtom } from "jotai";
import { useEffect } from "react";
/**
* Custom hooks to get chat messages for current(active) conversation
*
* @param offset for pagination purpose
* @returns
*/
const useChatMessages = (offset = 0) => {
const addOldChatMessages = useSetAtom(addOldMessagesAtom);
const currentConvo = useAtomValue(currentConversationAtom);
if (!currentConvo) {
throw new Error("activeConversation is null");
}
const convoStates = useAtomValue(conversationStatesAtom);
const updateConvoHasMore = useSetAtom(updateConversationHasMoreAtom);
const [getConversationMessages, { loading, error }] =
useLazyQuery<GetConversationMessagesQuery>(GetConversationMessagesDocument);
useEffect(() => {
const hasMore = convoStates[currentConvo.id]?.hasMore ?? true;
if (!hasMore) return;
const variables: GetConversationMessagesQueryVariables = {
conversation_id: currentConvo.id,
limit: MESSAGE_PER_PAGE,
offset: offset,
};
getConversationMessages({ variables }).then((data) => {
parseMessages(data.data?.messages ?? []).then((newMessages) => {
const isHasMore = newMessages.length === MESSAGE_PER_PAGE;
addOldChatMessages(newMessages);
updateConvoHasMore(currentConvo.id, isHasMore);
});
});
}, [offset, currentConvo.id]);
return {
loading,
error,
hasMore: convoStates[currentConvo.id]?.hasMore ?? true,
};
};
async function parseMessages(
messages: MessageDetailFragment[]
): Promise<ChatMessage[]> {
const newMessages: ChatMessage[] = [];
for (const m of messages) {
const chatMessage = await toChatMessage(m);
newMessages.push(chatMessage);
}
return newMessages;
}
export default useChatMessages;

View File

@ -1,17 +1,27 @@
import { import {
ProductDetailFragment,
CreateConversationMutation, CreateConversationMutation,
CreateConversationDocument, CreateConversationDocument,
CreateConversationMutationVariables, CreateConversationMutationVariables,
} from "@/graphql"; } from "@/graphql";
import { useStore } from "../_models/RootStore";
import useGetCurrentUser from "./useGetCurrentUser"; import useGetCurrentUser from "./useGetCurrentUser";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { MessageSenderType, MessageType } from "@/_models/ChatMessage";
import useSignIn from "./useSignIn"; import useSignIn from "./useSignIn";
import { useAtom, useSetAtom } from "jotai";
import {
addNewConversationStateAtom,
setActiveConvoIdAtom,
userConversationsAtom,
} from "@/_helpers/JotaiWrapper";
import { Conversation } from "@/_models/Conversation";
import { Product } from "@/_models/Product";
import { MessageSenderType, MessageType } from "@/_models/ChatMessage";
const useCreateConversation = () => { const useCreateConversation = () => {
const { historyStore } = useStore(); const [userConversations, setUserConversations] = useAtom(
userConversationsAtom
);
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
const addNewConvoState = useSetAtom(addNewConversationStateAtom);
const { user } = useGetCurrentUser(); const { user } = useGetCurrentUser();
const { signInWithKeyCloak } = useSignIn(); const { signInWithKeyCloak } = useSignIn();
const [createConversation] = useMutation<CreateConversationMutation>( const [createConversation] = useMutation<CreateConversationMutation>(
@ -19,7 +29,7 @@ const useCreateConversation = () => {
); );
const requestCreateConvo = async ( const requestCreateConvo = async (
product: ProductDetailFragment, product: Product,
forceCreate: boolean = false forceCreate: boolean = false
) => { ) => {
if (!user) { if (!user) {
@ -28,15 +38,15 @@ const useCreateConversation = () => {
} }
// search if any fresh convo with particular product id // search if any fresh convo with particular product id
const convo = historyStore.conversations.find( const convo = userConversations.find(
(convo) => (convo) => convo.product.slug === product.slug
convo.product.id === product.slug && convo.chatMessages.length <= 1
); );
if (convo && !forceCreate) { if (convo && !forceCreate) {
historyStore.setActiveConversationId(convo.id); setActiveConvoId(convo.id);
return; return;
} }
const variables: CreateConversationMutationVariables = { const variables: CreateConversationMutationVariables = {
data: { data: {
product_id: product.id, product_id: product.id,
@ -49,7 +59,7 @@ const useCreateConversation = () => {
content: product.greeting || "Hello there 👋", content: product.greeting || "Hello there 👋",
sender: MessageSenderType.Ai, sender: MessageSenderType.Ai,
sender_name: product.name, sender_name: product.name,
sender_avatar_url: product.image_url ?? "", sender_avatar_url: product.avatarUrl,
message_type: MessageType.Text, message_type: MessageType.Text,
message_sender_type: MessageSenderType.Ai, message_sender_type: MessageSenderType.Ai,
}, },
@ -60,14 +70,26 @@ const useCreateConversation = () => {
const result = await createConversation({ const result = await createConversation({
variables, variables,
}); });
const newConvo = result.data?.insert_conversations_one;
if (result.data?.insert_conversations_one) { if (newConvo) {
historyStore.createConversation( const mappedConvo: Conversation = {
result.data.insert_conversations_one, id: newConvo.id,
product, product: product,
user.id, user: {
user.displayName id: user.id,
); displayName: user.displayName,
},
lastTextMessage: newConvo.last_text_message ?? "",
createdAt: Date.now(),
updatedAt: Date.now(),
};
addNewConvoState(newConvo.id, {
hasMore: true,
waitingForResponse: false,
});
setUserConversations([...userConversations, mappedConvo]);
setActiveConvoId(newConvo.id);
} }
// if not found, create new convo and set it as current // if not found, create new convo and set it as current
}; };

View File

@ -0,0 +1,50 @@
import {
currentPromptAtom,
getActiveConvoIdAtom,
setActiveConvoIdAtom,
showingAdvancedPromptAtom,
showingProductDetailAtom,
userConversationsAtom,
} from "@/_helpers/JotaiWrapper";
import {
DeleteConversationDocument,
DeleteConversationMutation,
} from "@/graphql";
import { useMutation } from "@apollo/client";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
export default function useDeleteConversation() {
const [userConversations, setUserConversations] = useAtom(
userConversationsAtom
);
const setCurrentPrompt = useSetAtom(currentPromptAtom);
const setShowingProductDetail = useSetAtom(showingProductDetailAtom);
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
const activeConvoId = useAtomValue(getActiveConvoIdAtom);
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
const [deleteConversation] = useMutation<DeleteConversationMutation>(
DeleteConversationDocument
);
const deleteConvo = async () => {
if (activeConvoId) {
try {
await deleteConversation({ variables: { id: activeConvoId } });
setUserConversations(
userConversations.filter((c) => c.id !== activeConvoId)
);
setActiveConvoId(undefined);
setCurrentPrompt("");
setShowingProductDetail(false);
setShowingAdvancedPrompt(false);
} catch (err) {
console.error(err);
}
}
};
return {
deleteConvo,
};
}

View File

@ -1,15 +1,15 @@
// @ts-nocheck // @ts-nocheck
import { DefaultUser, User } from "@/_models/User";
import { Instance } from "mobx-state-tree";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import useSignOut from "./useSignOut"; import useSignOut from "./useSignOut";
import { DefaultUser, User } from "@/_models/User";
export default function useGetCurrentUser() { export default function useGetCurrentUser() {
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const { signOut } = useSignOut(); const { signOut } = useSignOut();
const [loading, setLoading] = useState(status === "loading"); const [loading, setLoading] = useState(status === "loading");
const [user, setUser] = useState<Instance<typeof User>>(); const [user, setUser] = useState<User>();
useEffect(() => { useEffect(() => {
if ( if (
status !== "loading" && status !== "loading" &&

View File

@ -0,0 +1,24 @@
import { ProductType, toProduct } from "@/_models/Product";
import { GetProductsDocument, GetProductsQuery } from "@/graphql";
import { useQuery } from "@apollo/client";
export default function useGetProducts() {
const { loading, data } = useQuery<GetProductsQuery>(GetProductsDocument, {
variables: { slug: "conversational" },
});
const allProducts = (data?.products ?? []).map((e) => toProduct(e));
const featured = allProducts.sort(() => 0.5 - Math.random()).slice(0, 3);
const conversational = allProducts.filter((e) => e.type === ProductType.LLM);
const generativeArts = allProducts.filter(
(e) => e.type === ProductType.GenerativeArt
);
return {
loading,
featured,
conversational,
generativeArts,
};
}

View File

@ -1,57 +1,35 @@
import { Instance } from "mobx-state-tree";
import { useStore } from "../_models/RootStore";
import { AiModelType } from "../_models/Product";
import { Conversation } from "../_models/Conversation";
import { User } from "../_models/User";
import { GetConversationsQuery, GetConversationsDocument } from "@/graphql"; import { GetConversationsQuery, GetConversationsDocument } from "@/graphql";
import { useLazyQuery } from "@apollo/client"; import { useLazyQuery } from "@apollo/client";
import { ConversationState, toConversation } from "@/_models/Conversation";
import { useSetAtom } from "jotai";
import {
conversationStatesAtom,
userConversationsAtom,
} from "@/_helpers/JotaiWrapper";
const useGetUserConversations = () => { const useGetUserConversations = () => {
const { historyStore } = useStore(); const setConversationStates = useSetAtom(conversationStatesAtom);
const setConversations = useSetAtom(userConversationsAtom);
const [getConvos] = useLazyQuery<GetConversationsQuery>( const [getConvos] = useLazyQuery<GetConversationsQuery>(
GetConversationsDocument GetConversationsDocument
); );
const getUserConversations = async (user: Instance<typeof User>) => { const getUserConversations = async () => {
const results = await getConvos(); const results = await getConvos();
if (!results || !results.data || results.data.conversations.length === 0) { if (!results || !results.data || results.data.conversations.length === 0) {
return; return;
} }
const convos = results.data.conversations; const convos = results.data.conversations.map((e) => toConversation(e));
const convoStates: Record<string, ConversationState> = {};
const finalConvo: Instance<typeof Conversation>[] = [];
// mapping
convos.forEach((convo) => { convos.forEach((convo) => {
const conversation = Conversation.create({ convoStates[convo.id] = {
id: convo.id!!, hasMore: true,
product: { waitingForResponse: false,
id: convo.conversation_product?.slug || convo.conversation_product?.id, };
name: convo.conversation_product?.name ?? "",
type:
convo.conversation_product?.inputs.slug === "llm"
? AiModelType.LLM
: convo.conversation_product?.inputs.slug === "sd"
? AiModelType.GenerativeArt
: AiModelType.ControlNet,
avatarUrl: convo.conversation_product?.image_url,
description: convo.conversation_product?.description,
modelDescription: convo.conversation_product?.description,
modelUrl: convo.conversation_product?.source_url,
modelVersion: convo.conversation_product?.version,
},
chatMessages: [],
user: user,
createdAt: new Date(convo.created_at).getTime(),
updatedAt: new Date(convo.updated_at).getTime(),
lastImageUrl: convo.last_image_url ?? "",
lastTextMessage: convo.last_text_message ?? "",
}); });
setConversationStates(convoStates);
finalConvo.push(conversation); setConversations(convos);
});
historyStore.setConversations(finalConvo);
}; };
return { return {

View File

@ -0,0 +1,383 @@
import {
addNewMessageAtom,
currentChatMessagesAtom,
currentConversationAtom,
currentPromptAtom,
currentStreamingMessageAtom,
setConvoLastImageAtom,
setConvoUpdatedAtAtom,
updateConversationWaitingForResponseAtom,
updateMessageAtom,
userConversationsAtom,
} from "@/_helpers/JotaiWrapper";
import {
ChatMessage,
MessageSenderType,
MessageStatus,
MessageType,
} from "@/_models/ChatMessage";
import { Conversation } from "@/_models/Conversation";
import { ProductType } from "@/_models/Product";
import {
CreateMessageDocument,
CreateMessageMutation,
CreateMessageMutationVariables,
GenerateImageDocument,
GenerateImageMutation,
GenerateImageMutationVariables,
UpdateMessageMutation,
UpdateMessageDocument,
UpdateMessageMutationVariables,
UpdateConversationMutation,
UpdateConversationDocument,
UpdateConversationMutationVariables,
} from "@/graphql";
import { useMutation } from "@apollo/client";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import useSignIn from "./useSignIn";
import useGetCurrentUser from "./useGetCurrentUser";
import { Role } from "@/_models/User";
export default function useSendChatMessage() {
const { user } = useGetCurrentUser();
const { signInWithKeyCloak } = useSignIn();
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom);
const [userConversations, setUserConversations] = useAtom(
userConversationsAtom
);
const addNewMessage = useSetAtom(addNewMessageAtom);
const activeConversation = useAtomValue(currentConversationAtom);
const currentMessages = useAtomValue(currentChatMessagesAtom);
const [createMessageMutation] = useMutation<CreateMessageMutation>(
CreateMessageDocument
);
const [updateMessageMutation] = useMutation<UpdateMessageMutation>(
UpdateMessageDocument
);
const [updateConversationMutation] = useMutation<UpdateConversationMutation>(
UpdateConversationDocument
);
const [imageGenerationMutation] = useMutation<GenerateImageMutation>(
GenerateImageDocument
);
const updateConvoWaitingState = useSetAtom(
updateConversationWaitingForResponseAtom
);
const updateMessageText = useSetAtom(updateMessageAtom);
const [, setTextMessage] = useAtom(currentStreamingMessageAtom);
const setConvoLastImageUrl = useSetAtom(setConvoLastImageAtom);
const setConvoUpdateAt = useSetAtom(setConvoUpdatedAtAtom);
const sendTextToTextMessage = async (
conversation: Conversation,
latestUserMessage: ChatMessage
) => {
// TODO: handle case timeout using higher order function
const messageToSend = [
latestUserMessage,
...currentMessages.slice(0, 4),
].reverse();
const latestMessages = messageToSend.map((e) => ({
role:
e.messageSenderType === MessageSenderType.User
? Role.User
: Role.Assistant,
content: e.text,
}));
const variables: CreateMessageMutationVariables = {
data: {
conversation_id: conversation.id,
sender: MessageSenderType.Ai,
message_sender_type: MessageSenderType.Ai,
message_type: MessageType.Text,
sender_avatar_url: conversation.product.avatarUrl,
sender_name: conversation.product.name,
prompt_cache: latestMessages,
status: MessageStatus.Pending,
},
};
const result = await createMessageMutation({
variables,
});
if (!result.data?.insert_messages_one?.id) {
console.error(
"Error creating user message",
JSON.stringify(result.errors)
);
updateConvoWaitingState(conversation.id, false);
return;
}
const aiResponseMessage: ChatMessage = {
id: result.data.insert_messages_one.id,
conversationId: conversation.id,
messageType: MessageType.Text,
messageSenderType: MessageSenderType.Ai,
senderUid: conversation.product.slug,
senderName: conversation.product.name,
senderAvatarUrl: conversation.product.avatarUrl ?? "/icons/app_icon.svg",
text: "",
status: MessageStatus.Pending,
createdAt: Date.now(),
};
setTextMessage(aiResponseMessage);
addNewMessage(aiResponseMessage);
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_OPENAPI_ENDPOINT}`,
{
method: "POST",
cache: "no-cache",
keepalive: true,
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({
messages: latestMessages,
model: "gpt-3.5-turbo",
stream: true,
max_tokens: 500,
}),
}
);
if (!response.ok) {
updateMessageText(
aiResponseMessage.id,
conversation.id,
"There is an error while retrieving the result. Please try again later."
);
} else {
const data = response.body;
if (!data) {
return;
}
const reader = data.getReader();
const decoder = new TextDecoder();
let done = false;
let currentResponse: string = "";
updateConvoWaitingState(conversation.id, false);
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
const chunkValue = decoder.decode(value);
chunkValue.split("\n").forEach((chunk) => {
console.log("chunk", chunk);
const text = parsedBuffer(chunk) ?? "";
currentResponse += text;
updateMessageText(
aiResponseMessage.id,
conversation.id,
currentResponse
);
});
}
mutateMessageText(
aiResponseMessage.id,
conversation.id,
currentResponse
);
}
} catch (err) {
const errorText =
"There is an error while retrieving the result. Please try again later.";
updateMessageText(aiResponseMessage.id, conversation.id, errorText);
mutateMessageText(aiResponseMessage.id, conversation.id, errorText);
}
updateConvoWaitingState(conversation.id, false);
};
const sendTextToImageMessage = async (conversation: Conversation) => {
// TODO: handle case timeout using higher order function
const variables: GenerateImageMutationVariables = {
model: conversation.product.slug,
prompt: currentPrompt,
neg_prompt: "",
seed: Math.floor(Math.random() * 429496729),
steps: 30,
width: 512,
height: 512,
};
const data = await imageGenerationMutation({
variables,
});
if (!data.data?.imageGeneration?.url) {
// TODO: display error
console.error("Error creating user message", JSON.stringify(data.errors));
updateConvoWaitingState(conversation.id, false);
return;
}
const imageUrl: string = data.data.imageGeneration.url;
const createMessageVariables: CreateMessageMutationVariables = {
data: {
conversation_id: conversation.id,
content: currentPrompt,
sender: MessageSenderType.Ai,
message_sender_type: MessageSenderType.Ai,
message_type: MessageType.Image,
sender_avatar_url: conversation.product.avatarUrl,
sender_name: conversation.product.name,
status: MessageStatus.Ready,
message_medias: {
data: [
{
media_url: imageUrl,
mime_type: "image/jpeg",
},
],
},
},
};
const result = await createMessageMutation({
variables: createMessageVariables,
});
if (!result.data?.insert_messages_one?.id) {
// TODO: display error
console.error(
"Error creating user message",
JSON.stringify(result.errors)
);
updateConvoWaitingState(conversation.id, false);
return;
}
const imageResponseMessage: ChatMessage = {
id: result.data.insert_messages_one.id,
conversationId: conversation.id,
messageType: MessageType.Image,
messageSenderType: MessageSenderType.Ai,
senderUid: conversation.product.slug,
senderName: conversation.product.name,
senderAvatarUrl: conversation.product.avatarUrl,
text: currentPrompt,
imageUrls: [imageUrl],
createdAt: Date.now(),
status: MessageStatus.Ready,
};
addNewMessage(imageResponseMessage);
setConvoUpdateAt(conversation.id);
setConvoLastImageUrl(conversation.id, imageUrl);
updateConvoWaitingState(conversation.id, false);
};
const sendChatMessage = async () => {
if (!user) {
signInWithKeyCloak();
return;
}
if (currentPrompt.trim().length === 0) return;
if (!activeConversation) {
console.error("No active conversation");
return;
}
updateConvoWaitingState(activeConversation.id, true);
const variables: CreateMessageMutationVariables = {
data: {
conversation_id: activeConversation.id,
content: currentPrompt,
sender: user.id,
message_sender_type: MessageSenderType.User,
message_type: MessageType.Text,
sender_avatar_url: user.avatarUrl,
sender_name: user.displayName,
},
};
const result = await createMessageMutation({ variables });
if (!result.data?.insert_messages_one?.id) {
// TODO: display error
console.error(
"Error creating user message",
JSON.stringify(result.errors)
);
updateConvoWaitingState(activeConversation.id, false);
return;
}
const userMesssage: ChatMessage = {
id: result.data.insert_messages_one.id,
conversationId: activeConversation.id,
messageType: MessageType.Text,
messageSenderType: MessageSenderType.User,
senderUid: user.id,
senderName: user.displayName,
senderAvatarUrl: user.avatarUrl ?? "/icons/app_icon.svg",
text: currentPrompt,
createdAt: Date.now(),
status: MessageStatus.Ready,
};
addNewMessage(userMesssage);
const newUserConversations = userConversations.map((e) => {
if (e.id === activeConversation.id) {
e.lastTextMessage = userMesssage.text;
}
return e;
});
setUserConversations(newUserConversations);
if (activeConversation.product.type === ProductType.LLM) {
await sendTextToTextMessage(activeConversation, userMesssage);
setCurrentPrompt("");
} else if (activeConversation.product.type === ProductType.GenerativeArt) {
await sendTextToImageMessage(activeConversation);
setCurrentPrompt("");
} else {
console.error(
"We do not support this model type yet:",
activeConversation.product.type
);
}
};
const parsedBuffer = (buffer: string) => {
try {
const json = buffer.replace("data: ", "");
return JSON.parse(json).choices[0].delta.content;
} catch (e) {
return "";
}
};
const mutateMessageText = (
messageId: string,
convId: string,
text: string
) => {
const variables: UpdateMessageMutationVariables = {
data: {
content: text,
status: MessageStatus.Ready,
},
id: messageId,
};
updateMessageMutation({
variables,
});
updateConversationMutation({
variables: {
id: convId,
lastMessageText: text,
},
});
};
return {
sendChatMessage,
};
}

View File

@ -1,12 +1,13 @@
import { signOut as signOutNextAuth } from "next-auth/react"; import { signOut as signOutNextAuth } from "next-auth/react";
export default function useSignOut() { export default function useSignOut() {
const signOut = () => { const signOut = async () => {
fetch(`api/auth/logout`, { method: "GET" }) try {
.then(() => signOutNextAuth({ callbackUrl: "/" })) await fetch(`api/auth/logout`, { method: "GET" });
.catch((e) => { await signOutNextAuth({ callbackUrl: "/" });
} catch (e) {
console.error(e); console.error(e);
}); }
}; };
return { signOut }; return { signOut };

View File

@ -1,5 +1,6 @@
import { types } from "mobx-state-tree"; import { MessageDetailFragment } from "@/graphql";
import { withSetPropAction } from "../_helpers/withSetPropAction"; import { remark } from "remark";
import html from "remark-html";
export enum MessageType { export enum MessageType {
Text = "Text", Text = "Text",
@ -18,18 +19,53 @@ export enum MessageStatus {
Pending = "pending", Pending = "pending",
} }
export const ChatMessage = types export interface ChatMessage {
.model("ChatMessage", { id: string;
id: types.string, conversationId: string;
conversationId: types.string, messageType: MessageType;
messageType: types.enumeration(Object.values(MessageType)), messageSenderType: MessageSenderType;
messageSenderType: types.enumeration(Object.values(MessageSenderType)), senderUid: string;
senderUid: types.string, senderName: string;
senderName: types.string, senderAvatarUrl: string;
senderAvatarUrl: types.maybeNull(types.string), text: string | undefined;
text: types.maybe(types.string), imageUrls?: string[] | undefined;
imageUrls: types.maybe(types.array(types.string)), createdAt: number;
createdAt: types.number, status: MessageStatus;
status: types.enumeration(Object.values(MessageStatus)), }
})
.actions(withSetPropAction); export const toChatMessage = async (
m: MessageDetailFragment
): Promise<ChatMessage> => {
const createdAt = new Date(m.created_at).getTime();
const imageUrls: string[] = [];
const imageUrl =
m.message_medias.length > 0 ? m.message_medias[0].media_url : null;
if (imageUrl) {
imageUrls.push(imageUrl);
}
const messageType = m.message_type
? MessageType[m.message_type as keyof typeof MessageType]
: MessageType.Text;
const messageSenderType = m.message_sender_type
? MessageSenderType[m.message_sender_type as keyof typeof MessageSenderType]
: MessageSenderType.Ai;
const content = m.content ?? "";
const processedContent = await remark().use(html).process(content);
const contentHtml = processedContent.toString();
return {
id: m.id,
conversationId: m.conversation_id,
messageType: messageType,
messageSenderType: messageSenderType,
senderUid: m.sender,
senderName: m.sender_name ?? "",
senderAvatarUrl: m.sender_avatar_url ?? "/icons/app_icon.svg",
text: contentHtml,
imageUrls: imageUrls,
createdAt: createdAt,
status: m.status as MessageStatus,
};
};

View File

@ -1,78 +1,38 @@
import { Instance, castToSnapshot, types } from "mobx-state-tree"; import { ConversationDetailFragment } from "@/graphql";
import { Product } from "./Product"; import { Product, toProduct } from "./Product";
import { ChatMessage } from "./ChatMessage";
import { User } from "../_models/User";
import { withSetPropAction } from "../_helpers/withSetPropAction";
import { mergeAndRemoveDuplicates } from "../_utils/message";
export const Conversation = types export interface Conversation {
.model("Conversation", { id: string;
/** product: Product;
* Unique identifier for the conversation createdAt: number;
*/ updatedAt?: number;
id: types.string, lastImageUrl?: string;
lastTextMessage?: string;
}
/** /**
* AI model that the conversation is using * Store the state of conversation like fetching, waiting for response, etc.
*/ */
product: Product, export type ConversationState = {
hasMore: boolean;
waitingForResponse: boolean;
};
/** export const toConversation = (
* Conversation's messages, should ordered by time (createdAt) convo: ConversationDetailFragment
*/ ): Conversation => {
chatMessages: types.optional(types.array(ChatMessage), []), const product = convo.conversation_product;
if (!product) {
/** throw new Error("Product is not defined");
* User who initiate the chat with the above AI model }
*/ return {
user: User, id: convo.id,
product: toProduct(product),
/** lastImageUrl: convo.last_image_url ?? undefined,
* Indicates whether the conversation is created by the user lastTextMessage: convo.last_text_message ?? undefined,
*/ createdAt: new Date(convo.created_at).getTime(),
createdAt: types.number, updatedAt: convo.updated_at
? new Date(convo.updated_at).getTime()
/** : undefined,
* Time the last message is sent };
*/ };
updatedAt: types.maybe(types.number),
/**
* Last image url sent by the model if any
*/
lastImageUrl: types.maybe(types.string),
/**
* Last text sent by the user if any
*/
lastTextMessage: types.maybe(types.string),
})
.volatile(() => ({
isFetching: false,
offset: 0,
hasMore: true,
isWaitingForModelResponse: false,
}))
.actions(withSetPropAction)
.actions((self) => ({
addMessage(message: Instance<typeof ChatMessage>) {
self.chatMessages.push(message);
},
pushMessages(messages: Instance<typeof ChatMessage>[]) {
const mergedMessages = mergeAndRemoveDuplicates(
self.chatMessages,
messages
);
self.chatMessages = castToSnapshot(mergedMessages);
},
setHasMore(hasMore: boolean) {
self.hasMore = hasMore;
},
setWaitingForModelResponse(isWaitingForModelResponse: boolean) {
self.isWaitingForModelResponse = isWaitingForModelResponse;
},
}));

View File

@ -1,604 +0,0 @@
import { Instance, castToSnapshot, flow, types } from "mobx-state-tree";
import { Conversation } from "./Conversation";
import { Product, AiModelType } from "./Product";
import { User } from "../_models/User";
import {
ChatMessage,
MessageSenderType,
MessageStatus,
MessageType,
} from "./ChatMessage";
import { MESSAGE_PER_PAGE } from "../_utils/const";
import { controlNetRequest } from "@/_services/controlnet";
import {
ApolloCache,
DefaultContext,
FetchResult,
LazyQueryExecFunction,
MutationFunctionOptions,
OperationVariables,
QueryResult,
} from "@apollo/client";
import {
ConversationDetailFragment,
CreateMessageMutation,
CreateMessageMutationVariables,
GenerateImageMutation,
GenerateImageMutationVariables,
GetConversationMessagesQuery,
GetConversationMessagesQueryVariables,
MessageDetailFragment,
ProductDetailFragment,
} from "@/graphql";
export enum Role {
User = "user",
Assistant = "assistant",
}
type CreateMessageMutationFunc = (
options?:
| MutationFunctionOptions<
CreateMessageMutation,
OperationVariables,
DefaultContext,
ApolloCache<any>
>
| undefined
) => Promise<FetchResult<CreateMessageMutation>>;
type ImageGenerationMutationFunc = (
options?:
| MutationFunctionOptions<
GenerateImageMutation,
OperationVariables,
DefaultContext,
ApolloCache<any>
>
| undefined
) => Promise<FetchResult<GenerateImageMutation>>;
export const History = types
.model("History", {
conversations: types.optional(types.array(Conversation), []),
activeConversationId: types.maybe(types.string),
})
.volatile(() => ({
showModelDetail: false,
showAdvancedPrompt: false,
}))
.views((self) => ({
getActiveConversation() {
if (self.activeConversationId) {
return self.conversations.find(
(c) => c.id === self.activeConversationId
);
}
},
getActiveMessages() {
if (self.activeConversationId) {
const conversation = self.conversations.find(
(c) => c.id === self.activeConversationId
);
if (conversation) {
return conversation.chatMessages;
}
}
return [];
},
getConversationById(conversationId: string) {
return self.conversations.find((c) => c.id === conversationId);
},
}))
.actions((self) => ({
// Model detail
toggleModelDetail() {
self.showModelDetail = !self.showModelDetail;
},
toggleAdvancedPrompt() {
self.showAdvancedPrompt = !self.showAdvancedPrompt;
},
closeModelDetail() {
if (self.showModelDetail) {
self.showModelDetail = false;
}
},
finishActiveConversationWaiting() {
self.getActiveConversation()?.setWaitingForModelResponse(false);
},
}))
.actions((self) => {
const fetchMoreMessages = flow(function* (
func: LazyQueryExecFunction<
GetConversationMessagesQuery,
OperationVariables
>
) {
const convoId = self.activeConversationId;
if (!convoId) {
console.error("No active conversation found");
return;
}
const convo = self.getConversationById(convoId);
if (!convo) {
console.error("Could not get convo", convoId);
return;
}
if (convo?.isFetching) {
return;
}
if (!convo.hasMore) {
console.info("Already load all messages of convo", convoId);
return;
}
convo.isFetching = true;
const variables: GetConversationMessagesQueryVariables = {
conversation_id: convoId,
limit: MESSAGE_PER_PAGE,
offset: convo.offset,
};
const result: QueryResult<
GetConversationMessagesQuery,
OperationVariables
> = yield func({ variables });
if (!result.data?.messages) {
return;
}
if (result.data?.messages.length < MESSAGE_PER_PAGE) {
convo.setHasMore(false);
}
convo.offset += result.data.messages.length;
const messages: Instance<typeof ChatMessage>[] = [];
result.data.messages.forEach((m: MessageDetailFragment) => {
const createdAt = new Date(m.created_at).getTime();
const imageUrls: string[] = [];
const imageUrl =
m.message_medias.length > 0 ? m.message_medias[0].media_url : null;
if (imageUrl) {
imageUrls.push(imageUrl);
}
const messageType = m.message_type
? MessageType[m.message_type as keyof typeof MessageType]
: MessageType.Text;
const messageSenderType = m.message_sender_type
? MessageSenderType[
m.message_sender_type as keyof typeof MessageSenderType
]
: MessageSenderType.Ai;
messages.push(
ChatMessage.create({
id: m.id,
conversationId: m.conversation_id,
messageType: messageType,
messageSenderType: messageSenderType,
senderUid: m.sender,
senderName: m.sender_name ?? "",
senderAvatarUrl: m.sender_avatar_url,
text: m.content ?? "",
status: m.status as MessageStatus,
imageUrls: imageUrls,
createdAt: createdAt,
})
);
});
convo.setProp(
"chatMessages",
messages.reverse().concat(convo.chatMessages)
);
convo.isFetching = false;
});
const deleteConversationById = flow(function* (convoId: string) {
const updateConversations = self.conversations.filter(
(c) => c.id !== convoId
);
self.conversations = castToSnapshot([...updateConversations]);
self.activeConversationId = undefined;
});
return {
fetchMoreMessages,
deleteConversationById,
};
})
.actions((self) => {
const setActiveConversationId = flow(function* (
convoId: string | undefined
) {
self.activeConversationId = convoId;
});
return { setActiveConversationId };
})
.actions((self) => ({
clearActiveConversationId() {
self.activeConversationId = undefined;
self.showModelDetail = false;
self.showAdvancedPrompt = false;
},
setConversations(conversations: Instance<typeof Conversation>[]) {
self.conversations = castToSnapshot(conversations);
},
clearAllConversations() {
self.conversations = castToSnapshot([]);
},
}))
.actions((self) => {
const sendTextToTextMessage = flow(function* (
create: CreateMessageMutationFunc,
conversation: Instance<typeof Conversation>
) {
// TODO: handle case timeout using higher order function
const latestMessages = conversation.chatMessages.slice(-5).map((e) => ({
role:
e.messageSenderType === MessageSenderType.User
? Role.User
: Role.Assistant,
content: e.text,
}));
const variables: CreateMessageMutationVariables = {
data: {
conversation_id: conversation.id,
content: "",
sender: MessageSenderType.Ai,
message_sender_type: MessageSenderType.Ai,
message_type: MessageType.Text,
sender_avatar_url: conversation.product.avatarUrl,
sender_name: conversation.product.name,
prompt_cache: latestMessages,
status: MessageStatus.Pending,
},
};
const result: FetchResult<CreateMessageMutation> = yield create({
variables,
});
if (!result.data?.insert_messages_one?.id) {
console.error(
"Error creating user message",
JSON.stringify(result.errors)
);
conversation.setWaitingForModelResponse(false);
return;
}
const aiResponseMessage = ChatMessage.create({
id: result.data.insert_messages_one.id,
conversationId: conversation.id,
messageType: MessageType.Text,
messageSenderType: MessageSenderType.Ai,
senderUid: conversation.product.id,
senderName: conversation.product.name,
senderAvatarUrl: conversation.product.avatarUrl ?? "",
text: "",
status: MessageStatus.Pending,
createdAt: Date.now(),
});
conversation.addMessage(aiResponseMessage);
});
const sendTextToImageMessage = flow(function* (
create: CreateMessageMutationFunc,
generateImage: ImageGenerationMutationFunc,
message: string,
conversation: Instance<typeof Conversation>
) {
// TODO: handle case timeout using higher order function
// const data = yield api.textToImage(conversation.product.id, message);
const variables: GenerateImageMutationVariables = {
model: conversation.product.id,
prompt: message,
neg_prompt: "",
seed: Math.floor(Math.random() * 429496729),
steps: 30,
width: 512,
height: 512,
};
const data: FetchResult<GenerateImageMutation> = yield generateImage({
variables,
});
if (!data.data?.imageGeneration?.url) {
// TODO: display error
console.error(
"Error creating user message",
JSON.stringify(data.errors)
);
conversation.setWaitingForModelResponse(false);
return;
}
const imageUrl: string = data.data.imageGeneration.url;
const createMessageVariables: CreateMessageMutationVariables = {
data: {
conversation_id: conversation.id,
content: message,
sender: MessageSenderType.Ai,
message_sender_type: MessageSenderType.Ai,
message_type: MessageType.Image,
sender_avatar_url: conversation.product.avatarUrl,
sender_name: conversation.product.name,
status: MessageStatus.Ready,
message_medias: {
data: [
{
media_url: imageUrl,
mime_type: "image/jpeg",
},
],
},
},
};
const result: FetchResult<CreateMessageMutation> = yield create({
variables: createMessageVariables,
});
if (!result.data?.insert_messages_one?.id) {
// TODO: display error
console.error(
"Error creating user message",
JSON.stringify(result.errors)
);
conversation.setWaitingForModelResponse(false);
return;
}
const imageResponseMessage = ChatMessage.create({
id: result.data.insert_messages_one.id,
conversationId: conversation.id,
messageType: MessageType.Image,
messageSenderType: MessageSenderType.Ai,
senderUid: conversation.product.id,
senderName: conversation.product.name,
senderAvatarUrl: conversation.product.avatarUrl,
text: message,
imageUrls: [imageUrl],
createdAt: Date.now(),
status: MessageStatus.Ready,
});
conversation.addMessage(imageResponseMessage);
conversation.setProp("updatedAt", Date.now());
conversation.setProp("lastImageUrl", imageUrl);
conversation.setWaitingForModelResponse(false);
});
return {
sendTextToTextMessage,
sendTextToImageMessage,
};
})
.actions((self) => {
const sendControlNetPrompt = flow(function* (
create: CreateMessageMutationFunc,
prompt: string,
negPrompt: string,
file: any // TODO: file type, for now I don't know what is that
) {
if (!self.activeConversationId) {
console.error("No active conversation found");
return;
}
const conversation = self.getActiveConversation();
if (!conversation) {
console.error(
"No active conversation found with id",
self.activeConversationId
);
return;
}
conversation.setWaitingForModelResponse(true);
const imageUrl = yield controlNetRequest("", prompt, negPrompt, file);
if (!imageUrl || !imageUrl.startsWith("https://")) {
console.error(
"Failed to invoking control net",
self.activeConversationId
);
return;
}
const message = `${prompt}. Negative: ${negPrompt}`;
const variables: CreateMessageMutationVariables = {
data: {
conversation_id: conversation.id,
content: message,
sender: MessageSenderType.Ai,
message_sender_type: MessageSenderType.Ai,
message_type: MessageType.ImageWithText,
sender_avatar_url: conversation.product.avatarUrl,
sender_name: conversation.product.name,
message_medias: {
data: [
{
media_url: imageUrl,
},
],
},
},
};
const result: FetchResult<CreateMessageMutation> = yield create({
variables,
});
if (!result.data?.insert_messages_one?.id) {
// TODO: display error
console.error(
"Error creating user message",
JSON.stringify(result.errors)
);
conversation.setWaitingForModelResponse(false);
return;
}
const chatMessage = ChatMessage.create({
id: result.data.insert_messages_one.id,
conversationId: self.activeConversationId,
messageType: MessageType.ImageWithText,
messageSenderType: MessageSenderType.Ai,
senderUid: conversation.product.id,
senderName: conversation.product.name,
senderAvatarUrl: conversation.product.avatarUrl,
text: message,
imageUrls: [imageUrl],
createdAt: Date.now(),
status: MessageStatus.Ready,
});
conversation.addMessage(chatMessage);
conversation.setProp("lastTextMessage", message);
conversation.setProp("lastImageUrl", imageUrl);
conversation.setWaitingForModelResponse(false);
});
const createConversation = flow(function* (
conversation: ConversationDetailFragment,
product: ProductDetailFragment,
userId: string,
displayName: string,
avatarUrl?: string
) {
let modelType: AiModelType | undefined = undefined;
if (product.inputs.slug === "llm") {
modelType = AiModelType.LLM;
} else if (product.inputs.slug === "sd") {
modelType = AiModelType.GenerativeArt;
} else if (product.inputs.slug === "controlnet") {
modelType = AiModelType.ControlNet;
} else {
console.error("Model type not supported");
return;
}
const productModel = Product.create({
name: product.name,
id: product.slug,
type: modelType,
description: product.description,
modelUrl: product.source_url,
modelVersion: product.version,
avatarUrl: product.image_url,
});
const newConvo = Conversation.create({
id: conversation.id,
product: productModel,
lastTextMessage: conversation.last_text_message ?? "",
user: User.create({
id: userId,
displayName,
avatarUrl,
}),
createdAt: Date.now(),
updatedAt: Date.now(),
});
self.conversations.push(newConvo);
self.activeConversationId = newConvo.id;
});
const sendMessage = flow(function* (
create: CreateMessageMutationFunc,
generateImage: ImageGenerationMutationFunc,
message: string,
userId: string,
displayName: string,
avatarUrl?: string
) {
if (!self.activeConversationId) {
console.error("No active conversation found");
return;
}
const conversation = self.getActiveConversation();
if (!conversation) {
console.error(
"No active conversation found with id",
self.activeConversationId
);
return;
}
conversation.setWaitingForModelResponse(true);
const variables: CreateMessageMutationVariables = {
data: {
conversation_id: conversation.id,
content: message,
sender: userId,
message_sender_type: MessageSenderType.User,
message_type: MessageType.Text,
sender_avatar_url: avatarUrl,
sender_name: displayName,
},
};
const result: FetchResult<CreateMessageMutation> = yield create({
variables,
});
if (!result.data?.insert_messages_one?.id) {
// TODO: display error
console.error(
"Error creating user message",
JSON.stringify(result.errors)
);
conversation.setWaitingForModelResponse(false);
return;
}
const userMesssage = ChatMessage.create({
id: result.data.insert_messages_one.id,
conversationId: self.activeConversationId,
messageType: MessageType.Text,
messageSenderType: MessageSenderType.User,
senderUid: userId,
senderName: displayName,
senderAvatarUrl: avatarUrl,
text: message,
createdAt: Date.now(),
status: MessageStatus.Ready,
});
conversation.addMessage(userMesssage);
conversation.setProp("lastTextMessage", message);
if (conversation.product.type === AiModelType.LLM) {
yield self.sendTextToTextMessage(create, conversation);
} else if (conversation.product.type === AiModelType.GenerativeArt) {
yield self.sendTextToImageMessage(
create,
generateImage,
message,
conversation
);
} else {
console.error(
"We do not support this model type yet:",
conversation.product.type
);
}
});
return {
sendMessage,
sendControlNetPrompt,
createConversation,
};
});

View File

@ -1,19 +0,0 @@
import { types } from "mobx-state-tree";
export const InputHeaderModel = types.model("InputHeader", {
accept: types.maybeNull(types.string),
contentType: types.maybeNull(types.string),
});
export const InputBodyModel = types.model("InputBody", {
name: types.string,
type: types.string,
example: types.maybeNull(types.string),
description: types.string,
});
export const InputModel = types.model("Input", {
slug: types.string,
header: InputHeaderModel,
body: types.array(InputBodyModel),
});

View File

@ -1,14 +0,0 @@
import { types } from "mobx-state-tree";
export const OutputPropertyModel = types.model("OutputProperty", {
name: types.string,
type: types.string,
description: types.string,
});
export const OutputModel = types.model("Output", {
slug: types.string,
type: types.string,
properties: types.maybeNull(types.array(OutputPropertyModel)),
description: types.string,
});

View File

@ -1,22 +1,80 @@
import { types } from "mobx-state-tree"; import { ProductDetailFragment } from "@/graphql";
import { InputModel } from "./Input"; import { ProductInput } from "./ProductInput";
import { OutputModel } from "./Output"; import { ProductOutput } from "./ProductOutput";
export enum AiModelType { export enum ProductType {
LLM = "LLM", LLM = "LLM",
GenerativeArt = "GenerativeArt", GenerativeArt = "GenerativeArt",
ControlNet = "ControlNet", ControlNet = "ControlNet",
} }
export const Product = types.model("Product", { export interface Product {
id: types.string, // TODO change to slug id: string;
name: types.string, slug: string;
type: types.enumeration(Object.values(AiModelType)), name: string;
description: types.maybeNull(types.string), description: string;
avatarUrl: types.maybeNull(types.string), avatarUrl: string;
modelVersion: types.maybeNull(types.string), longDescription: string;
modelUrl: types.maybeNull(types.string), technicalDescription: string;
modelDescription: types.maybeNull(types.string), author: string;
input: types.maybeNull(InputModel), version: string;
output: types.maybeNull(OutputModel), modelUrl: string;
}); nsfw: boolean;
greeting: string;
type: ProductType;
inputs?: ProductInput;
outputs?: ProductOutput;
createdAt: number;
updatedAt?: number;
}
export function toProduct(
productDetailFragment: ProductDetailFragment
): Product {
const {
id,
slug,
name,
description,
image_url,
long_description,
technical_description,
author,
version,
source_url,
nsfw,
greeting,
created_at,
updated_at,
} = productDetailFragment;
let modelType: ProductType | undefined = undefined;
if (productDetailFragment.inputs.slug === "llm") {
modelType = ProductType.LLM;
} else if (productDetailFragment.inputs.slug === "sd") {
modelType = ProductType.GenerativeArt;
} else if (productDetailFragment.inputs.slug === "controlnet") {
modelType = ProductType.ControlNet;
} else {
throw new Error("Model type not supported");
}
const product: Product = {
id,
slug,
name,
description: description ?? "",
avatarUrl: image_url ?? "/icons/app_icon.svg",
longDescription: long_description ?? "",
technicalDescription: technical_description ?? "",
author: author ?? "",
version: version ?? "",
modelUrl: source_url ?? "",
nsfw: nsfw ?? false,
greeting: greeting ?? "",
type: modelType,
createdAt: new Date(created_at).getTime(),
updatedAt: new Date(updated_at).getTime(),
};
return product;
}

View File

@ -0,0 +1,23 @@
export interface ProductInput {
body: ItemProperties[];
slug: string;
headers: ProductHeader;
}
export type ProductHeader = {
accept: string;
contentType: string;
};
export type ItemProperties = {
name: string;
type: string;
items?: ProductBodyItem[];
example?: unknown;
description?: string;
};
export type ProductBodyItem = {
type: string;
properties: ItemProperties[];
};

View File

@ -0,0 +1,8 @@
import { ItemProperties } from "./ProductInput";
export interface ProductOutput {
slug: string;
type: string;
properties: ItemProperties[];
description: string;
}

View File

@ -1,81 +0,0 @@
// Should change name to Product after we remove the old one
import { AiModelType } from "../_models/Product";
export interface Collection {
id: number;
created_at: string;
updated_at: string | undefined;
deleted_at: string | undefined;
slug: CollectionType;
name: string;
description: string;
products: ProductV2[];
}
export interface ProductV2 {
id: number;
created_at: string;
updated_at: string | undefined;
deleted_at: string | undefined;
slug: string;
name: string;
nsfw: boolean;
image_url: string;
description: string;
long_description: string;
technical_description: string;
author: string;
version: string;
source_url: string;
collections: Collection[];
prompts: Prompt[] | undefined;
inputs: InputResponse;
outputs: Record<string, unknown>;
greeting: string;
modelType: AiModelType;
}
export interface OutputResponse {}
export interface InputProperty {
name: string;
type: string;
example: string;
description: string;
}
export interface InputBody {
name: string;
type: string; // TODO make enum for this
items: InputArrayItem[] | undefined;
example: unknown;
description: string;
}
export interface InputArrayItem {
type: string;
properties: InputProperty[];
}
export interface InputResponse {
body: InputBody[];
slug: string;
headers: Record<string, string>;
}
export interface Prompt {
id: number;
created_at: string;
updated_at: string | undefined;
deleted_at: string | undefined;
slug: string;
content: string;
image_url: string | undefined;
products: ProductV2[] | undefined;
}
export type CollectionType = "conversational" | "text-to-image";

View File

@ -1,36 +0,0 @@
import { createContext, useContext } from "react";
import { Instance, types } from "mobx-state-tree";
import { History } from "./History";
import { values } from "mobx";
export const RootStore = types
.model("RootStore", {
historyStore: types.optional(History, {}),
})
.views((self) => ({
get activeConversationId() {
return values(self.historyStore.activeConversationId);
},
get conversations() {
return values(self.historyStore.conversations);
},
}));
export function initializeStore(): RootInstance {
const _store: RootInstance = RootStore.create({});
return _store;
}
export type RootInstance = Instance<typeof RootStore>;
const RootStoreContext = createContext<null | RootInstance>(null);
export const Provider = RootStoreContext.Provider;
export function useStore(): Instance<typeof RootStore> {
const store = useContext(RootStoreContext);
if (store === null) {
throw new Error("Store cannot be null, please add a context provider");
}
return store;
}

View File

@ -1,7 +0,0 @@
import { types } from "mobx-state-tree";
export const Shortcut = types.model("Shortcut", {
name: types.string,
title: types.string,
avatarUrl: types.string,
});

View File

@ -1,15 +1,18 @@
import { types } from "mobx-state-tree"; export interface User {
id: string;
export const User = types.model("User", { displayName: string;
id: types.string, avatarUrl: string;
displayName: types.optional(types.string, "Anonymous"), email?: string;
avatarUrl: types.maybe(types.string), }
email: types.maybe(types.string),
});
export const DefaultUser = { export const DefaultUser = {
id: "0", id: "0",
displayName: "Anonymous", displayName: "Anonymous",
avatarUrl: undefined, avatarUrl: "/icons/app_icon.svg",
email: "", email: "",
}; };
export enum Role {
User = "user",
Assistant = "assistant",
}

View File

@ -1 +1 @@
export const MESSAGE_PER_PAGE = 100; export const MESSAGE_PER_PAGE = 10;

View File

@ -1,4 +1,3 @@
import { Instance } from "mobx-state-tree";
import { ChatMessage } from "../_models/ChatMessage"; import { ChatMessage } from "../_models/ChatMessage";
/** /**
@ -10,12 +9,12 @@ import { ChatMessage } from "../_models/ChatMessage";
* @returns Merged array of messages * @returns Merged array of messages
*/ */
export function mergeAndRemoveDuplicates( export function mergeAndRemoveDuplicates(
arr1: Instance<typeof ChatMessage>[], arr1: ChatMessage[],
arr2: Instance<typeof ChatMessage>[] arr2: ChatMessage[]
): Instance<typeof ChatMessage>[] { ): ChatMessage[] {
const mergedArray = arr1.concat(arr2); const mergedArray = arr1.concat(arr2);
const uniqueIdMap = new Map<string, boolean>(); const uniqueIdMap = new Map<string, boolean>();
const result: Instance<typeof ChatMessage>[] = []; const result: ChatMessage[] = [];
for (const message of mergedArray) { for (const message of mergedArray) {
if (!uniqueIdMap.has(message.id)) { if (!uniqueIdMap.has(message.id)) {

View File

@ -1,7 +1,6 @@
import "./globals.css"; import "./globals.css";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import classNames from "classnames"; import classNames from "classnames";
import MobileShowcase from "@/_components/MobileShowcase";
import { Metadata } from "next"; import { Metadata } from "next";
import SessionProviderWrapper from "@/_components/SessionProviderWrapper"; import SessionProviderWrapper from "@/_components/SessionProviderWrapper";
@ -19,25 +18,16 @@ export const metadata: Metadata = {
}, },
}; };
export default function RootLayout({ type Props = {
children,
}: {
children: React.ReactNode; children: React.ReactNode;
}) { };
export default function RootLayout({ children }: Props) {
return ( return (
<SessionProviderWrapper> <SessionProviderWrapper>
<html lang="en"> {/* suppressHydrationWarning is for next-themes */}
<body <html lang="en" suppressHydrationWarning>
className={classNames( <body className={classNames(inter.className)}>{children}</body>
inter.className,
"flex flex-col w-full h-screen"
)}
>
<div className="hidden md:flex flex-col w-full h-screen">
{children}
</div>
<MobileShowcase />
</body>
</html> </html>
</SessionProviderWrapper> </SessionProviderWrapper>
); );

View File

@ -1,34 +1,23 @@
import Header from "@/_components/Header";
import { AdvancedPrompt } from "@/_components/AdvancedPrompt";
import ChatContainer from "@/_components/ChatContainer";
import { CompactSideBar } from "@/_components/CompactSideBar";
import { SidebarLeft } from "@/_components/SidebarLeft";
import { ApolloWrapper } from "./_helpers/ApolloWrapper"; import { ApolloWrapper } from "./_helpers/ApolloWrapper";
import { MobxWrapper } from "./_helpers/MobxWrapper";
import { ThemeWrapper } from "./_helpers/ThemeWrapper"; import { ThemeWrapper } from "./_helpers/ThemeWrapper";
import JotaiWrapper from "./_helpers/JotaiWrapper";
import LeftContainer from "./_components/LeftContainer";
import RightContainer from "./_components/RightContainer";
import { ModalWrapper } from "./_helpers/ModalWrapper";
const Page: React.FC = () => { const Page: React.FC = () => (
return (
<ApolloWrapper> <ApolloWrapper>
<MobxWrapper> <JotaiWrapper>
<ThemeWrapper> <ThemeWrapper>
<div className="flex w-full h-screen"> <ModalWrapper>
<div className="flex h-screen z-100"> <div className="flex">
<SidebarLeft /> <LeftContainer />
<CompactSideBar /> <RightContainer />
<AdvancedPrompt />
</div>
<div className="w-full max-h-screen flex-1 flex flex-col">
<div className="flex-shrink-0 flex-0">
<Header />
</div>
<ChatContainer />
</div>
</div> </div>
</ModalWrapper>
</ThemeWrapper> </ThemeWrapper>
</MobxWrapper> </JotaiWrapper>
</ApolloWrapper> </ApolloWrapper>
); );
};
export default Page; export default Page;

View File

@ -14,3 +14,4 @@ END_SESSION_URL=
REFRESH_TOKEN_URL= REFRESH_TOKEN_URL=
// For codegen only // For codegen only
HASURA_ADMIN_TOKEN= HASURA_ADMIN_TOKEN=
NEXT_PUBLIC_OPENAPI_ENDPOINT=

View File

@ -13,4 +13,6 @@ fragment ProductDetail on products {
inputs inputs
outputs outputs
nsfw nsfw
created_at
updated_at
} }

View File

@ -17,7 +17,7 @@ const documents = {
"fragment ConversationDetail on conversations {\n id\n product_id\n user_id\n last_image_url\n last_text_message\n created_at\n updated_at\n conversation_product {\n ...ProductDetail\n }\n}": types.ConversationDetailFragmentDoc, "fragment ConversationDetail on conversations {\n id\n product_id\n user_id\n last_image_url\n last_text_message\n created_at\n updated_at\n conversation_product {\n ...ProductDetail\n }\n}": types.ConversationDetailFragmentDoc,
"fragment MessageMedia on message_medias {\n id\n message_id\n media_url\n mime_type\n updated_at\n}": types.MessageMediaFragmentDoc, "fragment MessageMedia on message_medias {\n id\n message_id\n media_url\n mime_type\n updated_at\n}": types.MessageMediaFragmentDoc,
"fragment MessageDetail on messages {\n id\n conversation_id\n sender\n sender_name\n sender_avatar_url\n content\n message_type\n message_sender_type\n created_at\n updated_at\n status\n message_medias {\n ...MessageMedia\n }\n}": types.MessageDetailFragmentDoc, "fragment MessageDetail on messages {\n id\n conversation_id\n sender\n sender_name\n sender_avatar_url\n content\n message_type\n message_sender_type\n created_at\n updated_at\n status\n message_medias {\n ...MessageMedia\n }\n}": types.MessageDetailFragmentDoc,
"fragment ProductDetail on products {\n id\n name\n slug\n description\n long_description\n technical_description\n image_url\n author\n greeting\n source_url\n version\n inputs\n outputs\n nsfw\n}": types.ProductDetailFragmentDoc, "fragment ProductDetail on products {\n id\n name\n slug\n description\n long_description\n technical_description\n image_url\n author\n greeting\n source_url\n version\n inputs\n outputs\n nsfw\n created_at\n updated_at\n}": types.ProductDetailFragmentDoc,
"fragment PromptDetail on prompts {\n slug\n content\n image_url\n}": types.PromptDetailFragmentDoc, "fragment PromptDetail on prompts {\n slug\n content\n image_url\n}": types.PromptDetailFragmentDoc,
"mutation createConversation($data: conversations_insert_input!) {\n insert_conversations_one(object: $data) {\n ...ConversationDetail\n }\n}": types.CreateConversationDocument, "mutation createConversation($data: conversations_insert_input!) {\n insert_conversations_one(object: $data) {\n ...ConversationDetail\n }\n}": types.CreateConversationDocument,
"mutation createMessage($data: messages_insert_input!) {\n insert_messages_one(object: $data) {\n ...MessageDetail\n }\n}": types.CreateMessageDocument, "mutation createMessage($data: messages_insert_input!) {\n insert_messages_one(object: $data) {\n ...MessageDetail\n }\n}": types.CreateMessageDocument,
@ -26,13 +26,13 @@ const documents = {
"mutation updateConversation($id: uuid!, $lastMessageText: String, $lastMessageUrl: String) {\n update_conversations_by_pk(\n pk_columns: {id: $id}\n _set: {last_text_message: $lastMessageText, last_image_url: $lastMessageUrl}\n ) {\n ...ConversationDetail\n }\n}": types.UpdateConversationDocument, "mutation updateConversation($id: uuid!, $lastMessageText: String, $lastMessageUrl: String) {\n update_conversations_by_pk(\n pk_columns: {id: $id}\n _set: {last_text_message: $lastMessageText, last_image_url: $lastMessageUrl}\n ) {\n ...ConversationDetail\n }\n}": types.UpdateConversationDocument,
"mutation updateMessage($id: uuid = \"\", $data: messages_set_input!) {\n update_messages_by_pk(pk_columns: {id: $id}, _set: $data) {\n ...MessageDetail\n }\n}": types.UpdateMessageDocument, "mutation updateMessage($id: uuid = \"\", $data: messages_set_input!) {\n update_messages_by_pk(pk_columns: {id: $id}, _set: $data) {\n ...MessageDetail\n }\n}": types.UpdateMessageDocument,
"query getCollections {\n collections {\n ...CollectionDetail\n collection_products {\n products {\n ...ProductDetail\n product_prompts {\n prompts {\n ...PromptDetail\n }\n }\n }\n }\n }\n}": types.GetCollectionsDocument, "query getCollections {\n collections {\n ...CollectionDetail\n collection_products {\n products {\n ...ProductDetail\n product_prompts {\n prompts {\n ...PromptDetail\n }\n }\n }\n }\n }\n}": types.GetCollectionsDocument,
"query getConversationMessages($conversation_id: uuid = \"\", $limit: Int = 100, $offset: Int = 100) {\n messages(\n offset: $offset\n limit: $limit\n where: {conversation_id: {_eq: $conversation_id}}\n order_by: {updated_at: desc}\n ) {\n ...MessageDetail\n }\n}": types.GetConversationMessagesDocument, "query getConversationMessages($conversation_id: uuid = \"\", $limit: Int = 100, $offset: Int = 100) {\n messages(\n offset: $offset\n limit: $limit\n where: {conversation_id: {_eq: $conversation_id}}\n order_by: {created_at: desc}\n ) {\n ...MessageDetail\n }\n}": types.GetConversationMessagesDocument,
"query getConversations {\n conversations {\n ...ConversationDetail\n conversation_messages {\n ...MessageDetail\n message_medias {\n ...MessageMedia\n }\n }\n }\n}": types.GetConversationsDocument, "query getConversations {\n conversations(order_by: {updated_at: desc}) {\n ...ConversationDetail\n conversation_messages {\n ...MessageDetail\n message_medias {\n ...MessageMedia\n }\n }\n }\n}": types.GetConversationsDocument,
"query getProductsByCollectionSlug($slug: String = \"\") {\n products(where: {product_collections: {collections: {slug: {_eq: $slug}}}}) {\n ...ProductDetail\n product_prompts {\n prompts {\n ...PromptDetail\n }\n }\n product_collections {\n collections {\n ...CollectionDetail\n }\n }\n }\n}": types.GetProductsByCollectionSlugDocument, "query getProductsByCollectionSlug($slug: String = \"\") {\n products(where: {product_collections: {collections: {slug: {_eq: $slug}}}}) {\n ...ProductDetail\n product_prompts {\n prompts {\n ...PromptDetail\n }\n }\n product_collections {\n collections {\n ...CollectionDetail\n }\n }\n }\n}": types.GetProductsByCollectionSlugDocument,
"query getProductPrompts($productSlug: String = \"\") {\n prompts(where: {prompt_products: {products: {slug: {_eq: $productSlug}}}}) {\n ...PromptDetail\n }\n}": types.GetProductPromptsDocument, "query getProductPrompts($productSlug: String = \"\") {\n prompts(where: {prompt_products: {products: {slug: {_eq: $productSlug}}}}) {\n ...PromptDetail\n }\n}": types.GetProductPromptsDocument,
"query getProducts {\n products {\n ...ProductDetail\n product_prompts {\n prompts {\n ...PromptDetail\n }\n }\n product_collections {\n collections {\n ...CollectionDetail\n }\n }\n }\n}": types.GetProductsDocument, "query getProducts {\n products {\n ...ProductDetail\n product_prompts {\n prompts {\n ...PromptDetail\n }\n }\n product_collections {\n collections {\n ...CollectionDetail\n }\n }\n }\n}": types.GetProductsDocument,
"query getProductsIn($_in: [String!] = \"\") {\n products(where: {slug: {_in: $_in}}) {\n ...ProductDetail\n }\n}": types.GetProductsInDocument, "query getProductsIn($_in: [String!] = \"\") {\n products(where: {slug: {_in: $_in}}) {\n ...ProductDetail\n }\n}": types.GetProductsInDocument,
"subscription subscribeMessage($id: uuid = \"\") {\n messages_by_pk(id: $id) {\n content\n status\n }\n}": types.SubscribeMessageDocument, "subscription subscribeMessage($id: uuid = \"\") {\n messages_by_pk(id: $id) {\n id\n content\n status\n }\n}": types.SubscribeMessageDocument,
}; };
/** /**
@ -68,7 +68,7 @@ export function gql(source: "fragment MessageDetail on messages {\n id\n conve
/** /**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function gql(source: "fragment ProductDetail on products {\n id\n name\n slug\n description\n long_description\n technical_description\n image_url\n author\n greeting\n source_url\n version\n inputs\n outputs\n nsfw\n}"): (typeof documents)["fragment ProductDetail on products {\n id\n name\n slug\n description\n long_description\n technical_description\n image_url\n author\n greeting\n source_url\n version\n inputs\n outputs\n nsfw\n}"]; export function gql(source: "fragment ProductDetail on products {\n id\n name\n slug\n description\n long_description\n technical_description\n image_url\n author\n greeting\n source_url\n version\n inputs\n outputs\n nsfw\n created_at\n updated_at\n}"): (typeof documents)["fragment ProductDetail on products {\n id\n name\n slug\n description\n long_description\n technical_description\n image_url\n author\n greeting\n source_url\n version\n inputs\n outputs\n nsfw\n created_at\n updated_at\n}"];
/** /**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
@ -104,11 +104,11 @@ export function gql(source: "query getCollections {\n collections {\n ...Col
/** /**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function gql(source: "query getConversationMessages($conversation_id: uuid = \"\", $limit: Int = 100, $offset: Int = 100) {\n messages(\n offset: $offset\n limit: $limit\n where: {conversation_id: {_eq: $conversation_id}}\n order_by: {updated_at: desc}\n ) {\n ...MessageDetail\n }\n}"): (typeof documents)["query getConversationMessages($conversation_id: uuid = \"\", $limit: Int = 100, $offset: Int = 100) {\n messages(\n offset: $offset\n limit: $limit\n where: {conversation_id: {_eq: $conversation_id}}\n order_by: {updated_at: desc}\n ) {\n ...MessageDetail\n }\n}"]; export function gql(source: "query getConversationMessages($conversation_id: uuid = \"\", $limit: Int = 100, $offset: Int = 100) {\n messages(\n offset: $offset\n limit: $limit\n where: {conversation_id: {_eq: $conversation_id}}\n order_by: {created_at: desc}\n ) {\n ...MessageDetail\n }\n}"): (typeof documents)["query getConversationMessages($conversation_id: uuid = \"\", $limit: Int = 100, $offset: Int = 100) {\n messages(\n offset: $offset\n limit: $limit\n where: {conversation_id: {_eq: $conversation_id}}\n order_by: {created_at: desc}\n ) {\n ...MessageDetail\n }\n}"];
/** /**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function gql(source: "query getConversations {\n conversations {\n ...ConversationDetail\n conversation_messages {\n ...MessageDetail\n message_medias {\n ...MessageMedia\n }\n }\n }\n}"): (typeof documents)["query getConversations {\n conversations {\n ...ConversationDetail\n conversation_messages {\n ...MessageDetail\n message_medias {\n ...MessageMedia\n }\n }\n }\n}"]; export function gql(source: "query getConversations {\n conversations(order_by: {updated_at: desc}) {\n ...ConversationDetail\n conversation_messages {\n ...MessageDetail\n message_medias {\n ...MessageMedia\n }\n }\n }\n}"): (typeof documents)["query getConversations {\n conversations(order_by: {updated_at: desc}) {\n ...ConversationDetail\n conversation_messages {\n ...MessageDetail\n message_medias {\n ...MessageMedia\n }\n }\n }\n}"];
/** /**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
@ -128,7 +128,7 @@ export function gql(source: "query getProductsIn($_in: [String!] = \"\") {\n pr
/** /**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function gql(source: "subscription subscribeMessage($id: uuid = \"\") {\n messages_by_pk(id: $id) {\n content\n status\n }\n}"): (typeof documents)["subscription subscribeMessage($id: uuid = \"\") {\n messages_by_pk(id: $id) {\n content\n status\n }\n}"]; export function gql(source: "subscription subscribeMessage($id: uuid = \"\") {\n messages_by_pk(id: $id) {\n id\n content\n status\n }\n}"): (typeof documents)["subscription subscribeMessage($id: uuid = \"\") {\n messages_by_pk(id: $id) {\n id\n content\n status\n }\n}"];
export function gql(source: string) { export function gql(source: string) {
return (documents as any)[source] ?? {}; return (documents as any)[source] ?? {};

View File

@ -3639,13 +3639,13 @@ export type Uuid_Comparison_Exp = {
export type CollectionDetailFragment = { __typename?: 'collections', slug: string, name: string }; export type CollectionDetailFragment = { __typename?: 'collections', slug: string, name: string };
export type ConversationDetailFragment = { __typename?: 'conversations', id: any, product_id: any, user_id: string, last_image_url?: string | null, last_text_message?: string | null, created_at: any, updated_at: any, conversation_product?: { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean } | null }; export type ConversationDetailFragment = { __typename?: 'conversations', id: any, product_id: any, user_id: string, last_image_url?: string | null, last_text_message?: string | null, created_at: any, updated_at: any, conversation_product?: { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any } | null };
export type MessageMediaFragment = { __typename?: 'message_medias', id: any, message_id: any, media_url?: string | null, mime_type?: string | null, updated_at: any }; export type MessageMediaFragment = { __typename?: 'message_medias', id: any, message_id: any, media_url?: string | null, mime_type?: string | null, updated_at: any };
export type MessageDetailFragment = { __typename?: 'messages', id: any, conversation_id: any, sender: string, sender_name?: string | null, sender_avatar_url?: string | null, content?: string | null, message_type?: string | null, message_sender_type?: string | null, created_at: any, updated_at: any, status?: string | null, message_medias: Array<{ __typename?: 'message_medias', id: any, message_id: any, media_url?: string | null, mime_type?: string | null, updated_at: any }> }; export type MessageDetailFragment = { __typename?: 'messages', id: any, conversation_id: any, sender: string, sender_name?: string | null, sender_avatar_url?: string | null, content?: string | null, message_type?: string | null, message_sender_type?: string | null, created_at: any, updated_at: any, status?: string | null, message_medias: Array<{ __typename?: 'message_medias', id: any, message_id: any, media_url?: string | null, mime_type?: string | null, updated_at: any }> };
export type ProductDetailFragment = { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean }; export type ProductDetailFragment = { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any };
export type PromptDetailFragment = { __typename?: 'prompts', slug: string, content?: string | null, image_url?: string | null }; export type PromptDetailFragment = { __typename?: 'prompts', slug: string, content?: string | null, image_url?: string | null };
@ -3654,7 +3654,7 @@ export type CreateConversationMutationVariables = Exact<{
}>; }>;
export type CreateConversationMutation = { __typename?: 'mutation_root', insert_conversations_one?: { __typename?: 'conversations', id: any, product_id: any, user_id: string, last_image_url?: string | null, last_text_message?: string | null, created_at: any, updated_at: any, conversation_product?: { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean } | null } | null }; export type CreateConversationMutation = { __typename?: 'mutation_root', insert_conversations_one?: { __typename?: 'conversations', id: any, product_id: any, user_id: string, last_image_url?: string | null, last_text_message?: string | null, created_at: any, updated_at: any, conversation_product?: { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any } | null } | null };
export type CreateMessageMutationVariables = Exact<{ export type CreateMessageMutationVariables = Exact<{
data: Messages_Insert_Input; data: Messages_Insert_Input;
@ -3690,7 +3690,7 @@ export type UpdateConversationMutationVariables = Exact<{
}>; }>;
export type UpdateConversationMutation = { __typename?: 'mutation_root', update_conversations_by_pk?: { __typename?: 'conversations', id: any, product_id: any, user_id: string, last_image_url?: string | null, last_text_message?: string | null, created_at: any, updated_at: any, conversation_product?: { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean } | null } | null }; export type UpdateConversationMutation = { __typename?: 'mutation_root', update_conversations_by_pk?: { __typename?: 'conversations', id: any, product_id: any, user_id: string, last_image_url?: string | null, last_text_message?: string | null, created_at: any, updated_at: any, conversation_product?: { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any } | null } | null };
export type UpdateMessageMutationVariables = Exact<{ export type UpdateMessageMutationVariables = Exact<{
id?: InputMaybe<Scalars['uuid']['input']>; id?: InputMaybe<Scalars['uuid']['input']>;
@ -3703,7 +3703,7 @@ export type UpdateMessageMutation = { __typename?: 'mutation_root', update_messa
export type GetCollectionsQueryVariables = Exact<{ [key: string]: never; }>; export type GetCollectionsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCollectionsQuery = { __typename?: 'query_root', collections: Array<{ __typename?: 'collections', slug: string, name: string, collection_products: Array<{ __typename?: 'collection_products', products: Array<{ __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, product_prompts: Array<{ __typename?: 'product_prompts', prompts: Array<{ __typename?: 'prompts', slug: string, content?: string | null, image_url?: string | null }> }> }> }> }> }; export type GetCollectionsQuery = { __typename?: 'query_root', collections: Array<{ __typename?: 'collections', slug: string, name: string, collection_products: Array<{ __typename?: 'collection_products', products: Array<{ __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any, product_prompts: Array<{ __typename?: 'product_prompts', prompts: Array<{ __typename?: 'prompts', slug: string, content?: string | null, image_url?: string | null }> }> }> }> }> };
export type GetConversationMessagesQueryVariables = Exact<{ export type GetConversationMessagesQueryVariables = Exact<{
conversation_id?: InputMaybe<Scalars['uuid']['input']>; conversation_id?: InputMaybe<Scalars['uuid']['input']>;
@ -3717,14 +3717,14 @@ export type GetConversationMessagesQuery = { __typename?: 'query_root', messages
export type GetConversationsQueryVariables = Exact<{ [key: string]: never; }>; export type GetConversationsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetConversationsQuery = { __typename?: 'query_root', conversations: Array<{ __typename?: 'conversations', id: any, product_id: any, user_id: string, last_image_url?: string | null, last_text_message?: string | null, created_at: any, updated_at: any, conversation_messages: Array<{ __typename?: 'messages', id: any, conversation_id: any, sender: string, sender_name?: string | null, sender_avatar_url?: string | null, content?: string | null, message_type?: string | null, message_sender_type?: string | null, created_at: any, updated_at: any, status?: string | null, message_medias: Array<{ __typename?: 'message_medias', id: any, message_id: any, media_url?: string | null, mime_type?: string | null, updated_at: any }> }>, conversation_product?: { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean } | null }> }; export type GetConversationsQuery = { __typename?: 'query_root', conversations: Array<{ __typename?: 'conversations', id: any, product_id: any, user_id: string, last_image_url?: string | null, last_text_message?: string | null, created_at: any, updated_at: any, conversation_messages: Array<{ __typename?: 'messages', id: any, conversation_id: any, sender: string, sender_name?: string | null, sender_avatar_url?: string | null, content?: string | null, message_type?: string | null, message_sender_type?: string | null, created_at: any, updated_at: any, status?: string | null, message_medias: Array<{ __typename?: 'message_medias', id: any, message_id: any, media_url?: string | null, mime_type?: string | null, updated_at: any }> }>, conversation_product?: { __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any } | null }> };
export type GetProductsByCollectionSlugQueryVariables = Exact<{ export type GetProductsByCollectionSlugQueryVariables = Exact<{
slug?: InputMaybe<Scalars['String']['input']>; slug?: InputMaybe<Scalars['String']['input']>;
}>; }>;
export type GetProductsByCollectionSlugQuery = { __typename?: 'query_root', products: Array<{ __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, product_prompts: Array<{ __typename?: 'product_prompts', prompts: Array<{ __typename?: 'prompts', slug: string, content?: string | null, image_url?: string | null }> }>, product_collections: Array<{ __typename?: 'collection_products', collections: Array<{ __typename?: 'collections', slug: string, name: string }> }> }> }; export type GetProductsByCollectionSlugQuery = { __typename?: 'query_root', products: Array<{ __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any, product_prompts: Array<{ __typename?: 'product_prompts', prompts: Array<{ __typename?: 'prompts', slug: string, content?: string | null, image_url?: string | null }> }>, product_collections: Array<{ __typename?: 'collection_products', collections: Array<{ __typename?: 'collections', slug: string, name: string }> }> }> };
export type GetProductPromptsQueryVariables = Exact<{ export type GetProductPromptsQueryVariables = Exact<{
productSlug?: InputMaybe<Scalars['String']['input']>; productSlug?: InputMaybe<Scalars['String']['input']>;
@ -3736,39 +3736,39 @@ export type GetProductPromptsQuery = { __typename?: 'query_root', prompts: Array
export type GetProductsQueryVariables = Exact<{ [key: string]: never; }>; export type GetProductsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetProductsQuery = { __typename?: 'query_root', products: Array<{ __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, product_prompts: Array<{ __typename?: 'product_prompts', prompts: Array<{ __typename?: 'prompts', slug: string, content?: string | null, image_url?: string | null }> }>, product_collections: Array<{ __typename?: 'collection_products', collections: Array<{ __typename?: 'collections', slug: string, name: string }> }> }> }; export type GetProductsQuery = { __typename?: 'query_root', products: Array<{ __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any, product_prompts: Array<{ __typename?: 'product_prompts', prompts: Array<{ __typename?: 'prompts', slug: string, content?: string | null, image_url?: string | null }> }>, product_collections: Array<{ __typename?: 'collection_products', collections: Array<{ __typename?: 'collections', slug: string, name: string }> }> }> };
export type GetProductsInQueryVariables = Exact<{ export type GetProductsInQueryVariables = Exact<{
_in?: InputMaybe<Array<Scalars['String']['input']> | Scalars['String']['input']>; _in?: InputMaybe<Array<Scalars['String']['input']> | Scalars['String']['input']>;
}>; }>;
export type GetProductsInQuery = { __typename?: 'query_root', products: Array<{ __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean }> }; export type GetProductsInQuery = { __typename?: 'query_root', products: Array<{ __typename?: 'products', id: any, name: string, slug: string, description?: string | null, long_description?: string | null, technical_description?: string | null, image_url?: string | null, author?: string | null, greeting?: string | null, source_url?: string | null, version?: string | null, inputs?: any | null, outputs?: any | null, nsfw: boolean, created_at: any, updated_at: any }> };
export type SubscribeMessageSubscriptionVariables = Exact<{ export type SubscribeMessageSubscriptionVariables = Exact<{
id?: InputMaybe<Scalars['uuid']['input']>; id?: InputMaybe<Scalars['uuid']['input']>;
}>; }>;
export type SubscribeMessageSubscription = { __typename?: 'subscription_root', messages_by_pk?: { __typename?: 'messages', content?: string | null, status?: string | null } | null }; export type SubscribeMessageSubscription = { __typename?: 'subscription_root', messages_by_pk?: { __typename?: 'messages', id: any, content?: string | null, status?: string | null } | null };
export const CollectionDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"collections"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<CollectionDetailFragment, unknown>; export const CollectionDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"collections"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<CollectionDetailFragment, unknown>;
export const ProductDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}}]} as unknown as DocumentNode<ProductDetailFragment, unknown>; export const ProductDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<ProductDetailFragment, unknown>;
export const ConversationDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConversationDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"conversations"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product_id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"last_image_url"}},{"kind":"Field","name":{"kind":"Name","value":"last_text_message"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}}]} as unknown as DocumentNode<ConversationDetailFragment, unknown>; export const ConversationDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConversationDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"conversations"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product_id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"last_image_url"}},{"kind":"Field","name":{"kind":"Name","value":"last_text_message"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<ConversationDetailFragment, unknown>;
export const MessageMediaFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<MessageMediaFragment, unknown>; export const MessageMediaFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<MessageMediaFragment, unknown>;
export const MessageDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<MessageDetailFragment, unknown>; export const MessageDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<MessageDetailFragment, unknown>;
export const PromptDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}}]} as unknown as DocumentNode<PromptDetailFragment, unknown>; export const PromptDetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}}]} as unknown as DocumentNode<PromptDetailFragment, unknown>;
export const CreateConversationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createConversation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"conversations_insert_input"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"insert_conversations_one"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"object"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ConversationDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConversationDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"conversations"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product_id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"last_image_url"}},{"kind":"Field","name":{"kind":"Name","value":"last_text_message"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}}]} as unknown as DocumentNode<CreateConversationMutation, CreateConversationMutationVariables>; export const CreateConversationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createConversation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"conversations_insert_input"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"insert_conversations_one"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"object"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ConversationDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConversationDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"conversations"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product_id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"last_image_url"}},{"kind":"Field","name":{"kind":"Name","value":"last_text_message"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}}]} as unknown as DocumentNode<CreateConversationMutation, CreateConversationMutationVariables>;
export const CreateMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"messages_insert_input"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"insert_messages_one"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"object"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]} as unknown as DocumentNode<CreateMessageMutation, CreateMessageMutationVariables>; export const CreateMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"messages_insert_input"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"insert_messages_one"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"object"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]} as unknown as DocumentNode<CreateMessageMutation, CreateMessageMutationVariables>;
export const DeleteConversationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"deleteConversation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"delete_conversations_by_pk"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<DeleteConversationMutation, DeleteConversationMutationVariables>; export const DeleteConversationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"deleteConversation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"delete_conversations_by_pk"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<DeleteConversationMutation, DeleteConversationMutationVariables>;
export const GenerateImageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"generateImage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"model"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"neg_prompt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"prompt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"seed"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"steps"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"width"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"512"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"height"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"512"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"imageGeneration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"model"},"value":{"kind":"Variable","name":{"kind":"Name","value":"model"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"neg_prompt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"neg_prompt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"prompt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"prompt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"seed"},"value":{"kind":"Variable","name":{"kind":"Name","value":"seed"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"steps"},"value":{"kind":"Variable","name":{"kind":"Name","value":"steps"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"width"},"value":{"kind":"Variable","name":{"kind":"Name","value":"width"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"height"},"value":{"kind":"Variable","name":{"kind":"Name","value":"height"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode<GenerateImageMutation, GenerateImageMutationVariables>; export const GenerateImageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"generateImage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"model"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"neg_prompt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"prompt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"seed"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"steps"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"width"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"512"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"height"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"512"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"imageGeneration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"model"},"value":{"kind":"Variable","name":{"kind":"Name","value":"model"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"neg_prompt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"neg_prompt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"prompt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"prompt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"seed"},"value":{"kind":"Variable","name":{"kind":"Name","value":"seed"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"steps"},"value":{"kind":"Variable","name":{"kind":"Name","value":"steps"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"width"},"value":{"kind":"Variable","name":{"kind":"Name","value":"width"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"height"},"value":{"kind":"Variable","name":{"kind":"Name","value":"height"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode<GenerateImageMutation, GenerateImageMutationVariables>;
export const UpdateConversationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateConversation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastMessageText"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastMessageUrl"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update_conversations_by_pk"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk_columns"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"_set"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"last_text_message"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastMessageText"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"last_image_url"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastMessageUrl"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ConversationDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConversationDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"conversations"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product_id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"last_image_url"}},{"kind":"Field","name":{"kind":"Name","value":"last_text_message"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}}]} as unknown as DocumentNode<UpdateConversationMutation, UpdateConversationMutationVariables>; export const UpdateConversationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateConversation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastMessageText"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastMessageUrl"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update_conversations_by_pk"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk_columns"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"_set"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"last_text_message"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastMessageText"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"last_image_url"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastMessageUrl"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ConversationDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConversationDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"conversations"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product_id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"last_image_url"}},{"kind":"Field","name":{"kind":"Name","value":"last_text_message"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}}]} as unknown as DocumentNode<UpdateConversationMutation, UpdateConversationMutationVariables>;
export const UpdateMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"messages_set_input"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update_messages_by_pk"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk_columns"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"_set"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]} as unknown as DocumentNode<UpdateMessageMutation, UpdateMessageMutationVariables>; export const UpdateMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"messages_set_input"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update_messages_by_pk"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk_columns"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"_set"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]} as unknown as DocumentNode<UpdateMessageMutation, UpdateMessageMutationVariables>;
export const GetCollectionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getCollections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionDetail"}},{"kind":"Field","name":{"kind":"Name","value":"collection_products"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}},{"kind":"Field","name":{"kind":"Name","value":"product_prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PromptDetail"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"collections"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}}]} as unknown as DocumentNode<GetCollectionsQuery, GetCollectionsQueryVariables>; export const GetCollectionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getCollections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionDetail"}},{"kind":"Field","name":{"kind":"Name","value":"collection_products"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}},{"kind":"Field","name":{"kind":"Name","value":"product_prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PromptDetail"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"collections"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}}]} as unknown as DocumentNode<GetCollectionsQuery, GetCollectionsQueryVariables>;
export const GetConversationMessagesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getConversationMessages"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"conversation_id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"100"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"100"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messages"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"conversation_id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"_eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"conversation_id"}}}]}}]}},{"kind":"Argument","name":{"kind":"Name","value":"order_by"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"updated_at"},"value":{"kind":"EnumValue","value":"desc"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]} as unknown as DocumentNode<GetConversationMessagesQuery, GetConversationMessagesQueryVariables>; export const GetConversationMessagesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getConversationMessages"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"conversation_id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}},"defaultValue":{"kind":"StringValue","value":"","block":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"100"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"100"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messages"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"conversation_id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"_eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"conversation_id"}}}]}}]}},{"kind":"Argument","name":{"kind":"Name","value":"order_by"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created_at"},"value":{"kind":"EnumValue","value":"desc"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]} as unknown as DocumentNode<GetConversationMessagesQuery, GetConversationMessagesQueryVariables>;
export const GetConversationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getConversations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"conversations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ConversationDetail"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_messages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageDetail"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConversationDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"conversations"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product_id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"last_image_url"}},{"kind":"Field","name":{"kind":"Name","value":"last_text_message"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]} as unknown as DocumentNode<GetConversationsQuery, GetConversationsQueryVariables>; export const GetConversationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getConversations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"conversations"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"order_by"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"updated_at"},"value":{"kind":"EnumValue","value":"desc"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ConversationDetail"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_messages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageDetail"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageMedia"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"message_medias"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message_id"}},{"kind":"Field","name":{"kind":"Name","value":"media_url"}},{"kind":"Field","name":{"kind":"Name","value":"mime_type"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ConversationDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"conversations"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product_id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"last_image_url"}},{"kind":"Field","name":{"kind":"Name","value":"last_text_message"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MessageDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"messages"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"conversation_id"}},{"kind":"Field","name":{"kind":"Name","value":"sender"}},{"kind":"Field","name":{"kind":"Name","value":"sender_name"}},{"kind":"Field","name":{"kind":"Name","value":"sender_avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"message_type"}},{"kind":"Field","name":{"kind":"Name","value":"message_sender_type"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"message_medias"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MessageMedia"}}]}}]}}]} as unknown as DocumentNode<GetConversationsQuery, GetConversationsQueryVariables>;
export const GetProductsByCollectionSlugDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getProductsByCollectionSlug"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"product_collections"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"collections"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"slug"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"_eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}}]}}]}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}},{"kind":"Field","name":{"kind":"Name","value":"product_prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PromptDetail"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"product_collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionDetail"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"collections"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<GetProductsByCollectionSlugQuery, GetProductsByCollectionSlugQueryVariables>; export const GetProductsByCollectionSlugDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getProductsByCollectionSlug"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"product_collections"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"collections"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"slug"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"_eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}}]}}]}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}},{"kind":"Field","name":{"kind":"Name","value":"product_prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PromptDetail"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"product_collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionDetail"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"collections"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<GetProductsByCollectionSlugQuery, GetProductsByCollectionSlugQueryVariables>;
export const GetProductPromptsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getProductPrompts"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"productSlug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prompts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"prompt_products"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"products"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"slug"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"_eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"productSlug"}}}]}}]}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PromptDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}}]} as unknown as DocumentNode<GetProductPromptsQuery, GetProductPromptsQueryVariables>; export const GetProductPromptsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getProductPrompts"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"productSlug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"StringValue","value":"","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prompts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"prompt_products"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"products"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"slug"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"_eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"productSlug"}}}]}}]}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PromptDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}}]} as unknown as DocumentNode<GetProductPromptsQuery, GetProductPromptsQueryVariables>;
export const GetProductsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}},{"kind":"Field","name":{"kind":"Name","value":"product_prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PromptDetail"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"product_collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionDetail"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"collections"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<GetProductsQuery, GetProductsQueryVariables>; export const GetProductsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}},{"kind":"Field","name":{"kind":"Name","value":"product_prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prompts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PromptDetail"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"product_collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionDetail"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PromptDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"prompts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"collections"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<GetProductsQuery, GetProductsQueryVariables>;
export const GetProductsInDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getProductsIn"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"_in"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},"defaultValue":{"kind":"StringValue","value":"","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"slug"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"_in"},"value":{"kind":"Variable","name":{"kind":"Name","value":"_in"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}}]}}]} as unknown as DocumentNode<GetProductsInQuery, GetProductsInQueryVariables>; export const GetProductsInDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getProductsIn"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"_in"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},"defaultValue":{"kind":"StringValue","value":"","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"slug"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"_in"},"value":{"kind":"Variable","name":{"kind":"Name","value":"_in"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProductDetail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProductDetail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"products"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"long_description"}},{"kind":"Field","name":{"kind":"Name","value":"technical_description"}},{"kind":"Field","name":{"kind":"Name","value":"image_url"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"greeting"}},{"kind":"Field","name":{"kind":"Name","value":"source_url"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"inputs"}},{"kind":"Field","name":{"kind":"Name","value":"outputs"}},{"kind":"Field","name":{"kind":"Name","value":"nsfw"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<GetProductsInQuery, GetProductsInQueryVariables>;
export const SubscribeMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"subscribeMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}},"defaultValue":{"kind":"StringValue","value":"","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messages_by_pk"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode<SubscribeMessageSubscription, SubscribeMessageSubscriptionVariables>; export const SubscribeMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"subscribeMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"uuid"}},"defaultValue":{"kind":"StringValue","value":"","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messages_by_pk"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode<SubscribeMessageSubscription, SubscribeMessageSubscriptionVariables>;

View File

@ -7,7 +7,7 @@ query getConversationMessages(
offset: $offset, offset: $offset,
limit: $limit, limit: $limit,
where: { conversation_id: { _eq: $conversation_id } }, where: { conversation_id: { _eq: $conversation_id } },
order_by: {updated_at: desc} order_by: {created_at: desc}
) { ) {
...MessageDetail ...MessageDetail
} }

View File

@ -1,5 +1,5 @@
query getConversations { query getConversations {
conversations { conversations(order_by: {updated_at: desc}) {
...ConversationDetail ...ConversationDetail
conversation_messages { conversation_messages {
...MessageDetail ...MessageDetail

View File

@ -1,5 +1,6 @@
subscription subscribeMessage($id: uuid = "") { subscription subscribeMessage($id: uuid = "") {
messages_by_pk(id: $id) { messages_by_pk(id: $id) {
id
content content
status status
} }

File diff suppressed because it is too large Load Diff

View File

@ -28,20 +28,20 @@
"eslint-config-next": "13.4.10", "eslint-config-next": "13.4.10",
"graphql": "^16.8.0", "graphql": "^16.8.0",
"graphql-ws": "^5.14.0", "graphql-ws": "^5.14.0",
"jotai": "^2.4.0",
"jotai-optics": "^0.3.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"mobx": "^6.10.0",
"mobx-react-lite": "^4.0.3",
"mobx-state-tree": "^5.1.8",
"next": "13.4.10", "next": "13.4.10",
"next-auth": "^4.23.1", "next-auth": "^4.23.1",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"nextjs-openai": "^7.2.0", "optics-ts": "^2.4.1",
"openai-streams": "^6.2.0",
"postcss": "8.4.26", "postcss": "8.4.26",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.45.4", "react-hook-form": "^7.45.4",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"remark": "^14.0.3",
"remark-html": "^15.0.2",
"tailwindcss": "3.3.3", "tailwindcss": "3.3.3",
"typescript": "5.1.6" "typescript": "5.1.6"
}, },

Some files were not shown because too many files have changed in this diff Show More