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:
parent
cc39664ce4
commit
d55a83888b
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
40
web-client/app/_components/BasicPromptAccessories/index.tsx
Normal file
40
web-client/app/_components/BasicPromptAccessories/index.tsx
Normal 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;
|
||||||
20
web-client/app/_components/BasicPromptButton/index.tsx
Normal file
20
web-client/app/_components/BasicPromptButton/index.tsx
Normal 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);
|
||||||
38
web-client/app/_components/BasicPromptInput/index.tsx
Normal file
38
web-client/app/_components/BasicPromptInput/index.tsx
Normal 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;
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
66
web-client/app/_components/ChatBody/renderChatMessage.tsx
Normal file
66
web-client/app/_components/ChatBody/renderChatMessage.tsx
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
|
||||||
|
|||||||
19
web-client/app/_components/ChatItem/index.tsx
Normal file
19
web-client/app/_components/ChatItem/index.tsx
Normal 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;
|
||||||
@ -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;
|
||||||
|
|||||||
21
web-client/app/_components/CompactHistoryList/index.tsx
Normal file
21
web-client/app/_components/CompactHistoryList/index.tsx
Normal 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;
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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=""
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
24
web-client/app/_components/ExpandableHeader/index.tsx
Normal file
24
web-client/app/_components/ExpandableHeader/index.tsx
Normal 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;
|
||||||
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
22
web-client/app/_components/HamburgerButton/index.tsx
Normal file
22
web-client/app/_components/HamburgerButton/index.tsx
Normal 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);
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
19
web-client/app/_components/JanLogo/index.tsx
Normal file
19
web-client/app/_components/JanLogo/index.tsx
Normal 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);
|
||||||
24
web-client/app/_components/LeftContainer/index.tsx
Normal file
24
web-client/app/_components/LeftContainer/index.tsx
Normal 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;
|
||||||
20
web-client/app/_components/LeftSidebar/index.tsx
Normal file
20
web-client/app/_components/LeftSidebar/index.tsx
Normal 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;
|
||||||
27
web-client/app/_components/LoginButton/index.tsx
Normal file
27
web-client/app/_components/LoginButton/index.tsx
Normal 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;
|
||||||
13
web-client/app/_components/MainChat/index.tsx
Normal file
13
web-client/app/_components/MainChat/index.tsx
Normal 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;
|
||||||
11
web-client/app/_components/MainChatHeader/index.tsx
Normal file
11
web-client/app/_components/MainChatHeader/index.tsx
Normal 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;
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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%]"
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
25
web-client/app/_components/PrimaryButton/index.tsx
Normal file
25
web-client/app/_components/PrimaryButton/index.tsx
Normal 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;
|
||||||
29
web-client/app/_components/ProductOverview/index.tsx
Normal file
29
web-client/app/_components/ProductOverview/index.tsx
Normal 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;
|
||||||
14
web-client/app/_components/RightContainer/index.tsx
Normal file
14
web-client/app/_components/RightContainer/index.tsx
Normal 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;
|
||||||
@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
18
web-client/app/_components/SecondaryButton/index.tsx
Normal file
18
web-client/app/_components/SecondaryButton/index.tsx
Normal 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;
|
||||||
@ -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"
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -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]">
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
39
web-client/app/_components/TryItYourself/index.tsx
Normal file
39
web-client/app/_components/TryItYourself/index.tsx
Normal 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;
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
215
web-client/app/_helpers/JotaiWrapper.tsx
Normal file
215
web-client/app/_helpers/JotaiWrapper.tsx
Normal 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);
|
||||||
@ -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>;
|
|
||||||
};
|
|
||||||
19
web-client/app/_helpers/ModalWrapper.tsx
Normal file
19
web-client/app/_helpers/ModalWrapper.tsx
Normal 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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|||||||
@ -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
|
|
||||||
},
|
|
||||||
})
|
|
||||||
16
web-client/app/_hooks/useChatMessageSubscription.ts
Normal file
16
web-client/app/_hooks/useChatMessageSubscription.ts
Normal 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;
|
||||||
73
web-client/app/_hooks/useChatMessages.ts
Normal file
73
web-client/app/_hooks/useChatMessages.ts
Normal 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;
|
||||||
@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
50
web-client/app/_hooks/useDeleteConversation.ts
Normal file
50
web-client/app/_hooks/useDeleteConversation.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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" &&
|
||||||
|
|||||||
24
web-client/app/_hooks/useGetProducts.ts
Normal file
24
web-client/app/_hooks/useGetProducts.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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 {
|
||||||
|
|||||||
383
web-client/app/_hooks/useSendChatMessage.ts
Normal file
383
web-client/app/_hooks/useSendChatMessage.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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 };
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -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;
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@ -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),
|
|
||||||
});
|
|
||||||
@ -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,
|
|
||||||
});
|
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
|||||||
23
web-client/app/_models/ProductInput.ts
Normal file
23
web-client/app/_models/ProductInput.ts
Normal 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[];
|
||||||
|
};
|
||||||
8
web-client/app/_models/ProductOutput.ts
Normal file
8
web-client/app/_models/ProductOutput.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { ItemProperties } from "./ProductInput";
|
||||||
|
|
||||||
|
export interface ProductOutput {
|
||||||
|
slug: string;
|
||||||
|
type: string;
|
||||||
|
properties: ItemProperties[];
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
@ -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";
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
});
|
|
||||||
@ -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",
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
export const MESSAGE_PER_PAGE = 100;
|
export const MESSAGE_PER_PAGE = 10;
|
||||||
|
|||||||
@ -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)) {
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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=
|
||||||
@ -13,4 +13,6 @@ fragment ProductDetail on products {
|
|||||||
inputs
|
inputs
|
||||||
outputs
|
outputs
|
||||||
nsfw
|
nsfw
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
}
|
}
|
||||||
@ -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] ?? {};
|
||||||
|
|||||||
@ -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>;
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
query getConversations {
|
query getConversations {
|
||||||
conversations {
|
conversations(order_by: {updated_at: desc}) {
|
||||||
...ConversationDetail
|
...ConversationDetail
|
||||||
conversation_messages {
|
conversation_messages {
|
||||||
...MessageDetail
|
...MessageDetail
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
4369
web-client/package-lock.json
generated
4369
web-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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
Loading…
x
Reference in New Issue
Block a user