Setup prettier
This commit is contained in:
parent
5f536d8d3b
commit
03d560bde6
5
web/.prettierignore
Normal file
5
web/.prettierignore
Normal file
@ -0,0 +1,5 @@
|
||||
.next/
|
||||
node_modules/
|
||||
dist/
|
||||
*.hbs
|
||||
*.mdx
|
||||
8
web/.prettierrc
Normal file
8
web/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "consistent",
|
||||
"trailingComma": "es5",
|
||||
"endOfLine": "lf",
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
@ -1,19 +1,19 @@
|
||||
import { useAtomValue } from "jotai";
|
||||
import React from "react";
|
||||
import ModelTable from "../ModelTable";
|
||||
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import { useAtomValue } from 'jotai'
|
||||
import React from 'react'
|
||||
import ModelTable from '../ModelTable'
|
||||
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
|
||||
|
||||
const ActiveModelTable: React.FC = () => {
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom);
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
||||
|
||||
if (!activeModel) return null;
|
||||
if (!activeModel) return null
|
||||
|
||||
return (
|
||||
<div className="pl-[63px] pr-[89px]">
|
||||
<h3 className="text-xl leading-[25px] mb-[13px]">Active Model(s)</h3>
|
||||
<h3 className="mb-[13px] text-xl leading-[25px]">Active Model(s)</h3>
|
||||
<ModelTable models={[activeModel]} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ActiveModelTable;
|
||||
export default ActiveModelTable
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import DownloadModelContent from "../DownloadModelContent";
|
||||
import ModelDownloadButton from "../ModelDownloadButton";
|
||||
import ModelDownloadingButton from "../ModelDownloadingButton";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
import DownloadModelContent from '../DownloadModelContent'
|
||||
import ModelDownloadButton from '../ModelDownloadButton'
|
||||
import ModelDownloadingButton from '../ModelDownloadingButton'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
|
||||
type Props = {
|
||||
model: AssistantModel;
|
||||
isRecommend: boolean;
|
||||
required?: string;
|
||||
onDownloadClick?: (model: AssistantModel) => void;
|
||||
};
|
||||
model: AssistantModel
|
||||
isRecommend: boolean
|
||||
required?: string
|
||||
onDownloadClick?: (model: AssistantModel) => void
|
||||
}
|
||||
|
||||
const AvailableModelCard: React.FC<Props> = ({
|
||||
model,
|
||||
@ -18,36 +18,36 @@ const AvailableModelCard: React.FC<Props> = ({
|
||||
required,
|
||||
onDownloadClick,
|
||||
}) => {
|
||||
const downloadState = useAtomValue(modelDownloadStateAtom);
|
||||
const downloadState = useAtomValue(modelDownloadStateAtom)
|
||||
|
||||
let isDownloading = false;
|
||||
let total = 0;
|
||||
let transferred = 0;
|
||||
let isDownloading = false
|
||||
let total = 0
|
||||
let transferred = 0
|
||||
|
||||
if (model._id && downloadState[model._id]) {
|
||||
isDownloading =
|
||||
downloadState[model._id].error == null &&
|
||||
downloadState[model._id].percent < 1;
|
||||
downloadState[model._id].percent < 1
|
||||
|
||||
if (isDownloading) {
|
||||
total = downloadState[model._id].size.total;
|
||||
transferred = downloadState[model._id].size.transferred;
|
||||
total = downloadState[model._id].size.total
|
||||
transferred = downloadState[model._id].size.transferred
|
||||
}
|
||||
}
|
||||
|
||||
const downloadButton = isDownloading ? (
|
||||
<div className="w-1/5 flex items-start justify-end">
|
||||
<div className="flex w-1/5 items-start justify-end">
|
||||
<ModelDownloadingButton total={total} value={transferred} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-1/5 flex items-center justify-end">
|
||||
<div className="flex w-1/5 items-center justify-end">
|
||||
<ModelDownloadButton callback={() => onDownloadClick?.(model)} />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg border-gray-200">
|
||||
<div className="flex justify-between py-4 px-3 gap-2.5">
|
||||
<div className="rounded-lg border border-gray-200">
|
||||
<div className="flex justify-between gap-2.5 px-3 py-4">
|
||||
<DownloadModelContent
|
||||
required={required}
|
||||
author={model.author}
|
||||
@ -60,7 +60,7 @@ const AvailableModelCard: React.FC<Props> = ({
|
||||
</div>
|
||||
{/* <ViewModelDetailButton callback={handleViewDetails} /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default AvailableModelCard;
|
||||
export default AvailableModelCard
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { useSetAtom } from "jotai";
|
||||
import { InformationCircleIcon } from "@heroicons/react/24/outline";
|
||||
import SendButton from "../SendButton";
|
||||
import { showingAdvancedPromptAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { InformationCircleIcon } from '@heroicons/react/24/outline'
|
||||
import SendButton from '../SendButton'
|
||||
import { showingAdvancedPromptAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
|
||||
const BasicPromptAccessories: React.FC = () => {
|
||||
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
|
||||
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom)
|
||||
|
||||
const shouldShowAdvancedPrompt = false;
|
||||
const shouldShowAdvancedPrompt = false
|
||||
|
||||
return (
|
||||
<div className="absolute inset-x-0 bottom-0 flex justify-between py-2 pl-3 pr-2">
|
||||
@ -27,7 +27,7 @@ const BasicPromptAccessories: React.FC = () => {
|
||||
<SendButton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default BasicPromptAccessories;
|
||||
export default BasicPromptAccessories
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import React from "react";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { showingAdvancedPromptAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
import React from 'react'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { ChevronLeftIcon } from '@heroicons/react/24/outline'
|
||||
import { showingAdvancedPromptAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
|
||||
const BasicPromptButton: React.FC = () => {
|
||||
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
|
||||
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]"
|
||||
className="mx-2 mb-[10px] mt-3 flex flex-none items-center 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>
|
||||
<span className="text-xs font-semibold text-gray-500">BASIC PROMPT</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(BasicPromptButton);
|
||||
export default React.memo(BasicPromptButton)
|
||||
|
||||
@ -1,71 +1,71 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
|
||||
import { getActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
import { selectedModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import useCreateConversation from "@/_hooks/useCreateConversation";
|
||||
import useInitModel from "@/_hooks/useInitModel";
|
||||
import useSendChatMessage from "@/_hooks/useSendChatMessage";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { ChangeEvent, useEffect, useRef } from "react";
|
||||
import { currentPromptAtom } from '@/_helpers/JotaiWrapper'
|
||||
import { getActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
|
||||
import { selectedModelAtom } from '@/_helpers/atoms/Model.atom'
|
||||
import useCreateConversation from '@/_hooks/useCreateConversation'
|
||||
import useInitModel from '@/_hooks/useInitModel'
|
||||
import useSendChatMessage from '@/_hooks/useSendChatMessage'
|
||||
import { useAtom, useAtomValue } from 'jotai'
|
||||
import { ChangeEvent, useEffect, useRef } from 'react'
|
||||
|
||||
const BasicPromptInput: React.FC = () => {
|
||||
const activeConversationId = useAtomValue(getActiveConvoIdAtom);
|
||||
const selectedModel = useAtomValue(selectedModelAtom);
|
||||
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom);
|
||||
const { sendChatMessage } = useSendChatMessage();
|
||||
const { requestCreateConvo } = useCreateConversation();
|
||||
const activeConversationId = useAtomValue(getActiveConvoIdAtom)
|
||||
const selectedModel = useAtomValue(selectedModelAtom)
|
||||
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom)
|
||||
const { sendChatMessage } = useSendChatMessage()
|
||||
const { requestCreateConvo } = useCreateConversation()
|
||||
|
||||
const { initModel } = useInitModel();
|
||||
const { initModel } = useInitModel()
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const handleKeyDown = async (
|
||||
event: React.KeyboardEvent<HTMLTextAreaElement>
|
||||
) => {
|
||||
if (event.key === "Enter") {
|
||||
if (event.key === 'Enter') {
|
||||
if (!event.shiftKey) {
|
||||
if (activeConversationId) {
|
||||
event.preventDefault();
|
||||
sendChatMessage();
|
||||
event.preventDefault()
|
||||
sendChatMessage()
|
||||
} else {
|
||||
if (!selectedModel) {
|
||||
console.log("No model selected");
|
||||
return;
|
||||
console.log('No model selected')
|
||||
return
|
||||
}
|
||||
|
||||
await requestCreateConvo(selectedModel);
|
||||
await initModel(selectedModel);
|
||||
sendChatMessage();
|
||||
await requestCreateConvo(selectedModel)
|
||||
await initModel(selectedModel)
|
||||
sendChatMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
adjustTextareaHeight();
|
||||
}, [currentPrompt]);
|
||||
adjustTextareaHeight()
|
||||
}, [currentPrompt])
|
||||
|
||||
const handleMessageChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setCurrentPrompt(event.target.value);
|
||||
};
|
||||
setCurrentPrompt(event.target.value)
|
||||
}
|
||||
|
||||
// Auto adjust textarea height based on content
|
||||
const MAX_ROWS = 30;
|
||||
const MAX_ROWS = 30
|
||||
|
||||
const adjustTextareaHeight = () => {
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.style.height = "auto"; // 1 row
|
||||
const scrollHeight = textareaRef.current.scrollHeight;
|
||||
textareaRef.current.style.height = 'auto' // 1 row
|
||||
const scrollHeight = textareaRef.current.scrollHeight
|
||||
const maxScrollHeight =
|
||||
parseInt(window.getComputedStyle(textareaRef.current).lineHeight, 10) *
|
||||
MAX_ROWS;
|
||||
MAX_ROWS
|
||||
textareaRef.current.style.height = `${Math.min(
|
||||
scrollHeight,
|
||||
maxScrollHeight
|
||||
)}px`;
|
||||
)}px`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-indigo-600">
|
||||
@ -79,7 +79,7 @@ const BasicPromptInput: React.FC = () => {
|
||||
className="block w-full resize-none border-0 bg-transparent py-1.5 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||
placeholder="Message ..."
|
||||
rows={1}
|
||||
style={{ overflow: "auto" }}
|
||||
style={{ overflow: 'auto' }}
|
||||
/>
|
||||
{/* Spacer element to match the height of the toolbar */}
|
||||
<div className="py-2" aria-hidden="true">
|
||||
@ -89,7 +89,7 @@ const BasicPromptInput: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default BasicPromptInput;
|
||||
export default BasicPromptInput
|
||||
|
||||
@ -1,44 +1,44 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import React, { useCallback, useRef, useState, useEffect } from "react";
|
||||
import ChatItem from "../ChatItem";
|
||||
import { ChatMessage } from "@/_models/ChatMessage";
|
||||
import useChatMessages from "@/_hooks/useChatMessages";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { selectAtom } from "jotai/utils";
|
||||
import { getActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
import { chatMessages } from "@/_helpers/atoms/ChatMessage.atom";
|
||||
import React, { useCallback, useRef, useState, useEffect } from 'react'
|
||||
import ChatItem from '../ChatItem'
|
||||
import { ChatMessage } from '@/_models/ChatMessage'
|
||||
import useChatMessages from '@/_hooks/useChatMessages'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { selectAtom } from 'jotai/utils'
|
||||
import { getActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
|
||||
import { chatMessages } from '@/_helpers/atoms/ChatMessage.atom'
|
||||
|
||||
const ChatBody: React.FC = () => {
|
||||
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? "";
|
||||
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? ''
|
||||
const messageList = useAtomValue(
|
||||
selectAtom(
|
||||
chatMessages,
|
||||
useCallback((v) => v[activeConversationId], [activeConversationId]),
|
||||
),
|
||||
);
|
||||
const [content, setContent] = useState<React.JSX.Element[]>([]);
|
||||
useCallback((v) => v[activeConversationId], [activeConversationId])
|
||||
)
|
||||
)
|
||||
const [content, setContent] = useState<React.JSX.Element[]>([])
|
||||
|
||||
const [offset, setOffset] = useState(0);
|
||||
const { loading, hasMore } = useChatMessages(offset);
|
||||
const intersectObs = useRef<any>(null);
|
||||
const [offset, setOffset] = useState(0)
|
||||
const { loading, hasMore } = useChatMessages(offset)
|
||||
const intersectObs = useRef<any>(null)
|
||||
|
||||
const lastPostRef = useCallback(
|
||||
(message: ChatMessage) => {
|
||||
if (loading) return;
|
||||
if (loading) return
|
||||
|
||||
if (intersectObs.current) intersectObs.current.disconnect();
|
||||
if (intersectObs.current) intersectObs.current.disconnect()
|
||||
|
||||
intersectObs.current = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && hasMore) {
|
||||
setOffset((prevOffset) => prevOffset + 5);
|
||||
setOffset((prevOffset) => prevOffset + 5)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (message) intersectObs.current.observe(message);
|
||||
if (message) intersectObs.current.observe(message)
|
||||
},
|
||||
[loading, hasMore],
|
||||
);
|
||||
[loading, hasMore]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const list = messageList?.map((message, index) => {
|
||||
@ -46,18 +46,18 @@ const ChatBody: React.FC = () => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<ChatItem ref={lastPostRef} message={message} key={message.id} />
|
||||
);
|
||||
)
|
||||
}
|
||||
return <ChatItem message={message} key={message.id} />;
|
||||
});
|
||||
setContent(list);
|
||||
}, [messageList, lastPostRef]);
|
||||
return <ChatItem message={message} key={message.id} />
|
||||
})
|
||||
setContent(list)
|
||||
}, [messageList, lastPostRef])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col-reverse flex-1 py-4 overflow-y-auto scroll">
|
||||
<div className="scroll flex flex-1 flex-col-reverse overflow-y-auto py-4">
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ChatBody;
|
||||
export default ChatBody
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import SimpleControlNetMessage from "../SimpleControlNetMessage";
|
||||
import SimpleImageMessage from "../SimpleImageMessage";
|
||||
import SimpleTextMessage from "../SimpleTextMessage";
|
||||
import { ChatMessage, MessageType } from "@/_models/ChatMessage";
|
||||
import SimpleControlNetMessage from '../SimpleControlNetMessage'
|
||||
import SimpleImageMessage from '../SimpleImageMessage'
|
||||
import SimpleTextMessage from '../SimpleTextMessage'
|
||||
import { ChatMessage, MessageType } from '@/_models/ChatMessage'
|
||||
|
||||
export default function renderChatMessage({
|
||||
id,
|
||||
@ -22,9 +22,9 @@ export default function renderChatMessage({
|
||||
senderName={senderName}
|
||||
createdAt={createdAt}
|
||||
imageUrls={imageUrls ?? []}
|
||||
text={text ?? ""}
|
||||
text={text ?? ''}
|
||||
/>
|
||||
);
|
||||
)
|
||||
case MessageType.Image:
|
||||
return (
|
||||
<SimpleImageMessage
|
||||
@ -35,7 +35,7 @@ export default function renderChatMessage({
|
||||
imageUrls={imageUrls ?? []}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
)
|
||||
case MessageType.Text:
|
||||
return (
|
||||
<SimpleTextMessage
|
||||
@ -46,8 +46,8 @@ export default function renderChatMessage({
|
||||
senderType={messageSenderType}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
)
|
||||
default:
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import React, { forwardRef } from "react";
|
||||
import renderChatMessage from "../ChatBody/renderChatMessage";
|
||||
import { ChatMessage } from "@/_models/ChatMessage";
|
||||
import React, { forwardRef } from 'react'
|
||||
import renderChatMessage from '../ChatBody/renderChatMessage'
|
||||
import { ChatMessage } from '@/_models/ChatMessage'
|
||||
|
||||
type Props = {
|
||||
message: ChatMessage;
|
||||
};
|
||||
message: ChatMessage
|
||||
}
|
||||
|
||||
type Ref = HTMLDivElement;
|
||||
type Ref = HTMLDivElement
|
||||
|
||||
const ChatItem = forwardRef<Ref, Props>(({ message }, ref) => {
|
||||
const item = renderChatMessage(message);
|
||||
const item = renderChatMessage(message)
|
||||
|
||||
const content = ref ? <div ref={ref}>{item}</div> : item;
|
||||
const content = ref ? <div ref={ref}>{item}</div> : item
|
||||
|
||||
return content;
|
||||
});
|
||||
return content
|
||||
})
|
||||
|
||||
export default ChatItem;
|
||||
export default ChatItem
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import React from "react";
|
||||
import JanImage from "../JanImage";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { setActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
import React from 'react'
|
||||
import JanImage from '../JanImage'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { setActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
|
||||
|
||||
const CompactLogo: React.FC = () => {
|
||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
|
||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
|
||||
|
||||
return (
|
||||
<button onClick={() => setActiveConvoId(undefined)}>
|
||||
<JanImage imageUrl="icons/app_icon.svg" width={28} height={28} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(CompactLogo);
|
||||
export default React.memo(CompactLogo)
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { showConfirmDeleteConversationModalAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
import useDeleteConversation from "@/_hooks/useDeleteConversation";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
import { useAtom } from "jotai";
|
||||
import React, { Fragment, useRef } from "react";
|
||||
import { showConfirmDeleteConversationModalAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
import useDeleteConversation from '@/_hooks/useDeleteConversation'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
|
||||
import { useAtom } from 'jotai'
|
||||
import React, { Fragment, useRef } from 'react'
|
||||
|
||||
const ConfirmDeleteConversationModal: React.FC = () => {
|
||||
const [show, setShow] = useAtom(showConfirmDeleteConversationModalAtom);
|
||||
const cancelButtonRef = useRef(null);
|
||||
const { deleteConvo } = useDeleteConversation();
|
||||
const [show, setShow] = useAtom(showConfirmDeleteConversationModalAtom)
|
||||
const cancelButtonRef = useRef(null)
|
||||
const { deleteConvo } = useDeleteConversation()
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
deleteConvo().then(() => setShow(false));
|
||||
};
|
||||
deleteConvo().then(() => setShow(false))
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition.Root show={show} as={Fragment}>
|
||||
@ -92,7 +92,7 @@ const ConfirmDeleteConversationModal: React.FC = () => {
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmDeleteConversationModal;
|
||||
export default ConfirmDeleteConversationModal
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { useAtom } from "jotai";
|
||||
import { showConfirmDeleteModalAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
import React, { Fragment } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline'
|
||||
import { useAtom } from 'jotai'
|
||||
import { showConfirmDeleteModalAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
|
||||
const ConfirmDeleteModelModal: React.FC = () => {
|
||||
const [show, setShow] = useAtom(showConfirmDeleteModalAtom);
|
||||
const [show, setShow] = useAtom(showConfirmDeleteModalAtom)
|
||||
|
||||
const onConfirmDelete = () => {};
|
||||
const onConfirmDelete = () => {}
|
||||
|
||||
return (
|
||||
<Transition.Root show={show} as={Fragment}>
|
||||
@ -79,7 +79,7 @@ const ConfirmDeleteModelModal: React.FC = () => {
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ConfirmDeleteModelModal);
|
||||
export default React.memo(ConfirmDeleteModelModal)
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { useAtom } from "jotai";
|
||||
import useSignOut from "@/_hooks/useSignOut";
|
||||
import { showConfirmSignOutModalAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
import React, { Fragment } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline'
|
||||
import { useAtom } from 'jotai'
|
||||
import useSignOut from '@/_hooks/useSignOut'
|
||||
import { showConfirmSignOutModalAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
|
||||
const ConfirmSignOutModal: React.FC = () => {
|
||||
const [show, setShow] = useAtom(showConfirmSignOutModalAtom);
|
||||
const { signOut } = useSignOut();
|
||||
const [show, setShow] = useAtom(showConfirmSignOutModalAtom)
|
||||
const { signOut } = useSignOut()
|
||||
|
||||
const onLogOutClick = () => {
|
||||
signOut().then(() => setShow(false));
|
||||
};
|
||||
signOut().then(() => setShow(false))
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition.Root show={show} as={Fragment}>
|
||||
@ -83,7 +83,7 @@ const ConfirmSignOutModal: React.FC = () => {
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ConfirmSignOutModal);
|
||||
export default React.memo(ConfirmSignOutModal)
|
||||
|
||||
@ -1,43 +1,43 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import useCreateConversation from "@/_hooks/useCreateConversation";
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import useCreateConversation from '@/_hooks/useCreateConversation'
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
|
||||
type Props = {
|
||||
model: AssistantModel;
|
||||
};
|
||||
model: AssistantModel
|
||||
}
|
||||
|
||||
const ConversationalCard: React.FC<Props> = ({ model }) => {
|
||||
const { requestCreateConvo } = useCreateConversation();
|
||||
const { requestCreateConvo } = useCreateConversation()
|
||||
|
||||
const { name, avatarUrl, shortDescription } = model;
|
||||
const { name, avatarUrl, shortDescription } = model
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => requestCreateConvo(model)}
|
||||
className="flex flex-col justify-between flex-shrink-0 gap-3 bg-white p-4 w-52 rounded-lg text-left dark:bg-gray-700 hover:opacity-20"
|
||||
className="flex w-52 flex-shrink-0 flex-col justify-between gap-3 rounded-lg bg-white p-4 text-left hover:opacity-20 dark:bg-gray-700"
|
||||
>
|
||||
<div className="flex flex-col gap-2 box-border">
|
||||
<div className="box-border flex flex-col gap-2">
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
src={avatarUrl ?? ""}
|
||||
src={avatarUrl ?? ''}
|
||||
className="rounded-full"
|
||||
alt=""
|
||||
/>
|
||||
<h2 className="text-gray-900 font-semibold dark:text-white line-clamp-1 mt-2">
|
||||
<h2 className="mt-2 line-clamp-1 font-semibold text-gray-900 dark:text-white">
|
||||
{name}
|
||||
</h2>
|
||||
<span className="text-gray-600 mt-1 font-normal line-clamp-2">
|
||||
<span className="mt-1 line-clamp-2 font-normal text-gray-600">
|
||||
{shortDescription}
|
||||
</span>
|
||||
</div>
|
||||
<span className="flex text-xs leading-5 text-gray-500 items-center gap-0.5">
|
||||
<Image src={"icons/play.svg"} width={16} height={16} alt="" />
|
||||
<span className="flex items-center gap-0.5 text-xs leading-5 text-gray-500">
|
||||
<Image src={'icons/play.svg'} width={16} height={16} alt="" />
|
||||
32.2k runs
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ConversationalCard);
|
||||
export default React.memo(ConversationalCard)
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
import ConversationalCard from "../ConversationalCard";
|
||||
import { ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline";
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
import ConversationalCard from '../ConversationalCard'
|
||||
import { ChatBubbleBottomCenterTextIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
type Props = {
|
||||
models: AssistantModel[];
|
||||
};
|
||||
models: AssistantModel[]
|
||||
}
|
||||
|
||||
const ConversationalList: React.FC<Props> = ({ models }) => (
|
||||
<>
|
||||
<div className="flex items-center gap-3 mt-8 mb-2">
|
||||
<div className="mb-2 mt-8 flex items-center gap-3">
|
||||
<ChatBubbleBottomCenterTextIcon width={24} height={24} className="ml-6" />
|
||||
<span className="font-semibold text-gray-900 dark:text-white">
|
||||
Conversational
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 pl-6 flex w-full gap-2 overflow-x-scroll scroll overflow-hidden">
|
||||
<div className="scroll mt-2 flex w-full gap-2 overflow-hidden overflow-x-scroll pl-6">
|
||||
{models.map((item) => (
|
||||
<ConversationalCard key={item._id} model={item} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
)
|
||||
|
||||
export default ConversationalList;
|
||||
export default ConversationalList
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
const DiscordContainer = () => (
|
||||
<div className="border-t border-gray-200 p-3 gap-3 flex items-center justify-between">
|
||||
<div className="flex items-center justify-between gap-3 border-t border-gray-200 p-3">
|
||||
<Link
|
||||
className="flex items-center rounded-lg text-purple-700 text-xs leading-[18px] font-semibold gap-2"
|
||||
href={process.env.NEXT_PUBLIC_DISCORD_INVITATION_URL ?? "#"}
|
||||
className="flex items-center gap-2 rounded-lg text-xs font-semibold leading-[18px] text-purple-700"
|
||||
href={process.env.NEXT_PUBLIC_DISCORD_INVITATION_URL ?? '#'}
|
||||
target="_blank_"
|
||||
>
|
||||
<Image src={"icons/ico_Discord.svg"} width={20} height={20} alt="" />
|
||||
<Image src={'icons/ico_Discord.svg'} width={20} height={20} alt="" />
|
||||
Discord
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(DiscordContainer);
|
||||
export default React.memo(DiscordContainer)
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import DownloadModelTitle from "../DownloadModelTitle";
|
||||
import DownloadModelTitle from '../DownloadModelTitle'
|
||||
|
||||
type Props = {
|
||||
author: string;
|
||||
description: string;
|
||||
isRecommend: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
required?: string;
|
||||
};
|
||||
author: string
|
||||
description: string
|
||||
isRecommend: boolean
|
||||
name: string
|
||||
type: string
|
||||
required?: string
|
||||
}
|
||||
|
||||
const DownloadModelContent: React.FC<Props> = ({
|
||||
author,
|
||||
@ -18,23 +18,23 @@ const DownloadModelContent: React.FC<Props> = ({
|
||||
type,
|
||||
}) => {
|
||||
return (
|
||||
<div className="w-4/5 flex flex-col gap-2.5">
|
||||
<div className="flex w-4/5 flex-col gap-2.5">
|
||||
<div className="flex items-center gap-1">
|
||||
<h2 className="font-medium text-xl leading-[25px] tracking-[-0.4px] text-gray-900">
|
||||
<h2 className="text-xl font-medium leading-[25px] tracking-[-0.4px] text-gray-900">
|
||||
{name}
|
||||
</h2>
|
||||
<DownloadModelTitle title={type} />
|
||||
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
|
||||
<span className="text-xs leading-[18px] font-semibold text-purple-800">
|
||||
<div className="rounded-md bg-purple-100 px-2.5 py-0.5 text-center">
|
||||
<span className="text-xs font-semibold leading-[18px] text-purple-800">
|
||||
{author}
|
||||
</span>
|
||||
</div>
|
||||
{required && (
|
||||
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
|
||||
<div className="rounded-md bg-purple-100 px-2.5 py-0.5 text-center">
|
||||
<span className="text-xs leading-[18px] text-[#11192899]">
|
||||
Required{" "}
|
||||
Required{' '}
|
||||
</span>
|
||||
<span className="text-xs leading-[18px] font-semibold text-gray-900">
|
||||
<span className="text-xs font-semibold leading-[18px] text-gray-900">
|
||||
{required}
|
||||
</span>
|
||||
</div>
|
||||
@ -43,16 +43,16 @@ const DownloadModelContent: React.FC<Props> = ({
|
||||
<p className="text-xs leading-[18px] text-gray-500">{description}</p>
|
||||
<div
|
||||
className={`${
|
||||
isRecommend ? "flex" : "hidden"
|
||||
} w-fit justify-center items-center bg-green-50 rounded-full px-2.5 py-0.5 gap-2`}
|
||||
isRecommend ? 'flex' : 'hidden'
|
||||
} w-fit items-center justify-center gap-2 rounded-full bg-green-50 px-2.5 py-0.5`}
|
||||
>
|
||||
<div className="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
<span className="text-green-600 font-medium text-xs leading-18px">
|
||||
<div className="h-3 w-3 rounded-full bg-green-400"></div>
|
||||
<span className="leading-18px text-xs font-medium text-green-600">
|
||||
Recommend
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default DownloadModelContent;
|
||||
export default DownloadModelContent
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
type Props = {
|
||||
title: string;
|
||||
};
|
||||
title: string
|
||||
}
|
||||
|
||||
export const DownloadModelTitle: React.FC<Props> = ({ title }) => (
|
||||
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
|
||||
<span className="text-xs leading-[18px] font-medium text-purple-800">
|
||||
<div className="rounded-md bg-purple-100 px-2.5 py-0.5 text-center">
|
||||
<span className="text-xs font-medium leading-[18px] text-purple-800">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default DownloadModelTitle;
|
||||
export default DownloadModelTitle
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
import DownloadModelContent from "../DownloadModelContent";
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
import DownloadModelContent from '../DownloadModelContent'
|
||||
|
||||
type Props = {
|
||||
model: AssistantModel;
|
||||
isRecommend: boolean;
|
||||
required?: string;
|
||||
transferred?: number;
|
||||
onDeleteClick?: (model: AssistantModel) => void;
|
||||
};
|
||||
model: AssistantModel
|
||||
isRecommend: boolean
|
||||
required?: string
|
||||
transferred?: number
|
||||
onDeleteClick?: (model: AssistantModel) => void
|
||||
}
|
||||
|
||||
const DownloadedModelCard: React.FC<Props> = ({
|
||||
model,
|
||||
@ -15,8 +15,8 @@ const DownloadedModelCard: React.FC<Props> = ({
|
||||
required,
|
||||
onDeleteClick,
|
||||
}) => (
|
||||
<div className="border rounded-lg border-gray-200">
|
||||
<div className="flex justify-between py-4 px-3 gap-2.5">
|
||||
<div className="rounded-lg border border-gray-200">
|
||||
<div className="flex justify-between gap-2.5 px-3 py-4">
|
||||
<DownloadModelContent
|
||||
required={required}
|
||||
author={model.author}
|
||||
@ -30,6 +30,6 @@ const DownloadedModelCard: React.FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default DownloadedModelCard;
|
||||
export default DownloadedModelCard
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
import React from "react";
|
||||
import SearchBar from "../SearchBar";
|
||||
import ModelTable from "../ModelTable";
|
||||
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import React from 'react'
|
||||
import SearchBar from '../SearchBar'
|
||||
import ModelTable from '../ModelTable'
|
||||
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
|
||||
|
||||
const DownloadedModelTable: React.FC = () => {
|
||||
const { downloadedModels } = useGetDownloadedModels();
|
||||
const { downloadedModels } = useGetDownloadedModels()
|
||||
|
||||
if (!downloadedModels || downloadedModels.length === 0) return null;
|
||||
if (!downloadedModels || downloadedModels.length === 0) return null
|
||||
|
||||
return (
|
||||
<div className="pl-[63px] pr-[89px]">
|
||||
<h3 className="text-xl leading-[25px] mt-[50px]">Downloaded Models</h3>
|
||||
<div className="py-5 w-[568px]">
|
||||
<h3 className="mt-[50px] text-xl leading-[25px]">Downloaded Models</h3>
|
||||
<div className="w-[568px] py-5">
|
||||
<SearchBar />
|
||||
</div>
|
||||
<ModelTable models={downloadedModels} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default DownloadedModelTable;
|
||||
export default DownloadedModelTable
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
||||
import { useAtomValue } from "jotai";
|
||||
import ModelDownloadingTable from "../ModelDownloadingTable";
|
||||
import { DownloadState } from "@/_models/DownloadState";
|
||||
import React, { Fragment } from 'react'
|
||||
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import ModelDownloadingTable from '../ModelDownloadingTable'
|
||||
import { DownloadState } from '@/_models/DownloadState'
|
||||
|
||||
const DownloadingModelTable: React.FC = () => {
|
||||
const modelDownloadState = useAtomValue(modelDownloadStateAtom);
|
||||
const modelDownloadState = useAtomValue(modelDownloadStateAtom)
|
||||
|
||||
const isAnyModelDownloading = Object.values(modelDownloadState).length > 0;
|
||||
const isAnyModelDownloading = Object.values(modelDownloadState).length > 0
|
||||
|
||||
if (!isAnyModelDownloading) return null;
|
||||
if (!isAnyModelDownloading) return null
|
||||
|
||||
const downloadStates: DownloadState[] = [];
|
||||
const downloadStates: DownloadState[] = []
|
||||
for (const [, value] of Object.entries(modelDownloadState)) {
|
||||
downloadStates.push(value);
|
||||
downloadStates.push(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pl-[63px] pr-[89px]">
|
||||
<h3 className="text-xl leading-[25px] mt-[50px] mb-4">
|
||||
<h3 className="mb-4 mt-[50px] text-xl leading-[25px]">
|
||||
Downloading Models
|
||||
</h3>
|
||||
<ModelDownloadingTable downloadStates={downloadStates} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default DownloadingModelTable;
|
||||
export default DownloadingModelTable
|
||||
|
||||
@ -1,64 +1,64 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import Image from "next/image";
|
||||
|
||||
function classNames(...classes: any) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
data: string[];
|
||||
};
|
||||
|
||||
export const DropdownsList: React.FC<Props> = ({ data, title }) => {
|
||||
const [checked, setChecked] = useState(data[0]);
|
||||
|
||||
return (
|
||||
<Menu as="div" className="relative w-full text-left">
|
||||
<div className="pt-2 gap-2 flex flex-col">
|
||||
<h2 className="text-[#111928] text-sm">{title}</h2>
|
||||
<Menu.Button className="inline-flex w-full items-center justify-between gap-x-1.5 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">
|
||||
{checked}
|
||||
<Image
|
||||
src={"icons/unicorn_angle-down.svg"}
|
||||
width={12}
|
||||
height={12}
|
||||
alt=""
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-10 mt-2 w-full origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<div className="py-1">
|
||||
{data.map((item, index) => (
|
||||
<Menu.Item key={index}>
|
||||
{({ active }) => (
|
||||
<a
|
||||
onClick={() => setChecked(item)}
|
||||
href="#"
|
||||
className={classNames(
|
||||
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
|
||||
"block px-4 py-2 text-sm"
|
||||
)}
|
||||
>
|
||||
{item}
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
import { Fragment, useState } from 'react'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import Image from 'next/image'
|
||||
|
||||
function classNames(...classes: any) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
data: string[]
|
||||
}
|
||||
|
||||
export const DropdownsList: React.FC<Props> = ({ data, title }) => {
|
||||
const [checked, setChecked] = useState(data[0])
|
||||
|
||||
return (
|
||||
<Menu as="div" className="relative w-full text-left">
|
||||
<div className="flex flex-col gap-2 pt-2">
|
||||
<h2 className="text-sm text-[#111928]">{title}</h2>
|
||||
<Menu.Button className="inline-flex w-full items-center justify-between gap-x-1.5 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">
|
||||
{checked}
|
||||
<Image
|
||||
src={'icons/unicorn_angle-down.svg'}
|
||||
width={12}
|
||||
height={12}
|
||||
alt=""
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-10 mt-2 w-full origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<div className="py-1">
|
||||
{data.map((item, index) => (
|
||||
<Menu.Item key={index}>
|
||||
{({ active }) => (
|
||||
<a
|
||||
onClick={() => setChecked(item)}
|
||||
href="#"
|
||||
className={classNames(
|
||||
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
|
||||
'block px-4 py-2 text-sm'
|
||||
)}
|
||||
>
|
||||
{item}
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
import SelectModels from "../ModelSelector";
|
||||
import InputToolbar from "../InputToolbar";
|
||||
import React from 'react'
|
||||
import SelectModels from '../ModelSelector'
|
||||
import InputToolbar from '../InputToolbar'
|
||||
|
||||
const EmptyChatContainer: React.FC = () => (
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<SelectModels />
|
||||
</div>
|
||||
<InputToolbar />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default EmptyChatContainer;
|
||||
export default EmptyChatContainer
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/outline";
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
expanded: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
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-xs leading-[12px] pl-1">
|
||||
<h2 className="pl-1 text-xs font-bold leading-[12px] text-gray-400">
|
||||
{title}
|
||||
</h2>
|
||||
<div className="mr-2">
|
||||
@ -19,6 +19,6 @@ const ExpandableHeader: React.FC<Props> = ({ title, expanded, onClick }) => (
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
)
|
||||
|
||||
export default ExpandableHeader;
|
||||
export default ExpandableHeader
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import HeaderTitle from "../HeaderTitle";
|
||||
import SearchBar, { SearchType } from "../SearchBar";
|
||||
import ExploreModelList from "../ExploreModelList";
|
||||
import ExploreModelFilter from "../ExploreModelFilter";
|
||||
import HeaderTitle from '../HeaderTitle'
|
||||
import SearchBar, { SearchType } from '../SearchBar'
|
||||
import ExploreModelList from '../ExploreModelList'
|
||||
import ExploreModelFilter from '../ExploreModelFilter'
|
||||
|
||||
const ExploreModelContainer: React.FC = () => (
|
||||
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
|
||||
<div className="flex flex-1 flex-col overflow-hidden px-16 pt-14">
|
||||
<HeaderTitle title="Explore Models" />
|
||||
{/* <SearchBar
|
||||
type={SearchType.Model}
|
||||
placeholder="Owner name like TheBloke, bhlim etc.."
|
||||
/> */}
|
||||
<div className="flex flex-1 gap-x-10 mt-9 overflow-hidden">
|
||||
<div className="mt-9 flex flex-1 gap-x-10 overflow-hidden">
|
||||
<ExploreModelFilter />
|
||||
<ExploreModelList />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default ExploreModelContainer;
|
||||
export default ExploreModelContainer
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
import React from "react";
|
||||
import SearchBar from "../SearchBar";
|
||||
import SimpleCheckbox from "../SimpleCheckbox";
|
||||
import SimpleTag from "../SimpleTag";
|
||||
import { TagType } from "../SimpleTag/TagType";
|
||||
import React from 'react'
|
||||
import SearchBar from '../SearchBar'
|
||||
import SimpleCheckbox from '../SimpleCheckbox'
|
||||
import SimpleTag from '../SimpleTag'
|
||||
import { TagType } from '../SimpleTag/TagType'
|
||||
|
||||
const tags = [
|
||||
"Roleplay",
|
||||
"Llama",
|
||||
"Story",
|
||||
"Casual",
|
||||
"Professional",
|
||||
"CodeLlama",
|
||||
"Coding",
|
||||
];
|
||||
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
|
||||
'Roleplay',
|
||||
'Llama',
|
||||
'Story',
|
||||
'Casual',
|
||||
'Professional',
|
||||
'CodeLlama',
|
||||
'Coding',
|
||||
]
|
||||
const checkboxs = ['GGUF', 'TensorRT', 'Meow', 'JigglyPuff']
|
||||
|
||||
const ExploreModelFilter: React.FC = () => {
|
||||
const enabled = false;
|
||||
if (!enabled) return null;
|
||||
const enabled = false
|
||||
if (!enabled) return null
|
||||
|
||||
return (
|
||||
<div className="w-64">
|
||||
<h2 className="font-semibold text-xs mb-[15px]">Tags</h2>
|
||||
<h2 className="mb-[15px] text-xs font-semibold">Tags</h2>
|
||||
<SearchBar placeholder="Filter by tags" />
|
||||
<div className="flex flex-wrap gap-[9px] mt-[14px]">
|
||||
<div className="mt-[14px] flex flex-wrap gap-[9px]">
|
||||
{tags.map((item) => (
|
||||
<SimpleTag key={item} title={item} type={item as TagType} />
|
||||
))}
|
||||
@ -35,7 +35,7 @@ const ExploreModelFilter: React.FC = () => {
|
||||
))}
|
||||
</fieldset>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ExploreModelFilter;
|
||||
export default ExploreModelFilter
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import ExploreModelItemHeader from "../ExploreModelItemHeader";
|
||||
import ModelVersionList from "../ModelVersionList";
|
||||
import { Fragment, forwardRef, useEffect, useState } from "react";
|
||||
import SimpleTag from "../SimpleTag";
|
||||
import ExploreModelItemHeader from '../ExploreModelItemHeader'
|
||||
import ModelVersionList from '../ModelVersionList'
|
||||
import { Fragment, forwardRef, useEffect, useState } from 'react'
|
||||
import SimpleTag from '../SimpleTag'
|
||||
import {
|
||||
MiscellanousTag,
|
||||
NumOfBit,
|
||||
@ -13,37 +13,37 @@ import {
|
||||
RamRequired,
|
||||
UsecaseTag,
|
||||
VersionTag,
|
||||
} from "@/_components/SimpleTag/TagType";
|
||||
import { displayDate } from "@/_utils/datetime";
|
||||
import { Product } from "@/_models/Product";
|
||||
import useGetMostSuitableModelVersion from "@/_hooks/useGetMostSuitableModelVersion";
|
||||
import { toGigabytes } from "@/_utils/converter";
|
||||
} from '@/_components/SimpleTag/TagType'
|
||||
import { displayDate } from '@/_utils/datetime'
|
||||
import { Product } from '@/_models/Product'
|
||||
import useGetMostSuitableModelVersion from '@/_hooks/useGetMostSuitableModelVersion'
|
||||
import { toGigabytes } from '@/_utils/converter'
|
||||
|
||||
type Props = {
|
||||
model: Product;
|
||||
};
|
||||
model: Product
|
||||
}
|
||||
|
||||
const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
||||
const [show, setShow] = useState(false);
|
||||
const [show, setShow] = useState(false)
|
||||
|
||||
const { availableVersions } = model;
|
||||
const { availableVersions } = model
|
||||
const { suitableModel, getMostSuitableModelVersion } =
|
||||
useGetMostSuitableModelVersion();
|
||||
useGetMostSuitableModelVersion()
|
||||
|
||||
useEffect(() => {
|
||||
getMostSuitableModelVersion(availableVersions);
|
||||
}, [availableVersions]);
|
||||
getMostSuitableModelVersion(availableVersions)
|
||||
}, [availableVersions])
|
||||
|
||||
if (!suitableModel) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
const { quantMethod, bits, maxRamRequired, usecase } = suitableModel;
|
||||
const { quantMethod, bits, maxRamRequired, usecase } = suitableModel
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex flex-col border border-gray-200 rounded-md mb-4"
|
||||
className="mb-4 flex flex-col rounded-md border border-gray-200"
|
||||
>
|
||||
<ExploreModelItemHeader
|
||||
suitableModel={suitableModel}
|
||||
@ -51,7 +51,7 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
||||
/>
|
||||
<div className="flex flex-col px-[26px] py-[22px]">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-1 flex flex-col gap-8">
|
||||
<div className="flex flex-1 flex-col gap-8">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-sm font-medium text-gray-500">
|
||||
Release Date
|
||||
@ -81,7 +81,7 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col gap-8">
|
||||
<div className="flex flex-1 flex-col gap-8">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-500">Author</div>
|
||||
<div className="text-sm font-normal text-gray-900">
|
||||
@ -107,13 +107,13 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 mt-[26px]">
|
||||
<div className="mt-[26px] flex flex-col gap-1">
|
||||
<span className="text-sm font-medium text-gray-500">About</span>
|
||||
<span className="text-sm font-normal text-gray-500">
|
||||
{model.longDescription}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col mt-5 gap-2">
|
||||
<div className="mt-5 flex flex-col gap-2">
|
||||
<span className="text-sm font-medium text-gray-500">Tags</span>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{model.tags.map((tag) => (
|
||||
@ -133,19 +133,19 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
||||
<ModelVersionList
|
||||
model={model}
|
||||
versions={model.availableVersions}
|
||||
recommendedVersion={suitableModel?._id ?? ""}
|
||||
recommendedVersion={suitableModel?._id ?? ''}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShow(!show)}
|
||||
className="bg-[#FBFBFB] text-gray-500 text-sm text-left py-2 px-4 border-t border-gray-200"
|
||||
className="border-t border-gray-200 bg-[#FBFBFB] px-4 py-2 text-left text-sm text-gray-500"
|
||||
>
|
||||
{!show ? "+ Show Available Versions" : "- Collapse"}
|
||||
{!show ? '+ Show Available Versions' : '- Collapse'}
|
||||
</button>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
export default ExploreModelItem;
|
||||
export default ExploreModelItem
|
||||
|
||||
@ -1,72 +1,72 @@
|
||||
import SimpleTag from "../SimpleTag";
|
||||
import PrimaryButton from "../PrimaryButton";
|
||||
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter";
|
||||
import SecondaryButton from "../SecondaryButton";
|
||||
import { Product } from "@/_models/Product";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import { ModelVersion } from "@/_models/ModelVersion";
|
||||
import useGetPerformanceTag from "@/_hooks/useGetPerformanceTag";
|
||||
import useDownloadModel from "@/_hooks/useDownloadModel";
|
||||
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
||||
import { atom, useAtomValue, useSetAtom } from "jotai";
|
||||
import SimpleTag from '../SimpleTag'
|
||||
import PrimaryButton from '../PrimaryButton'
|
||||
import { formatDownloadPercentage, toGigabytes } from '@/_utils/converter'
|
||||
import SecondaryButton from '../SecondaryButton'
|
||||
import { Product } from '@/_models/Product'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import { ModelVersion } from '@/_models/ModelVersion'
|
||||
import useGetPerformanceTag from '@/_hooks/useGetPerformanceTag'
|
||||
import useDownloadModel from '@/_hooks/useDownloadModel'
|
||||
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
|
||||
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai'
|
||||
import {
|
||||
MainViewState,
|
||||
setMainViewStateAtom,
|
||||
} from "@/_helpers/atoms/MainView.atom";
|
||||
} from '@/_helpers/atoms/MainView.atom'
|
||||
|
||||
type Props = {
|
||||
suitableModel: ModelVersion;
|
||||
exploreModel: Product;
|
||||
};
|
||||
suitableModel: ModelVersion
|
||||
exploreModel: Product
|
||||
}
|
||||
|
||||
const ExploreModelItemHeader: React.FC<Props> = ({
|
||||
suitableModel,
|
||||
exploreModel,
|
||||
}) => {
|
||||
const { downloadModel } = useDownloadModel();
|
||||
const { downloadedModels } = useGetDownloadedModels();
|
||||
const { downloadModel } = useDownloadModel()
|
||||
const { downloadedModels } = useGetDownloadedModels()
|
||||
const { performanceTag, title, getPerformanceForModel } =
|
||||
useGetPerformanceTag();
|
||||
useGetPerformanceTag()
|
||||
const downloadAtom = useMemo(
|
||||
() => atom((get) => get(modelDownloadStateAtom)[suitableModel._id]),
|
||||
[suitableModel._id]
|
||||
);
|
||||
const downloadState = useAtomValue(downloadAtom);
|
||||
const setMainViewState = useSetAtom(setMainViewStateAtom);
|
||||
)
|
||||
const downloadState = useAtomValue(downloadAtom)
|
||||
const setMainViewState = useSetAtom(setMainViewStateAtom)
|
||||
|
||||
useEffect(() => {
|
||||
getPerformanceForModel(suitableModel);
|
||||
}, [suitableModel]);
|
||||
getPerformanceForModel(suitableModel)
|
||||
}, [suitableModel])
|
||||
|
||||
const onDownloadClick = useCallback(() => {
|
||||
downloadModel(exploreModel, suitableModel);
|
||||
}, [exploreModel, suitableModel]);
|
||||
downloadModel(exploreModel, suitableModel)
|
||||
}, [exploreModel, suitableModel])
|
||||
|
||||
const isDownloaded =
|
||||
downloadedModels.find((model) => model._id === suitableModel._id) != null;
|
||||
downloadedModels.find((model) => model._id === suitableModel._id) != null
|
||||
|
||||
let downloadButton = (
|
||||
<PrimaryButton
|
||||
title={
|
||||
suitableModel.size
|
||||
? `Download (${toGigabytes(suitableModel.size)})`
|
||||
: "Download"
|
||||
: 'Download'
|
||||
}
|
||||
onClick={() => onDownloadClick()}
|
||||
/>
|
||||
);
|
||||
)
|
||||
|
||||
if (isDownloaded) {
|
||||
downloadButton = (
|
||||
<PrimaryButton
|
||||
title="View Downloaded Model"
|
||||
onClick={() => {
|
||||
setMainViewState(MainViewState.MyModel);
|
||||
setMainViewState(MainViewState.MyModel)
|
||||
}}
|
||||
className="bg-green-500 hover:bg-green-400"
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
if (downloadState != null) {
|
||||
@ -78,11 +78,11 @@ const ExploreModelItemHeader: React.FC<Props> = ({
|
||||
downloadState.percent
|
||||
)})`}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
||||
<div className="flex items-center justify-between border-b border-gray-200 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{exploreModel.name}</span>
|
||||
{performanceTag && (
|
||||
@ -91,7 +91,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({
|
||||
</div>
|
||||
{downloadButton}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ExploreModelItemHeader;
|
||||
export default ExploreModelItemHeader
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import React, { useEffect } from "react";
|
||||
import ExploreModelItem from "../ExploreModelItem";
|
||||
import { getConfiguredModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import useGetConfiguredModels from "@/_hooks/useGetConfiguredModels";
|
||||
import { Waveform } from "@uiball/loaders";
|
||||
import React, { useEffect } from 'react'
|
||||
import ExploreModelItem from '../ExploreModelItem'
|
||||
import { getConfiguredModels } from '@/_hooks/useGetDownloadedModels'
|
||||
import useGetConfiguredModels from '@/_hooks/useGetConfiguredModels'
|
||||
import { Waveform } from '@uiball/loaders'
|
||||
|
||||
const ExploreModelList: React.FC = () => {
|
||||
const { loading, models } = useGetConfiguredModels();
|
||||
const { loading, models } = useGetConfiguredModels()
|
||||
|
||||
useEffect(() => {
|
||||
getConfiguredModels();
|
||||
}, []);
|
||||
getConfiguredModels()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 overflow-y-auto scroll">
|
||||
<div className="scroll flex flex-1 flex-col overflow-y-auto">
|
||||
{loading && (
|
||||
<div className="mx-auto">
|
||||
<Waveform size={24} color="#CBD5E0" />
|
||||
@ -22,7 +22,7 @@ const ExploreModelList: React.FC = () => {
|
||||
<ExploreModelItem key={item._id} model={item} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ExploreModelList;
|
||||
export default ExploreModelList
|
||||
|
||||
@ -1,29 +1,23 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
// DEPRECATED
|
||||
export default function Footer() {
|
||||
return (
|
||||
<div className="flex items-center justify-between container m-auto">
|
||||
<div className="container m-auto flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Image src={"icons/app_icon.svg"} width={32} height={32} alt="" />
|
||||
<Image src={'icons/app_icon.svg'} width={32} height={32} alt="" />
|
||||
<span>Jan</span>
|
||||
</div>
|
||||
<div className="flex gap-4 my-6">
|
||||
<Link
|
||||
href="/privacy"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="my-6 flex gap-4">
|
||||
<Link href="/privacy" className="cursor-pointer">
|
||||
Privacy
|
||||
</Link>
|
||||
<span>•</span>
|
||||
<Link
|
||||
href="/support"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Link href="/support" className="cursor-pointer">
|
||||
Support
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { showingMobilePaneAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
import { Bars3Icon } from "@heroicons/react/24/outline";
|
||||
import { useSetAtom } from "jotai";
|
||||
import React from "react";
|
||||
import { showingMobilePaneAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
import { Bars3Icon } from '@heroicons/react/24/outline'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import React from 'react'
|
||||
|
||||
const HamburgerButton: React.FC = () => {
|
||||
const setShowingMobilePane = useSetAtom(showingMobilePaneAtom);
|
||||
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"
|
||||
className="inline-flex items-center justify-center self-end 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);
|
||||
export default React.memo(HamburgerButton)
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import React from "react";
|
||||
import UserProfileDropDown from "../UserProfileDropDown";
|
||||
import LoginButton from "../LoginButton";
|
||||
import HamburgerButton from "../HamburgerButton";
|
||||
|
||||
const Header: React.FC = () => {
|
||||
return (
|
||||
<header className="flex border-b-[1px] border-gray-200 p-3 dark:bg-gray-800">
|
||||
<nav className="flex-1 justify-center">
|
||||
<HamburgerButton />
|
||||
</nav>
|
||||
<div className="h-[30px]" />
|
||||
<LoginButton />
|
||||
<UserProfileDropDown />
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
import React from 'react'
|
||||
import UserProfileDropDown from '../UserProfileDropDown'
|
||||
import LoginButton from '../LoginButton'
|
||||
import HamburgerButton from '../HamburgerButton'
|
||||
|
||||
const Header: React.FC = () => {
|
||||
return (
|
||||
<header className="flex border-b-[1px] border-gray-200 p-3 dark:bg-gray-800">
|
||||
<nav className="flex-1 justify-center">
|
||||
<HamburgerButton />
|
||||
</nav>
|
||||
<div className="h-[30px]" />
|
||||
<LoginButton />
|
||||
<UserProfileDropDown />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
import React from 'react'
|
||||
import { ArrowLeftIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
const HeaderBackButton: React.FC = () => {
|
||||
return (
|
||||
@ -7,7 +7,7 @@ const HeaderBackButton: React.FC = () => {
|
||||
<ArrowLeftIcon width={24} height={24} />
|
||||
<span className="text-sm">Back</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(HeaderBackButton);
|
||||
export default React.memo(HeaderBackButton)
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import React from "react";
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
className?: string;
|
||||
};
|
||||
title: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const HeaderTitle: React.FC<Props> = ({ title, className }) => (
|
||||
<h2
|
||||
className={`my-5 font-semibold text-[34px] tracking-[-0.4px] leading-[41px] ${className}`}
|
||||
className={`my-5 text-[34px] font-semibold leading-[41px] tracking-[-0.4px] ${className}`}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(HeaderTitle);
|
||||
export default React.memo(HeaderTitle)
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
import React from "react";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import Image from "next/image";
|
||||
import { Conversation } from "@/_models/Conversation";
|
||||
import { ModelManagementService } from "@janhq/core";
|
||||
import { executeSerial } from "../../../../electron/core/plugin-manager/execution/extension-manager";
|
||||
import React from 'react'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import Image from 'next/image'
|
||||
import { Conversation } from '@/_models/Conversation'
|
||||
import { ModelManagementService } from '@janhq/core'
|
||||
import { executeSerial } from '../../../../electron/core/plugin-manager/execution/extension-manager'
|
||||
import {
|
||||
conversationStatesAtom,
|
||||
getActiveConvoIdAtom,
|
||||
setActiveConvoIdAtom,
|
||||
updateConversationErrorAtom,
|
||||
updateConversationWaitingForResponseAtom,
|
||||
} from "@/_helpers/atoms/Conversation.atom";
|
||||
} from '@/_helpers/atoms/Conversation.atom'
|
||||
import {
|
||||
setMainViewStateAtom,
|
||||
MainViewState,
|
||||
} from "@/_helpers/atoms/MainView.atom";
|
||||
import useInitModel from "@/_hooks/useInitModel";
|
||||
import { displayDate } from "@/_utils/datetime";
|
||||
} from '@/_helpers/atoms/MainView.atom'
|
||||
import useInitModel from '@/_hooks/useInitModel'
|
||||
import { displayDate } from '@/_utils/datetime'
|
||||
|
||||
type Props = {
|
||||
conversation: Conversation;
|
||||
avatarUrl?: string;
|
||||
name: string;
|
||||
summary?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
conversation: Conversation
|
||||
avatarUrl?: string
|
||||
name: string
|
||||
summary?: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
const HistoryItem: React.FC<Props> = ({
|
||||
conversation,
|
||||
@ -33,82 +33,80 @@ const HistoryItem: React.FC<Props> = ({
|
||||
summary,
|
||||
updatedAt,
|
||||
}) => {
|
||||
const setMainViewState = useSetAtom(setMainViewStateAtom);
|
||||
const conversationStates = useAtomValue(conversationStatesAtom);
|
||||
const activeConvoId = useAtomValue(getActiveConvoIdAtom);
|
||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
|
||||
const updateConvWaiting = useSetAtom(
|
||||
updateConversationWaitingForResponseAtom
|
||||
);
|
||||
const updateConvError = useSetAtom(updateConversationErrorAtom);
|
||||
const isSelected = activeConvoId === conversation._id;
|
||||
const setMainViewState = useSetAtom(setMainViewStateAtom)
|
||||
const conversationStates = useAtomValue(conversationStatesAtom)
|
||||
const activeConvoId = useAtomValue(getActiveConvoIdAtom)
|
||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
|
||||
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
|
||||
const updateConvError = useSetAtom(updateConversationErrorAtom)
|
||||
const isSelected = activeConvoId === conversation._id
|
||||
|
||||
const { initModel } = useInitModel();
|
||||
const { initModel } = useInitModel()
|
||||
|
||||
const onClick = async () => {
|
||||
const model = await executeSerial(
|
||||
ModelManagementService.GetModelById,
|
||||
conversation.modelId
|
||||
);
|
||||
)
|
||||
|
||||
if (conversation._id) updateConvWaiting(conversation._id, true);
|
||||
if (conversation._id) updateConvWaiting(conversation._id, true)
|
||||
initModel(model).then((res: any) => {
|
||||
if (conversation._id) updateConvWaiting(conversation._id, false);
|
||||
if (conversation._id) updateConvWaiting(conversation._id, false)
|
||||
|
||||
if (res?.error && conversation._id) {
|
||||
updateConvError(conversation._id, res.error);
|
||||
updateConvError(conversation._id, res.error)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (activeConvoId !== conversation._id) {
|
||||
setMainViewState(MainViewState.Conversation);
|
||||
setActiveConvoId(conversation._id);
|
||||
setMainViewState(MainViewState.Conversation)
|
||||
setActiveConvoId(conversation._id)
|
||||
}
|
||||
};
|
||||
|
||||
const backgroundColor = isSelected
|
||||
? "bg-gray-100 dark:bg-gray-700"
|
||||
: "bg-white dark:bg-gray-500";
|
||||
|
||||
let rightImageUrl: string | undefined;
|
||||
if (conversationStates[conversation._id ?? ""]?.waitingForResponse === true) {
|
||||
rightImageUrl = "icons/loading.svg";
|
||||
}
|
||||
|
||||
const description = conversation?.lastMessage ?? "No new message";
|
||||
const backgroundColor = isSelected
|
||||
? 'bg-gray-100 dark:bg-gray-700'
|
||||
: 'bg-white dark:bg-gray-500'
|
||||
|
||||
let rightImageUrl: string | undefined
|
||||
if (conversationStates[conversation._id ?? '']?.waitingForResponse === true) {
|
||||
rightImageUrl = 'icons/loading.svg'
|
||||
}
|
||||
|
||||
const description = conversation?.lastMessage ?? 'No new message'
|
||||
|
||||
return (
|
||||
<li
|
||||
role="button"
|
||||
className={`flex flex-row ml-3 mr-2 rounded p-3 ${backgroundColor} hover:bg-hover-light`}
|
||||
className={`ml-3 mr-2 flex flex-row rounded p-3 ${backgroundColor} hover:bg-hover-light`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="w-8 h-8">
|
||||
<div className="h-8 w-8">
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
src={avatarUrl ?? "icons/app_icon.svg"}
|
||||
src={avatarUrl ?? 'icons/app_icon.svg'}
|
||||
className="aspect-square rounded-full"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col ml-2 flex-1">
|
||||
<div className="ml-2 flex flex-1 flex-col">
|
||||
{/* title */}
|
||||
<div className="flex">
|
||||
<span className="flex-1 text-gray-900 line-clamp-1">
|
||||
<span className="line-clamp-1 flex-1 text-gray-900">
|
||||
{summary ?? name}
|
||||
</span>
|
||||
<span className="text-xs leading-5 text-gray-500 line-clamp-1">
|
||||
<span className="line-clamp-1 text-xs leading-5 text-gray-500">
|
||||
{updatedAt && displayDate(new Date(updatedAt).getTime())}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* description */}
|
||||
<span className="mt-1 text-gray-400 line-clamp-2">{description}</span>
|
||||
<span className="mt-1 line-clamp-2 text-gray-400">{description}</span>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default HistoryItem;
|
||||
export default HistoryItem
|
||||
|
||||
@ -1,57 +1,57 @@
|
||||
import HistoryItem from "../HistoryItem";
|
||||
import { useEffect, useState } from "react";
|
||||
import ExpandableHeader from "../ExpandableHeader";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { searchAtom } from "@/_helpers/JotaiWrapper";
|
||||
import useGetUserConversations from "@/_hooks/useGetUserConversations";
|
||||
import SidebarEmptyHistory from "../SidebarEmptyHistory";
|
||||
import { userConversationsAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
|
||||
const HistoryList: React.FC = () => {
|
||||
const conversations = useAtomValue(userConversationsAtom);
|
||||
const searchText = useAtomValue(searchAtom);
|
||||
const [expand, setExpand] = useState<boolean>(true);
|
||||
const { getUserConversations } = useGetUserConversations();
|
||||
|
||||
useEffect(() => {
|
||||
getUserConversations();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow pt-3 gap-2 overflow-hidden">
|
||||
<ExpandableHeader
|
||||
title="CHAT HISTORY"
|
||||
expanded={expand}
|
||||
onClick={() => setExpand(!expand)}
|
||||
/>
|
||||
<ul
|
||||
className={`flex flex-col gap-1 mt-1 overflow-y-auto scroll ${
|
||||
!expand ? "hidden " : "block"
|
||||
}`}
|
||||
>
|
||||
{conversations.length > 0 ? (
|
||||
conversations
|
||||
.filter(
|
||||
(e) =>
|
||||
searchText.trim() === "" ||
|
||||
e.name?.toLowerCase().includes(searchText.toLowerCase().trim())
|
||||
)
|
||||
.map((convo) => (
|
||||
<HistoryItem
|
||||
key={convo._id}
|
||||
conversation={convo}
|
||||
summary={convo.summary}
|
||||
avatarUrl={convo.image}
|
||||
name={convo.name || "Jan"}
|
||||
updatedAt={convo.updatedAt ?? ""}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<SidebarEmptyHistory />
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HistoryList;
|
||||
import HistoryItem from '../HistoryItem'
|
||||
import { useEffect, useState } from 'react'
|
||||
import ExpandableHeader from '../ExpandableHeader'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { searchAtom } from '@/_helpers/JotaiWrapper'
|
||||
import useGetUserConversations from '@/_hooks/useGetUserConversations'
|
||||
import SidebarEmptyHistory from '../SidebarEmptyHistory'
|
||||
import { userConversationsAtom } from '@/_helpers/atoms/Conversation.atom'
|
||||
|
||||
const HistoryList: React.FC = () => {
|
||||
const conversations = useAtomValue(userConversationsAtom)
|
||||
const searchText = useAtomValue(searchAtom)
|
||||
const [expand, setExpand] = useState<boolean>(true)
|
||||
const { getUserConversations } = useGetUserConversations()
|
||||
|
||||
useEffect(() => {
|
||||
getUserConversations()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-grow flex-col gap-2 overflow-hidden pt-3">
|
||||
<ExpandableHeader
|
||||
title="CHAT HISTORY"
|
||||
expanded={expand}
|
||||
onClick={() => setExpand(!expand)}
|
||||
/>
|
||||
<ul
|
||||
className={`scroll mt-1 flex flex-col gap-1 overflow-y-auto ${
|
||||
!expand ? 'hidden ' : 'block'
|
||||
}`}
|
||||
>
|
||||
{conversations.length > 0 ? (
|
||||
conversations
|
||||
.filter(
|
||||
(e) =>
|
||||
searchText.trim() === '' ||
|
||||
e.name?.toLowerCase().includes(searchText.toLowerCase().trim())
|
||||
)
|
||||
.map((convo) => (
|
||||
<HistoryItem
|
||||
key={convo._id}
|
||||
conversation={convo}
|
||||
summary={convo.summary}
|
||||
avatarUrl={convo.image}
|
||||
name={convo.name || 'Jan'}
|
||||
updatedAt={convo.updatedAt ?? ''}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<SidebarEmptyHistory />
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HistoryList
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import BasicPromptInput from "../BasicPromptInput";
|
||||
import BasicPromptAccessories from "../BasicPromptAccessories";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { showingAdvancedPromptAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
import SecondaryButton from "../SecondaryButton";
|
||||
import { Fragment } from "react";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import useCreateConversation from "@/_hooks/useCreateConversation";
|
||||
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import { currentConvoStateAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
import BasicPromptInput from '../BasicPromptInput'
|
||||
import BasicPromptAccessories from '../BasicPromptAccessories'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { showingAdvancedPromptAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
import SecondaryButton from '../SecondaryButton'
|
||||
import { Fragment } from 'react'
|
||||
import { PlusIcon } from '@heroicons/react/24/outline'
|
||||
import useCreateConversation from '@/_hooks/useCreateConversation'
|
||||
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
|
||||
import { currentConvoStateAtom } from '@/_helpers/atoms/Conversation.atom'
|
||||
|
||||
const InputToolbar: React.FC = () => {
|
||||
const showingAdvancedPrompt = useAtomValue(showingAdvancedPromptAtom);
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom);
|
||||
const { requestCreateConvo } = useCreateConversation();
|
||||
const currentConvoState = useAtomValue(currentConvoStateAtom);
|
||||
const showingAdvancedPrompt = useAtomValue(showingAdvancedPromptAtom)
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
||||
const { requestCreateConvo } = useCreateConversation()
|
||||
const currentConvoState = useAtomValue(currentConvoStateAtom)
|
||||
|
||||
if (showingAdvancedPrompt) {
|
||||
return <div />;
|
||||
return <div />
|
||||
}
|
||||
|
||||
// TODO: implement regenerate
|
||||
@ -26,20 +26,20 @@ const InputToolbar: React.FC = () => {
|
||||
|
||||
const onNewConversationClick = () => {
|
||||
if (activeModel) {
|
||||
requestCreateConvo(activeModel);
|
||||
requestCreateConvo(activeModel)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{currentConvoState?.error && (
|
||||
<div className="flex flex-row justify-center">
|
||||
<span className="mx-5 my-2 text-red-500 text-sm">
|
||||
<span className="mx-5 my-2 text-sm text-red-500">
|
||||
{currentConvoState?.error?.toString()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-center gap-2 my-3">
|
||||
<div className="my-3 flex justify-center gap-2">
|
||||
{/* <SecondaryButton title="Regenerate" onClick={onRegenerateClick} /> */}
|
||||
<SecondaryButton
|
||||
onClick={onNewConversationClick}
|
||||
@ -48,14 +48,14 @@ const InputToolbar: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
{/* My text input */}
|
||||
<div className="flex items-start space-x-4 mx-12 md:mx-32 2xl:mx-64 mb-5">
|
||||
<div className="min-w-0 flex-1 relative">
|
||||
<div className="mx-12 mb-5 flex items-start space-x-4 md:mx-32 2xl:mx-64">
|
||||
<div className="relative min-w-0 flex-1">
|
||||
<BasicPromptInput />
|
||||
<BasicPromptAccessories />
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default InputToolbar;
|
||||
export default InputToolbar
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import React from "react";
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
imageUrl: string;
|
||||
className?: string;
|
||||
alt?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
imageUrl: string
|
||||
className?: string
|
||||
alt?: string
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
const JanImage: React.FC<Props> = ({
|
||||
imageUrl,
|
||||
className = "",
|
||||
alt = "",
|
||||
className = '',
|
||||
alt = '',
|
||||
width,
|
||||
height,
|
||||
}) => {
|
||||
const [attempt, setAttempt] = React.useState(0);
|
||||
const [attempt, setAttempt] = React.useState(0)
|
||||
|
||||
return (
|
||||
<img
|
||||
@ -27,7 +27,7 @@ const JanImage: React.FC<Props> = ({
|
||||
key={attempt}
|
||||
onError={() => setAttempt(attempt + 1)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default JanImage;
|
||||
export default JanImage
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { setActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
import { useSetAtom } from "jotai";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
import { setActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
|
||||
const JanLogo: React.FC = () => {
|
||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
|
||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
|
||||
return (
|
||||
<button
|
||||
className="p-3 flex gap-0.5 items-center"
|
||||
className="flex items-center gap-0.5 p-3"
|
||||
onClick={() => setActiveConvoId(undefined)}
|
||||
>
|
||||
<Image src={"icons/app_icon.svg"} width={28} height={28} alt="" />
|
||||
<Image src={"icons/Jan.svg"} width={27} height={12} alt="" />
|
||||
<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);
|
||||
export default React.memo(JanLogo)
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import React, { Fragment } from "react";
|
||||
import SidebarFooter from "../SidebarFooter";
|
||||
import SidebarHeader from "../SidebarHeader";
|
||||
import SidebarMenu from "../SidebarMenu";
|
||||
import HistoryList from "../HistoryList";
|
||||
import NewChatButton from "../NewChatButton";
|
||||
import React, { Fragment } from 'react'
|
||||
import SidebarFooter from '../SidebarFooter'
|
||||
import SidebarHeader from '../SidebarHeader'
|
||||
import SidebarMenu from '../SidebarMenu'
|
||||
import HistoryList from '../HistoryList'
|
||||
import NewChatButton from '../NewChatButton'
|
||||
|
||||
const LeftContainer: React.FC = () => (
|
||||
<Fragment>
|
||||
@ -13,6 +13,6 @@ const LeftContainer: React.FC = () => (
|
||||
<SidebarMenu />
|
||||
<SidebarFooter />
|
||||
</Fragment>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(LeftContainer);
|
||||
export default React.memo(LeftContainer)
|
||||
|
||||
@ -1,20 +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;
|
||||
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 flex-shrink-0 flex-col overflow-hidden border-r border-gray-200 dark:bg-gray-800 lg:inset-y-0 lg:flex lg:w-72 lg:flex-col">
|
||||
<JanLogo />
|
||||
<div className="flex flex-1 flex-col gap-3 overflow-x-hidden">
|
||||
<SearchBar />
|
||||
{/* <ShortcutList /> */}
|
||||
<HistoryList />
|
||||
</div>
|
||||
<DiscordContainer />
|
||||
</div>
|
||||
)
|
||||
|
||||
export default LeftSidebar
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
const LoadingIndicator = () => {
|
||||
|
||||
return (
|
||||
<div className="typingIndicatorContainer">
|
||||
<div className="typingIndicatorBubble">
|
||||
@ -8,7 +7,7 @@ const LoadingIndicator = () => {
|
||||
<div className="typingIndicatorBubbleDot"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default LoadingIndicator;
|
||||
export default LoadingIndicator
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
const LoginButton: React.FC = () => {
|
||||
// const { signInWithKeyCloak } = useSignIn();
|
||||
@ -18,7 +18,7 @@ const LoginButton: React.FC = () => {
|
||||
// </button>
|
||||
// </div>
|
||||
// );
|
||||
return <div />;
|
||||
};
|
||||
return <div />
|
||||
}
|
||||
|
||||
export default LoginButton;
|
||||
export default LoginButton
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import ChatBody from "../ChatBody";
|
||||
import InputToolbar from "../InputToolbar";
|
||||
import MainChatHeader from "../MainChatHeader";
|
||||
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">
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<MainChatHeader />
|
||||
<ChatBody />
|
||||
<InputToolbar />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default MainChat;
|
||||
export default MainChat
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import ModelMenu from "../ModelMenu";
|
||||
import UserToolbar from "../UserToolbar";
|
||||
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">
|
||||
<div className="flex w-full justify-between border-b border-gray-200 px-3 py-1 shadow-sm dark:bg-gray-950">
|
||||
<UserToolbar />
|
||||
<ModelMenu />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default MainChatHeader;
|
||||
export default MainChatHeader
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import React from "react";
|
||||
import LeftContainer from "../LeftContainer";
|
||||
import RightContainer from "../RightContainer";
|
||||
import { Variants, motion } from "framer-motion";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { leftSideBarExpandStateAtom } from "@/_helpers/atoms/LeftSideBarExpand.atom";
|
||||
import React from 'react'
|
||||
import LeftContainer from '../LeftContainer'
|
||||
import RightContainer from '../RightContainer'
|
||||
import { Variants, motion } from 'framer-motion'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { leftSideBarExpandStateAtom } from '@/_helpers/atoms/LeftSideBarExpand.atom'
|
||||
|
||||
const leftSideBarVariants: Variants = {
|
||||
show: {
|
||||
@ -15,31 +15,31 @@ const leftSideBarVariants: Variants = {
|
||||
transition: { duration: 0.1 },
|
||||
},
|
||||
hide: {
|
||||
x: "-100%",
|
||||
x: '-100%',
|
||||
width: 0,
|
||||
opacity: 0,
|
||||
transition: { duration: 0.1 },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const MainContainer: React.FC = () => {
|
||||
const leftSideBarExpand = useAtomValue(leftSideBarExpandStateAtom);
|
||||
const leftSideBarExpand = useAtomValue(leftSideBarExpandStateAtom)
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={leftSideBarExpand ? "show" : "hide"}
|
||||
animate={leftSideBarExpand ? 'show' : 'hide'}
|
||||
variants={leftSideBarVariants}
|
||||
className="w-80 flex-shrink-0 py-3 h-screen border-r border-gray-200 flex flex-col"
|
||||
className="flex h-screen w-80 flex-shrink-0 flex-col border-r border-gray-200 py-3"
|
||||
>
|
||||
<LeftContainer />
|
||||
</motion.div>
|
||||
<div className="flex flex-col flex-1 h-screen">
|
||||
<div className="flex h-screen flex-1 flex-col">
|
||||
<RightContainer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default MainContainer;
|
||||
export default MainContainer
|
||||
|
||||
@ -1,35 +1,35 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { useAtomValue } from "jotai";
|
||||
import Welcome from "../WelcomeContainer";
|
||||
import { Preferences } from "../Preferences";
|
||||
import MyModelContainer from "../MyModelContainer";
|
||||
import ExploreModelContainer from "../ExploreModelContainer";
|
||||
import { useAtomValue } from 'jotai'
|
||||
import Welcome from '../WelcomeContainer'
|
||||
import { Preferences } from '../Preferences'
|
||||
import MyModelContainer from '../MyModelContainer'
|
||||
import ExploreModelContainer from '../ExploreModelContainer'
|
||||
import {
|
||||
MainViewState,
|
||||
getMainViewStateAtom,
|
||||
} from "@/_helpers/atoms/MainView.atom";
|
||||
import EmptyChatContainer from "../EmptyChatContainer";
|
||||
import MainChat from "../MainChat";
|
||||
} from '@/_helpers/atoms/MainView.atom'
|
||||
import EmptyChatContainer from '../EmptyChatContainer'
|
||||
import MainChat from '../MainChat'
|
||||
|
||||
const MainView: React.FC = () => {
|
||||
const viewState = useAtomValue(getMainViewStateAtom);
|
||||
const viewState = useAtomValue(getMainViewStateAtom)
|
||||
|
||||
switch (viewState) {
|
||||
case MainViewState.ConversationEmptyModel:
|
||||
return <EmptyChatContainer />;
|
||||
return <EmptyChatContainer />
|
||||
case MainViewState.ExploreModel:
|
||||
return <ExploreModelContainer />;
|
||||
return <ExploreModelContainer />
|
||||
case MainViewState.Setting:
|
||||
return <Preferences />;
|
||||
return <Preferences />
|
||||
case MainViewState.ResourceMonitor:
|
||||
case MainViewState.MyModel:
|
||||
return <MyModelContainer />;
|
||||
return <MyModelContainer />
|
||||
case MainViewState.Welcome:
|
||||
return <Welcome />;
|
||||
return <Welcome />
|
||||
default:
|
||||
return <MainChat />;
|
||||
return <MainChat />
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MainView;
|
||||
export default MainView
|
||||
|
||||
@ -1,53 +1,53 @@
|
||||
import Link from "next/link";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { Fragment } from "react";
|
||||
// import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { showConfirmSignOutModalAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
|
||||
export const MenuHeader: React.FC = () => {
|
||||
const setShowConfirmSignOutModal = useSetAtom(showConfirmSignOutModalAtom);
|
||||
// const { user } = useGetCurrentUser();
|
||||
|
||||
return <div></div>;
|
||||
|
||||
// return (
|
||||
// <Transition
|
||||
// as={Fragment}
|
||||
// enter="transition ease-out duration-200"
|
||||
// enterFrom="opacity-0 translate-y-1"
|
||||
// enterTo="opacity-100 translate-y-0"
|
||||
// leave="transition ease-in duration-150"
|
||||
// leaveFrom="opacity-100 translate-y-0"
|
||||
// leaveTo="opacity-0 translate-y-1"
|
||||
// >
|
||||
// <Popover.Panel className="absolute shadow-profile -right-2 top-full z-10 mt-3 w-[224px] overflow-hidden rounded-[6px] bg-white shadow-lg ring-1 ring-gray-200">
|
||||
// <div className="py-3 px-4 gap-2 flex flex-col">
|
||||
// <h2 className="text-[20px] leading-[25px] tracking-[-0.4px] font-bold text-[#111928]">
|
||||
// {user.displayName}
|
||||
// </h2>
|
||||
// <span className="text-[#6B7280] leading-[17.5px] text-sm">
|
||||
// {user.email}
|
||||
// </span>
|
||||
// </div>
|
||||
// <hr />
|
||||
// <button
|
||||
// onClick={() => setShowConfirmSignOutModal(true)}
|
||||
// className="px-4 py-3 text-sm w-full text-left text-gray-700"
|
||||
// >
|
||||
// Sign Out
|
||||
// </button>
|
||||
// <hr />
|
||||
// <div className="flex gap-2 px-4 py-2 justify-center items-center">
|
||||
// <Link href="/privacy">
|
||||
// <span className="text-[#6B7280] text-xs">Privacy</span>
|
||||
// </Link>
|
||||
// <div className="w-1 h-1 bg-[#D9D9D9] rounded-lg" />
|
||||
// <Link href="/support">
|
||||
// <span className="text-[#6B7280] text-xs">Support</span>
|
||||
// </Link>
|
||||
// </div>
|
||||
// </Popover.Panel>
|
||||
// </Transition>
|
||||
// );
|
||||
};
|
||||
import Link from 'next/link'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { Fragment } from 'react'
|
||||
// import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { showConfirmSignOutModalAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
|
||||
export const MenuHeader: React.FC = () => {
|
||||
const setShowConfirmSignOutModal = useSetAtom(showConfirmSignOutModalAtom)
|
||||
// const { user } = useGetCurrentUser();
|
||||
|
||||
return <div></div>
|
||||
|
||||
// return (
|
||||
// <Transition
|
||||
// as={Fragment}
|
||||
// enter="transition ease-out duration-200"
|
||||
// enterFrom="opacity-0 translate-y-1"
|
||||
// enterTo="opacity-100 translate-y-0"
|
||||
// leave="transition ease-in duration-150"
|
||||
// leaveFrom="opacity-100 translate-y-0"
|
||||
// leaveTo="opacity-0 translate-y-1"
|
||||
// >
|
||||
// <Popover.Panel className="absolute shadow-profile -right-2 top-full z-10 mt-3 w-[224px] overflow-hidden rounded-[6px] bg-white shadow-lg ring-1 ring-gray-200">
|
||||
// <div className="py-3 px-4 gap-2 flex flex-col">
|
||||
// <h2 className="text-[20px] leading-[25px] tracking-[-0.4px] font-bold text-[#111928]">
|
||||
// {user.displayName}
|
||||
// </h2>
|
||||
// <span className="text-[#6B7280] leading-[17.5px] text-sm">
|
||||
// {user.email}
|
||||
// </span>
|
||||
// </div>
|
||||
// <hr />
|
||||
// <button
|
||||
// onClick={() => setShowConfirmSignOutModal(true)}
|
||||
// className="px-4 py-3 text-sm w-full text-left text-gray-700"
|
||||
// >
|
||||
// Sign Out
|
||||
// </button>
|
||||
// <hr />
|
||||
// <div className="flex gap-2 px-4 py-2 justify-center items-center">
|
||||
// <Link href="/privacy">
|
||||
// <span className="text-[#6B7280] text-xs">Privacy</span>
|
||||
// </Link>
|
||||
// <div className="w-1 h-1 bg-[#D9D9D9] rounded-lg" />
|
||||
// <Link href="/support">
|
||||
// <span className="text-[#6B7280] text-xs">Support</span>
|
||||
// </Link>
|
||||
// </div>
|
||||
// </Popover.Panel>
|
||||
// </Transition>
|
||||
// );
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import React, { useRef } from "react";
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import Image from "next/image";
|
||||
import { useAtom } from "jotai";
|
||||
import { showingMobilePaneAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
import React, { useRef } from 'react'
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import Image from 'next/image'
|
||||
import { useAtom } from 'jotai'
|
||||
import { showingMobilePaneAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
|
||||
const MobileMenuPane: React.FC = () => {
|
||||
const [show, setShow] = useAtom(showingMobilePaneAtom);
|
||||
let loginRef = useRef(null);
|
||||
const [show, setShow] = useAtom(showingMobilePaneAtom)
|
||||
let loginRef = useRef(null)
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@ -54,7 +54,7 @@ const MobileMenuPane: React.FC = () => {
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default MobileMenuPane;
|
||||
export default MobileMenuPane
|
||||
|
||||
@ -1,46 +1,50 @@
|
||||
import React from "react";
|
||||
import PrimaryButton from "../PrimaryButton";
|
||||
import React from 'react'
|
||||
import PrimaryButton from '../PrimaryButton'
|
||||
|
||||
export enum ModelActionType {
|
||||
Start = "Start",
|
||||
Stop = "Stop",
|
||||
Start = 'Start',
|
||||
Stop = 'Stop',
|
||||
}
|
||||
|
||||
type ModelActionStyle = {
|
||||
title: string;
|
||||
backgroundColor: string;
|
||||
textColor: string;
|
||||
};
|
||||
title: string
|
||||
backgroundColor: string
|
||||
textColor: string
|
||||
}
|
||||
|
||||
const modelActionMapper: Record<ModelActionType, ModelActionStyle> = {
|
||||
[ModelActionType.Start]: {
|
||||
title: "Start",
|
||||
backgroundColor: "bg-blue-500 hover:bg-blue-600",
|
||||
textColor: "text-white",
|
||||
title: 'Start',
|
||||
backgroundColor: 'bg-blue-500 hover:bg-blue-600',
|
||||
textColor: 'text-white',
|
||||
},
|
||||
[ModelActionType.Stop]: {
|
||||
title: "Stop",
|
||||
backgroundColor: "bg-red-500 hover:bg-red-600",
|
||||
textColor: "text-white",
|
||||
title: 'Stop',
|
||||
backgroundColor: 'bg-red-500 hover:bg-red-600',
|
||||
textColor: 'text-white',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type: ModelActionType;
|
||||
onActionClick: (type: ModelActionType) => void;
|
||||
};
|
||||
type: ModelActionType
|
||||
onActionClick: (type: ModelActionType) => void
|
||||
}
|
||||
|
||||
const ModelActionButton: React.FC<Props> = ({ type, onActionClick }) => {
|
||||
const styles = modelActionMapper[type];
|
||||
const styles = modelActionMapper[type]
|
||||
const onClick = () => {
|
||||
onActionClick(type);
|
||||
};
|
||||
onActionClick(type)
|
||||
}
|
||||
|
||||
return (
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<PrimaryButton title={styles.title} onClick={onClick} className={styles.backgroundColor} />
|
||||
<PrimaryButton
|
||||
title={styles.title}
|
||||
onClick={onClick}
|
||||
className={styles.backgroundColor}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelActionButton;
|
||||
export default ModelActionButton
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { EllipsisVerticalIcon } from "@heroicons/react/20/solid";
|
||||
import { Fragment } from "react";
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { EllipsisVerticalIcon } from '@heroicons/react/20/solid'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
type Props = {
|
||||
onDeleteClick: () => void;
|
||||
};
|
||||
onDeleteClick: () => void
|
||||
}
|
||||
|
||||
const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => (
|
||||
<Menu as="div" className="relative flex-none">
|
||||
@ -26,7 +26,7 @@ const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => (
|
||||
{({ active }) => (
|
||||
<button
|
||||
className={`${
|
||||
active ? "bg-violet-500 text-white" : "text-gray-900"
|
||||
active ? 'bg-violet-500 text-white' : 'text-gray-900'
|
||||
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
||||
onClick={onDeleteClick}
|
||||
>
|
||||
@ -37,6 +37,6 @@ const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => (
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
)
|
||||
|
||||
export default ModelActionMenu;
|
||||
export default ModelActionMenu
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
||||
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
type Props = {
|
||||
callback: () => void;
|
||||
};
|
||||
callback: () => void
|
||||
}
|
||||
|
||||
const ModelDownloadButton: React.FC<Props> = ({ callback }) => {
|
||||
return (
|
||||
<button
|
||||
className="bg-[#1A56DB] rounded-lg py-2 px-3 flex items-center gap-2"
|
||||
className="flex items-center gap-2 rounded-lg bg-[#1A56DB] px-3 py-2"
|
||||
onClick={callback}
|
||||
>
|
||||
<ArrowDownTrayIcon width={16} height={16} color="#FFFFFF" />
|
||||
<span className="text-xs leading-[18px] text-[#fff] font-medium">
|
||||
<span className="text-xs font-medium leading-[18px] text-[#fff]">
|
||||
Download
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelDownloadButton;
|
||||
export default ModelDownloadButton
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import { toGigabytes } from "@/_utils/converter";
|
||||
import { toGigabytes } from '@/_utils/converter'
|
||||
|
||||
type Props = {
|
||||
total: number;
|
||||
value: number;
|
||||
};
|
||||
total: number
|
||||
value: number
|
||||
}
|
||||
|
||||
const ModelDownloadingButton: React.FC<Props> = ({ total, value }) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<button className="py-2 px-3 flex gap-2 border text-xs leading-[18px] border-gray-200 rounded-lg">
|
||||
<button className="flex gap-2 rounded-lg border border-gray-200 px-3 py-2 text-xs leading-[18px]">
|
||||
Downloading...
|
||||
</button>
|
||||
<div className="py-0.5 px-2.5 bg-gray-200 rounded">
|
||||
<div className="rounded bg-gray-200 px-2.5 py-0.5">
|
||||
<span className="text-xs font-medium text-gray-800">
|
||||
{toGigabytes(value)} / {toGigabytes(total)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelDownloadingButton;
|
||||
export default ModelDownloadingButton
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import React from "react";
|
||||
import { DownloadState } from "@/_models/DownloadState";
|
||||
import React from 'react'
|
||||
import { DownloadState } from '@/_models/DownloadState'
|
||||
import {
|
||||
formatDownloadPercentage,
|
||||
formatDownloadSpeed,
|
||||
toGigabytes,
|
||||
} from "@/_utils/converter";
|
||||
} from '@/_utils/converter'
|
||||
|
||||
type Props = {
|
||||
downloadState: DownloadState;
|
||||
};
|
||||
downloadState: DownloadState
|
||||
}
|
||||
|
||||
const ModelDownloadingRow: React.FC<Props> = ({ downloadState }) => (
|
||||
<tr
|
||||
className="border-b border-gray-200 last:border-b-0 last:rounded-lg"
|
||||
className="border-b border-gray-200 last:rounded-lg last:border-b-0"
|
||||
key={downloadState.fileName}
|
||||
>
|
||||
<td className="flex flex-col whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900">
|
||||
@ -31,6 +31,6 @@ const ModelDownloadingRow: React.FC<Props> = ({ downloadState }) => (
|
||||
{formatDownloadSpeed(downloadState.speed)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
|
||||
export default ModelDownloadingRow;
|
||||
export default ModelDownloadingRow
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import React from "react";
|
||||
import ModelTableHeader from "../ModelTableHeader";
|
||||
import { DownloadState } from "@/_models/DownloadState";
|
||||
import ModelDownloadingRow from "../ModelDownloadingRow";
|
||||
import React from 'react'
|
||||
import ModelTableHeader from '../ModelTableHeader'
|
||||
import { DownloadState } from '@/_models/DownloadState'
|
||||
import ModelDownloadingRow from '../ModelDownloadingRow'
|
||||
|
||||
type Props = {
|
||||
downloadStates: DownloadState[];
|
||||
};
|
||||
downloadStates: DownloadState[]
|
||||
}
|
||||
|
||||
const tableHeaders = ["MODEL", "TRANSFERRED", "SIZE", "PERCENTAGE", "SPEED"];
|
||||
const tableHeaders = ['MODEL', 'TRANSFERRED', 'SIZE', 'PERCENTAGE', 'SPEED']
|
||||
|
||||
const ModelDownloadingTable: React.FC<Props> = ({ downloadStates }) => (
|
||||
<div className="flow-root border rounded-lg border-gray-200 min-w-full align-middle shadow-lg">
|
||||
<div className="flow-root min-w-full rounded-lg border border-gray-200 align-middle shadow-lg">
|
||||
<table className="min-w-full">
|
||||
<thead className="bg-gray-50 border-b border-gray-200">
|
||||
<thead className="border-b border-gray-200 bg-gray-50">
|
||||
<tr className="rounded-t-lg">
|
||||
{tableHeaders.map((item) => (
|
||||
<ModelTableHeader key={item} title={item} />
|
||||
))}
|
||||
<th scope="col" className="relative px-6 py-3 w-fit">
|
||||
<th scope="col" className="relative w-fit px-6 py-3">
|
||||
<span className="sr-only">Edit</span>
|
||||
</th>
|
||||
</tr>
|
||||
@ -29,6 +29,6 @@ const ModelDownloadingTable: React.FC<Props> = ({ downloadStates }) => (
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(ModelDownloadingTable);
|
||||
export default React.memo(ModelDownloadingTable)
|
||||
|
||||
@ -1,42 +1,42 @@
|
||||
import Image from "next/image";
|
||||
import ModelInfoItem from "../ModelInfoItem";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
modelName: string;
|
||||
inferenceTime: string;
|
||||
hardware: string;
|
||||
pricing: string;
|
||||
};
|
||||
|
||||
const ModelInfo: React.FC<Props> = ({
|
||||
modelName,
|
||||
inferenceTime,
|
||||
hardware,
|
||||
pricing,
|
||||
}) => (
|
||||
<div className="flex flex-col rounded-lg border border-gray-200 p-3 gap-3">
|
||||
<h2 className="font-semibold text-sm text-gray-900 dark:text-white">
|
||||
{modelName} is available via Jan API
|
||||
</h2>
|
||||
<div className="flex items-start gap-4">
|
||||
<ModelInfoItem description={inferenceTime} name="Inference Time" />
|
||||
<ModelInfoItem description={hardware} name="Hardware" />
|
||||
</div>
|
||||
<hr />
|
||||
<div className="flex justify-between items-center ">
|
||||
<div className="flex flex-col">
|
||||
<h2 className="text-xl tracking-[-0.4px] font-semibold">{pricing}</h2>
|
||||
<span className="text-xs leading-[18px] text-[#6B7280]">
|
||||
Average Cost / Call
|
||||
</span>
|
||||
</div>
|
||||
<button className="px-3 py-2 bg-[#1F2A37] flex gap-2 items-center rounded-lg">
|
||||
<Image src={"icons/code.svg"} width={16} height={17} alt="" />
|
||||
<span className="text-white text-sm font-medium">Get API Key</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default React.memo(ModelInfo);
|
||||
import Image from 'next/image'
|
||||
import ModelInfoItem from '../ModelInfoItem'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
modelName: string
|
||||
inferenceTime: string
|
||||
hardware: string
|
||||
pricing: string
|
||||
}
|
||||
|
||||
const ModelInfo: React.FC<Props> = ({
|
||||
modelName,
|
||||
inferenceTime,
|
||||
hardware,
|
||||
pricing,
|
||||
}) => (
|
||||
<div className="flex flex-col gap-3 rounded-lg border border-gray-200 p-3">
|
||||
<h2 className="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{modelName} is available via Jan API
|
||||
</h2>
|
||||
<div className="flex items-start gap-4">
|
||||
<ModelInfoItem description={inferenceTime} name="Inference Time" />
|
||||
<ModelInfoItem description={hardware} name="Hardware" />
|
||||
</div>
|
||||
<hr />
|
||||
<div className="flex items-center justify-between ">
|
||||
<div className="flex flex-col">
|
||||
<h2 className="text-xl font-semibold tracking-[-0.4px]">{pricing}</h2>
|
||||
<span className="text-xs leading-[18px] text-[#6B7280]">
|
||||
Average Cost / Call
|
||||
</span>
|
||||
</div>
|
||||
<button className="flex items-center gap-2 rounded-lg bg-[#1F2A37] px-3 py-2">
|
||||
<Image src={'icons/code.svg'} width={16} height={17} alt="" />
|
||||
<span className="text-sm font-medium text-white">Get API Key</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default React.memo(ModelInfo)
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const ModelInfoItem: React.FC<Props> = ({ description, name }) => (
|
||||
<div className="flex flex-col flex-1">
|
||||
<span className="text-gray-500 font-normal text-sm">{name}</span>
|
||||
<span className="font-normal text-sm">{description}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default React.memo(ModelInfoItem);
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const ModelInfoItem: React.FC<Props> = ({ description, name }) => (
|
||||
<div className="flex flex-1 flex-col">
|
||||
<span className="text-sm font-normal text-gray-500">{name}</span>
|
||||
<span className="text-sm font-normal">{description}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default React.memo(ModelInfoItem)
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { useSetAtom } from "jotai";
|
||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||
import { showConfirmDeleteConversationModalAtom } from "@/_helpers/atoms/Modal.atom";
|
||||
|
||||
const ModelMenu: React.FC = () => {
|
||||
const setShowConfirmDeleteConversationModal = useSetAtom(
|
||||
showConfirmDeleteConversationModalAtom
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={() => setShowConfirmDeleteConversationModal(true)}>
|
||||
<TrashIcon width={24} height={24} color="#9CA3AF" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelMenu;
|
||||
'use client'
|
||||
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { TrashIcon } from '@heroicons/react/24/outline'
|
||||
import { showConfirmDeleteConversationModalAtom } from '@/_helpers/atoms/Modal.atom'
|
||||
|
||||
const ModelMenu: React.FC = () => {
|
||||
const setShowConfirmDeleteConversationModal = useSetAtom(
|
||||
showConfirmDeleteConversationModalAtom
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={() => setShowConfirmDeleteConversationModal(true)}>
|
||||
<TrashIcon width={24} height={24} color="#9CA3AF" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelMenu
|
||||
|
||||
@ -1,50 +1,50 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { ModelStatus, ModelStatusComponent } from "../ModelStatusComponent";
|
||||
import ModelActionMenu from "../ModelActionMenu";
|
||||
import { useAtomValue } from "jotai";
|
||||
import ModelActionButton, { ModelActionType } from "../ModelActionButton";
|
||||
import useStartStopModel from "@/_hooks/useStartStopModel";
|
||||
import useDeleteModel from "@/_hooks/useDeleteModel";
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import { toGigabytes } from "@/_utils/converter";
|
||||
import React, { useCallback } from 'react'
|
||||
import { ModelStatus, ModelStatusComponent } from '../ModelStatusComponent'
|
||||
import ModelActionMenu from '../ModelActionMenu'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import ModelActionButton, { ModelActionType } from '../ModelActionButton'
|
||||
import useStartStopModel from '@/_hooks/useStartStopModel'
|
||||
import useDeleteModel from '@/_hooks/useDeleteModel'
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
|
||||
import { toGigabytes } from '@/_utils/converter'
|
||||
|
||||
type Props = {
|
||||
model: AssistantModel;
|
||||
};
|
||||
model: AssistantModel
|
||||
}
|
||||
|
||||
const ModelRow: React.FC<Props> = ({ model }) => {
|
||||
const { startModel, stopModel } = useStartStopModel();
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom);
|
||||
const { deleteModel } = useDeleteModel();
|
||||
const { startModel, stopModel } = useStartStopModel()
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
||||
const { deleteModel } = useDeleteModel()
|
||||
|
||||
let status = ModelStatus.Installed;
|
||||
let status = ModelStatus.Installed
|
||||
if (activeModel && activeModel._id === model._id) {
|
||||
status = ModelStatus.Active;
|
||||
status = ModelStatus.Active
|
||||
}
|
||||
|
||||
let actionButtonType = ModelActionType.Start;
|
||||
let actionButtonType = ModelActionType.Start
|
||||
if (activeModel && activeModel._id === model._id) {
|
||||
actionButtonType = ModelActionType.Stop;
|
||||
actionButtonType = ModelActionType.Stop
|
||||
}
|
||||
|
||||
const onModelActionClick = (action: ModelActionType) => {
|
||||
if (action === ModelActionType.Start) {
|
||||
startModel(model._id);
|
||||
startModel(model._id)
|
||||
} else {
|
||||
stopModel(model._id);
|
||||
stopModel(model._id)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const onDeleteClick = useCallback(() => {
|
||||
deleteModel(model);
|
||||
}, [model]);
|
||||
deleteModel(model)
|
||||
}, [model])
|
||||
|
||||
return (
|
||||
<tr className="border-b border-gray-200 last:border-b-0 last:rounded-lg">
|
||||
<tr className="border-b border-gray-200 last:rounded-lg last:border-b-0">
|
||||
<td className="flex flex-col whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900">
|
||||
{model.name}
|
||||
<span className="text-gray-500 font-normal">{model.version}</span>
|
||||
<span className="font-normal text-gray-500">{model.version}</span>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
||||
<div className="flex flex-col justify-start">
|
||||
@ -61,11 +61,11 @@ const ModelRow: React.FC<Props> = ({ model }) => {
|
||||
type={actionButtonType}
|
||||
onActionClick={onModelActionClick}
|
||||
/>
|
||||
<td className="relative whitespace-nowrap px-6 py-4 w-fit text-right text-sm font-medium">
|
||||
<td className="relative w-fit whitespace-nowrap px-6 py-4 text-right text-sm font-medium">
|
||||
<ModelActionMenu onDeleteClick={onDeleteClick} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelRow;
|
||||
export default ModelRow
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { searchingModelText } from "@/_helpers/JotaiWrapper";
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { searchingModelText } from '@/_helpers/JotaiWrapper'
|
||||
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const ModelSearchBar: React.FC = () => {
|
||||
const setSearchtext = useSetAtom(searchingModelText);
|
||||
const [text, setText] = useState("");
|
||||
const setSearchtext = useSetAtom(searchingModelText)
|
||||
const [text, setText] = useState('')
|
||||
useEffect(() => {
|
||||
setSearchtext(text);
|
||||
}, [text, setSearchtext]);
|
||||
setSearchtext(text)
|
||||
}, [text, setSearchtext])
|
||||
return (
|
||||
<div className="py-[27px] flex items-center justify-center">
|
||||
<div className="w-[520px] h-[42px] flex items-center">
|
||||
<div className="flex items-center justify-center py-[27px]">
|
||||
<div className="flex h-[42px] w-[520px] items-center">
|
||||
<input
|
||||
className="outline-none bg-gray-300 text-sm h-full rounded-tl-lg rounded-bl-lg leading-[17.5px] border border-gray-300 py-3 px-4 flex-1"
|
||||
className="h-full flex-1 rounded-bl-lg rounded-tl-lg border border-gray-300 bg-gray-300 px-4 py-3 text-sm leading-[17.5px] outline-none"
|
||||
placeholder="Search model"
|
||||
value={text}
|
||||
onChange={(text) => setText(text.currentTarget.value)}
|
||||
/>
|
||||
<button className="flex items-center justify-center bg-gray-800 border border-gray-800 p-2 w-[42px] rounded-tr-lg rounded-br-lg h-[42px]">
|
||||
<button className="flex h-[42px] w-[42px] items-center justify-center rounded-br-lg rounded-tr-lg border border-gray-800 bg-gray-800 p-2">
|
||||
<MagnifyingGlassIcon width={20} height={20} color="#FFFFFF" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelSearchBar;
|
||||
export default ModelSearchBar
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
import { Fragment, useEffect } from "react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { selectedModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import { downloadedModelAtom } from "@/_helpers/atoms/DownloadedModel.atom";
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
import { Fragment, useEffect } from 'react'
|
||||
import { Listbox, Transition } from '@headlessui/react'
|
||||
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'
|
||||
import { useAtom, useAtomValue } from 'jotai'
|
||||
import { selectedModelAtom } from '@/_helpers/atoms/Model.atom'
|
||||
import { downloadedModelAtom } from '@/_helpers/atoms/DownloadedModel.atom'
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
|
||||
function classNames(...classes: any) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
const SelectModels: React.FC = () => {
|
||||
const downloadedModels = useAtomValue(downloadedModelAtom);
|
||||
const [selectedModel, setSelectedModel] = useAtom(selectedModelAtom);
|
||||
const downloadedModels = useAtomValue(downloadedModelAtom)
|
||||
const [selectedModel, setSelectedModel] = useAtom(selectedModelAtom)
|
||||
|
||||
useEffect(() => {
|
||||
if (downloadedModels && downloadedModels.length > 0) {
|
||||
onModelSelected(downloadedModels[0]);
|
||||
onModelSelected(downloadedModels[0])
|
||||
}
|
||||
}, [downloadedModels]);
|
||||
}, [downloadedModels])
|
||||
|
||||
const onModelSelected = (model: AssistantModel) => {
|
||||
setSelectedModel(model);
|
||||
};
|
||||
setSelectedModel(model)
|
||||
}
|
||||
|
||||
if (!selectedModel) {
|
||||
return <div>You have not downloaded any model!</div>;
|
||||
return <div>You have not downloaded any model!</div>
|
||||
}
|
||||
|
||||
return (
|
||||
@ -68,8 +68,8 @@ const SelectModels: React.FC = () => {
|
||||
key={model._id}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
active ? "bg-blue-600 text-white" : "text-gray-900",
|
||||
"relative cursor-default select-none py-2 pl-3 pr-9",
|
||||
active ? 'bg-blue-600 text-white' : 'text-gray-900',
|
||||
'relative cursor-default select-none py-2 pl-3 pr-9'
|
||||
)
|
||||
}
|
||||
value={model}
|
||||
@ -84,8 +84,8 @@ const SelectModels: React.FC = () => {
|
||||
/>
|
||||
<span
|
||||
className={classNames(
|
||||
selected ? "font-semibold" : "font-normal",
|
||||
"ml-3 block truncate",
|
||||
selected ? 'font-semibold' : 'font-normal',
|
||||
'ml-3 block truncate'
|
||||
)}
|
||||
>
|
||||
{model.name}
|
||||
@ -95,8 +95,8 @@ const SelectModels: React.FC = () => {
|
||||
{selected ? (
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-blue-600",
|
||||
"absolute inset-y-0 right-0 flex items-center pr-4",
|
||||
active ? 'text-white' : 'text-blue-600',
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4'
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
||||
@ -112,7 +112,7 @@ const SelectModels: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</Listbox>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectModels;
|
||||
export default SelectModels
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import React from 'react'
|
||||
|
||||
export type ModelStatusType = {
|
||||
title: string;
|
||||
textColor: string;
|
||||
backgroundColor: string;
|
||||
};
|
||||
title: string
|
||||
textColor: string
|
||||
backgroundColor: string
|
||||
}
|
||||
|
||||
export enum ModelStatus {
|
||||
Installed,
|
||||
@ -14,33 +14,33 @@ export enum ModelStatus {
|
||||
|
||||
export const ModelStatusMapper: Record<ModelStatus, ModelStatusType> = {
|
||||
[ModelStatus.Installed]: {
|
||||
title: "Installed",
|
||||
textColor: "text-black",
|
||||
backgroundColor: "bg-gray-100",
|
||||
title: 'Installed',
|
||||
textColor: 'text-black',
|
||||
backgroundColor: 'bg-gray-100',
|
||||
},
|
||||
[ModelStatus.Active]: {
|
||||
title: "Active",
|
||||
textColor: "text-black",
|
||||
backgroundColor: "bg-green-100",
|
||||
title: 'Active',
|
||||
textColor: 'text-black',
|
||||
backgroundColor: 'bg-green-100',
|
||||
},
|
||||
[ModelStatus.RunningInNitro]: {
|
||||
title: "Running in Nitro",
|
||||
textColor: "text-black",
|
||||
backgroundColor: "bg-green-100",
|
||||
title: 'Running in Nitro',
|
||||
textColor: 'text-black',
|
||||
backgroundColor: 'bg-green-100',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type Props = {
|
||||
status: ModelStatus;
|
||||
};
|
||||
status: ModelStatus
|
||||
}
|
||||
|
||||
export const ModelStatusComponent: React.FC<Props> = ({ status }) => {
|
||||
const statusType = ModelStatusMapper[status];
|
||||
const statusType = ModelStatusMapper[status]
|
||||
return (
|
||||
<div
|
||||
className={`rounded-[10px] py-0.5 px-2.5 w-fit text-xs font-medium ${statusType.backgroundColor}`}
|
||||
className={`w-fit rounded-[10px] px-2.5 py-0.5 text-xs font-medium ${statusType.backgroundColor}`}
|
||||
>
|
||||
{statusType.title}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import React from "react";
|
||||
import ModelRow from "../ModelRow";
|
||||
import ModelTableHeader from "../ModelTableHeader";
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
import React from 'react'
|
||||
import ModelRow from '../ModelRow'
|
||||
import ModelTableHeader from '../ModelTableHeader'
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
|
||||
type Props = {
|
||||
models: AssistantModel[];
|
||||
};
|
||||
models: AssistantModel[]
|
||||
}
|
||||
|
||||
const tableHeaders = ["MODEL", "FORMAT", "SIZE", "STATUS", "ACTIONS"];
|
||||
const tableHeaders = ['MODEL', 'FORMAT', 'SIZE', 'STATUS', 'ACTIONS']
|
||||
|
||||
const ModelTable: React.FC<Props> = ({ models }) => (
|
||||
<div className="flow-root border rounded-lg border-gray-200 min-w-full align-middle shadow-lg">
|
||||
<div className="flow-root min-w-full rounded-lg border border-gray-200 align-middle shadow-lg">
|
||||
<table className="min-w-full">
|
||||
<thead className="bg-gray-50 border-b border-gray-200">
|
||||
<thead className="border-b border-gray-200 bg-gray-50">
|
||||
<tr className="rounded-t-lg">
|
||||
{tableHeaders.map((item) => (
|
||||
<ModelTableHeader key={item} title={item} />
|
||||
))}
|
||||
<th scope="col" className="relative px-6 py-3 w-fit">
|
||||
<th scope="col" className="relative w-fit px-6 py-3">
|
||||
<span className="sr-only">Edit</span>
|
||||
</th>
|
||||
</tr>
|
||||
@ -29,6 +29,6 @@ const ModelTable: React.FC<Props> = ({ models }) => (
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(ModelTable);
|
||||
export default React.memo(ModelTable)
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import React from "react";
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
};
|
||||
title: string
|
||||
}
|
||||
|
||||
const ModelTableHeader: React.FC<Props> = ({ title }) => (
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left first:rounded-tl-lg last:rounded-tr-lg text-xs font-medium uppercase tracking-wide text-gray-500"
|
||||
className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500 first:rounded-tl-lg last:rounded-tr-lg"
|
||||
>
|
||||
{title}
|
||||
</th>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(ModelTableHeader);
|
||||
export default React.memo(ModelTableHeader)
|
||||
|
||||
@ -1,70 +1,70 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter";
|
||||
import Image from "next/image";
|
||||
import { Product } from "@/_models/Product";
|
||||
import useDownloadModel from "@/_hooks/useDownloadModel";
|
||||
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
||||
import { atom, useAtomValue } from "jotai";
|
||||
import { ModelVersion } from "@/_models/ModelVersion";
|
||||
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import SimpleTag from "../SimpleTag";
|
||||
import { RamRequired, UsecaseTag } from "../SimpleTag/TagType";
|
||||
import React, { useMemo } from 'react'
|
||||
import { formatDownloadPercentage, toGigabytes } from '@/_utils/converter'
|
||||
import Image from 'next/image'
|
||||
import { Product } from '@/_models/Product'
|
||||
import useDownloadModel from '@/_hooks/useDownloadModel'
|
||||
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
|
||||
import { atom, useAtomValue } from 'jotai'
|
||||
import { ModelVersion } from '@/_models/ModelVersion'
|
||||
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
|
||||
import SimpleTag from '../SimpleTag'
|
||||
import { RamRequired, UsecaseTag } from '../SimpleTag/TagType'
|
||||
|
||||
type Props = {
|
||||
model: Product;
|
||||
modelVersion: ModelVersion;
|
||||
isRecommended: boolean;
|
||||
};
|
||||
model: Product
|
||||
modelVersion: ModelVersion
|
||||
isRecommended: boolean
|
||||
}
|
||||
|
||||
const ModelVersionItem: React.FC<Props> = ({
|
||||
model,
|
||||
modelVersion,
|
||||
isRecommended,
|
||||
}) => {
|
||||
const { downloadModel } = useDownloadModel();
|
||||
const { downloadedModels } = useGetDownloadedModels();
|
||||
const { downloadModel } = useDownloadModel()
|
||||
const { downloadedModels } = useGetDownloadedModels()
|
||||
const isDownloaded =
|
||||
downloadedModels.find((model) => model._id === modelVersion._id) != null;
|
||||
downloadedModels.find((model) => model._id === modelVersion._id) != null
|
||||
|
||||
const downloadAtom = useMemo(
|
||||
() => atom((get) => get(modelDownloadStateAtom)[modelVersion._id ?? ""]),
|
||||
() => atom((get) => get(modelDownloadStateAtom)[modelVersion._id ?? '']),
|
||||
[modelVersion._id]
|
||||
);
|
||||
const downloadState = useAtomValue(downloadAtom);
|
||||
)
|
||||
const downloadState = useAtomValue(downloadAtom)
|
||||
|
||||
const onDownloadClick = () => {
|
||||
downloadModel(model, modelVersion);
|
||||
};
|
||||
downloadModel(model, modelVersion)
|
||||
}
|
||||
|
||||
let downloadButton = (
|
||||
<button
|
||||
className="text-indigo-600 text-sm font-medium"
|
||||
className="text-sm font-medium text-indigo-600"
|
||||
onClick={onDownloadClick}
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
);
|
||||
)
|
||||
|
||||
if (downloadState) {
|
||||
downloadButton = (
|
||||
<div>{formatDownloadPercentage(downloadState.percent)}</div>
|
||||
);
|
||||
)
|
||||
} else if (isDownloaded) {
|
||||
downloadButton = <div>Downloaded</div>;
|
||||
downloadButton = <div>Downloaded</div>
|
||||
}
|
||||
|
||||
const { maxRamRequired, usecase } = modelVersion;
|
||||
const { maxRamRequired, usecase } = modelVersion
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center gap-4 pl-3 pt-3 pr-4 pb-3 border-t border-gray-200 first:border-t-0">
|
||||
<div className="flex items-center justify-between gap-4 border-t border-gray-200 pb-3 pl-3 pr-4 pt-3 first:border-t-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Image src={"/icons/app_icon.svg"} width={14} height={20} alt="" />
|
||||
<span className="font-sm text-gray-900 flex-1">
|
||||
<Image src={'/icons/app_icon.svg'} width={14} height={20} alt="" />
|
||||
<span className="font-sm flex-1 text-gray-900">
|
||||
{modelVersion.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex gap-2 justify-end">
|
||||
<div className="flex justify-end gap-2">
|
||||
<SimpleTag
|
||||
title={usecase}
|
||||
type={UsecaseTag.UsecaseDefault}
|
||||
@ -76,13 +76,13 @@ const ModelVersionItem: React.FC<Props> = ({
|
||||
clickable={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="px-2.5 py-0.5 bg-gray-200 text-xs font-medium rounded">
|
||||
<div className="rounded bg-gray-200 px-2.5 py-0.5 text-xs font-medium">
|
||||
{toGigabytes(modelVersion.size)}
|
||||
</div>
|
||||
{downloadButton}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelVersionItem;
|
||||
export default ModelVersionItem
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import ModelVersionItem from "../ModelVersionItem";
|
||||
import { Product } from "@/_models/Product";
|
||||
import { ModelVersion } from "@/_models/ModelVersion";
|
||||
import React from 'react'
|
||||
import ModelVersionItem from '../ModelVersionItem'
|
||||
import { Product } from '@/_models/Product'
|
||||
import { ModelVersion } from '@/_models/ModelVersion'
|
||||
|
||||
type Props = {
|
||||
model: Product;
|
||||
versions: ModelVersion[];
|
||||
recommendedVersion: string;
|
||||
};
|
||||
model: Product
|
||||
versions: ModelVersion[]
|
||||
recommendedVersion: string
|
||||
}
|
||||
|
||||
const ModelVersionList: React.FC<Props> = ({
|
||||
model,
|
||||
@ -15,11 +15,11 @@ const ModelVersionList: React.FC<Props> = ({
|
||||
recommendedVersion,
|
||||
}) => {
|
||||
return (
|
||||
<div className="px-4 py-5 border-t border-gray-200">
|
||||
<div className="border-t border-gray-200 px-4 py-5">
|
||||
<div className="text-sm font-medium text-gray-500">
|
||||
Available Versions
|
||||
</div>
|
||||
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
||||
<div className="overflow-hidden rounded-lg border border-gray-200">
|
||||
{versions.map((item) => (
|
||||
<ModelVersionItem
|
||||
key={item._id}
|
||||
@ -30,7 +30,7 @@ const ModelVersionList: React.FC<Props> = ({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelVersionList;
|
||||
export default ModelVersionList
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
import ProgressBar from "../ProgressBar";
|
||||
import SystemItem from "../SystemItem";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { appDownloadProgress } from "@/_helpers/JotaiWrapper";
|
||||
import useGetAppVersion from "@/_hooks/useGetAppVersion";
|
||||
import useGetSystemResources from "@/_hooks/useGetSystemResources";
|
||||
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
||||
import { DownloadState } from "@/_models/DownloadState";
|
||||
import { formatDownloadPercentage } from "@/_utils/converter";
|
||||
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import ProgressBar from '../ProgressBar'
|
||||
import SystemItem from '../SystemItem'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { appDownloadProgress } from '@/_helpers/JotaiWrapper'
|
||||
import useGetAppVersion from '@/_hooks/useGetAppVersion'
|
||||
import useGetSystemResources from '@/_hooks/useGetSystemResources'
|
||||
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
|
||||
import { DownloadState } from '@/_models/DownloadState'
|
||||
import { formatDownloadPercentage } from '@/_utils/converter'
|
||||
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
|
||||
|
||||
const MonitorBar: React.FC = () => {
|
||||
const progress = useAtomValue(appDownloadProgress);
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom);
|
||||
const { version } = useGetAppVersion();
|
||||
const { ram, cpu } = useGetSystemResources();
|
||||
const modelDownloadStates = useAtomValue(modelDownloadStateAtom);
|
||||
const progress = useAtomValue(appDownloadProgress)
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
||||
const { version } = useGetAppVersion()
|
||||
const { ram, cpu } = useGetSystemResources()
|
||||
const modelDownloadStates = useAtomValue(modelDownloadStateAtom)
|
||||
|
||||
const downloadStates: DownloadState[] = [];
|
||||
const downloadStates: DownloadState[] = []
|
||||
for (const [, value] of Object.entries(modelDownloadStates)) {
|
||||
downloadStates.push(value);
|
||||
downloadStates.push(value)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -26,7 +26,7 @@ const MonitorBar: React.FC = () => {
|
||||
{progress && progress >= 0 ? (
|
||||
<ProgressBar total={100} used={progress} />
|
||||
) : null}
|
||||
<div className="flex-1 justify-end flex items-center gap-8 px-2">
|
||||
<div className="flex flex-1 items-center justify-end gap-8 px-2">
|
||||
{downloadStates.length > 0 && (
|
||||
<SystemItem
|
||||
name="Downloading"
|
||||
@ -38,12 +38,12 @@ const MonitorBar: React.FC = () => {
|
||||
<SystemItem name="CPU" value={`${cpu}%`} />
|
||||
<SystemItem name="Mem" value={`${ram}%`} />
|
||||
{activeModel && (
|
||||
<SystemItem name={`Active model: ${activeModel.name}`} value={""} />
|
||||
<SystemItem name={`Active model: ${activeModel.name}`} value={''} />
|
||||
)}
|
||||
<span className="text-gray-900 text-sm">v{version}</span>
|
||||
<span className="text-sm text-gray-900">v{version}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default MonitorBar;
|
||||
export default MonitorBar
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import HeaderTitle from "../HeaderTitle";
|
||||
import DownloadedModelTable from "../DownloadedModelTable";
|
||||
import ActiveModelTable from "../ActiveModelTable";
|
||||
import DownloadingModelTable from "../DownloadingModelTable";
|
||||
|
||||
const MyModelContainer: React.FC = () => (
|
||||
<div className="flex flex-col flex-1 pt-[60px]">
|
||||
<HeaderTitle title="My Models" className="pl-[63px] pr-[89px]" />
|
||||
<div className="pb-6 overflow-y-auto scroll">
|
||||
<ActiveModelTable />
|
||||
<DownloadingModelTable />
|
||||
<DownloadedModelTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MyModelContainer;
|
||||
import HeaderTitle from '../HeaderTitle'
|
||||
import DownloadedModelTable from '../DownloadedModelTable'
|
||||
import ActiveModelTable from '../ActiveModelTable'
|
||||
import DownloadingModelTable from '../DownloadingModelTable'
|
||||
|
||||
const MyModelContainer: React.FC = () => (
|
||||
<div className="flex flex-1 flex-col pt-[60px]">
|
||||
<HeaderTitle title="My Models" className="pl-[63px] pr-[89px]" />
|
||||
<div className="scroll overflow-y-auto pb-6">
|
||||
<ActiveModelTable />
|
||||
<DownloadingModelTable />
|
||||
<DownloadedModelTable />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default MyModelContainer
|
||||
|
||||
@ -1,45 +1,45 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import React from "react";
|
||||
import SecondaryButton from "../SecondaryButton";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import React from 'react'
|
||||
import SecondaryButton from '../SecondaryButton'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import {
|
||||
MainViewState,
|
||||
setMainViewStateAtom,
|
||||
} from "@/_helpers/atoms/MainView.atom";
|
||||
import useCreateConversation from "@/_hooks/useCreateConversation";
|
||||
import useInitModel from "@/_hooks/useInitModel";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
} from '@/_helpers/atoms/MainView.atom'
|
||||
import useCreateConversation from '@/_hooks/useCreateConversation'
|
||||
import useInitModel from '@/_hooks/useInitModel'
|
||||
import { PlusIcon } from '@heroicons/react/24/outline'
|
||||
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
|
||||
const NewChatButton: React.FC = () => {
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom);
|
||||
const setMainView = useSetAtom(setMainViewStateAtom);
|
||||
const { requestCreateConvo } = useCreateConversation();
|
||||
const { initModel } = useInitModel();
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
||||
const setMainView = useSetAtom(setMainViewStateAtom)
|
||||
const { requestCreateConvo } = useCreateConversation()
|
||||
const { initModel } = useInitModel()
|
||||
|
||||
const onClick = () => {
|
||||
if (!activeModel) {
|
||||
setMainView(MainViewState.ConversationEmptyModel);
|
||||
setMainView(MainViewState.ConversationEmptyModel)
|
||||
} else {
|
||||
createConversationAndInitModel(activeModel);
|
||||
createConversationAndInitModel(activeModel)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const createConversationAndInitModel = async (model: AssistantModel) => {
|
||||
await requestCreateConvo(model);
|
||||
await initModel(model);
|
||||
};
|
||||
await requestCreateConvo(model)
|
||||
await initModel(model)
|
||||
}
|
||||
|
||||
return (
|
||||
<SecondaryButton
|
||||
title={"New Chat"}
|
||||
title={'New Chat'}
|
||||
onClick={onClick}
|
||||
className="my-5 mx-3"
|
||||
className="mx-3 my-5"
|
||||
icon={<PlusIcon width={16} height={16} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(NewChatButton);
|
||||
export default React.memo(NewChatButton)
|
||||
|
||||
@ -1,25 +1,32 @@
|
||||
"use client";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { plugins, extensionPoints } from "@/../../electron/core/plugin-manager/execution/index";
|
||||
import { ChartPieIcon, CommandLineIcon, PlayIcon } from "@heroicons/react/24/outline";
|
||||
'use client'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
plugins,
|
||||
extensionPoints,
|
||||
} from '@/../../electron/core/plugin-manager/execution/index'
|
||||
import {
|
||||
ChartPieIcon,
|
||||
CommandLineIcon,
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
|
||||
import classNames from "classnames";
|
||||
import { PluginService, preferences } from "@janhq/core";
|
||||
import { execute } from "../../../electron/core/plugin-manager/execution/extension-manager";
|
||||
import LoadingIndicator from "./LoadingIndicator";
|
||||
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'
|
||||
import classNames from 'classnames'
|
||||
import { PluginService, preferences } from '@janhq/core'
|
||||
import { execute } from '../../../electron/core/plugin-manager/execution/extension-manager'
|
||||
import LoadingIndicator from './LoadingIndicator'
|
||||
|
||||
export const Preferences = () => {
|
||||
const [search, setSearch] = useState<string>("");
|
||||
const [activePlugins, setActivePlugins] = useState<any[]>([]);
|
||||
const [preferenceItems, setPreferenceItems] = useState<any[]>([]);
|
||||
const [preferenceValues, setPreferenceValues] = useState<any[]>([]);
|
||||
const [isTestAvailable, setIsTestAvailable] = useState(false);
|
||||
const [fileName, setFileName] = useState("");
|
||||
const [pluginCatalog, setPluginCatalog] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const experimentRef = useRef(null);
|
||||
const preferenceRef = useRef(null);
|
||||
const [search, setSearch] = useState<string>('')
|
||||
const [activePlugins, setActivePlugins] = useState<any[]>([])
|
||||
const [preferenceItems, setPreferenceItems] = useState<any[]>([])
|
||||
const [preferenceValues, setPreferenceValues] = useState<any[]>([])
|
||||
const [isTestAvailable, setIsTestAvailable] = useState(false)
|
||||
const [fileName, setFileName] = useState('')
|
||||
const [pluginCatalog, setPluginCatalog] = useState<any[]>([])
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const experimentRef = useRef(null)
|
||||
const preferenceRef = useRef(null)
|
||||
|
||||
/**
|
||||
* Loads the plugin catalog module from a CDN and sets it as the plugin catalog state.
|
||||
@ -28,10 +35,10 @@ export const Preferences = () => {
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
import(/* webpackIgnore: true */ PLUGIN_CATALOGS).then((module) => {
|
||||
console.log(module);
|
||||
setPluginCatalog(module.default);
|
||||
});
|
||||
}, []);
|
||||
console.log(module)
|
||||
setPluginCatalog(module.default)
|
||||
})
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Fetches the active plugins and their preferences from the `plugins` and `preferences` modules.
|
||||
@ -42,36 +49,42 @@ export const Preferences = () => {
|
||||
*/
|
||||
useEffect(() => {
|
||||
const getActivePlugins = async () => {
|
||||
const plgs = await plugins.getActive();
|
||||
setActivePlugins(plgs);
|
||||
const plgs = await plugins.getActive()
|
||||
setActivePlugins(plgs)
|
||||
|
||||
if (extensionPoints.get("experimentComponent")) {
|
||||
const components = await Promise.all(extensionPoints.execute("experimentComponent"));
|
||||
if (extensionPoints.get('experimentComponent')) {
|
||||
const components = await Promise.all(
|
||||
extensionPoints.execute('experimentComponent')
|
||||
)
|
||||
if (components.length > 0) {
|
||||
setIsTestAvailable(true);
|
||||
setIsTestAvailable(true)
|
||||
}
|
||||
components.forEach((e) => {
|
||||
if (experimentRef.current) {
|
||||
// @ts-ignore
|
||||
experimentRef.current.appendChild(e);
|
||||
experimentRef.current.appendChild(e)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if (extensionPoints.get("PluginPreferences")) {
|
||||
const data = await Promise.all(extensionPoints.execute("PluginPreferences"));
|
||||
setPreferenceItems(Array.isArray(data) ? data : []);
|
||||
if (extensionPoints.get('PluginPreferences')) {
|
||||
const data = await Promise.all(
|
||||
extensionPoints.execute('PluginPreferences')
|
||||
)
|
||||
setPreferenceItems(Array.isArray(data) ? data : [])
|
||||
Promise.all(
|
||||
(Array.isArray(data) ? data : []).map((e) =>
|
||||
preferences.get(e.pluginName, e.preferenceKey).then((k) => ({ key: e.preferenceKey, value: k }))
|
||||
preferences
|
||||
.get(e.pluginName, e.preferenceKey)
|
||||
.then((k) => ({ key: e.preferenceKey, value: k }))
|
||||
)
|
||||
).then((data) => {
|
||||
setPreferenceValues(data);
|
||||
});
|
||||
setPreferenceValues(data)
|
||||
})
|
||||
}
|
||||
};
|
||||
getActivePlugins();
|
||||
}, []);
|
||||
}
|
||||
getActivePlugins()
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Installs a plugin by calling the `plugins.install` function with the plugin file path.
|
||||
@ -79,15 +92,15 @@ export const Preferences = () => {
|
||||
* @param e - The event object.
|
||||
*/
|
||||
const install = async (e: any) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
//@ts-ignore
|
||||
const pluginFile = new FormData(e.target).get("plugin-file").path;
|
||||
const pluginFile = new FormData(e.target).get('plugin-file').path
|
||||
|
||||
// Send the filename of the to be installed plugin
|
||||
// to the main process for installation
|
||||
const installed = await plugins.install([pluginFile]);
|
||||
if (installed) window.coreAPI?.relaunch();
|
||||
};
|
||||
const installed = await plugins.install([pluginFile])
|
||||
if (installed) window.coreAPI?.relaunch()
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls a plugin by calling the `plugins.uninstall` function with the plugin name.
|
||||
@ -97,9 +110,9 @@ export const Preferences = () => {
|
||||
const uninstall = async (name: string) => {
|
||||
// Send the filename of the to be uninstalled plugin
|
||||
// to the main process for removal
|
||||
const res = await plugins.uninstall([name]);
|
||||
if (res) window.coreAPI?.relaunch();
|
||||
};
|
||||
const res = await plugins.uninstall([name])
|
||||
if (res) window.coreAPI?.relaunch()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a plugin by calling the `window.pluggableElectronIpc.update` function with the plugin name.
|
||||
@ -108,12 +121,12 @@ export const Preferences = () => {
|
||||
* @param plugin - The name of the plugin to update.
|
||||
*/
|
||||
const update = async (plugin: string) => {
|
||||
if (typeof window !== "undefined") {
|
||||
if (typeof window !== 'undefined') {
|
||||
// @ts-ignore
|
||||
await window.pluggableElectronIpc.update([plugin], true);
|
||||
window.coreAPI?.relaunch();
|
||||
await window.pluggableElectronIpc.update([plugin], true)
|
||||
window.coreAPI?.relaunch()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a remote plugin tarball and installs it using the `plugins.install` function.
|
||||
@ -121,22 +134,22 @@ export const Preferences = () => {
|
||||
* @param pluginName - The name of the remote plugin to download and install.
|
||||
*/
|
||||
const downloadTarball = async (pluginName: string) => {
|
||||
setIsLoading(true);
|
||||
const pluginPath = await window.coreAPI?.installRemotePlugin(pluginName);
|
||||
const installed = await plugins.install([pluginPath]);
|
||||
setIsLoading(false);
|
||||
if (installed) window.coreAPI.relaunch();
|
||||
};
|
||||
setIsLoading(true)
|
||||
const pluginPath = await window.coreAPI?.installRemotePlugin(pluginName)
|
||||
const installed = await plugins.install([pluginPath])
|
||||
setIsLoading(false)
|
||||
if (installed) window.coreAPI.relaunch()
|
||||
}
|
||||
/**
|
||||
* Notifies plugins of a preference update by executing the `PluginService.OnPreferencesUpdate` event.
|
||||
* If a timeout is already set, it is cleared before setting a new timeout to execute the event.
|
||||
*/
|
||||
let timeout: any | undefined = undefined;
|
||||
let timeout: any | undefined = undefined
|
||||
function notifyPreferenceUpdate() {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
timeout = setTimeout(() => execute(PluginService.OnPreferencesUpdate), 100);
|
||||
timeout = setTimeout(() => execute(PluginService.OnPreferencesUpdate), 100)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,21 +158,21 @@ export const Preferences = () => {
|
||||
* @param event - The change event object.
|
||||
*/
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
const file = event.target.files?.[0]
|
||||
if (file) {
|
||||
setFileName(file.name);
|
||||
setFileName(file.name)
|
||||
} else {
|
||||
setFileName("");
|
||||
setFileName('')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen overflow-scroll">
|
||||
<div className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 bg-white shadow-sm sm:gap-x-6 sm:px-6 px-8">
|
||||
<div className="h-screen w-full overflow-scroll">
|
||||
<div className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 bg-white px-8 shadow-sm sm:gap-x-6 sm:px-6">
|
||||
{/* Separator */}
|
||||
<div className="h-6 w-px bg-gray-900/10 hidden" aria-hidden="true" />
|
||||
<div className="hidden h-6 w-px bg-gray-900/10" aria-hidden="true" />
|
||||
|
||||
<div className="flex flex-1 self-stretch gap-x-6">
|
||||
<div className="flex flex-1 gap-x-6 self-stretch">
|
||||
<form className="relative flex flex-1" action="#" method="GET">
|
||||
<label htmlFor="search-field" className="sr-only">
|
||||
Search
|
||||
@ -182,22 +195,25 @@ export const Preferences = () => {
|
||||
</div>
|
||||
|
||||
<main className="py-5">
|
||||
<div className="sm:px-6 px-8">
|
||||
<div className="px-8 sm:px-6">
|
||||
{/* Content */}
|
||||
<div className="flex flex-row items-center my-4">
|
||||
<div className="my-4 flex flex-row items-center">
|
||||
<ChartPieIcon width={30} />
|
||||
Install Plugin
|
||||
</div>
|
||||
<form id="plugin-file" onSubmit={install}>
|
||||
<div className="flex flex-row items-center space-x-10">
|
||||
<div className="flex items-center justify-center w-[300px]">
|
||||
<label className="h-[120px] flex flex-col items-center justify-center w-full border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
|
||||
<div className="flex w-[300px] items-center justify-center">
|
||||
<label className="dark:hover:bg-bray-800 flex h-[120px] w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-gray-500 dark:hover:bg-gray-600">
|
||||
{!fileName ? (
|
||||
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
||||
<div className="flex flex-col items-center justify-center pb-6 pt-5">
|
||||
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span className="font-semibold">Click to upload</span> or drag and drop
|
||||
<span className="font-semibold">Click to upload</span>{' '}
|
||||
or drag and drop
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
TGZ (MAX 50MB)
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">TGZ (MAX 50MB)</p>
|
||||
</div>
|
||||
) : (
|
||||
<>{fileName}</>
|
||||
@ -216,8 +232,8 @@ export const Preferences = () => {
|
||||
<button
|
||||
type="submit"
|
||||
className={classNames(
|
||||
"rounded-md px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600",
|
||||
fileName ? "bg-blue-500 hover:bg-blue-300" : "bg-gray-500"
|
||||
'rounded-md px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600',
|
||||
fileName ? 'bg-blue-500 hover:bg-blue-300' : 'bg-gray-500'
|
||||
)}
|
||||
>
|
||||
Install Plugin
|
||||
@ -225,10 +241,10 @@ export const Preferences = () => {
|
||||
|
||||
<button
|
||||
className={classNames(
|
||||
"bg-blue-500 hover:bg-blue-300 rounded-md px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||
'rounded-md bg-blue-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600'
|
||||
)}
|
||||
onClick={() => {
|
||||
window.coreAPI?.reloadPlugins();
|
||||
window.coreAPI?.reloadPlugins()
|
||||
}}
|
||||
>
|
||||
Reload Plugins
|
||||
@ -237,37 +253,49 @@ export const Preferences = () => {
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="flex flex-row items-center my-4">
|
||||
<div className="my-4 flex flex-row items-center">
|
||||
<CommandLineIcon width={30} />
|
||||
Installed Plugins
|
||||
</div>
|
||||
<div className="grid grid-cols-2 items-stretch gap-4">
|
||||
{activePlugins
|
||||
.filter((e) => search.trim() === "" || e.name.toLowerCase().includes(search.toLowerCase()))
|
||||
.filter(
|
||||
(e) =>
|
||||
search.trim() === '' ||
|
||||
e.name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
.map((e) => (
|
||||
<div
|
||||
key={e.name}
|
||||
data-testid="plugin-item"
|
||||
className="flex flex-col h-full p-6 bg-white border border-gray-200 rounded-sm dark:border-gray-300"
|
||||
className="flex h-full flex-col rounded-sm border border-gray-200 bg-white p-6 dark:border-gray-300"
|
||||
>
|
||||
<div className="flex flex-row space-x-2 items-center">
|
||||
<span className="relative inline-block mt-1">
|
||||
<img className="h-14 w-14 rounded-md" src={e.icon ?? "icons/app_icon.svg"} alt="" />
|
||||
<div className="flex flex-row items-center space-x-2">
|
||||
<span className="relative mt-1 inline-block">
|
||||
<img
|
||||
className="h-14 w-14 rounded-md"
|
||||
src={e.icon ?? 'icons/app_icon.svg'}
|
||||
alt=""
|
||||
/>
|
||||
</span>
|
||||
<div className="flex flex-col">
|
||||
<p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white">{e.name}</p>
|
||||
<p className="font-normal text-gray-700 dark:text-gray-400">Version: {e.version}</p>
|
||||
<p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||
{e.name}
|
||||
</p>
|
||||
<p className="font-normal text-gray-700 dark:text-gray-400">
|
||||
Version: {e.version}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="flex-1 mt-2 text-sm font-normal text-gray-500 dark:text-gray-400 w-full">
|
||||
<p className="mt-2 w-full flex-1 text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||
{e.description ?? "Jan's Plugin"}
|
||||
</p>
|
||||
<div className="flex flex-row space-x-5">
|
||||
<button
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
uninstall(e.name);
|
||||
uninstall(e.name)
|
||||
}}
|
||||
className="mt-5 rounded-md bg-red-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
|
||||
>
|
||||
@ -276,7 +304,7 @@ export const Preferences = () => {
|
||||
<button
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
update(e.name);
|
||||
update(e.name)
|
||||
}}
|
||||
className="mt-5 rounded-md bg-blue-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||
>
|
||||
@ -287,46 +315,62 @@ export const Preferences = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center my-4">
|
||||
<div className="my-4 flex flex-row items-center">
|
||||
<CommandLineIcon width={30} />
|
||||
Explore Plugins
|
||||
</div>
|
||||
<div className="grid grid-cols-2 items-stretch gap-4">
|
||||
{pluginCatalog
|
||||
.filter((e: any) => search.trim() === "" || e.name.toLowerCase().includes(search.toLowerCase()))
|
||||
.filter(
|
||||
(e: any) =>
|
||||
search.trim() === '' ||
|
||||
e.name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
.map((e: any) => (
|
||||
<div
|
||||
key={e.name}
|
||||
data-testid="plugin-item"
|
||||
className="flex flex-col h-full p-6 bg-white border border-gray-200 rounded-sm dark:border-gray-300"
|
||||
className="flex h-full flex-col rounded-sm border border-gray-200 bg-white p-6 dark:border-gray-300"
|
||||
>
|
||||
<div className="flex flex-row space-x-2 items-center">
|
||||
<span className="relative inline-block mt-1">
|
||||
<img className="h-14 w-14 rounded-md" src={e.icon ?? "icons/app_icon.svg"} alt="" />
|
||||
<div className="flex flex-row items-center space-x-2">
|
||||
<span className="relative mt-1 inline-block">
|
||||
<img
|
||||
className="h-14 w-14 rounded-md"
|
||||
src={e.icon ?? 'icons/app_icon.svg'}
|
||||
alt=""
|
||||
/>
|
||||
</span>
|
||||
<div className="flex flex-col">
|
||||
<p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white">{e.name}</p>
|
||||
<p className="font-normal text-gray-700 dark:text-gray-400">Version: {e.version}</p>
|
||||
<p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||
{e.name}
|
||||
</p>
|
||||
<p className="font-normal text-gray-700 dark:text-gray-400">
|
||||
Version: {e.version}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="flex-1 mt-2 text-sm font-normal text-gray-500 dark:text-gray-400 w-full">
|
||||
<p className="mt-2 w-full flex-1 text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||
{e.description ?? "Jan's Plugin"}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-row space-x-5">
|
||||
{e.version !== activePlugins.filter((p) => p.name === e.name)[0]?.version && (
|
||||
{e.version !==
|
||||
activePlugins.filter((p) => p.name === e.name)[0]
|
||||
?.version && (
|
||||
<button
|
||||
type="submit"
|
||||
onClick={() => downloadTarball(e.name)}
|
||||
className={classNames(
|
||||
"mt-5 rounded-md px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600",
|
||||
'mt-5 rounded-md px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600',
|
||||
activePlugins.some((p) => p.name === e.name)
|
||||
? "bg-blue-500 hover:bg-blue-600"
|
||||
: "bg-red-500 hover:bg-red-600"
|
||||
? 'bg-blue-500 hover:bg-blue-600'
|
||||
: 'bg-red-500 hover:bg-red-600'
|
||||
)}
|
||||
>
|
||||
{activePlugins.some((p) => p.name === e.name) ? "Update" : "Install"}
|
||||
{activePlugins.some((p) => p.name === e.name)
|
||||
? 'Update'
|
||||
: 'Install'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@ -334,33 +378,41 @@ export const Preferences = () => {
|
||||
))}
|
||||
</div>
|
||||
{activePlugins.length > 0 && isTestAvailable && (
|
||||
<div className="flex flex-row items-center my-4">
|
||||
<div className="my-4 flex flex-row items-center">
|
||||
<PlayIcon width={30} />
|
||||
Test Plugins
|
||||
</div>
|
||||
)}
|
||||
<div className="h-full w-full" ref={experimentRef}></div>
|
||||
|
||||
<div className="flex flex-row items-center my-4">
|
||||
<div className="my-4 flex flex-row items-center">
|
||||
<PlayIcon width={30} />
|
||||
Preferences
|
||||
</div>
|
||||
<div className="h-full w-full flex flex-col" ref={preferenceRef}>
|
||||
<div className="flex h-full w-full flex-col" ref={preferenceRef}>
|
||||
{preferenceItems?.map((e) => (
|
||||
<div key={e.preferenceKey} className="flex flex-col mb-4">
|
||||
<div key={e.preferenceKey} className="mb-4 flex flex-col">
|
||||
<div>
|
||||
<span className="text-[16px] text-gray-600">Setting:</span>{" "}
|
||||
<span className="text-[16px] text-gray-900">{e.preferenceName}</span>
|
||||
<span className="text-[16px] text-gray-600">Setting:</span>{' '}
|
||||
<span className="text-[16px] text-gray-900">
|
||||
{e.preferenceName}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[14px] text-gray-400">{e.preferenceDescription}</span>
|
||||
<div className="flex flex-row space-x-4 items-center mt-2">
|
||||
<span className="text-[14px] text-gray-400">
|
||||
{e.preferenceDescription}
|
||||
</span>
|
||||
<div className="mt-2 flex flex-row items-center space-x-4">
|
||||
<input
|
||||
className="text-gray-500 w-1/3 rounded-sm border-gray-300 border-[1px] h-8"
|
||||
defaultValue={preferenceValues.filter((v) => v.key === e.preferenceKey)[0]?.value}
|
||||
className="h-8 w-1/3 rounded-sm border-[1px] border-gray-300 text-gray-500"
|
||||
defaultValue={
|
||||
preferenceValues.filter(
|
||||
(v) => v.key === e.preferenceKey
|
||||
)[0]?.value
|
||||
}
|
||||
onChange={(event) => {
|
||||
preferences
|
||||
.set(e.pluginName, e.preferenceKey, event.target.value)
|
||||
.then(() => notifyPreferenceUpdate());
|
||||
.then(() => notifyPreferenceUpdate())
|
||||
}}
|
||||
></input>
|
||||
</div>
|
||||
@ -370,11 +422,11 @@ export const Preferences = () => {
|
||||
</div>
|
||||
</main>
|
||||
{isLoading && (
|
||||
<div className="z-50 absolute inset-0 bg-gray-900/90 flex justify-center items-center text-white">
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-gray-900/90 text-white">
|
||||
<LoadingIndicator />
|
||||
Installing...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
fullWidth?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
title: string
|
||||
onClick: () => void
|
||||
fullWidth?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const PrimaryButton: React.FC<Props> = ({
|
||||
title,
|
||||
@ -16,12 +16,12 @@ const PrimaryButton: React.FC<Props> = ({
|
||||
<button
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
className={`rounded-md bg-blue-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-50 line-clamp-1 flex-shrink-0 ${className} ${
|
||||
fullWidth ? "flex-1 " : ""
|
||||
className={`line-clamp-1 flex-shrink-0 rounded-md bg-blue-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-50 ${className} ${
|
||||
fullWidth ? 'flex-1 ' : ''
|
||||
}}`}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
);
|
||||
)
|
||||
|
||||
export default PrimaryButton;
|
||||
export default PrimaryButton
|
||||
|
||||
@ -1,26 +1,26 @@
|
||||
import Image from "next/image";
|
||||
|
||||
type Props = {
|
||||
total: number;
|
||||
used: number;
|
||||
};
|
||||
|
||||
const ProgressBar: React.FC<Props> = ({ used, total }) => (
|
||||
<div className="flex gap-2.5 items-center p-[10px]">
|
||||
<div className="text-xs leading-[18px] gap-0.5 flex items-center">
|
||||
<Image src={"icons/app_icon.svg"} width={18} height={18} alt="" />
|
||||
Updating
|
||||
</div>
|
||||
<div className="w-[150px] relative bg-blue-200 h-1 rounded-md flex">
|
||||
<div
|
||||
className="absolute top-0 left-0 h-full rounded-md bg-blue-600"
|
||||
style={{ width: `${((used / total) * 100).toFixed(2)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs leading-[18px]">
|
||||
{((used / total) * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ProgressBar;
|
||||
import Image from 'next/image'
|
||||
|
||||
type Props = {
|
||||
total: number
|
||||
used: number
|
||||
}
|
||||
|
||||
const ProgressBar: React.FC<Props> = ({ used, total }) => (
|
||||
<div className="flex items-center gap-2.5 p-[10px]">
|
||||
<div className="flex items-center gap-0.5 text-xs leading-[18px]">
|
||||
<Image src={'icons/app_icon.svg'} width={18} height={18} alt="" />
|
||||
Updating
|
||||
</div>
|
||||
<div className="relative flex h-1 w-[150px] rounded-md bg-blue-200">
|
||||
<div
|
||||
className="absolute left-0 top-0 h-full rounded-md bg-blue-600"
|
||||
style={{ width: `${((used / total) * 100).toFixed(2)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs leading-[18px]">
|
||||
{((used / total) * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default ProgressBar
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Fragment } from "react";
|
||||
import MainView from "../MainView";
|
||||
import MonitorBar from "../MonitorBar";
|
||||
import { Fragment } from 'react'
|
||||
import MainView from '../MainView'
|
||||
import MonitorBar from '../MonitorBar'
|
||||
|
||||
const RightContainer = () => (
|
||||
<Fragment>
|
||||
<MainView />
|
||||
<MonitorBar />
|
||||
</Fragment>
|
||||
);
|
||||
)
|
||||
|
||||
export default RightContainer;
|
||||
export default RightContainer
|
||||
|
||||
@ -1,45 +1,45 @@
|
||||
import { modelSearchAtom } from "@/_helpers/JotaiWrapper";
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
export enum SearchType {
|
||||
Model = "model",
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type?: SearchType;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
const SearchBar: React.FC<Props> = ({ type, placeholder }) => {
|
||||
const setModelSearch = useSetAtom(modelSearchAtom);
|
||||
let placeholderText = placeholder ? placeholder : "Search (⌘K)";
|
||||
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
setModelSearch(value);
|
||||
}, 300);
|
||||
|
||||
return (
|
||||
<div className="relative mt-3 flex items-center">
|
||||
<div className="absolute top-0 left-2 h-full flex items-center">
|
||||
<MagnifyingGlassIcon
|
||||
width={16}
|
||||
height={16}
|
||||
color="#3C3C43"
|
||||
opacity={0.6}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
id="search"
|
||||
placeholder={placeholderText}
|
||||
onChange={(e) => debounced(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"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchBar;
|
||||
import { modelSearchAtom } from '@/_helpers/JotaiWrapper'
|
||||
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
|
||||
export enum SearchType {
|
||||
Model = 'model',
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type?: SearchType
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
const SearchBar: React.FC<Props> = ({ type, placeholder }) => {
|
||||
const setModelSearch = useSetAtom(modelSearchAtom)
|
||||
let placeholderText = placeholder ? placeholder : 'Search (⌘K)'
|
||||
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
setModelSearch(value)
|
||||
}, 300)
|
||||
|
||||
return (
|
||||
<div className="relative mt-3 flex items-center">
|
||||
<div className="absolute left-2 top-0 flex h-full items-center">
|
||||
<MagnifyingGlassIcon
|
||||
width={16}
|
||||
height={16}
|
||||
color="#3C3C43"
|
||||
opacity={0.6}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
id="search"
|
||||
placeholder={placeholderText}
|
||||
onChange={(e) => debounced(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"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchBar
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
title: string
|
||||
onClick?: () => void
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
icon?: React.ReactNode
|
||||
}
|
||||
|
||||
const SecondaryButton: React.FC<Props> = ({
|
||||
title,
|
||||
@ -19,11 +19,11 @@ const SecondaryButton: React.FC<Props> = ({
|
||||
disabled={disabled}
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`flex items-center justify-center gap-1 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 ${className} flex-shrink-0 line-clamp-1`}
|
||||
className={`flex items-center justify-center gap-1 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 ${className} line-clamp-1 flex-shrink-0`}
|
||||
>
|
||||
{icon}
|
||||
{title}
|
||||
</button>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(SecondaryButton);
|
||||
export default React.memo(SecondaryButton)
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
|
||||
import { currentConvoStateAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
import useSendChatMessage from "@/_hooks/useSendChatMessage";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { currentPromptAtom } from '@/_helpers/JotaiWrapper'
|
||||
import { currentConvoStateAtom } from '@/_helpers/atoms/Conversation.atom'
|
||||
import useSendChatMessage from '@/_hooks/useSendChatMessage'
|
||||
import { useAtom, useAtomValue } from 'jotai'
|
||||
|
||||
const SendButton: React.FC = () => {
|
||||
const [currentPrompt] = useAtom(currentPromptAtom);
|
||||
const currentConvoState = useAtomValue(currentConvoStateAtom);
|
||||
const [currentPrompt] = useAtom(currentPromptAtom)
|
||||
const currentConvoState = useAtomValue(currentConvoStateAtom)
|
||||
|
||||
const { sendChatMessage } = useSendChatMessage();
|
||||
const isWaitingForResponse = currentConvoState?.waitingForResponse ?? false;
|
||||
const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse;
|
||||
const { sendChatMessage } = useSendChatMessage()
|
||||
const isWaitingForResponse = currentConvoState?.waitingForResponse ?? false
|
||||
const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse
|
||||
|
||||
const disabledStyle = {
|
||||
backgroundColor: "#F3F4F6",
|
||||
};
|
||||
backgroundColor: '#F3F4F6',
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
@ -24,7 +24,7 @@ const SendButton: React.FC = () => {
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SendButton;
|
||||
export default SendButton
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
import React, { ReactNode } from "react";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
'use client'
|
||||
import React, { ReactNode } from 'react'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
|
||||
const SessionProviderWrapper: React.FC<{ children: ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
};
|
||||
return <SessionProvider>{children}</SessionProvider>
|
||||
}
|
||||
|
||||
export default SessionProviderWrapper;
|
||||
export default SessionProviderWrapper
|
||||
|
||||
@ -1,73 +1,73 @@
|
||||
import Image from "next/image";
|
||||
import useCreateConversation from "@/_hooks/useCreateConversation";
|
||||
import PrimaryButton from "../PrimaryButton";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import Image from 'next/image'
|
||||
import useCreateConversation from '@/_hooks/useCreateConversation'
|
||||
import PrimaryButton from '../PrimaryButton'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
MainViewState,
|
||||
setMainViewStateAtom,
|
||||
} from "@/_helpers/atoms/MainView.atom";
|
||||
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import useInitModel from "@/_hooks/useInitModel";
|
||||
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import { AssistantModel } from "@/_models/AssistantModel";
|
||||
} from '@/_helpers/atoms/MainView.atom'
|
||||
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
|
||||
import useInitModel from '@/_hooks/useInitModel'
|
||||
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
|
||||
import { AssistantModel } from '@/_models/AssistantModel'
|
||||
|
||||
enum ActionButton {
|
||||
DownloadModel = "Download a Model",
|
||||
StartChat = "Start a Conversation",
|
||||
DownloadModel = 'Download a Model',
|
||||
StartChat = 'Start a Conversation',
|
||||
}
|
||||
|
||||
const SidebarEmptyHistory: React.FC = () => {
|
||||
const { downloadedModels } = useGetDownloadedModels();
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom);
|
||||
const setMainView = useSetAtom(setMainViewStateAtom);
|
||||
const { requestCreateConvo } = useCreateConversation();
|
||||
const [action, setAction] = useState(ActionButton.DownloadModel);
|
||||
const { downloadedModels } = useGetDownloadedModels()
|
||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
||||
const setMainView = useSetAtom(setMainViewStateAtom)
|
||||
const { requestCreateConvo } = useCreateConversation()
|
||||
const [action, setAction] = useState(ActionButton.DownloadModel)
|
||||
|
||||
const { initModel } = useInitModel();
|
||||
const { initModel } = useInitModel()
|
||||
|
||||
useEffect(() => {
|
||||
if (downloadedModels.length > 0) {
|
||||
setAction(ActionButton.StartChat);
|
||||
setAction(ActionButton.StartChat)
|
||||
} else {
|
||||
setAction(ActionButton.DownloadModel);
|
||||
setAction(ActionButton.DownloadModel)
|
||||
}
|
||||
}, [downloadedModels]);
|
||||
}, [downloadedModels])
|
||||
|
||||
const onClick = () => {
|
||||
if (action === ActionButton.DownloadModel) {
|
||||
setMainView(MainViewState.ExploreModel);
|
||||
setMainView(MainViewState.ExploreModel)
|
||||
} else {
|
||||
if (!activeModel) {
|
||||
setMainView(MainViewState.ConversationEmptyModel);
|
||||
setMainView(MainViewState.ConversationEmptyModel)
|
||||
} else {
|
||||
createConversationAndInitModel(activeModel);
|
||||
createConversationAndInitModel(activeModel)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const createConversationAndInitModel = async (model: AssistantModel) => {
|
||||
await requestCreateConvo(model);
|
||||
await initModel(model);
|
||||
};
|
||||
await requestCreateConvo(model)
|
||||
await initModel(model)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center py-10 gap-3">
|
||||
<div className="flex flex-col items-center gap-3 py-10">
|
||||
<Image
|
||||
src={"icons/chat-bubble-oval-left.svg"}
|
||||
src={'icons/chat-bubble-oval-left.svg'}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6">
|
||||
<div className="text-center text-gray-900 text-sm">No Chat History</div>
|
||||
<div className="text-center text-gray-500 text-sm">
|
||||
<div className="text-center text-sm text-gray-900">No Chat History</div>
|
||||
<div className="text-center text-sm text-gray-500">
|
||||
Get started by creating a new chat.
|
||||
</div>
|
||||
<PrimaryButton title={action} onClick={onClick} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarEmptyHistory;
|
||||
export default SidebarEmptyHistory
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import React from "react";
|
||||
import SecondaryButton from "../SecondaryButton";
|
||||
import React from 'react'
|
||||
import SecondaryButton from '../SecondaryButton'
|
||||
|
||||
const SidebarFooter: React.FC = () => (
|
||||
<div className="flex justify-between items-center gap-2 mx-3">
|
||||
<div className="mx-3 flex items-center justify-between gap-2">
|
||||
<SecondaryButton
|
||||
title={"Discord"}
|
||||
title={'Discord'}
|
||||
onClick={() =>
|
||||
window.electronAPI?.openExternalUrl("https://discord.gg/AsJ8krTT3N")
|
||||
window.electronAPI?.openExternalUrl('https://discord.gg/AsJ8krTT3N')
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
<SecondaryButton
|
||||
title={"Twitter"}
|
||||
title={'Twitter'}
|
||||
onClick={() =>
|
||||
window.electronAPI?.openExternalUrl("https://twitter.com/janhq_")
|
||||
window.electronAPI?.openExternalUrl('https://twitter.com/janhq_')
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(SidebarFooter);
|
||||
export default React.memo(SidebarFooter)
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
|
||||
const SidebarHeader: React.FC = () => (
|
||||
<div className="flex flex-col gap-2.5 px-3">
|
||||
<Image src={"icons/Jan_AppIcon.svg"} width={68} height={28} alt="" />
|
||||
<Image src={'icons/Jan_AppIcon.svg'} width={68} height={28} alt="" />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(SidebarHeader);
|
||||
export default React.memo(SidebarHeader)
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
import React from "react";
|
||||
import SidebarMenuItem from "../SidebarMenuItem";
|
||||
import { MainViewState } from "@/_helpers/atoms/MainView.atom";
|
||||
import React from 'react'
|
||||
import SidebarMenuItem from '../SidebarMenuItem'
|
||||
import { MainViewState } from '@/_helpers/atoms/MainView.atom'
|
||||
|
||||
const menu = [
|
||||
{
|
||||
name: "Explore Models",
|
||||
icon: "Search_gray",
|
||||
name: 'Explore Models',
|
||||
icon: 'Search_gray',
|
||||
state: MainViewState.ExploreModel,
|
||||
},
|
||||
{
|
||||
name: "My Models",
|
||||
icon: "ViewGrid",
|
||||
name: 'My Models',
|
||||
icon: 'ViewGrid',
|
||||
state: MainViewState.MyModel,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
icon: "Cog",
|
||||
name: 'Settings',
|
||||
icon: 'Cog',
|
||||
state: MainViewState.Setting,
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
const SidebarMenu: React.FC = () => (
|
||||
<ul role="list" className="mx-1 mt-2 space-y-1 mb-2">
|
||||
<ul role="list" className="mx-1 mb-2 mt-2 space-y-1">
|
||||
{menu.map((item) => (
|
||||
<SidebarMenuItem
|
||||
title={item.name}
|
||||
@ -31,6 +31,6 @@ const SidebarMenu: React.FC = () => (
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
)
|
||||
|
||||
export default React.memo(SidebarMenu);
|
||||
export default React.memo(SidebarMenu)
|
||||
|
||||
@ -1,32 +1,32 @@
|
||||
import React from "react";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import Image from "next/image";
|
||||
import React from 'react'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import Image from 'next/image'
|
||||
import {
|
||||
MainViewState,
|
||||
getMainViewStateAtom,
|
||||
setMainViewStateAtom,
|
||||
} from "@/_helpers/atoms/MainView.atom";
|
||||
} from '@/_helpers/atoms/MainView.atom'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
viewState: MainViewState;
|
||||
iconName: string;
|
||||
};
|
||||
title: string
|
||||
viewState: MainViewState
|
||||
iconName: string
|
||||
}
|
||||
|
||||
const SidebarMenuItem: React.FC<Props> = ({ title, viewState, iconName }) => {
|
||||
const currentState = useAtomValue(getMainViewStateAtom);
|
||||
const setMainViewState = useSetAtom(setMainViewStateAtom);
|
||||
const currentState = useAtomValue(getMainViewStateAtom)
|
||||
const setMainViewState = useSetAtom(setMainViewStateAtom)
|
||||
|
||||
let className =
|
||||
"text-gray-600 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md text-base py-2 px-3 w-full";
|
||||
'text-gray-600 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md text-base py-2 px-3 w-full'
|
||||
if (currentState == viewState) {
|
||||
className =
|
||||
"bg-gray-100 text-indigo-600 group flex gap-x-3 rounded-md text-base py-2 px-3 w-full";
|
||||
'bg-gray-100 text-indigo-600 group flex gap-x-3 rounded-md text-base py-2 px-3 w-full'
|
||||
}
|
||||
|
||||
const onClick = () => {
|
||||
setMainViewState(viewState);
|
||||
};
|
||||
setMainViewState(viewState)
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={title}>
|
||||
@ -35,7 +35,7 @@ const SidebarMenuItem: React.FC<Props> = ({ title, viewState, iconName }) => {
|
||||
<span className="truncate">{title}</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarMenuItem;
|
||||
export default SidebarMenuItem
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
type Props = {
|
||||
name: string;
|
||||
};
|
||||
name: string
|
||||
}
|
||||
|
||||
const SimpleCheckbox: React.FC<Props> = ({ name }) => (
|
||||
<div className="relative flex items-center gap-[11px]">
|
||||
@ -17,6 +17,6 @@ const SimpleCheckbox: React.FC<Props> = ({ name }) => (
|
||||
<label htmlFor="offers">{name}</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default SimpleCheckbox;
|
||||
export default SimpleCheckbox
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { displayDate } from "@/_utils/datetime";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import JanImage from "./JanImage";
|
||||
import Image from "next/image";
|
||||
import { displayDate } from '@/_utils/datetime'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import JanImage from './JanImage'
|
||||
import Image from 'next/image'
|
||||
|
||||
type Props = {
|
||||
avatarUrl?: string;
|
||||
senderName: string;
|
||||
text: string;
|
||||
createdAt: number;
|
||||
imageUrls: string[];
|
||||
};
|
||||
avatarUrl?: string
|
||||
senderName: string
|
||||
text: string
|
||||
createdAt: number
|
||||
imageUrls: string[]
|
||||
}
|
||||
|
||||
const SimpleControlNetMessage: React.FC<Props> = ({
|
||||
avatarUrl = "",
|
||||
avatarUrl = '',
|
||||
senderName,
|
||||
imageUrls,
|
||||
text,
|
||||
@ -29,30 +29,30 @@ const SimpleControlNetMessage: React.FC<Props> = ({
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1 justify-start items-baseline">
|
||||
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px]">
|
||||
<div className="flex items-baseline justify-start gap-1">
|
||||
<div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="text-xs leading-[13.2px] font-medium text-gray-400 ml-2">
|
||||
<div className="ml-2 text-xs font-medium leading-[13.2px] text-gray-400">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3 flex-col">
|
||||
<p className="leading-[20px] whitespace-break-spaces text-sm font-normal dark:text-[#d1d5db]">
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="whitespace-break-spaces text-sm font-normal leading-[20px] dark:text-[#d1d5db]">
|
||||
{text}
|
||||
</p>
|
||||
<JanImage
|
||||
imageUrl={imageUrls[0]}
|
||||
className="w-72 aspect-square rounded-lg"
|
||||
className="aspect-square w-72 rounded-lg"
|
||||
/>
|
||||
<div className="flex flex-row justify-start items-start w-full gap-2">
|
||||
<div className="flex w-full flex-row items-start justify-start gap-2">
|
||||
<Link
|
||||
href={imageUrls[0] || "#"}
|
||||
href={imageUrls[0] || '#'}
|
||||
target="_blank_"
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
|
||||
className="flex items-center gap-1 rounded-xl bg-[#F3F4F6] px-2 py-1"
|
||||
>
|
||||
<Image src="icons/download.svg" width={16} height={16} alt="" />
|
||||
<span className="leading-[20px] text-[14px] text-[#111928]">
|
||||
<span className="text-[14px] leading-[20px] text-[#111928]">
|
||||
Download
|
||||
</span>
|
||||
</Link>
|
||||
@ -60,7 +60,7 @@ const SimpleControlNetMessage: React.FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SimpleControlNetMessage;
|
||||
export default SimpleControlNetMessage
|
||||
|
||||
@ -1,72 +1,72 @@
|
||||
import Image from "next/image";
|
||||
import JanImage from "../JanImage";
|
||||
import { displayDate } from "@/_utils/datetime";
|
||||
import Link from "next/link";
|
||||
|
||||
type Props = {
|
||||
avatarUrl?: string;
|
||||
senderName: string;
|
||||
text?: string;
|
||||
createdAt: number;
|
||||
imageUrls: string[];
|
||||
};
|
||||
|
||||
const SimpleImageMessage: React.FC<Props> = ({
|
||||
avatarUrl = "",
|
||||
senderName,
|
||||
imageUrls,
|
||||
text,
|
||||
createdAt,
|
||||
}) => {
|
||||
// TODO handle regenerate image case
|
||||
return (
|
||||
<div className="flex items-start gap-2">
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={avatarUrl}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1 justify-start items-baseline">
|
||||
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="text-xs leading-[13.2px] font-medium text-gray-400 ml-2">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 flex-col">
|
||||
<JanImage
|
||||
imageUrl={imageUrls[0]}
|
||||
className="w-72 aspect-square rounded-lg"
|
||||
/>
|
||||
<div className="flex flex-row justify-start items-start w-full gap-2">
|
||||
<Link
|
||||
href={imageUrls[0] || "#"}
|
||||
target="_blank_"
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
|
||||
>
|
||||
<Image src="icons/download.svg" width={16} height={16} alt="" />
|
||||
<span className="leading-[20px] text-sm text-[#111928]">
|
||||
Download
|
||||
</span>
|
||||
</Link>
|
||||
<button
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
|
||||
// onClick={() => sendChatMessage()}
|
||||
>
|
||||
<Image src="icons/refresh.svg" width={16} height={16} alt="" />
|
||||
<span className="leading-[20px] text-sm text-[#111928]">
|
||||
Re-generate
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimpleImageMessage;
|
||||
import Image from 'next/image'
|
||||
import JanImage from '../JanImage'
|
||||
import { displayDate } from '@/_utils/datetime'
|
||||
import Link from 'next/link'
|
||||
|
||||
type Props = {
|
||||
avatarUrl?: string
|
||||
senderName: string
|
||||
text?: string
|
||||
createdAt: number
|
||||
imageUrls: string[]
|
||||
}
|
||||
|
||||
const SimpleImageMessage: React.FC<Props> = ({
|
||||
avatarUrl = '',
|
||||
senderName,
|
||||
imageUrls,
|
||||
text,
|
||||
createdAt,
|
||||
}) => {
|
||||
// TODO handle regenerate image case
|
||||
return (
|
||||
<div className="flex items-start gap-2">
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={avatarUrl}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-baseline justify-start gap-1">
|
||||
<div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="ml-2 text-xs font-medium leading-[13.2px] text-gray-400">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<JanImage
|
||||
imageUrl={imageUrls[0]}
|
||||
className="aspect-square w-72 rounded-lg"
|
||||
/>
|
||||
<div className="flex w-full flex-row items-start justify-start gap-2">
|
||||
<Link
|
||||
href={imageUrls[0] || '#'}
|
||||
target="_blank_"
|
||||
className="flex items-center gap-1 rounded-xl bg-[#F3F4F6] px-2 py-1"
|
||||
>
|
||||
<Image src="icons/download.svg" width={16} height={16} alt="" />
|
||||
<span className="text-sm leading-[20px] text-[#111928]">
|
||||
Download
|
||||
</span>
|
||||
</Link>
|
||||
<button
|
||||
className="flex items-center gap-1 rounded-xl bg-[#F3F4F6] px-2 py-1"
|
||||
// onClick={() => sendChatMessage()}
|
||||
>
|
||||
<Image src="icons/refresh.svg" width={16} height={16} alt="" />
|
||||
<span className="text-sm leading-[20px] text-[#111928]">
|
||||
Re-generate
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SimpleImageMessage
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import { TagType } from "./TagType";
|
||||
import { TagType } from './TagType'
|
||||
|
||||
export const tagStyleMapper: Record<TagType, string> = {
|
||||
GGUF: "bg-yellow-100 text-yellow-800",
|
||||
GGUF: 'bg-yellow-100 text-yellow-800',
|
||||
PerformancePositive:
|
||||
"text-green-700 ring-1 ring-inset ring-green-600/20 bg-green-50",
|
||||
'text-green-700 ring-1 ring-inset ring-green-600/20 bg-green-50',
|
||||
PerformanceNeutral:
|
||||
"bg-yellow-50 text-yellow-800 ring-1 ring-inset ring-yellow-600/20",
|
||||
'bg-yellow-50 text-yellow-800 ring-1 ring-inset ring-yellow-600/20',
|
||||
PerformanceNegative:
|
||||
"bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
|
||||
HardwareCompatible: "bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
|
||||
'bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10',
|
||||
HardwareCompatible: 'bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10',
|
||||
HardwareIncompatible:
|
||||
"bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
|
||||
FreeStyle: "bg-gray-100 text-gray-800",
|
||||
ExpectPerformanceMedium: "bg-yellow-100 text-yellow-800",
|
||||
Version: "bg-red-100 text-yellow-800",
|
||||
Default: "bg-blue-100 text-blue-800",
|
||||
RamDefault: "bg-green-50 text-green-700",
|
||||
UsecaseDefault: "bg-orange-100 text-yellow-800",
|
||||
MiscellanousDefault: "bg-blue-100 text-blue-800",
|
||||
};
|
||||
'bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10',
|
||||
FreeStyle: 'bg-gray-100 text-gray-800',
|
||||
ExpectPerformanceMedium: 'bg-yellow-100 text-yellow-800',
|
||||
Version: 'bg-red-100 text-yellow-800',
|
||||
Default: 'bg-blue-100 text-blue-800',
|
||||
RamDefault: 'bg-green-50 text-green-700',
|
||||
UsecaseDefault: 'bg-orange-100 text-yellow-800',
|
||||
MiscellanousDefault: 'bg-blue-100 text-blue-800',
|
||||
}
|
||||
|
||||
@ -1,51 +1,51 @@
|
||||
export enum ModelPerformance {
|
||||
PerformancePositive = "PerformancePositive",
|
||||
PerformancePositive = 'PerformancePositive',
|
||||
|
||||
PerformanceNeutral = "PerformanceNeutral",
|
||||
PerformanceNeutral = 'PerformanceNeutral',
|
||||
|
||||
PerformanceNegative = "PerformanceNegative",
|
||||
PerformanceNegative = 'PerformanceNegative',
|
||||
}
|
||||
|
||||
export enum HardwareCompatibility {
|
||||
HardwareCompatible = "HardwareCompatible",
|
||||
HardwareCompatible = 'HardwareCompatible',
|
||||
|
||||
HardwareIncompatible = "HardwareIncompatible",
|
||||
HardwareIncompatible = 'HardwareIncompatible',
|
||||
}
|
||||
|
||||
export enum ExpectedPerformance {
|
||||
ExpectPerformanceMedium = "ExpectPerformanceMedium",
|
||||
ExpectPerformanceMedium = 'ExpectPerformanceMedium',
|
||||
}
|
||||
|
||||
export enum ModelFormat {
|
||||
GGUF = "GGUF",
|
||||
GGUF = 'GGUF',
|
||||
}
|
||||
|
||||
export enum FreestyleTag {
|
||||
FreeStyle = "FreeStyle",
|
||||
FreeStyle = 'FreeStyle',
|
||||
}
|
||||
|
||||
export enum VersionTag {
|
||||
Version = "Version",
|
||||
Version = 'Version',
|
||||
}
|
||||
|
||||
export enum QuantMethodTag {
|
||||
Default = "Default",
|
||||
Default = 'Default',
|
||||
}
|
||||
|
||||
export enum NumOfBit {
|
||||
Default = "Default",
|
||||
Default = 'Default',
|
||||
}
|
||||
|
||||
export enum RamRequired {
|
||||
RamDefault = "RamDefault",
|
||||
RamDefault = 'RamDefault',
|
||||
}
|
||||
|
||||
export enum UsecaseTag {
|
||||
UsecaseDefault = "UsecaseDefault",
|
||||
UsecaseDefault = 'UsecaseDefault',
|
||||
}
|
||||
|
||||
export enum MiscellanousTag {
|
||||
MiscellanousDefault = "MiscellanousDefault",
|
||||
MiscellanousDefault = 'MiscellanousDefault',
|
||||
}
|
||||
|
||||
export type TagType =
|
||||
@ -59,4 +59,4 @@ export type TagType =
|
||||
| NumOfBit
|
||||
| RamRequired
|
||||
| UsecaseTag
|
||||
| MiscellanousTag;
|
||||
| MiscellanousTag
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import { TagType } from "./TagType";
|
||||
import { tagStyleMapper } from "./TagStyleMapper";
|
||||
import React from 'react'
|
||||
import { TagType } from './TagType'
|
||||
import { tagStyleMapper } from './TagStyleMapper'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
type: TagType;
|
||||
clickable?: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
title: string
|
||||
type: TagType
|
||||
clickable?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const SimpleTag: React.FC<Props> = ({
|
||||
onClick,
|
||||
@ -15,25 +15,25 @@ const SimpleTag: React.FC<Props> = ({
|
||||
title,
|
||||
type,
|
||||
}) => {
|
||||
if (!title || title.length === 0) return null;
|
||||
if (!title || title.length === 0) return null
|
||||
if (!clickable) {
|
||||
return (
|
||||
<div
|
||||
className={`px-2.5 py-0.5 rounded text-xs font-medium items-center line-clamp-1 max-w-[40%] ${tagStyleMapper[type]}`}
|
||||
className={`line-clamp-1 max-w-[40%] items-center rounded px-2.5 py-0.5 text-xs font-medium ${tagStyleMapper[type]}`}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`px-2.5 py-0.5 rounded text-xs font-medium items-center line-clamp-1 max-w-[40%] ${tagStyleMapper[type]}`}
|
||||
className={`line-clamp-1 max-w-[40%] items-center rounded px-2.5 py-0.5 text-xs font-medium ${tagStyleMapper[type]}`}
|
||||
>
|
||||
{title} x
|
||||
</button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(SimpleTag);
|
||||
export default React.memo(SimpleTag)
|
||||
|
||||
@ -1,84 +1,84 @@
|
||||
import React from "react";
|
||||
import { displayDate } from "@/_utils/datetime";
|
||||
import Image from "next/image";
|
||||
import { MessageSenderType } from "@/_models/ChatMessage";
|
||||
import LoadingIndicator from "../LoadingIndicator";
|
||||
import { Marked } from "marked";
|
||||
import { markedHighlight } from "marked-highlight";
|
||||
import hljs from "highlight.js";
|
||||
|
||||
type Props = {
|
||||
avatarUrl: string;
|
||||
senderName: string;
|
||||
createdAt: number;
|
||||
senderType: MessageSenderType;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
const marked = new Marked(
|
||||
markedHighlight({
|
||||
langPrefix: "hljs",
|
||||
highlight(code, lang) {
|
||||
if (lang === undefined || lang === "") {
|
||||
return hljs.highlightAuto(code).value;
|
||||
}
|
||||
return hljs.highlight(code, { language: lang }).value;
|
||||
},
|
||||
}),
|
||||
{
|
||||
renderer: {
|
||||
code(code, lang, escaped) {
|
||||
return `<pre class="hljs"><code class="language-${escape(
|
||||
lang ?? ""
|
||||
)}">${escaped ? code : escape(code)}</code></pre>`;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const SimpleTextMessage: React.FC<Props> = ({
|
||||
senderName,
|
||||
createdAt,
|
||||
senderType,
|
||||
avatarUrl = "",
|
||||
text = "",
|
||||
}) => {
|
||||
const backgroundColor =
|
||||
senderType === MessageSenderType.User ? "" : "bg-gray-100";
|
||||
|
||||
const parsedText = marked.parse(text);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-start gap-2 px-12 md:px-32 2xl:px-64 ${backgroundColor} py-5`}
|
||||
>
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src={avatarUrl}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<div className="flex gap-1 justify-start items-baseline">
|
||||
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px] dark:text-[#d1d5db]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="text-xs leading-[13.2px] font-medium text-gray-400">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
{text === "" ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<span
|
||||
className="text-sm leading-loose font-normal"
|
||||
dangerouslySetInnerHTML={{ __html: parsedText }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SimpleTextMessage);
|
||||
import React from 'react'
|
||||
import { displayDate } from '@/_utils/datetime'
|
||||
import Image from 'next/image'
|
||||
import { MessageSenderType } from '@/_models/ChatMessage'
|
||||
import LoadingIndicator from '../LoadingIndicator'
|
||||
import { Marked } from 'marked'
|
||||
import { markedHighlight } from 'marked-highlight'
|
||||
import hljs from 'highlight.js'
|
||||
|
||||
type Props = {
|
||||
avatarUrl: string
|
||||
senderName: string
|
||||
createdAt: number
|
||||
senderType: MessageSenderType
|
||||
text?: string
|
||||
}
|
||||
|
||||
const marked = new Marked(
|
||||
markedHighlight({
|
||||
langPrefix: 'hljs',
|
||||
highlight(code, lang) {
|
||||
if (lang === undefined || lang === '') {
|
||||
return hljs.highlightAuto(code).value
|
||||
}
|
||||
return hljs.highlight(code, { language: lang }).value
|
||||
},
|
||||
}),
|
||||
{
|
||||
renderer: {
|
||||
code(code, lang, escaped) {
|
||||
return `<pre class="hljs"><code class="language-${escape(
|
||||
lang ?? ''
|
||||
)}">${escaped ? code : escape(code)}</code></pre>`
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const SimpleTextMessage: React.FC<Props> = ({
|
||||
senderName,
|
||||
createdAt,
|
||||
senderType,
|
||||
avatarUrl = '',
|
||||
text = '',
|
||||
}) => {
|
||||
const backgroundColor =
|
||||
senderType === MessageSenderType.User ? '' : 'bg-gray-100'
|
||||
|
||||
const parsedText = marked.parse(text)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-start gap-2 px-12 md:px-32 2xl:px-64 ${backgroundColor} py-5`}
|
||||
>
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src={avatarUrl}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<div className="flex items-baseline justify-start gap-1">
|
||||
<div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B] dark:text-[#d1d5db]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="text-xs font-medium leading-[13.2px] text-gray-400">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
{text === '' ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<span
|
||||
className="text-sm font-normal leading-loose"
|
||||
dangerouslySetInnerHTML={{ __html: parsedText }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(SimpleTextMessage)
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
type Props = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const SystemItem: React.FC<Props> = ({ name, value }) => (
|
||||
<div className="flex gap-2 pl-4 my-1">
|
||||
<div className="flex gap-2.5 w-max font-bold text-gray-900 text-sm">
|
||||
{name}
|
||||
</div>
|
||||
<span className="text-gray-900 text-sm">{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SystemItem;
|
||||
type Props = {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const SystemItem: React.FC<Props> = ({ name, value }) => (
|
||||
<div className="my-1 flex gap-2 pl-4">
|
||||
<div className="flex w-max gap-2.5 text-sm font-bold text-gray-900">
|
||||
{name}
|
||||
</div>
|
||||
<span className="text-sm text-gray-900">{value}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default SystemItem
|
||||
|
||||
@ -1,35 +1,35 @@
|
||||
import Image from "next/image";
|
||||
type Props = {
|
||||
onTabClick: (clickedTab: "description" | "api") => void;
|
||||
tab: string;
|
||||
};
|
||||
|
||||
export const TabModelDetail: React.FC<Props> = ({ onTabClick, tab }) => {
|
||||
const btns = [
|
||||
{
|
||||
name: "api",
|
||||
icon: "icons/unicorn_arrow.svg",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
icon: "icons/unicorn_exclamation-circle.svg",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex gap-0.5 rounded p-1 w-full bg-gray-200">
|
||||
{btns.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onTabClick(item.name as "description" | "api")}
|
||||
className={`w-1/2 capitalize flex items-center justify-center py-[6px] px-3 gap-2 relative text-sm leading-5 ${
|
||||
tab !== item.name ? "" : "bg-white rounded shadow-sm"
|
||||
}`}
|
||||
>
|
||||
<Image src={item.icon} width={20} height={20} alt="" />
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import Image from 'next/image'
|
||||
type Props = {
|
||||
onTabClick: (clickedTab: 'description' | 'api') => void
|
||||
tab: string
|
||||
}
|
||||
|
||||
export const TabModelDetail: React.FC<Props> = ({ onTabClick, tab }) => {
|
||||
const btns = [
|
||||
{
|
||||
name: 'api',
|
||||
icon: 'icons/unicorn_arrow.svg',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
icon: 'icons/unicorn_exclamation-circle.svg',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex w-full gap-0.5 rounded bg-gray-200 p-1">
|
||||
{btns.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onTabClick(item.name as 'description' | 'api')}
|
||||
className={`relative flex w-1/2 items-center justify-center gap-2 px-3 py-[6px] text-sm capitalize leading-5 ${
|
||||
tab !== item.name ? '' : 'rounded bg-white shadow-sm'
|
||||
}`}
|
||||
>
|
||||
<Image src={item.icon} width={20} height={20} alt="" />
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import React from "react";
|
||||
import { Popover } from "@headlessui/react";
|
||||
import { MenuHeader } from "../MenuHeader";
|
||||
import React from 'react'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { MenuHeader } from '../MenuHeader'
|
||||
// import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
|
||||
|
||||
const UserProfileDropDown: React.FC = () => {
|
||||
// const { loading, user } = useGetCurrentUser();
|
||||
return <div></div>;
|
||||
return <div></div>
|
||||
// if (loading || !user) {
|
||||
// return <div></div>;
|
||||
// }
|
||||
@ -31,6 +31,6 @@ const UserProfileDropDown: React.FC = () => {
|
||||
// </Popover>
|
||||
// </Popover.Group>
|
||||
// );
|
||||
};
|
||||
}
|
||||
|
||||
export default UserProfileDropDown;
|
||||
export default UserProfileDropDown
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { currentConversationAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
import { useAtomValue } from "jotai";
|
||||
import Image from "next/image";
|
||||
|
||||
const UserToolbar: React.FC = () => {
|
||||
const currentConvo = useAtomValue(currentConversationAtom);
|
||||
|
||||
const avatarUrl = currentConvo?.image;
|
||||
const title = currentConvo?.summary ?? currentConvo?.name ?? "";
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 p-1">
|
||||
<Image
|
||||
className="rounded-full aspect-square w-8 h-8"
|
||||
src={avatarUrl ?? "icons/app_icon.svg"}
|
||||
alt=""
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
<span className="flex gap-0.5 leading-6 text-base font-semibold">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserToolbar;
|
||||
'use client'
|
||||
|
||||
import { currentConversationAtom } from '@/_helpers/atoms/Conversation.atom'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import Image from 'next/image'
|
||||
|
||||
const UserToolbar: React.FC = () => {
|
||||
const currentConvo = useAtomValue(currentConversationAtom)
|
||||
|
||||
const avatarUrl = currentConvo?.image
|
||||
const title = currentConvo?.summary ?? currentConvo?.name ?? ''
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 p-1">
|
||||
<Image
|
||||
className="aspect-square h-8 w-8 rounded-full"
|
||||
src={avatarUrl ?? 'icons/app_icon.svg'}
|
||||
alt=""
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
<span className="flex gap-0.5 text-base font-semibold leading-6">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserToolbar
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
type Props = {
|
||||
callback: () => void;
|
||||
};
|
||||
callback: () => void
|
||||
}
|
||||
|
||||
const ViewModelDetailButton: React.FC<Props> = ({ callback }) => {
|
||||
return (
|
||||
<div className="px-4 pb-4">
|
||||
<button
|
||||
onClick={callback}
|
||||
className="bg-gray-100 py-1 px-2.5 w-full flex items-center justify-center gap-1 rounded-lg"
|
||||
className="flex w-full items-center justify-center gap-1 rounded-lg bg-gray-100 px-2.5 py-1"
|
||||
>
|
||||
<span className="text-xs leading-[18px]">View Details</span>
|
||||
<ChevronDownIcon width={18} height={18} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ViewModelDetailButton;
|
||||
export default ViewModelDetailButton
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
import Image from "next/image";
|
||||
import { useSetAtom } from "jotai";
|
||||
import Image from 'next/image'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import {
|
||||
setMainViewStateAtom,
|
||||
MainViewState,
|
||||
} from "@/_helpers/atoms/MainView.atom";
|
||||
import SecondaryButton from "../SecondaryButton";
|
||||
} from '@/_helpers/atoms/MainView.atom'
|
||||
import SecondaryButton from '../SecondaryButton'
|
||||
|
||||
const Welcome: React.FC = () => {
|
||||
const setMainViewState = useSetAtom(setMainViewStateAtom);
|
||||
const setMainViewState = useSetAtom(setMainViewStateAtom)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="px-[200px] flex-1 flex flex-col gap-5 justify-center items-start">
|
||||
<Image src={"icons/Jan_AppIcon.svg"} width={44} height={45} alt="" />
|
||||
<span className="font-semibold text-gray-500 text-5xl">
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex flex-1 flex-col items-start justify-center gap-5 px-[200px]">
|
||||
<Image src={'icons/Jan_AppIcon.svg'} width={44} height={45} alt="" />
|
||||
<span className="text-5xl font-semibold text-gray-500">
|
||||
Welcome,
|
||||
<br />
|
||||
let’s download your first model
|
||||
</span>
|
||||
<SecondaryButton
|
||||
title={"Explore models"}
|
||||
title={'Explore models'}
|
||||
onClick={() => setMainViewState(MainViewState.ExploreModel)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Welcome;
|
||||
export default Welcome
|
||||
|
||||
@ -1,34 +1,47 @@
|
||||
import { addNewMessageAtom, updateMessageAtom } from "@/_helpers/atoms/ChatMessage.atom";
|
||||
import { toChatMessage } from "@/_models/ChatMessage";
|
||||
import { events, EventName, NewMessageResponse } from "@janhq/core";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import {
|
||||
addNewMessageAtom,
|
||||
updateMessageAtom,
|
||||
} from '@/_helpers/atoms/ChatMessage.atom'
|
||||
import { toChatMessage } from '@/_models/ChatMessage'
|
||||
import { events, EventName, NewMessageResponse } from '@janhq/core'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { ReactNode, useEffect } from 'react'
|
||||
|
||||
export default function EventHandler({ children }: { children: ReactNode }) {
|
||||
const addNewMessage = useSetAtom(addNewMessageAtom);
|
||||
const updateMessage = useSetAtom(updateMessageAtom);
|
||||
const addNewMessage = useSetAtom(addNewMessageAtom)
|
||||
const updateMessage = useSetAtom(updateMessageAtom)
|
||||
|
||||
function handleNewMessageResponse(message: NewMessageResponse) {
|
||||
const newResponse = toChatMessage(message);
|
||||
addNewMessage(newResponse);
|
||||
const newResponse = toChatMessage(message)
|
||||
addNewMessage(newResponse)
|
||||
}
|
||||
async function handleMessageResponseUpdate(messageResponse: NewMessageResponse) {
|
||||
if (messageResponse.conversationId && messageResponse._id && messageResponse.message)
|
||||
updateMessage(messageResponse._id, messageResponse.conversationId, messageResponse.message);
|
||||
async function handleMessageResponseUpdate(
|
||||
messageResponse: NewMessageResponse
|
||||
) {
|
||||
if (
|
||||
messageResponse.conversationId &&
|
||||
messageResponse._id &&
|
||||
messageResponse.message
|
||||
)
|
||||
updateMessage(
|
||||
messageResponse._id,
|
||||
messageResponse.conversationId,
|
||||
messageResponse.message
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (window.corePlugin.events) {
|
||||
events.on(EventName.OnNewMessageResponse, handleNewMessageResponse);
|
||||
events.on(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate);
|
||||
events.on(EventName.OnNewMessageResponse, handleNewMessageResponse)
|
||||
events.on(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate)
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
events.off(EventName.OnNewMessageResponse, handleNewMessageResponse);
|
||||
events.off(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate);
|
||||
};
|
||||
}, []);
|
||||
return <> {children}</>;
|
||||
events.off(EventName.OnNewMessageResponse, handleNewMessageResponse)
|
||||
events.off(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate)
|
||||
}
|
||||
}, [])
|
||||
return <> {children}</>
|
||||
}
|
||||
|
||||
@ -1,68 +1,86 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { useSetAtom } from "jotai";
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import { appDownloadProgress } from "./JotaiWrapper";
|
||||
import { DownloadState } from "@/_models/DownloadState";
|
||||
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
|
||||
import { ModelManagementService } from "@janhq/core";
|
||||
import { setDownloadStateAtom, setDownloadStateSuccessAtom } from "./atoms/DownloadState.atom";
|
||||
import { getDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import { downloadedModelAtom } from "./atoms/DownloadedModel.atom";
|
||||
import EventHandler from "./EventHandler";
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { ReactNode, useEffect } from 'react'
|
||||
import { appDownloadProgress } from './JotaiWrapper'
|
||||
import { DownloadState } from '@/_models/DownloadState'
|
||||
import { executeSerial } from '../../../electron/core/plugin-manager/execution/extension-manager'
|
||||
import { ModelManagementService } from '@janhq/core'
|
||||
import {
|
||||
setDownloadStateAtom,
|
||||
setDownloadStateSuccessAtom,
|
||||
} from './atoms/DownloadState.atom'
|
||||
import { getDownloadedModels } from '@/_hooks/useGetDownloadedModels'
|
||||
import { downloadedModelAtom } from './atoms/DownloadedModel.atom'
|
||||
import EventHandler from './EventHandler'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function EventListenerWrapper({ children }: Props) {
|
||||
const setDownloadState = useSetAtom(setDownloadStateAtom);
|
||||
const setDownloadStateSuccess = useSetAtom(setDownloadStateSuccessAtom);
|
||||
const setProgress = useSetAtom(appDownloadProgress);
|
||||
const setDownloadedModels = useSetAtom(downloadedModelAtom);
|
||||
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
||||
const setDownloadStateSuccess = useSetAtom(setDownloadStateSuccessAtom)
|
||||
const setProgress = useSetAtom(appDownloadProgress)
|
||||
const setDownloadedModels = useSetAtom(downloadedModelAtom)
|
||||
|
||||
useEffect(() => {
|
||||
if (window && window.electronAPI) {
|
||||
window.electronAPI.onFileDownloadUpdate((_event: string, state: DownloadState | undefined) => {
|
||||
if (!state) return;
|
||||
setDownloadState(state);
|
||||
});
|
||||
|
||||
window.electronAPI.onFileDownloadError((_event: string, callback: any) => {
|
||||
console.log("Download error", callback);
|
||||
});
|
||||
|
||||
window.electronAPI.onFileDownloadSuccess((_event: string, callback: any) => {
|
||||
if (callback && callback.fileName) {
|
||||
setDownloadStateSuccess(callback.fileName);
|
||||
|
||||
executeSerial(ModelManagementService.UpdateFinishedDownloadAt, callback.fileName).then(() => {
|
||||
getDownloadedModels().then((models) => {
|
||||
setDownloadedModels(models);
|
||||
});
|
||||
});
|
||||
window.electronAPI.onFileDownloadUpdate(
|
||||
(_event: string, state: DownloadState | undefined) => {
|
||||
if (!state) return
|
||||
setDownloadState(state)
|
||||
}
|
||||
});
|
||||
)
|
||||
|
||||
window.electronAPI.onAppUpdateDownloadUpdate((_event: string, progress: any) => {
|
||||
setProgress(progress.percent);
|
||||
console.log("app update progress:", progress.percent);
|
||||
});
|
||||
window.electronAPI.onFileDownloadError(
|
||||
(_event: string, callback: any) => {
|
||||
console.log('Download error', callback)
|
||||
}
|
||||
)
|
||||
|
||||
window.electronAPI.onAppUpdateDownloadError((_event: string, callback: any) => {
|
||||
console.log("Download error", callback);
|
||||
setProgress(-1);
|
||||
});
|
||||
window.electronAPI.onFileDownloadSuccess(
|
||||
(_event: string, callback: any) => {
|
||||
if (callback && callback.fileName) {
|
||||
setDownloadStateSuccess(callback.fileName)
|
||||
|
||||
window.electronAPI.onAppUpdateDownloadSuccess((_event: string, callback: any) => {
|
||||
setProgress(-1);
|
||||
});
|
||||
executeSerial(
|
||||
ModelManagementService.UpdateFinishedDownloadAt,
|
||||
callback.fileName
|
||||
).then(() => {
|
||||
getDownloadedModels().then((models) => {
|
||||
setDownloadedModels(models)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
window.electronAPI.onAppUpdateDownloadUpdate(
|
||||
(_event: string, progress: any) => {
|
||||
setProgress(progress.percent)
|
||||
console.log('app update progress:', progress.percent)
|
||||
}
|
||||
)
|
||||
|
||||
window.electronAPI.onAppUpdateDownloadError(
|
||||
(_event: string, callback: any) => {
|
||||
console.log('Download error', callback)
|
||||
setProgress(-1)
|
||||
}
|
||||
)
|
||||
|
||||
window.electronAPI.onAppUpdateDownloadSuccess(
|
||||
(_event: string, callback: any) => {
|
||||
setProgress(-1)
|
||||
}
|
||||
)
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div id="eventlistener">
|
||||
<EventHandler>{children}</EventHandler>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { Provider, atom } from "jotai";
|
||||
import { ReactNode } from "react";
|
||||
import { Provider, atom } from 'jotai'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function JotaiWrapper({ children }: Props) {
|
||||
return <Provider>{children}</Provider>;
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const currentPromptAtom = atom<string>("");
|
||||
export default function JotaiWrapper({ children }: Props) {
|
||||
return <Provider>{children}</Provider>
|
||||
}
|
||||
|
||||
export const appDownloadProgress = atom<number>(-1);
|
||||
export const searchingModelText = atom<string>("");
|
||||
export const currentPromptAtom = atom<string>('')
|
||||
|
||||
export const searchAtom = atom<string>("");
|
||||
export const appDownloadProgress = atom<number>(-1)
|
||||
export const searchingModelText = atom<string>('')
|
||||
|
||||
export const modelSearchAtom = atom<string>("");
|
||||
export const searchAtom = atom<string>('')
|
||||
|
||||
export const modelSearchAtom = atom<string>('')
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import ConfirmDeleteConversationModal from "@/_components/ConfirmDeleteConversationModal";
|
||||
import ConfirmDeleteModelModal from "@/_components/ConfirmDeleteModelModal";
|
||||
import ConfirmSignOutModal from "@/_components/ConfirmSignOutModal";
|
||||
import MobileMenuPane from "@/_components/MobileMenuPane";
|
||||
import { ReactNode } from "react";
|
||||
import ConfirmDeleteConversationModal from '@/_components/ConfirmDeleteConversationModal'
|
||||
import ConfirmDeleteModelModal from '@/_components/ConfirmDeleteModelModal'
|
||||
import ConfirmSignOutModal from '@/_components/ConfirmSignOutModal'
|
||||
import MobileMenuPane from '@/_components/MobileMenuPane'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const ModalWrapper: React.FC<Props> = ({ children }) => (
|
||||
<>
|
||||
@ -18,4 +18,4 @@ export const ModalWrapper: React.FC<Props> = ({ children }) => (
|
||||
<ConfirmDeleteModelModal />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
)
|
||||
|
||||
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