Merge pull request #412 from janhq/setup/prettier

misc: setup prettier
This commit is contained in:
Faisal Amir 2023-10-20 13:19:49 +07:00 committed by GitHub
commit 05b40ac390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
158 changed files with 3057 additions and 2877 deletions

5
web/.prettierignore Normal file
View File

@ -0,0 +1,5 @@
.next/
node_modules/
dist/
*.hbs
*.mdx

8
web/.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"semi": false,
"singleQuote": true,
"quoteProps": "consistent",
"trailingComma": "es5",
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"]
}

View File

@ -1,19 +1,19 @@
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import React from "react"; import React from 'react'
import ModelTable from "../ModelTable"; import ModelTable from '../ModelTable'
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom"; import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
const ActiveModelTable: React.FC = () => { const ActiveModelTable: React.FC = () => {
const activeModel = useAtomValue(activeAssistantModelAtom); const activeModel = useAtomValue(activeAssistantModelAtom)
if (!activeModel) return null; if (!activeModel) return null
return ( return (
<div className="pl-[63px] pr-[89px]"> <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]} /> <ModelTable models={[activeModel]} />
</div> </div>
); )
}; }
export default ActiveModelTable; export default ActiveModelTable

View File

@ -1,16 +1,16 @@
import DownloadModelContent from "../DownloadModelContent"; import DownloadModelContent from '../DownloadModelContent'
import ModelDownloadButton from "../ModelDownloadButton"; import ModelDownloadButton from '../ModelDownloadButton'
import ModelDownloadingButton from "../ModelDownloadingButton"; import ModelDownloadingButton from '../ModelDownloadingButton'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom"; import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
type Props = { type Props = {
model: AssistantModel; model: AssistantModel
isRecommend: boolean; isRecommend: boolean
required?: string; required?: string
onDownloadClick?: (model: AssistantModel) => void; onDownloadClick?: (model: AssistantModel) => void
}; }
const AvailableModelCard: React.FC<Props> = ({ const AvailableModelCard: React.FC<Props> = ({
model, model,
@ -18,36 +18,36 @@ const AvailableModelCard: React.FC<Props> = ({
required, required,
onDownloadClick, onDownloadClick,
}) => { }) => {
const downloadState = useAtomValue(modelDownloadStateAtom); const downloadState = useAtomValue(modelDownloadStateAtom)
let isDownloading = false; let isDownloading = false
let total = 0; let total = 0
let transferred = 0; let transferred = 0
if (model._id && downloadState[model._id]) { if (model._id && downloadState[model._id]) {
isDownloading = isDownloading =
downloadState[model._id].error == null && downloadState[model._id].error == null &&
downloadState[model._id].percent < 1; downloadState[model._id].percent < 1
if (isDownloading) { if (isDownloading) {
total = downloadState[model._id].size.total; total = downloadState[model._id].size.total
transferred = downloadState[model._id].size.transferred; transferred = downloadState[model._id].size.transferred
} }
} }
const downloadButton = isDownloading ? ( 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} /> <ModelDownloadingButton total={total} value={transferred} />
</div> </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)} /> <ModelDownloadButton callback={() => onDownloadClick?.(model)} />
</div> </div>
); )
return ( return (
<div className="border rounded-lg border-gray-200"> <div className="rounded-lg border border-gray-200">
<div className="flex justify-between py-4 px-3 gap-2.5"> <div className="flex justify-between gap-2.5 px-3 py-4">
<DownloadModelContent <DownloadModelContent
required={required} required={required}
author={model.author} author={model.author}
@ -60,7 +60,7 @@ const AvailableModelCard: React.FC<Props> = ({
</div> </div>
{/* <ViewModelDetailButton callback={handleViewDetails} /> */} {/* <ViewModelDetailButton callback={handleViewDetails} /> */}
</div> </div>
); )
}; }
export default AvailableModelCard; export default AvailableModelCard

View File

@ -1,14 +1,14 @@
"use client"; 'use client'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { InformationCircleIcon } from "@heroicons/react/24/outline"; import { InformationCircleIcon } from '@heroicons/react/24/outline'
import SendButton from "../SendButton"; import SendButton from '../SendButton'
import { showingAdvancedPromptAtom } from "@/_helpers/atoms/Modal.atom"; import { showingAdvancedPromptAtom } from '@/_helpers/atoms/Modal.atom'
const BasicPromptAccessories: React.FC = () => { const BasicPromptAccessories: React.FC = () => {
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom); const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom)
const shouldShowAdvancedPrompt = false; const shouldShowAdvancedPrompt = false
return ( return (
<div className="absolute inset-x-0 bottom-0 flex justify-between py-2 pl-3 pr-2"> <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 /> <SendButton />
</div> </div>
</div> </div>
); )
}; }
export default BasicPromptAccessories; export default BasicPromptAccessories

View File

@ -1,20 +1,20 @@
import React from "react"; import React from 'react'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { ChevronLeftIcon } from "@heroicons/react/24/outline"; import { ChevronLeftIcon } from '@heroicons/react/24/outline'
import { showingAdvancedPromptAtom } from "@/_helpers/atoms/Modal.atom"; import { showingAdvancedPromptAtom } from '@/_helpers/atoms/Modal.atom'
const BasicPromptButton: React.FC = () => { const BasicPromptButton: React.FC = () => {
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom); const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom)
return ( return (
<button <button
onClick={() => setShowingAdvancedPrompt(false)} 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} /> <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> </button>
); )
}; }
export default React.memo(BasicPromptButton); export default React.memo(BasicPromptButton)

View File

@ -1,71 +1,71 @@
"use client"; 'use client'
import { currentPromptAtom } from "@/_helpers/JotaiWrapper"; import { currentPromptAtom } from '@/_helpers/JotaiWrapper'
import { getActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom"; import { getActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
import { selectedModelAtom } from "@/_helpers/atoms/Model.atom"; import { selectedModelAtom } from '@/_helpers/atoms/Model.atom'
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from '@/_hooks/useCreateConversation'
import useInitModel from "@/_hooks/useInitModel"; import useInitModel from '@/_hooks/useInitModel'
import useSendChatMessage from "@/_hooks/useSendChatMessage"; import useSendChatMessage from '@/_hooks/useSendChatMessage'
import { useAtom, useAtomValue } from "jotai"; import { useAtom, useAtomValue } from 'jotai'
import { ChangeEvent, useEffect, useRef } from "react"; import { ChangeEvent, useEffect, useRef } from 'react'
const BasicPromptInput: React.FC = () => { const BasicPromptInput: React.FC = () => {
const activeConversationId = useAtomValue(getActiveConvoIdAtom); const activeConversationId = useAtomValue(getActiveConvoIdAtom)
const selectedModel = useAtomValue(selectedModelAtom); const selectedModel = useAtomValue(selectedModelAtom)
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom); const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom)
const { sendChatMessage } = useSendChatMessage(); const { sendChatMessage } = useSendChatMessage()
const { requestCreateConvo } = useCreateConversation(); const { requestCreateConvo } = useCreateConversation()
const { initModel } = useInitModel(); const { initModel } = useInitModel()
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null)
const handleKeyDown = async ( const handleKeyDown = async (
event: React.KeyboardEvent<HTMLTextAreaElement> event: React.KeyboardEvent<HTMLTextAreaElement>
) => { ) => {
if (event.key === "Enter") { if (event.key === 'Enter') {
if (!event.shiftKey) { if (!event.shiftKey) {
if (activeConversationId) { if (activeConversationId) {
event.preventDefault(); event.preventDefault()
sendChatMessage(); sendChatMessage()
} else { } else {
if (!selectedModel) { if (!selectedModel) {
console.log("No model selected"); console.log('No model selected')
return; return
} }
await requestCreateConvo(selectedModel); await requestCreateConvo(selectedModel)
await initModel(selectedModel); await initModel(selectedModel)
sendChatMessage(); sendChatMessage()
}
} }
} }
} }
};
useEffect(() => { useEffect(() => {
adjustTextareaHeight(); adjustTextareaHeight()
}, [currentPrompt]); }, [currentPrompt])
const handleMessageChange = (event: ChangeEvent<HTMLTextAreaElement>) => { const handleMessageChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setCurrentPrompt(event.target.value); setCurrentPrompt(event.target.value)
}; }
// Auto adjust textarea height based on content // Auto adjust textarea height based on content
const MAX_ROWS = 30; const MAX_ROWS = 30
const adjustTextareaHeight = () => { const adjustTextareaHeight = () => {
if (textareaRef.current) { if (textareaRef.current) {
textareaRef.current.style.height = "auto"; // 1 row textareaRef.current.style.height = 'auto' // 1 row
const scrollHeight = textareaRef.current.scrollHeight; const scrollHeight = textareaRef.current.scrollHeight
const maxScrollHeight = const maxScrollHeight =
parseInt(window.getComputedStyle(textareaRef.current).lineHeight, 10) * parseInt(window.getComputedStyle(textareaRef.current).lineHeight, 10) *
MAX_ROWS; MAX_ROWS
textareaRef.current.style.height = `${Math.min( textareaRef.current.style.height = `${Math.min(
scrollHeight, scrollHeight,
maxScrollHeight maxScrollHeight
)}px`; )}px`
}
} }
};
return ( 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"> <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" 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 ..." placeholder="Message ..."
rows={1} rows={1}
style={{ overflow: "auto" }} style={{ overflow: 'auto' }}
/> />
{/* Spacer element to match the height of the toolbar */} {/* Spacer element to match the height of the toolbar */}
<div className="py-2" aria-hidden="true"> <div className="py-2" aria-hidden="true">
@ -89,7 +89,7 @@ const BasicPromptInput: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
); )
}; }
export default BasicPromptInput; export default BasicPromptInput

View File

@ -1,44 +1,44 @@
"use client"; 'use client'
import React, { useCallback, useRef, useState, useEffect } from "react"; import React, { useCallback, useRef, useState, useEffect } from 'react'
import ChatItem from "../ChatItem"; import ChatItem from '../ChatItem'
import { ChatMessage } from "@/_models/ChatMessage"; import { ChatMessage } from '@/_models/ChatMessage'
import useChatMessages from "@/_hooks/useChatMessages"; import useChatMessages from '@/_hooks/useChatMessages'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import { selectAtom } from "jotai/utils"; import { selectAtom } from 'jotai/utils'
import { getActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom"; import { getActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
import { chatMessages } from "@/_helpers/atoms/ChatMessage.atom"; import { chatMessages } from '@/_helpers/atoms/ChatMessage.atom'
const ChatBody: React.FC = () => { const ChatBody: React.FC = () => {
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? ""; const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? ''
const messageList = useAtomValue( const messageList = useAtomValue(
selectAtom( selectAtom(
chatMessages, chatMessages,
useCallback((v) => v[activeConversationId], [activeConversationId]), useCallback((v) => v[activeConversationId], [activeConversationId])
), )
); )
const [content, setContent] = useState<React.JSX.Element[]>([]); const [content, setContent] = useState<React.JSX.Element[]>([])
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0)
const { loading, hasMore } = useChatMessages(offset); const { loading, hasMore } = useChatMessages(offset)
const intersectObs = useRef<any>(null); const intersectObs = useRef<any>(null)
const lastPostRef = useCallback( const lastPostRef = useCallback(
(message: ChatMessage) => { (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) => { intersectObs.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) { 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(() => { useEffect(() => {
const list = messageList?.map((message, index) => { const list = messageList?.map((message, index) => {
@ -46,18 +46,18 @@ const ChatBody: React.FC = () => {
return ( return (
// @ts-ignore // @ts-ignore
<ChatItem ref={lastPostRef} message={message} key={message.id} /> <ChatItem ref={lastPostRef} message={message} key={message.id} />
); )
} }
return <ChatItem message={message} key={message.id} />; return <ChatItem message={message} key={message.id} />
}); })
setContent(list); setContent(list)
}, [messageList, lastPostRef]); }, [messageList, lastPostRef])
return ( 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} {content}
</div> </div>
); )
}; }
export default ChatBody; export default ChatBody

View File

@ -1,7 +1,7 @@
import SimpleControlNetMessage from "../SimpleControlNetMessage"; import SimpleControlNetMessage from '../SimpleControlNetMessage'
import SimpleImageMessage from "../SimpleImageMessage"; import SimpleImageMessage from '../SimpleImageMessage'
import SimpleTextMessage from "../SimpleTextMessage"; import SimpleTextMessage from '../SimpleTextMessage'
import { ChatMessage, MessageType } from "@/_models/ChatMessage"; import { ChatMessage, MessageType } from '@/_models/ChatMessage'
export default function renderChatMessage({ export default function renderChatMessage({
id, id,
@ -22,9 +22,9 @@ export default function renderChatMessage({
senderName={senderName} senderName={senderName}
createdAt={createdAt} createdAt={createdAt}
imageUrls={imageUrls ?? []} imageUrls={imageUrls ?? []}
text={text ?? ""} text={text ?? ''}
/> />
); )
case MessageType.Image: case MessageType.Image:
return ( return (
<SimpleImageMessage <SimpleImageMessage
@ -35,7 +35,7 @@ export default function renderChatMessage({
imageUrls={imageUrls ?? []} imageUrls={imageUrls ?? []}
text={text} text={text}
/> />
); )
case MessageType.Text: case MessageType.Text:
return ( return (
<SimpleTextMessage <SimpleTextMessage
@ -46,8 +46,8 @@ export default function renderChatMessage({
senderType={messageSenderType} senderType={messageSenderType}
text={text} text={text}
/> />
); )
default: default:
return null; return null
} }
} }

View File

@ -1,20 +1,20 @@
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
import React, { forwardRef } from "react"; import React, { forwardRef } from 'react'
import renderChatMessage from "../ChatBody/renderChatMessage"; import renderChatMessage from '../ChatBody/renderChatMessage'
import { ChatMessage } from "@/_models/ChatMessage"; import { ChatMessage } from '@/_models/ChatMessage'
type Props = { type Props = {
message: ChatMessage; message: ChatMessage
}; }
type Ref = HTMLDivElement; type Ref = HTMLDivElement
const ChatItem = forwardRef<Ref, Props>(({ message }, ref) => { 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

View File

@ -1,16 +1,16 @@
import React from "react"; import React from 'react'
import JanImage from "../JanImage"; import JanImage from '../JanImage'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { setActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom"; import { setActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
const CompactLogo: React.FC = () => { const CompactLogo: React.FC = () => {
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom); const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
return ( return (
<button onClick={() => setActiveConvoId(undefined)}> <button onClick={() => setActiveConvoId(undefined)}>
<JanImage imageUrl="icons/app_icon.svg" width={28} height={28} /> <JanImage imageUrl="icons/app_icon.svg" width={28} height={28} />
</button> </button>
); )
}; }
export default React.memo(CompactLogo); export default React.memo(CompactLogo)

View File

@ -1,18 +1,18 @@
import { showConfirmDeleteConversationModalAtom } from "@/_helpers/atoms/Modal.atom"; import { showConfirmDeleteConversationModalAtom } from '@/_helpers/atoms/Modal.atom'
import useDeleteConversation from "@/_hooks/useDeleteConversation"; import useDeleteConversation from '@/_hooks/useDeleteConversation'
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from '@headlessui/react'
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
import { useAtom } from "jotai"; import { useAtom } from 'jotai'
import React, { Fragment, useRef } from "react"; import React, { Fragment, useRef } from 'react'
const ConfirmDeleteConversationModal: React.FC = () => { const ConfirmDeleteConversationModal: React.FC = () => {
const [show, setShow] = useAtom(showConfirmDeleteConversationModalAtom); const [show, setShow] = useAtom(showConfirmDeleteConversationModalAtom)
const cancelButtonRef = useRef(null); const cancelButtonRef = useRef(null)
const { deleteConvo } = useDeleteConversation(); const { deleteConvo } = useDeleteConversation()
const onConfirmDelete = () => { const onConfirmDelete = () => {
deleteConvo().then(() => setShow(false)); deleteConvo().then(() => setShow(false))
}; }
return ( return (
<Transition.Root show={show} as={Fragment}> <Transition.Root show={show} as={Fragment}>
@ -92,7 +92,7 @@ const ConfirmDeleteConversationModal: React.FC = () => {
</div> </div>
</Dialog> </Dialog>
</Transition.Root> </Transition.Root>
); )
}; }
export default ConfirmDeleteConversationModal; export default ConfirmDeleteConversationModal

View File

@ -1,13 +1,13 @@
import React, { Fragment } from "react"; import React, { Fragment } from 'react'
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from '@headlessui/react'
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline'
import { useAtom } from "jotai"; import { useAtom } from 'jotai'
import { showConfirmDeleteModalAtom } from "@/_helpers/atoms/Modal.atom"; import { showConfirmDeleteModalAtom } from '@/_helpers/atoms/Modal.atom'
const ConfirmDeleteModelModal: React.FC = () => { const ConfirmDeleteModelModal: React.FC = () => {
const [show, setShow] = useAtom(showConfirmDeleteModalAtom); const [show, setShow] = useAtom(showConfirmDeleteModalAtom)
const onConfirmDelete = () => {}; const onConfirmDelete = () => {}
return ( return (
<Transition.Root show={show} as={Fragment}> <Transition.Root show={show} as={Fragment}>
@ -79,7 +79,7 @@ const ConfirmDeleteModelModal: React.FC = () => {
</div> </div>
</Dialog> </Dialog>
</Transition.Root> </Transition.Root>
); )
}; }
export default React.memo(ConfirmDeleteModelModal); export default React.memo(ConfirmDeleteModelModal)

View File

@ -1,17 +1,17 @@
import React, { Fragment } from "react"; import React, { Fragment } from 'react'
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from '@headlessui/react'
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline'
import { useAtom } from "jotai"; import { useAtom } from 'jotai'
import useSignOut from "@/_hooks/useSignOut"; import useSignOut from '@/_hooks/useSignOut'
import { showConfirmSignOutModalAtom } from "@/_helpers/atoms/Modal.atom"; import { showConfirmSignOutModalAtom } from '@/_helpers/atoms/Modal.atom'
const ConfirmSignOutModal: React.FC = () => { const ConfirmSignOutModal: React.FC = () => {
const [show, setShow] = useAtom(showConfirmSignOutModalAtom); const [show, setShow] = useAtom(showConfirmSignOutModalAtom)
const { signOut } = useSignOut(); const { signOut } = useSignOut()
const onLogOutClick = () => { const onLogOutClick = () => {
signOut().then(() => setShow(false)); signOut().then(() => setShow(false))
}; }
return ( return (
<Transition.Root show={show} as={Fragment}> <Transition.Root show={show} as={Fragment}>
@ -83,7 +83,7 @@ const ConfirmSignOutModal: React.FC = () => {
</div> </div>
</Dialog> </Dialog>
</Transition.Root> </Transition.Root>
); )
}; }
export default React.memo(ConfirmSignOutModal); export default React.memo(ConfirmSignOutModal)

View File

@ -1,43 +1,43 @@
import React from "react"; import React from 'react'
import Image from "next/image"; import Image from 'next/image'
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from '@/_hooks/useCreateConversation'
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
type Props = { type Props = {
model: AssistantModel; model: AssistantModel
}; }
const ConversationalCard: React.FC<Props> = ({ model }) => { const ConversationalCard: React.FC<Props> = ({ model }) => {
const { requestCreateConvo } = useCreateConversation(); const { requestCreateConvo } = useCreateConversation()
const { name, avatarUrl, shortDescription } = model; const { name, avatarUrl, shortDescription } = model
return ( return (
<button <button
onClick={() => requestCreateConvo(model)} 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 <Image
width={32} width={32}
height={32} height={32}
src={avatarUrl ?? ""} src={avatarUrl ?? ''}
className="rounded-full" className="rounded-full"
alt="" 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} {name}
</h2> </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} {shortDescription}
</span> </span>
</div> </div>
<span className="flex text-xs leading-5 text-gray-500 items-center gap-0.5"> <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="" /> <Image src={'icons/play.svg'} width={16} height={16} alt="" />
32.2k runs 32.2k runs
</span> </span>
</button> </button>
); )
}; }
export default React.memo(ConversationalCard); export default React.memo(ConversationalCard)

View File

@ -1,25 +1,25 @@
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
import ConversationalCard from "../ConversationalCard"; import ConversationalCard from '../ConversationalCard'
import { ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline"; import { ChatBubbleBottomCenterTextIcon } from '@heroicons/react/24/outline'
type Props = { type Props = {
models: AssistantModel[]; models: AssistantModel[]
}; }
const ConversationalList: React.FC<Props> = ({ models }) => ( 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" /> <ChatBubbleBottomCenterTextIcon width={24} height={24} className="ml-6" />
<span className="font-semibold text-gray-900 dark:text-white"> <span className="font-semibold text-gray-900 dark:text-white">
Conversational Conversational
</span> </span>
</div> </div>
<div className="mt-2 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) => ( {models.map((item) => (
<ConversationalCard key={item._id} model={item} /> <ConversationalCard key={item._id} model={item} />
))} ))}
</div> </div>
</> </>
); )
export default ConversationalList; export default ConversationalList

View File

@ -1,18 +1,18 @@
import React from "react"; import React from 'react'
import Link from "next/link"; import Link from 'next/link'
import Image from "next/image"; import Image from 'next/image'
const DiscordContainer = () => ( 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 <Link
className="flex items-center rounded-lg text-purple-700 text-xs leading-[18px] font-semibold gap-2" 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 ?? "#"} href={process.env.NEXT_PUBLIC_DISCORD_INVITATION_URL ?? '#'}
target="_blank_" 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 Discord
</Link> </Link>
</div> </div>
); )
export default React.memo(DiscordContainer); export default React.memo(DiscordContainer)

View File

@ -1,13 +1,13 @@
import DownloadModelTitle from "../DownloadModelTitle"; import DownloadModelTitle from '../DownloadModelTitle'
type Props = { type Props = {
author: string; author: string
description: string; description: string
isRecommend: boolean; isRecommend: boolean
name: string; name: string
type: string; type: string
required?: string; required?: string
}; }
const DownloadModelContent: React.FC<Props> = ({ const DownloadModelContent: React.FC<Props> = ({
author, author,
@ -18,23 +18,23 @@ const DownloadModelContent: React.FC<Props> = ({
type, type,
}) => { }) => {
return ( 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"> <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} {name}
</h2> </h2>
<DownloadModelTitle title={type} /> <DownloadModelTitle title={type} />
<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] font-semibold text-purple-800"> <span className="text-xs font-semibold leading-[18px] text-purple-800">
{author} {author}
</span> </span>
</div> </div>
{required && ( {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]"> <span className="text-xs leading-[18px] text-[#11192899]">
Required{" "} Required{' '}
</span> </span>
<span className="text-xs leading-[18px] font-semibold text-gray-900"> <span className="text-xs font-semibold leading-[18px] text-gray-900">
{required} {required}
</span> </span>
</div> </div>
@ -43,16 +43,16 @@ const DownloadModelContent: React.FC<Props> = ({
<p className="text-xs leading-[18px] text-gray-500">{description}</p> <p className="text-xs leading-[18px] text-gray-500">{description}</p>
<div <div
className={`${ className={`${
isRecommend ? "flex" : "hidden" isRecommend ? 'flex' : 'hidden'
} w-fit justify-center items-center bg-green-50 rounded-full px-2.5 py-0.5 gap-2`} } 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> <div className="h-3 w-3 rounded-full bg-green-400"></div>
<span className="text-green-600 font-medium text-xs leading-18px"> <span className="leading-18px text-xs font-medium text-green-600">
Recommend Recommend
</span> </span>
</div> </div>
</div> </div>
); )
}; }
export default DownloadModelContent; export default DownloadModelContent

View File

@ -1,13 +1,13 @@
type Props = { type Props = {
title: string; title: string
}; }
export const DownloadModelTitle: React.FC<Props> = ({ title }) => ( export const DownloadModelTitle: React.FC<Props> = ({ title }) => (
<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] font-medium text-purple-800"> <span className="text-xs font-medium leading-[18px] text-purple-800">
{title} {title}
</span> </span>
</div> </div>
); )
export default DownloadModelTitle; export default DownloadModelTitle

View File

@ -1,13 +1,13 @@
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
import DownloadModelContent from "../DownloadModelContent"; import DownloadModelContent from '../DownloadModelContent'
type Props = { type Props = {
model: AssistantModel; model: AssistantModel
isRecommend: boolean; isRecommend: boolean
required?: string; required?: string
transferred?: number; transferred?: number
onDeleteClick?: (model: AssistantModel) => void; onDeleteClick?: (model: AssistantModel) => void
}; }
const DownloadedModelCard: React.FC<Props> = ({ const DownloadedModelCard: React.FC<Props> = ({
model, model,
@ -15,8 +15,8 @@ const DownloadedModelCard: React.FC<Props> = ({
required, required,
onDeleteClick, onDeleteClick,
}) => ( }) => (
<div className="border rounded-lg border-gray-200"> <div className="rounded-lg border border-gray-200">
<div className="flex justify-between py-4 px-3 gap-2.5"> <div className="flex justify-between gap-2.5 px-3 py-4">
<DownloadModelContent <DownloadModelContent
required={required} required={required}
author={model.author} author={model.author}
@ -30,6 +30,6 @@ const DownloadedModelCard: React.FC<Props> = ({
</div> </div>
</div> </div>
</div> </div>
); )
export default DownloadedModelCard; export default DownloadedModelCard

View File

@ -1,22 +1,22 @@
import React from "react"; import React from 'react'
import SearchBar from "../SearchBar"; import SearchBar from '../SearchBar'
import ModelTable from "../ModelTable"; import ModelTable from '../ModelTable'
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels"; import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
const DownloadedModelTable: React.FC = () => { 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 ( return (
<div className="pl-[63px] pr-[89px]"> <div className="pl-[63px] pr-[89px]">
<h3 className="text-xl leading-[25px] mt-[50px]">Downloaded Models</h3> <h3 className="mt-[50px] text-xl leading-[25px]">Downloaded Models</h3>
<div className="py-5 w-[568px]"> <div className="w-[568px] py-5">
<SearchBar /> <SearchBar />
</div> </div>
<ModelTable models={downloadedModels} /> <ModelTable models={downloadedModels} />
</div> </div>
); )
}; }
export default DownloadedModelTable; export default DownloadedModelTable

View File

@ -1,29 +1,29 @@
import React, { Fragment } from "react"; import React, { Fragment } from 'react'
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom"; import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import ModelDownloadingTable from "../ModelDownloadingTable"; import ModelDownloadingTable from '../ModelDownloadingTable'
import { DownloadState } from "@/_models/DownloadState"; import { DownloadState } from '@/_models/DownloadState'
const DownloadingModelTable: React.FC = () => { 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)) { for (const [, value] of Object.entries(modelDownloadState)) {
downloadStates.push(value); downloadStates.push(value)
} }
return ( return (
<div className="pl-[63px] pr-[89px]"> <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 Downloading Models
</h3> </h3>
<ModelDownloadingTable downloadStates={downloadStates} /> <ModelDownloadingTable downloadStates={downloadStates} />
</div> </div>
); )
}; }
export default DownloadingModelTable; export default DownloadingModelTable

View File

@ -1,27 +1,27 @@
import { Fragment, useState } from "react"; import { Fragment, useState } from 'react'
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from '@headlessui/react'
import Image from "next/image"; import Image from 'next/image'
function classNames(...classes: any) { function classNames(...classes: any) {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(' ')
} }
type Props = { type Props = {
title: string; title: string
data: string[]; data: string[]
}; }
export const DropdownsList: React.FC<Props> = ({ data, title }) => { export const DropdownsList: React.FC<Props> = ({ data, title }) => {
const [checked, setChecked] = useState(data[0]); const [checked, setChecked] = useState(data[0])
return ( return (
<Menu as="div" className="relative w-full text-left"> <Menu as="div" className="relative w-full text-left">
<div className="pt-2 gap-2 flex flex-col"> <div className="flex flex-col gap-2 pt-2">
<h2 className="text-[#111928] text-sm">{title}</h2> <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"> <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} {checked}
<Image <Image
src={"icons/unicorn_angle-down.svg"} src={'icons/unicorn_angle-down.svg'}
width={12} width={12}
height={12} height={12}
alt="" alt=""
@ -47,8 +47,8 @@ export const DropdownsList: React.FC<Props> = ({ data, title }) => {
onClick={() => setChecked(item)} onClick={() => setChecked(item)}
href="#" href="#"
className={classNames( className={classNames(
active ? "bg-gray-100 text-gray-900" : "text-gray-700", active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
"block px-4 py-2 text-sm" 'block px-4 py-2 text-sm'
)} )}
> >
{item} {item}
@ -60,5 +60,5 @@ export const DropdownsList: React.FC<Props> = ({ data, title }) => {
</Menu.Items> </Menu.Items>
</Transition> </Transition>
</Menu> </Menu>
); )
}; }

View File

@ -1,14 +1,14 @@
import React from "react"; import React from 'react'
import SelectModels from "../ModelSelector"; import SelectModels from '../ModelSelector'
import InputToolbar from "../InputToolbar"; import InputToolbar from '../InputToolbar'
const EmptyChatContainer: React.FC = () => ( 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"> <div className="flex flex-1 items-center justify-center">
<SelectModels /> <SelectModels />
</div> </div>
<InputToolbar /> <InputToolbar />
</div> </div>
); )
export default EmptyChatContainer; export default EmptyChatContainer

View File

@ -1,14 +1,14 @@
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/outline"; import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'
type Props = { type Props = {
title: string; title: string
expanded: boolean; expanded: boolean
onClick: () => void; onClick: () => void
}; }
const ExpandableHeader: React.FC<Props> = ({ title, expanded, onClick }) => ( const ExpandableHeader: React.FC<Props> = ({ title, expanded, onClick }) => (
<button onClick={onClick} className="flex items-center justify-between px-2"> <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} {title}
</h2> </h2>
<div className="mr-2"> <div className="mr-2">
@ -19,6 +19,6 @@ const ExpandableHeader: React.FC<Props> = ({ title, expanded, onClick }) => (
)} )}
</div> </div>
</button> </button>
); )
export default ExpandableHeader; export default ExpandableHeader

View File

@ -1,20 +1,20 @@
import HeaderTitle from "../HeaderTitle"; import HeaderTitle from '../HeaderTitle'
import SearchBar, { SearchType } from "../SearchBar"; import SearchBar, { SearchType } from '../SearchBar'
import ExploreModelList from "../ExploreModelList"; import ExploreModelList from '../ExploreModelList'
import ExploreModelFilter from "../ExploreModelFilter"; import ExploreModelFilter from '../ExploreModelFilter'
const ExploreModelContainer: React.FC = () => ( 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" /> <HeaderTitle title="Explore Models" />
{/* <SearchBar {/* <SearchBar
type={SearchType.Model} type={SearchType.Model}
placeholder="Owner name like TheBloke, bhlim etc.." 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 /> <ExploreModelFilter />
<ExploreModelList /> <ExploreModelList />
</div> </div>
</div> </div>
); )
export default ExploreModelContainer; export default ExploreModelContainer

View File

@ -1,29 +1,29 @@
import React from "react"; import React from 'react'
import SearchBar from "../SearchBar"; import SearchBar from '../SearchBar'
import SimpleCheckbox from "../SimpleCheckbox"; import SimpleCheckbox from '../SimpleCheckbox'
import SimpleTag from "../SimpleTag"; import SimpleTag from '../SimpleTag'
import { TagType } from "../SimpleTag/TagType"; import { TagType } from '../SimpleTag/TagType'
const tags = [ const tags = [
"Roleplay", 'Roleplay',
"Llama", 'Llama',
"Story", 'Story',
"Casual", 'Casual',
"Professional", 'Professional',
"CodeLlama", 'CodeLlama',
"Coding", 'Coding',
]; ]
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"]; const checkboxs = ['GGUF', 'TensorRT', 'Meow', 'JigglyPuff']
const ExploreModelFilter: React.FC = () => { const ExploreModelFilter: React.FC = () => {
const enabled = false; const enabled = false
if (!enabled) return null; if (!enabled) return null
return ( return (
<div className="w-64"> <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" /> <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) => ( {tags.map((item) => (
<SimpleTag key={item} title={item} type={item as TagType} /> <SimpleTag key={item} title={item} type={item as TagType} />
))} ))}
@ -35,7 +35,7 @@ const ExploreModelFilter: React.FC = () => {
))} ))}
</fieldset> </fieldset>
</div> </div>
); )
}; }
export default ExploreModelFilter; export default ExploreModelFilter

View File

@ -1,11 +1,11 @@
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
"use client"; 'use client'
import ExploreModelItemHeader from "../ExploreModelItemHeader"; import ExploreModelItemHeader from '../ExploreModelItemHeader'
import ModelVersionList from "../ModelVersionList"; import ModelVersionList from '../ModelVersionList'
import { Fragment, forwardRef, useEffect, useState } from "react"; import { Fragment, forwardRef, useEffect, useState } from 'react'
import SimpleTag from "../SimpleTag"; import SimpleTag from '../SimpleTag'
import { import {
MiscellanousTag, MiscellanousTag,
NumOfBit, NumOfBit,
@ -13,37 +13,37 @@ import {
RamRequired, RamRequired,
UsecaseTag, UsecaseTag,
VersionTag, VersionTag,
} from "@/_components/SimpleTag/TagType"; } from '@/_components/SimpleTag/TagType'
import { displayDate } from "@/_utils/datetime"; import { displayDate } from '@/_utils/datetime'
import { Product } from "@/_models/Product"; import { Product } from '@/_models/Product'
import useGetMostSuitableModelVersion from "@/_hooks/useGetMostSuitableModelVersion"; import useGetMostSuitableModelVersion from '@/_hooks/useGetMostSuitableModelVersion'
import { toGigabytes } from "@/_utils/converter"; import { toGigabytes } from '@/_utils/converter'
type Props = { type Props = {
model: Product; model: Product
}; }
const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => { 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 } = const { suitableModel, getMostSuitableModelVersion } =
useGetMostSuitableModelVersion(); useGetMostSuitableModelVersion()
useEffect(() => { useEffect(() => {
getMostSuitableModelVersion(availableVersions); getMostSuitableModelVersion(availableVersions)
}, [availableVersions]); }, [availableVersions])
if (!suitableModel) { if (!suitableModel) {
return null; return null
} }
const { quantMethod, bits, maxRamRequired, usecase } = suitableModel; const { quantMethod, bits, maxRamRequired, usecase } = suitableModel
return ( return (
<div <div
ref={ref} 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 <ExploreModelItemHeader
suitableModel={suitableModel} 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 flex-col px-[26px] py-[22px]">
<div className="flex justify-between"> <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="flex flex-col gap-1">
<div className="text-sm font-medium text-gray-500"> <div className="text-sm font-medium text-gray-500">
Release Date Release Date
@ -81,7 +81,7 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
</div> </div>
</div> </div>
</div> </div>
<div className="flex-1 flex flex-col gap-8"> <div className="flex flex-1 flex-col gap-8">
<div> <div>
<div className="text-sm font-medium text-gray-500">Author</div> <div className="text-sm font-medium text-gray-500">Author</div>
<div className="text-sm font-normal text-gray-900"> <div className="text-sm font-normal text-gray-900">
@ -107,13 +107,13 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
</div> </div>
</div> </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-medium text-gray-500">About</span>
<span className="text-sm font-normal text-gray-500"> <span className="text-sm font-normal text-gray-500">
{model.longDescription} {model.longDescription}
</span> </span>
</div> </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> <span className="text-sm font-medium text-gray-500">Tags</span>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{model.tags.map((tag) => ( {model.tags.map((tag) => (
@ -133,19 +133,19 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
<ModelVersionList <ModelVersionList
model={model} model={model}
versions={model.availableVersions} versions={model.availableVersions}
recommendedVersion={suitableModel?._id ?? ""} recommendedVersion={suitableModel?._id ?? ''}
/> />
)} )}
<button <button
onClick={() => setShow(!show)} 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> </button>
</Fragment> </Fragment>
)} )}
</div> </div>
); )
}); })
export default ExploreModelItem; export default ExploreModelItem

View File

@ -1,72 +1,72 @@
import SimpleTag from "../SimpleTag"; import SimpleTag from '../SimpleTag'
import PrimaryButton from "../PrimaryButton"; import PrimaryButton from '../PrimaryButton'
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter"; import { formatDownloadPercentage, toGigabytes } from '@/_utils/converter'
import SecondaryButton from "../SecondaryButton"; import SecondaryButton from '../SecondaryButton'
import { Product } from "@/_models/Product"; import { Product } from '@/_models/Product'
import { useCallback, useEffect, useMemo } from "react"; import { useCallback, useEffect, useMemo } from 'react'
import { ModelVersion } from "@/_models/ModelVersion"; import { ModelVersion } from '@/_models/ModelVersion'
import useGetPerformanceTag from "@/_hooks/useGetPerformanceTag"; import useGetPerformanceTag from '@/_hooks/useGetPerformanceTag'
import useDownloadModel from "@/_hooks/useDownloadModel"; import useDownloadModel from '@/_hooks/useDownloadModel'
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels"; import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom"; import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { atom, useAtomValue, useSetAtom } from "jotai"; import { atom, useAtomValue, useSetAtom } from 'jotai'
import { import {
MainViewState, MainViewState,
setMainViewStateAtom, setMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom"; } from '@/_helpers/atoms/MainView.atom'
type Props = { type Props = {
suitableModel: ModelVersion; suitableModel: ModelVersion
exploreModel: Product; exploreModel: Product
}; }
const ExploreModelItemHeader: React.FC<Props> = ({ const ExploreModelItemHeader: React.FC<Props> = ({
suitableModel, suitableModel,
exploreModel, exploreModel,
}) => { }) => {
const { downloadModel } = useDownloadModel(); const { downloadModel } = useDownloadModel()
const { downloadedModels } = useGetDownloadedModels(); const { downloadedModels } = useGetDownloadedModels()
const { performanceTag, title, getPerformanceForModel } = const { performanceTag, title, getPerformanceForModel } =
useGetPerformanceTag(); useGetPerformanceTag()
const downloadAtom = useMemo( const downloadAtom = useMemo(
() => atom((get) => get(modelDownloadStateAtom)[suitableModel._id]), () => atom((get) => get(modelDownloadStateAtom)[suitableModel._id]),
[suitableModel._id] [suitableModel._id]
); )
const downloadState = useAtomValue(downloadAtom); const downloadState = useAtomValue(downloadAtom)
const setMainViewState = useSetAtom(setMainViewStateAtom); const setMainViewState = useSetAtom(setMainViewStateAtom)
useEffect(() => { useEffect(() => {
getPerformanceForModel(suitableModel); getPerformanceForModel(suitableModel)
}, [suitableModel]); }, [suitableModel])
const onDownloadClick = useCallback(() => { const onDownloadClick = useCallback(() => {
downloadModel(exploreModel, suitableModel); downloadModel(exploreModel, suitableModel)
}, [exploreModel, suitableModel]); }, [exploreModel, suitableModel])
const isDownloaded = const isDownloaded =
downloadedModels.find((model) => model._id === suitableModel._id) != null; downloadedModels.find((model) => model._id === suitableModel._id) != null
let downloadButton = ( let downloadButton = (
<PrimaryButton <PrimaryButton
title={ title={
suitableModel.size suitableModel.size
? `Download (${toGigabytes(suitableModel.size)})` ? `Download (${toGigabytes(suitableModel.size)})`
: "Download" : 'Download'
} }
onClick={() => onDownloadClick()} onClick={() => onDownloadClick()}
/> />
); )
if (isDownloaded) { if (isDownloaded) {
downloadButton = ( downloadButton = (
<PrimaryButton <PrimaryButton
title="View Downloaded Model" title="View Downloaded Model"
onClick={() => { onClick={() => {
setMainViewState(MainViewState.MyModel); setMainViewState(MainViewState.MyModel)
}} }}
className="bg-green-500 hover:bg-green-400" className="bg-green-500 hover:bg-green-400"
/> />
); )
} }
if (downloadState != null) { if (downloadState != null) {
@ -78,11 +78,11 @@ const ExploreModelItemHeader: React.FC<Props> = ({
downloadState.percent downloadState.percent
)})`} )})`}
/> />
); )
} }
return ( 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"> <div className="flex items-center gap-2">
<span>{exploreModel.name}</span> <span>{exploreModel.name}</span>
{performanceTag && ( {performanceTag && (
@ -91,7 +91,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({
</div> </div>
{downloadButton} {downloadButton}
</div> </div>
); )
}; }
export default ExploreModelItemHeader; export default ExploreModelItemHeader

View File

@ -1,18 +1,18 @@
import React, { useEffect } from "react"; import React, { useEffect } from 'react'
import ExploreModelItem from "../ExploreModelItem"; import ExploreModelItem from '../ExploreModelItem'
import { getConfiguredModels } from "@/_hooks/useGetDownloadedModels"; import { getConfiguredModels } from '@/_hooks/useGetDownloadedModels'
import useGetConfiguredModels from "@/_hooks/useGetConfiguredModels"; import useGetConfiguredModels from '@/_hooks/useGetConfiguredModels'
import { Waveform } from "@uiball/loaders"; import { Waveform } from '@uiball/loaders'
const ExploreModelList: React.FC = () => { const ExploreModelList: React.FC = () => {
const { loading, models } = useGetConfiguredModels(); const { loading, models } = useGetConfiguredModels()
useEffect(() => { useEffect(() => {
getConfiguredModels(); getConfiguredModels()
}, []); }, [])
return ( return (
<div className="flex flex-col flex-1 overflow-y-auto scroll"> <div className="scroll flex flex-1 flex-col overflow-y-auto">
{loading && ( {loading && (
<div className="mx-auto"> <div className="mx-auto">
<Waveform size={24} color="#CBD5E0" /> <Waveform size={24} color="#CBD5E0" />
@ -22,7 +22,7 @@ const ExploreModelList: React.FC = () => {
<ExploreModelItem key={item._id} model={item} /> <ExploreModelItem key={item._id} model={item} />
))} ))}
</div> </div>
); )
}; }
export default ExploreModelList; export default ExploreModelList

View File

@ -1,29 +1,23 @@
import Image from "next/image"; import Image from 'next/image'
import Link from "next/link"; import Link from 'next/link'
// DEPRECATED // DEPRECATED
export default function Footer() { export default function Footer() {
return ( 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"> <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> <span>Jan</span>
</div> </div>
<div className="flex gap-4 my-6"> <div className="my-6 flex gap-4">
<Link <Link href="/privacy" className="cursor-pointer">
href="/privacy"
className="cursor-pointer"
>
Privacy Privacy
</Link> </Link>
<span>&#8226;</span> <span>&#8226;</span>
<Link <Link href="/support" className="cursor-pointer">
href="/support"
className="cursor-pointer"
>
Support Support
</Link> </Link>
</div> </div>
</div> </div>
); )
} }

View File

@ -1,22 +1,22 @@
"use client"; 'use client'
import { showingMobilePaneAtom } from "@/_helpers/atoms/Modal.atom"; import { showingMobilePaneAtom } from '@/_helpers/atoms/Modal.atom'
import { Bars3Icon } from "@heroicons/react/24/outline"; import { Bars3Icon } from '@heroicons/react/24/outline'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import React from "react"; import React from 'react'
const HamburgerButton: React.FC = () => { const HamburgerButton: React.FC = () => {
const setShowingMobilePane = useSetAtom(showingMobilePaneAtom); const setShowingMobilePane = useSetAtom(showingMobilePaneAtom)
return ( return (
<button <button
type="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)} onClick={() => setShowingMobilePane(true)}
> >
<span className="sr-only">Open main menu</span> <span className="sr-only">Open main menu</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" /> <Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button> </button>
); )
}; }
export default React.memo(HamburgerButton); export default React.memo(HamburgerButton)

View File

@ -1,7 +1,7 @@
import React from "react"; import React from 'react'
import UserProfileDropDown from "../UserProfileDropDown"; import UserProfileDropDown from '../UserProfileDropDown'
import LoginButton from "../LoginButton"; import LoginButton from '../LoginButton'
import HamburgerButton from "../HamburgerButton"; import HamburgerButton from '../HamburgerButton'
const Header: React.FC = () => { const Header: React.FC = () => {
return ( return (
@ -13,7 +13,7 @@ const Header: React.FC = () => {
<LoginButton /> <LoginButton />
<UserProfileDropDown /> <UserProfileDropDown />
</header> </header>
); )
}; }
export default Header; export default Header

View File

@ -1,5 +1,5 @@
import React from "react"; import React from 'react'
import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { ArrowLeftIcon } from '@heroicons/react/24/outline'
const HeaderBackButton: React.FC = () => { const HeaderBackButton: React.FC = () => {
return ( return (
@ -7,7 +7,7 @@ const HeaderBackButton: React.FC = () => {
<ArrowLeftIcon width={24} height={24} /> <ArrowLeftIcon width={24} height={24} />
<span className="text-sm">Back</span> <span className="text-sm">Back</span>
</button> </button>
); )
}; }
export default React.memo(HeaderBackButton); export default React.memo(HeaderBackButton)

View File

@ -1,16 +1,16 @@
import React from "react"; import React from 'react'
type Props = { type Props = {
title: string; title: string
className?: string; className?: string
}; }
const HeaderTitle: React.FC<Props> = ({ title, className }) => ( const HeaderTitle: React.FC<Props> = ({ title, className }) => (
<h2 <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} {title}
</h2> </h2>
); )
export default React.memo(HeaderTitle); export default React.memo(HeaderTitle)

View File

@ -1,30 +1,30 @@
import React from "react"; import React from 'react'
import { useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from 'jotai'
import Image from "next/image"; import Image from 'next/image'
import { Conversation } from "@/_models/Conversation"; import { Conversation } from '@/_models/Conversation'
import { ModelManagementService } from "@janhq/core"; import { ModelManagementService } from '@janhq/core'
import { executeSerial } from "../../../../electron/core/plugin-manager/execution/extension-manager"; import { executeSerial } from '../../../../electron/core/plugin-manager/execution/extension-manager'
import { import {
conversationStatesAtom, conversationStatesAtom,
getActiveConvoIdAtom, getActiveConvoIdAtom,
setActiveConvoIdAtom, setActiveConvoIdAtom,
updateConversationErrorAtom, updateConversationErrorAtom,
updateConversationWaitingForResponseAtom, updateConversationWaitingForResponseAtom,
} from "@/_helpers/atoms/Conversation.atom"; } from '@/_helpers/atoms/Conversation.atom'
import { import {
setMainViewStateAtom, setMainViewStateAtom,
MainViewState, MainViewState,
} from "@/_helpers/atoms/MainView.atom"; } from '@/_helpers/atoms/MainView.atom'
import useInitModel from "@/_hooks/useInitModel"; import useInitModel from '@/_hooks/useInitModel'
import { displayDate } from "@/_utils/datetime"; import { displayDate } from '@/_utils/datetime'
type Props = { type Props = {
conversation: Conversation; conversation: Conversation
avatarUrl?: string; avatarUrl?: string
name: string; name: string
summary?: string; summary?: string
updatedAt?: string; updatedAt?: string
}; }
const HistoryItem: React.FC<Props> = ({ const HistoryItem: React.FC<Props> = ({
conversation, conversation,
@ -33,82 +33,80 @@ const HistoryItem: React.FC<Props> = ({
summary, summary,
updatedAt, updatedAt,
}) => { }) => {
const setMainViewState = useSetAtom(setMainViewStateAtom); const setMainViewState = useSetAtom(setMainViewStateAtom)
const conversationStates = useAtomValue(conversationStatesAtom); const conversationStates = useAtomValue(conversationStatesAtom)
const activeConvoId = useAtomValue(getActiveConvoIdAtom); const activeConvoId = useAtomValue(getActiveConvoIdAtom)
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom); const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
const updateConvWaiting = useSetAtom( const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
updateConversationWaitingForResponseAtom const updateConvError = useSetAtom(updateConversationErrorAtom)
); const isSelected = activeConvoId === conversation._id
const updateConvError = useSetAtom(updateConversationErrorAtom);
const isSelected = activeConvoId === conversation._id;
const { initModel } = useInitModel(); const { initModel } = useInitModel()
const onClick = async () => { const onClick = async () => {
const model = await executeSerial( const model = await executeSerial(
ModelManagementService.GetModelById, ModelManagementService.GetModelById,
conversation.modelId conversation.modelId
); )
if (conversation._id) updateConvWaiting(conversation._id, true); if (conversation._id) updateConvWaiting(conversation._id, true)
initModel(model).then((res: any) => { initModel(model).then((res: any) => {
if (conversation._id) updateConvWaiting(conversation._id, false); if (conversation._id) updateConvWaiting(conversation._id, false)
if (res?.error && conversation._id) { if (res?.error && conversation._id) {
updateConvError(conversation._id, res.error); updateConvError(conversation._id, res.error)
} }
}); })
if (activeConvoId !== conversation._id) { if (activeConvoId !== conversation._id) {
setMainViewState(MainViewState.Conversation); setMainViewState(MainViewState.Conversation)
setActiveConvoId(conversation._id); setActiveConvoId(conversation._id)
}
} }
};
const backgroundColor = isSelected const backgroundColor = isSelected
? "bg-gray-100 dark:bg-gray-700" ? 'bg-gray-100 dark:bg-gray-700'
: "bg-white dark:bg-gray-500"; : 'bg-white dark:bg-gray-500'
let rightImageUrl: string | undefined; let rightImageUrl: string | undefined
if (conversationStates[conversation._id ?? ""]?.waitingForResponse === true) { if (conversationStates[conversation._id ?? '']?.waitingForResponse === true) {
rightImageUrl = "icons/loading.svg"; rightImageUrl = 'icons/loading.svg'
} }
const description = conversation?.lastMessage ?? "No new message"; const description = conversation?.lastMessage ?? 'No new message'
return ( return (
<li <li
role="button" 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} onClick={onClick}
> >
<div className="w-8 h-8"> <div className="h-8 w-8">
<Image <Image
width={32} width={32}
height={32} height={32}
src={avatarUrl ?? "icons/app_icon.svg"} src={avatarUrl ?? 'icons/app_icon.svg'}
className="aspect-square rounded-full" className="aspect-square rounded-full"
alt="" alt=""
/> />
</div> </div>
<div className="flex flex-col ml-2 flex-1"> <div className="ml-2 flex flex-1 flex-col">
{/* title */} {/* title */}
<div className="flex"> <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} {summary ?? name}
</span> </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())} {updatedAt && displayDate(new Date(updatedAt).getTime())}
</span> </span>
</div> </div>
{/* description */} {/* 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> </div>
</li> </li>
); )
}; }
export default HistoryItem; export default HistoryItem

View File

@ -1,39 +1,39 @@
import HistoryItem from "../HistoryItem"; import HistoryItem from '../HistoryItem'
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react'
import ExpandableHeader from "../ExpandableHeader"; import ExpandableHeader from '../ExpandableHeader'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import { searchAtom } from "@/_helpers/JotaiWrapper"; import { searchAtom } from '@/_helpers/JotaiWrapper'
import useGetUserConversations from "@/_hooks/useGetUserConversations"; import useGetUserConversations from '@/_hooks/useGetUserConversations'
import SidebarEmptyHistory from "../SidebarEmptyHistory"; import SidebarEmptyHistory from '../SidebarEmptyHistory'
import { userConversationsAtom } from "@/_helpers/atoms/Conversation.atom"; import { userConversationsAtom } from '@/_helpers/atoms/Conversation.atom'
const HistoryList: React.FC = () => { const HistoryList: React.FC = () => {
const conversations = useAtomValue(userConversationsAtom); const conversations = useAtomValue(userConversationsAtom)
const searchText = useAtomValue(searchAtom); const searchText = useAtomValue(searchAtom)
const [expand, setExpand] = useState<boolean>(true); const [expand, setExpand] = useState<boolean>(true)
const { getUserConversations } = useGetUserConversations(); const { getUserConversations } = useGetUserConversations()
useEffect(() => { useEffect(() => {
getUserConversations(); getUserConversations()
}, []); }, [])
return ( return (
<div className="flex flex-col flex-grow pt-3 gap-2 overflow-hidden"> <div className="flex flex-grow flex-col gap-2 overflow-hidden pt-3">
<ExpandableHeader <ExpandableHeader
title="CHAT HISTORY" title="CHAT HISTORY"
expanded={expand} expanded={expand}
onClick={() => setExpand(!expand)} onClick={() => setExpand(!expand)}
/> />
<ul <ul
className={`flex flex-col gap-1 mt-1 overflow-y-auto scroll ${ className={`scroll mt-1 flex flex-col gap-1 overflow-y-auto ${
!expand ? "hidden " : "block" !expand ? 'hidden ' : 'block'
}`} }`}
> >
{conversations.length > 0 ? ( {conversations.length > 0 ? (
conversations conversations
.filter( .filter(
(e) => (e) =>
searchText.trim() === "" || searchText.trim() === '' ||
e.name?.toLowerCase().includes(searchText.toLowerCase().trim()) e.name?.toLowerCase().includes(searchText.toLowerCase().trim())
) )
.map((convo) => ( .map((convo) => (
@ -42,8 +42,8 @@ const HistoryList: React.FC = () => {
conversation={convo} conversation={convo}
summary={convo.summary} summary={convo.summary}
avatarUrl={convo.image} avatarUrl={convo.image}
name={convo.name || "Jan"} name={convo.name || 'Jan'}
updatedAt={convo.updatedAt ?? ""} updatedAt={convo.updatedAt ?? ''}
/> />
)) ))
) : ( ) : (
@ -51,7 +51,7 @@ const HistoryList: React.FC = () => {
)} )}
</ul> </ul>
</div> </div>
); )
}; }
export default HistoryList; export default HistoryList

View File

@ -1,24 +1,24 @@
"use client"; 'use client'
import BasicPromptInput from "../BasicPromptInput"; import BasicPromptInput from '../BasicPromptInput'
import BasicPromptAccessories from "../BasicPromptAccessories"; import BasicPromptAccessories from '../BasicPromptAccessories'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import { showingAdvancedPromptAtom } from "@/_helpers/atoms/Modal.atom"; import { showingAdvancedPromptAtom } from '@/_helpers/atoms/Modal.atom'
import SecondaryButton from "../SecondaryButton"; import SecondaryButton from '../SecondaryButton'
import { Fragment } from "react"; import { Fragment } from 'react'
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from '@heroicons/react/24/outline'
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from '@/_hooks/useCreateConversation'
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom"; import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import { currentConvoStateAtom } from "@/_helpers/atoms/Conversation.atom"; import { currentConvoStateAtom } from '@/_helpers/atoms/Conversation.atom'
const InputToolbar: React.FC = () => { const InputToolbar: React.FC = () => {
const showingAdvancedPrompt = useAtomValue(showingAdvancedPromptAtom); const showingAdvancedPrompt = useAtomValue(showingAdvancedPromptAtom)
const activeModel = useAtomValue(activeAssistantModelAtom); const activeModel = useAtomValue(activeAssistantModelAtom)
const { requestCreateConvo } = useCreateConversation(); const { requestCreateConvo } = useCreateConversation()
const currentConvoState = useAtomValue(currentConvoStateAtom); const currentConvoState = useAtomValue(currentConvoStateAtom)
if (showingAdvancedPrompt) { if (showingAdvancedPrompt) {
return <div />; return <div />
} }
// TODO: implement regenerate // TODO: implement regenerate
@ -26,20 +26,20 @@ const InputToolbar: React.FC = () => {
const onNewConversationClick = () => { const onNewConversationClick = () => {
if (activeModel) { if (activeModel) {
requestCreateConvo(activeModel); requestCreateConvo(activeModel)
}
} }
};
return ( return (
<Fragment> <Fragment>
{currentConvoState?.error && ( {currentConvoState?.error && (
<div className="flex flex-row justify-center"> <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()} {currentConvoState?.error?.toString()}
</span> </span>
</div> </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 title="Regenerate" onClick={onRegenerateClick} /> */}
<SecondaryButton <SecondaryButton
onClick={onNewConversationClick} onClick={onNewConversationClick}
@ -48,14 +48,14 @@ const InputToolbar: React.FC = () => {
/> />
</div> </div>
{/* My text input */} {/* My text input */}
<div className="flex items-start space-x-4 mx-12 md:mx-32 2xl:mx-64 mb-5"> <div className="mx-12 mb-5 flex items-start space-x-4 md:mx-32 2xl:mx-64">
<div className="min-w-0 flex-1 relative"> <div className="relative min-w-0 flex-1">
<BasicPromptInput /> <BasicPromptInput />
<BasicPromptAccessories /> <BasicPromptAccessories />
</div> </div>
</div> </div>
</Fragment> </Fragment>
); )
}; }
export default InputToolbar; export default InputToolbar

View File

@ -1,21 +1,21 @@
import React from "react"; import React from 'react'
type Props = { type Props = {
imageUrl: string; imageUrl: string
className?: string; className?: string
alt?: string; alt?: string
width?: number; width?: number
height?: number; height?: number
}; }
const JanImage: React.FC<Props> = ({ const JanImage: React.FC<Props> = ({
imageUrl, imageUrl,
className = "", className = '',
alt = "", alt = '',
width, width,
height, height,
}) => { }) => {
const [attempt, setAttempt] = React.useState(0); const [attempt, setAttempt] = React.useState(0)
return ( return (
<img <img
@ -27,7 +27,7 @@ const JanImage: React.FC<Props> = ({
key={attempt} key={attempt}
onError={() => setAttempt(attempt + 1)} onError={() => setAttempt(attempt + 1)}
/> />
); )
}; }
export default JanImage; export default JanImage

View File

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

View File

@ -1,9 +1,9 @@
import React, { Fragment } from "react"; import React, { Fragment } from 'react'
import SidebarFooter from "../SidebarFooter"; import SidebarFooter from '../SidebarFooter'
import SidebarHeader from "../SidebarHeader"; import SidebarHeader from '../SidebarHeader'
import SidebarMenu from "../SidebarMenu"; import SidebarMenu from '../SidebarMenu'
import HistoryList from "../HistoryList"; import HistoryList from '../HistoryList'
import NewChatButton from "../NewChatButton"; import NewChatButton from '../NewChatButton'
const LeftContainer: React.FC = () => ( const LeftContainer: React.FC = () => (
<Fragment> <Fragment>
@ -13,6 +13,6 @@ const LeftContainer: React.FC = () => (
<SidebarMenu /> <SidebarMenu />
<SidebarFooter /> <SidebarFooter />
</Fragment> </Fragment>
); )
export default React.memo(LeftContainer); export default React.memo(LeftContainer)

View File

@ -1,20 +1,20 @@
import React from "react"; import React from 'react'
import SearchBar from "../SearchBar"; import SearchBar from '../SearchBar'
// import ShortcutList from "../ShortcutList"; // import ShortcutList from "../ShortcutList";
import HistoryList from "../HistoryList"; import HistoryList from '../HistoryList'
import DiscordContainer from "../DiscordContainer"; import DiscordContainer from '../DiscordContainer'
import JanLogo from "../JanLogo"; import JanLogo from '../JanLogo'
const LeftSidebar: React.FC = () => ( 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"> <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 /> <JanLogo />
<div className="flex flex-col flex-1 gap-3 overflow-x-hidden"> <div className="flex flex-1 flex-col gap-3 overflow-x-hidden">
<SearchBar /> <SearchBar />
{/* <ShortcutList /> */} {/* <ShortcutList /> */}
<HistoryList /> <HistoryList />
</div> </div>
<DiscordContainer /> <DiscordContainer />
</div> </div>
); )
export default LeftSidebar; export default LeftSidebar

View File

@ -1,5 +1,4 @@
const LoadingIndicator = () => { const LoadingIndicator = () => {
return ( return (
<div className="typingIndicatorContainer"> <div className="typingIndicatorContainer">
<div className="typingIndicatorBubble"> <div className="typingIndicatorBubble">
@ -8,7 +7,7 @@ const LoadingIndicator = () => {
<div className="typingIndicatorBubbleDot"></div> <div className="typingIndicatorBubbleDot"></div>
</div> </div>
</div> </div>
); )
}; }
export default LoadingIndicator; export default LoadingIndicator

View File

@ -1,4 +1,4 @@
"use client"; 'use client'
const LoginButton: React.FC = () => { const LoginButton: React.FC = () => {
// const { signInWithKeyCloak } = useSignIn(); // const { signInWithKeyCloak } = useSignIn();
@ -18,7 +18,7 @@ const LoginButton: React.FC = () => {
// </button> // </button>
// </div> // </div>
// ); // );
return <div />; return <div />
}; }
export default LoginButton; export default LoginButton

View File

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

View File

@ -1,11 +1,11 @@
import ModelMenu from "../ModelMenu"; import ModelMenu from '../ModelMenu'
import UserToolbar from "../UserToolbar"; import UserToolbar from '../UserToolbar'
const MainChatHeader: React.FC = () => ( 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 /> <UserToolbar />
<ModelMenu /> <ModelMenu />
</div> </div>
); )
export default MainChatHeader; export default MainChatHeader

View File

@ -1,11 +1,11 @@
"use client"; 'use client'
import React from "react"; import React from 'react'
import LeftContainer from "../LeftContainer"; import LeftContainer from '../LeftContainer'
import RightContainer from "../RightContainer"; import RightContainer from '../RightContainer'
import { Variants, motion } from "framer-motion"; import { Variants, motion } from 'framer-motion'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import { leftSideBarExpandStateAtom } from "@/_helpers/atoms/LeftSideBarExpand.atom"; import { leftSideBarExpandStateAtom } from '@/_helpers/atoms/LeftSideBarExpand.atom'
const leftSideBarVariants: Variants = { const leftSideBarVariants: Variants = {
show: { show: {
@ -15,31 +15,31 @@ const leftSideBarVariants: Variants = {
transition: { duration: 0.1 }, transition: { duration: 0.1 },
}, },
hide: { hide: {
x: "-100%", x: '-100%',
width: 0, width: 0,
opacity: 0, opacity: 0,
transition: { duration: 0.1 }, transition: { duration: 0.1 },
}, },
}; }
const MainContainer: React.FC = () => { const MainContainer: React.FC = () => {
const leftSideBarExpand = useAtomValue(leftSideBarExpandStateAtom); const leftSideBarExpand = useAtomValue(leftSideBarExpandStateAtom)
return ( return (
<div className="flex"> <div className="flex">
<motion.div <motion.div
initial={false} initial={false}
animate={leftSideBarExpand ? "show" : "hide"} animate={leftSideBarExpand ? 'show' : 'hide'}
variants={leftSideBarVariants} 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 /> <LeftContainer />
</motion.div> </motion.div>
<div className="flex flex-col flex-1 h-screen"> <div className="flex h-screen flex-1 flex-col">
<RightContainer /> <RightContainer />
</div> </div>
</div> </div>
); )
}; }
export default MainContainer; export default MainContainer

View File

@ -1,35 +1,35 @@
"use client"; 'use client'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import Welcome from "../WelcomeContainer"; import Welcome from '../WelcomeContainer'
import { Preferences } from "../Preferences"; import { Preferences } from '../Preferences'
import MyModelContainer from "../MyModelContainer"; import MyModelContainer from '../MyModelContainer'
import ExploreModelContainer from "../ExploreModelContainer"; import ExploreModelContainer from '../ExploreModelContainer'
import { import {
MainViewState, MainViewState,
getMainViewStateAtom, getMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom"; } from '@/_helpers/atoms/MainView.atom'
import EmptyChatContainer from "../EmptyChatContainer"; import EmptyChatContainer from '../EmptyChatContainer'
import MainChat from "../MainChat"; import MainChat from '../MainChat'
const MainView: React.FC = () => { const MainView: React.FC = () => {
const viewState = useAtomValue(getMainViewStateAtom); const viewState = useAtomValue(getMainViewStateAtom)
switch (viewState) { switch (viewState) {
case MainViewState.ConversationEmptyModel: case MainViewState.ConversationEmptyModel:
return <EmptyChatContainer />; return <EmptyChatContainer />
case MainViewState.ExploreModel: case MainViewState.ExploreModel:
return <ExploreModelContainer />; return <ExploreModelContainer />
case MainViewState.Setting: case MainViewState.Setting:
return <Preferences />; return <Preferences />
case MainViewState.ResourceMonitor: case MainViewState.ResourceMonitor:
case MainViewState.MyModel: case MainViewState.MyModel:
return <MyModelContainer />; return <MyModelContainer />
case MainViewState.Welcome: case MainViewState.Welcome:
return <Welcome />; return <Welcome />
default: default:
return <MainChat />; return <MainChat />
} }
}; }
export default MainView; export default MainView

View File

@ -1,15 +1,15 @@
import Link from "next/link"; import Link from 'next/link'
import { Popover, Transition } from "@headlessui/react"; import { Popover, Transition } from '@headlessui/react'
import { Fragment } from "react"; import { Fragment } from 'react'
// import useGetCurrentUser from "@/_hooks/useGetCurrentUser"; // import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { showConfirmSignOutModalAtom } from "@/_helpers/atoms/Modal.atom"; import { showConfirmSignOutModalAtom } from '@/_helpers/atoms/Modal.atom'
export const MenuHeader: React.FC = () => { export const MenuHeader: React.FC = () => {
const setShowConfirmSignOutModal = useSetAtom(showConfirmSignOutModalAtom); const setShowConfirmSignOutModal = useSetAtom(showConfirmSignOutModalAtom)
// const { user } = useGetCurrentUser(); // const { user } = useGetCurrentUser();
return <div></div>; return <div></div>
// return ( // return (
// <Transition // <Transition
@ -50,4 +50,4 @@ export const MenuHeader: React.FC = () => {
// </Popover.Panel> // </Popover.Panel>
// </Transition> // </Transition>
// ); // );
}; }

View File

@ -1,13 +1,13 @@
import React, { useRef } from "react"; import React, { useRef } from 'react'
import { Dialog } from "@headlessui/react"; import { Dialog } from '@headlessui/react'
import { XMarkIcon } from "@heroicons/react/24/outline"; import { XMarkIcon } from '@heroicons/react/24/outline'
import Image from "next/image"; import Image from 'next/image'
import { useAtom } from "jotai"; import { useAtom } from 'jotai'
import { showingMobilePaneAtom } from "@/_helpers/atoms/Modal.atom"; import { showingMobilePaneAtom } from '@/_helpers/atoms/Modal.atom'
const MobileMenuPane: React.FC = () => { const MobileMenuPane: React.FC = () => {
const [show, setShow] = useAtom(showingMobilePaneAtom); const [show, setShow] = useAtom(showingMobilePaneAtom)
let loginRef = useRef(null); let loginRef = useRef(null)
return ( return (
<Dialog <Dialog
@ -54,7 +54,7 @@ const MobileMenuPane: React.FC = () => {
</div> </div>
</Dialog.Panel> </Dialog.Panel>
</Dialog> </Dialog>
); )
}; }
export default MobileMenuPane; export default MobileMenuPane

View File

@ -1,46 +1,50 @@
import React from "react"; import React from 'react'
import PrimaryButton from "../PrimaryButton"; import PrimaryButton from '../PrimaryButton'
export enum ModelActionType { export enum ModelActionType {
Start = "Start", Start = 'Start',
Stop = "Stop", Stop = 'Stop',
} }
type ModelActionStyle = { type ModelActionStyle = {
title: string; title: string
backgroundColor: string; backgroundColor: string
textColor: string; textColor: string
}; }
const modelActionMapper: Record<ModelActionType, ModelActionStyle> = { const modelActionMapper: Record<ModelActionType, ModelActionStyle> = {
[ModelActionType.Start]: { [ModelActionType.Start]: {
title: "Start", title: 'Start',
backgroundColor: "bg-blue-500 hover:bg-blue-600", backgroundColor: 'bg-blue-500 hover:bg-blue-600',
textColor: "text-white", textColor: 'text-white',
}, },
[ModelActionType.Stop]: { [ModelActionType.Stop]: {
title: "Stop", title: 'Stop',
backgroundColor: "bg-red-500 hover:bg-red-600", backgroundColor: 'bg-red-500 hover:bg-red-600',
textColor: "text-white", textColor: 'text-white',
}, },
}; }
type Props = { type Props = {
type: ModelActionType; type: ModelActionType
onActionClick: (type: ModelActionType) => void; onActionClick: (type: ModelActionType) => void
}; }
const ModelActionButton: React.FC<Props> = ({ type, onActionClick }) => { const ModelActionButton: React.FC<Props> = ({ type, onActionClick }) => {
const styles = modelActionMapper[type]; const styles = modelActionMapper[type]
const onClick = () => { const onClick = () => {
onActionClick(type); onActionClick(type)
}; }
return ( return (
<td className="whitespace-nowrap px-6 py-4 text-sm"> <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> </td>
); )
}; }
export default ModelActionButton; export default ModelActionButton

View File

@ -1,10 +1,10 @@
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from '@headlessui/react'
import { EllipsisVerticalIcon } from "@heroicons/react/20/solid"; import { EllipsisVerticalIcon } from '@heroicons/react/20/solid'
import { Fragment } from "react"; import { Fragment } from 'react'
type Props = { type Props = {
onDeleteClick: () => void; onDeleteClick: () => void
}; }
const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => ( const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => (
<Menu as="div" className="relative flex-none"> <Menu as="div" className="relative flex-none">
@ -26,7 +26,7 @@ const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => (
{({ active }) => ( {({ active }) => (
<button <button
className={`${ 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`} } group flex w-full items-center rounded-md px-2 py-2 text-sm`}
onClick={onDeleteClick} onClick={onDeleteClick}
> >
@ -37,6 +37,6 @@ const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => (
</Menu.Items> </Menu.Items>
</Transition> </Transition>
</Menu> </Menu>
); )
export default ModelActionMenu; export default ModelActionMenu

View File

@ -1,21 +1,21 @@
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline"; import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'
type Props = { type Props = {
callback: () => void; callback: () => void
}; }
const ModelDownloadButton: React.FC<Props> = ({ callback }) => { const ModelDownloadButton: React.FC<Props> = ({ callback }) => {
return ( return (
<button <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} onClick={callback}
> >
<ArrowDownTrayIcon width={16} height={16} color="#FFFFFF" /> <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 Download
</span> </span>
</button> </button>
); )
}; }
export default ModelDownloadButton; export default ModelDownloadButton

View File

@ -1,23 +1,23 @@
import { toGigabytes } from "@/_utils/converter"; import { toGigabytes } from '@/_utils/converter'
type Props = { type Props = {
total: number; total: number
value: number; value: number
}; }
const ModelDownloadingButton: React.FC<Props> = ({ total, value }) => { const ModelDownloadingButton: React.FC<Props> = ({ total, value }) => {
return ( return (
<div className="flex flex-col gap-1"> <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... Downloading...
</button> </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"> <span className="text-xs font-medium text-gray-800">
{toGigabytes(value)} / {toGigabytes(total)} {toGigabytes(value)} / {toGigabytes(total)}
</span> </span>
</div> </div>
</div> </div>
); )
}; }
export default ModelDownloadingButton; export default ModelDownloadingButton

View File

@ -1,18 +1,18 @@
import React from "react"; import React from 'react'
import { DownloadState } from "@/_models/DownloadState"; import { DownloadState } from '@/_models/DownloadState'
import { import {
formatDownloadPercentage, formatDownloadPercentage,
formatDownloadSpeed, formatDownloadSpeed,
toGigabytes, toGigabytes,
} from "@/_utils/converter"; } from '@/_utils/converter'
type Props = { type Props = {
downloadState: DownloadState; downloadState: DownloadState
}; }
const ModelDownloadingRow: React.FC<Props> = ({ downloadState }) => ( const ModelDownloadingRow: React.FC<Props> = ({ downloadState }) => (
<tr <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} key={downloadState.fileName}
> >
<td className="flex flex-col whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"> <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)} {formatDownloadSpeed(downloadState.speed)}
</td> </td>
</tr> </tr>
); )
export default ModelDownloadingRow; export default ModelDownloadingRow

View File

@ -1,23 +1,23 @@
import React from "react"; import React from 'react'
import ModelTableHeader from "../ModelTableHeader"; import ModelTableHeader from '../ModelTableHeader'
import { DownloadState } from "@/_models/DownloadState"; import { DownloadState } from '@/_models/DownloadState'
import ModelDownloadingRow from "../ModelDownloadingRow"; import ModelDownloadingRow from '../ModelDownloadingRow'
type Props = { 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 }) => ( 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"> <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"> <tr className="rounded-t-lg">
{tableHeaders.map((item) => ( {tableHeaders.map((item) => (
<ModelTableHeader key={item} title={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> <span className="sr-only">Edit</span>
</th> </th>
</tr> </tr>
@ -29,6 +29,6 @@ const ModelDownloadingTable: React.FC<Props> = ({ downloadStates }) => (
</tbody> </tbody>
</table> </table>
</div> </div>
); )
export default React.memo(ModelDownloadingTable); export default React.memo(ModelDownloadingTable)

View File

@ -1,13 +1,13 @@
import Image from "next/image"; import Image from 'next/image'
import ModelInfoItem from "../ModelInfoItem"; import ModelInfoItem from '../ModelInfoItem'
import React from "react"; import React from 'react'
type Props = { type Props = {
modelName: string; modelName: string
inferenceTime: string; inferenceTime: string
hardware: string; hardware: string
pricing: string; pricing: string
}; }
const ModelInfo: React.FC<Props> = ({ const ModelInfo: React.FC<Props> = ({
modelName, modelName,
@ -15,8 +15,8 @@ const ModelInfo: React.FC<Props> = ({
hardware, hardware,
pricing, pricing,
}) => ( }) => (
<div className="flex flex-col rounded-lg border border-gray-200 p-3 gap-3"> <div className="flex flex-col gap-3 rounded-lg border border-gray-200 p-3">
<h2 className="font-semibold text-sm text-gray-900 dark:text-white"> <h2 className="text-sm font-semibold text-gray-900 dark:text-white">
{modelName} is available via Jan API {modelName} is available via Jan API
</h2> </h2>
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
@ -24,19 +24,19 @@ const ModelInfo: React.FC<Props> = ({
<ModelInfoItem description={hardware} name="Hardware" /> <ModelInfoItem description={hardware} name="Hardware" />
</div> </div>
<hr /> <hr />
<div className="flex justify-between items-center "> <div className="flex items-center justify-between ">
<div className="flex flex-col"> <div className="flex flex-col">
<h2 className="text-xl tracking-[-0.4px] font-semibold">{pricing}</h2> <h2 className="text-xl font-semibold tracking-[-0.4px]">{pricing}</h2>
<span className="text-xs leading-[18px] text-[#6B7280]"> <span className="text-xs leading-[18px] text-[#6B7280]">
Average Cost / Call Average Cost / Call
</span> </span>
</div> </div>
<button className="px-3 py-2 bg-[#1F2A37] flex gap-2 items-center rounded-lg"> <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="" /> <Image src={'icons/code.svg'} width={16} height={17} alt="" />
<span className="text-white text-sm font-medium">Get API Key</span> <span className="text-sm font-medium text-white">Get API Key</span>
</button> </button>
</div> </div>
</div> </div>
); )
export default React.memo(ModelInfo); export default React.memo(ModelInfo)

View File

@ -1,15 +1,15 @@
import React from "react"; import React from 'react'
type Props = { type Props = {
name: string; name: string
description: string; description: string
}; }
const ModelInfoItem: React.FC<Props> = ({ description, name }) => ( const ModelInfoItem: React.FC<Props> = ({ description, name }) => (
<div className="flex flex-col flex-1"> <div className="flex flex-1 flex-col">
<span className="text-gray-500 font-normal text-sm">{name}</span> <span className="text-sm font-normal text-gray-500">{name}</span>
<span className="font-normal text-sm">{description}</span> <span className="text-sm font-normal">{description}</span>
</div> </div>
); )
export default React.memo(ModelInfoItem); export default React.memo(ModelInfoItem)

View File

@ -1,13 +1,13 @@
"use client"; 'use client'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { TrashIcon } from "@heroicons/react/24/outline"; import { TrashIcon } from '@heroicons/react/24/outline'
import { showConfirmDeleteConversationModalAtom } from "@/_helpers/atoms/Modal.atom"; import { showConfirmDeleteConversationModalAtom } from '@/_helpers/atoms/Modal.atom'
const ModelMenu: React.FC = () => { const ModelMenu: React.FC = () => {
const setShowConfirmDeleteConversationModal = useSetAtom( const setShowConfirmDeleteConversationModal = useSetAtom(
showConfirmDeleteConversationModalAtom showConfirmDeleteConversationModalAtom
); )
return ( return (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@ -15,7 +15,7 @@ const ModelMenu: React.FC = () => {
<TrashIcon width={24} height={24} color="#9CA3AF" /> <TrashIcon width={24} height={24} color="#9CA3AF" />
</button> </button>
</div> </div>
); )
}; }
export default ModelMenu; export default ModelMenu

View File

@ -1,50 +1,50 @@
import React, { useCallback } from "react"; import React, { useCallback } from 'react'
import { ModelStatus, ModelStatusComponent } from "../ModelStatusComponent"; import { ModelStatus, ModelStatusComponent } from '../ModelStatusComponent'
import ModelActionMenu from "../ModelActionMenu"; import ModelActionMenu from '../ModelActionMenu'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import ModelActionButton, { ModelActionType } from "../ModelActionButton"; import ModelActionButton, { ModelActionType } from '../ModelActionButton'
import useStartStopModel from "@/_hooks/useStartStopModel"; import useStartStopModel from '@/_hooks/useStartStopModel'
import useDeleteModel from "@/_hooks/useDeleteModel"; import useDeleteModel from '@/_hooks/useDeleteModel'
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom"; import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import { toGigabytes } from "@/_utils/converter"; import { toGigabytes } from '@/_utils/converter'
type Props = { type Props = {
model: AssistantModel; model: AssistantModel
}; }
const ModelRow: React.FC<Props> = ({ model }) => { const ModelRow: React.FC<Props> = ({ model }) => {
const { startModel, stopModel } = useStartStopModel(); const { startModel, stopModel } = useStartStopModel()
const activeModel = useAtomValue(activeAssistantModelAtom); const activeModel = useAtomValue(activeAssistantModelAtom)
const { deleteModel } = useDeleteModel(); const { deleteModel } = useDeleteModel()
let status = ModelStatus.Installed; let status = ModelStatus.Installed
if (activeModel && activeModel._id === model._id) { 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) { if (activeModel && activeModel._id === model._id) {
actionButtonType = ModelActionType.Stop; actionButtonType = ModelActionType.Stop
} }
const onModelActionClick = (action: ModelActionType) => { const onModelActionClick = (action: ModelActionType) => {
if (action === ModelActionType.Start) { if (action === ModelActionType.Start) {
startModel(model._id); startModel(model._id)
} else { } else {
stopModel(model._id); stopModel(model._id)
}
} }
};
const onDeleteClick = useCallback(() => { const onDeleteClick = useCallback(() => {
deleteModel(model); deleteModel(model)
}, [model]); }, [model])
return ( 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"> <td className="flex flex-col whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900">
{model.name} {model.name}
<span className="text-gray-500 font-normal">{model.version}</span> <span className="font-normal text-gray-500">{model.version}</span>
</td> </td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500"> <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
<div className="flex flex-col justify-start"> <div className="flex flex-col justify-start">
@ -61,11 +61,11 @@ const ModelRow: React.FC<Props> = ({ model }) => {
type={actionButtonType} type={actionButtonType}
onActionClick={onModelActionClick} 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} /> <ModelActionMenu onDeleteClick={onDeleteClick} />
</td> </td>
</tr> </tr>
); )
}; }
export default ModelRow; export default ModelRow

View File

@ -1,31 +1,31 @@
"use client"; 'use client'
import { searchingModelText } from "@/_helpers/JotaiWrapper"; import { searchingModelText } from '@/_helpers/JotaiWrapper'
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react'
const ModelSearchBar: React.FC = () => { const ModelSearchBar: React.FC = () => {
const setSearchtext = useSetAtom(searchingModelText); const setSearchtext = useSetAtom(searchingModelText)
const [text, setText] = useState(""); const [text, setText] = useState('')
useEffect(() => { useEffect(() => {
setSearchtext(text); setSearchtext(text)
}, [text, setSearchtext]); }, [text, setSearchtext])
return ( return (
<div className="py-[27px] flex items-center justify-center"> <div className="flex items-center justify-center py-[27px]">
<div className="w-[520px] h-[42px] flex items-center"> <div className="flex h-[42px] w-[520px] items-center">
<input <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" placeholder="Search model"
value={text} value={text}
onChange={(text) => setText(text.currentTarget.value)} 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" /> <MagnifyingGlassIcon width={20} height={20} color="#FFFFFF" />
</button> </button>
</div> </div>
</div> </div>
); )
}; }
export default ModelSearchBar; export default ModelSearchBar

View File

@ -1,31 +1,31 @@
import { Fragment, useEffect } from "react"; import { Fragment, useEffect } from 'react'
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from '@headlessui/react'
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid"; import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'
import { useAtom, useAtomValue } from "jotai"; import { useAtom, useAtomValue } from 'jotai'
import { selectedModelAtom } from "@/_helpers/atoms/Model.atom"; import { selectedModelAtom } from '@/_helpers/atoms/Model.atom'
import { downloadedModelAtom } from "@/_helpers/atoms/DownloadedModel.atom"; import { downloadedModelAtom } from '@/_helpers/atoms/DownloadedModel.atom'
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
function classNames(...classes: any) { function classNames(...classes: any) {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(' ')
} }
const SelectModels: React.FC = () => { const SelectModels: React.FC = () => {
const downloadedModels = useAtomValue(downloadedModelAtom); const downloadedModels = useAtomValue(downloadedModelAtom)
const [selectedModel, setSelectedModel] = useAtom(selectedModelAtom); const [selectedModel, setSelectedModel] = useAtom(selectedModelAtom)
useEffect(() => { useEffect(() => {
if (downloadedModels && downloadedModels.length > 0) { if (downloadedModels && downloadedModels.length > 0) {
onModelSelected(downloadedModels[0]); onModelSelected(downloadedModels[0])
} }
}, [downloadedModels]); }, [downloadedModels])
const onModelSelected = (model: AssistantModel) => { const onModelSelected = (model: AssistantModel) => {
setSelectedModel(model); setSelectedModel(model)
}; }
if (!selectedModel) { if (!selectedModel) {
return <div>You have not downloaded any model!</div>; return <div>You have not downloaded any model!</div>
} }
return ( return (
@ -68,8 +68,8 @@ const SelectModels: React.FC = () => {
key={model._id} key={model._id}
className={({ active }) => className={({ active }) =>
classNames( classNames(
active ? "bg-blue-600 text-white" : "text-gray-900", active ? 'bg-blue-600 text-white' : 'text-gray-900',
"relative cursor-default select-none py-2 pl-3 pr-9", 'relative cursor-default select-none py-2 pl-3 pr-9'
) )
} }
value={model} value={model}
@ -84,8 +84,8 @@ const SelectModels: React.FC = () => {
/> />
<span <span
className={classNames( className={classNames(
selected ? "font-semibold" : "font-normal", selected ? 'font-semibold' : 'font-normal',
"ml-3 block truncate", 'ml-3 block truncate'
)} )}
> >
{model.name} {model.name}
@ -95,8 +95,8 @@ const SelectModels: React.FC = () => {
{selected ? ( {selected ? (
<span <span
className={classNames( className={classNames(
active ? "text-white" : "text-blue-600", active ? 'text-white' : 'text-blue-600',
"absolute inset-y-0 right-0 flex items-center pr-4", 'absolute inset-y-0 right-0 flex items-center pr-4'
)} )}
> >
<CheckIcon className="h-5 w-5" aria-hidden="true" /> <CheckIcon className="h-5 w-5" aria-hidden="true" />
@ -112,7 +112,7 @@ const SelectModels: React.FC = () => {
</div> </div>
)} )}
</Listbox> </Listbox>
); )
}; }
export default SelectModels; export default SelectModels

View File

@ -1,10 +1,10 @@
import React from "react"; import React from 'react'
export type ModelStatusType = { export type ModelStatusType = {
title: string; title: string
textColor: string; textColor: string
backgroundColor: string; backgroundColor: string
}; }
export enum ModelStatus { export enum ModelStatus {
Installed, Installed,
@ -14,33 +14,33 @@ export enum ModelStatus {
export const ModelStatusMapper: Record<ModelStatus, ModelStatusType> = { export const ModelStatusMapper: Record<ModelStatus, ModelStatusType> = {
[ModelStatus.Installed]: { [ModelStatus.Installed]: {
title: "Installed", title: 'Installed',
textColor: "text-black", textColor: 'text-black',
backgroundColor: "bg-gray-100", backgroundColor: 'bg-gray-100',
}, },
[ModelStatus.Active]: { [ModelStatus.Active]: {
title: "Active", title: 'Active',
textColor: "text-black", textColor: 'text-black',
backgroundColor: "bg-green-100", backgroundColor: 'bg-green-100',
}, },
[ModelStatus.RunningInNitro]: { [ModelStatus.RunningInNitro]: {
title: "Running in Nitro", title: 'Running in Nitro',
textColor: "text-black", textColor: 'text-black',
backgroundColor: "bg-green-100", backgroundColor: 'bg-green-100',
}, },
}; }
type Props = { type Props = {
status: ModelStatus; status: ModelStatus
}; }
export const ModelStatusComponent: React.FC<Props> = ({ status }) => { export const ModelStatusComponent: React.FC<Props> = ({ status }) => {
const statusType = ModelStatusMapper[status]; const statusType = ModelStatusMapper[status]
return ( return (
<div <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} {statusType.title}
</div> </div>
); )
}; }

View File

@ -1,23 +1,23 @@
import React from "react"; import React from 'react'
import ModelRow from "../ModelRow"; import ModelRow from '../ModelRow'
import ModelTableHeader from "../ModelTableHeader"; import ModelTableHeader from '../ModelTableHeader'
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
type Props = { 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 }) => ( 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"> <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"> <tr className="rounded-t-lg">
{tableHeaders.map((item) => ( {tableHeaders.map((item) => (
<ModelTableHeader key={item} title={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> <span className="sr-only">Edit</span>
</th> </th>
</tr> </tr>
@ -29,6 +29,6 @@ const ModelTable: React.FC<Props> = ({ models }) => (
</tbody> </tbody>
</table> </table>
</div> </div>
); )
export default React.memo(ModelTable); export default React.memo(ModelTable)

View File

@ -1,16 +1,16 @@
import React from "react"; import React from 'react'
type Props = { type Props = {
title: string; title: string
}; }
const ModelTableHeader: React.FC<Props> = ({ title }) => ( const ModelTableHeader: React.FC<Props> = ({ title }) => (
<th <th
scope="col" 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} {title}
</th> </th>
); )
export default React.memo(ModelTableHeader); export default React.memo(ModelTableHeader)

View File

@ -1,70 +1,70 @@
import React, { useMemo } from "react"; import React, { useMemo } from 'react'
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter"; import { formatDownloadPercentage, toGigabytes } from '@/_utils/converter'
import Image from "next/image"; import Image from 'next/image'
import { Product } from "@/_models/Product"; import { Product } from '@/_models/Product'
import useDownloadModel from "@/_hooks/useDownloadModel"; import useDownloadModel from '@/_hooks/useDownloadModel'
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom"; import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { atom, useAtomValue } from "jotai"; import { atom, useAtomValue } from 'jotai'
import { ModelVersion } from "@/_models/ModelVersion"; import { ModelVersion } from '@/_models/ModelVersion'
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels"; import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import SimpleTag from "../SimpleTag"; import SimpleTag from '../SimpleTag'
import { RamRequired, UsecaseTag } from "../SimpleTag/TagType"; import { RamRequired, UsecaseTag } from '../SimpleTag/TagType'
type Props = { type Props = {
model: Product; model: Product
modelVersion: ModelVersion; modelVersion: ModelVersion
isRecommended: boolean; isRecommended: boolean
}; }
const ModelVersionItem: React.FC<Props> = ({ const ModelVersionItem: React.FC<Props> = ({
model, model,
modelVersion, modelVersion,
isRecommended, isRecommended,
}) => { }) => {
const { downloadModel } = useDownloadModel(); const { downloadModel } = useDownloadModel()
const { downloadedModels } = useGetDownloadedModels(); const { downloadedModels } = useGetDownloadedModels()
const isDownloaded = const isDownloaded =
downloadedModels.find((model) => model._id === modelVersion._id) != null; downloadedModels.find((model) => model._id === modelVersion._id) != null
const downloadAtom = useMemo( const downloadAtom = useMemo(
() => atom((get) => get(modelDownloadStateAtom)[modelVersion._id ?? ""]), () => atom((get) => get(modelDownloadStateAtom)[modelVersion._id ?? '']),
[modelVersion._id] [modelVersion._id]
); )
const downloadState = useAtomValue(downloadAtom); const downloadState = useAtomValue(downloadAtom)
const onDownloadClick = () => { const onDownloadClick = () => {
downloadModel(model, modelVersion); downloadModel(model, modelVersion)
}; }
let downloadButton = ( let downloadButton = (
<button <button
className="text-indigo-600 text-sm font-medium" className="text-sm font-medium text-indigo-600"
onClick={onDownloadClick} onClick={onDownloadClick}
> >
Download Download
</button> </button>
); )
if (downloadState) { if (downloadState) {
downloadButton = ( downloadButton = (
<div>{formatDownloadPercentage(downloadState.percent)}</div> <div>{formatDownloadPercentage(downloadState.percent)}</div>
); )
} else if (isDownloaded) { } else if (isDownloaded) {
downloadButton = <div>Downloaded</div>; downloadButton = <div>Downloaded</div>
} }
const { maxRamRequired, usecase } = modelVersion; const { maxRamRequired, usecase } = modelVersion
return ( 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"> <div className="flex items-center gap-2">
<Image src={"/icons/app_icon.svg"} width={14} height={20} alt="" /> <Image src={'/icons/app_icon.svg'} width={14} height={20} alt="" />
<span className="font-sm text-gray-900 flex-1"> <span className="font-sm flex-1 text-gray-900">
{modelVersion.name} {modelVersion.name}
</span> </span>
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex gap-2 justify-end"> <div className="flex justify-end gap-2">
<SimpleTag <SimpleTag
title={usecase} title={usecase}
type={UsecaseTag.UsecaseDefault} type={UsecaseTag.UsecaseDefault}
@ -76,13 +76,13 @@ const ModelVersionItem: React.FC<Props> = ({
clickable={false} clickable={false}
/> />
</div> </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)} {toGigabytes(modelVersion.size)}
</div> </div>
{downloadButton} {downloadButton}
</div> </div>
</div> </div>
); )
}; }
export default ModelVersionItem; export default ModelVersionItem

View File

@ -1,13 +1,13 @@
import React from "react"; import React from 'react'
import ModelVersionItem from "../ModelVersionItem"; import ModelVersionItem from '../ModelVersionItem'
import { Product } from "@/_models/Product"; import { Product } from '@/_models/Product'
import { ModelVersion } from "@/_models/ModelVersion"; import { ModelVersion } from '@/_models/ModelVersion'
type Props = { type Props = {
model: Product; model: Product
versions: ModelVersion[]; versions: ModelVersion[]
recommendedVersion: string; recommendedVersion: string
}; }
const ModelVersionList: React.FC<Props> = ({ const ModelVersionList: React.FC<Props> = ({
model, model,
@ -15,11 +15,11 @@ const ModelVersionList: React.FC<Props> = ({
recommendedVersion, recommendedVersion,
}) => { }) => {
return ( 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"> <div className="text-sm font-medium text-gray-500">
Available Versions Available Versions
</div> </div>
<div className="border border-gray-200 rounded-lg overflow-hidden"> <div className="overflow-hidden rounded-lg border border-gray-200">
{versions.map((item) => ( {versions.map((item) => (
<ModelVersionItem <ModelVersionItem
key={item._id} key={item._id}
@ -30,7 +30,7 @@ const ModelVersionList: React.FC<Props> = ({
))} ))}
</div> </div>
</div> </div>
); )
}; }
export default ModelVersionList; export default ModelVersionList

View File

@ -1,24 +1,24 @@
import ProgressBar from "../ProgressBar"; import ProgressBar from '../ProgressBar'
import SystemItem from "../SystemItem"; import SystemItem from '../SystemItem'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import { appDownloadProgress } from "@/_helpers/JotaiWrapper"; import { appDownloadProgress } from '@/_helpers/JotaiWrapper'
import useGetAppVersion from "@/_hooks/useGetAppVersion"; import useGetAppVersion from '@/_hooks/useGetAppVersion'
import useGetSystemResources from "@/_hooks/useGetSystemResources"; import useGetSystemResources from '@/_hooks/useGetSystemResources'
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom"; import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { DownloadState } from "@/_models/DownloadState"; import { DownloadState } from '@/_models/DownloadState'
import { formatDownloadPercentage } from "@/_utils/converter"; import { formatDownloadPercentage } from '@/_utils/converter'
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom"; import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
const MonitorBar: React.FC = () => { const MonitorBar: React.FC = () => {
const progress = useAtomValue(appDownloadProgress); const progress = useAtomValue(appDownloadProgress)
const activeModel = useAtomValue(activeAssistantModelAtom); const activeModel = useAtomValue(activeAssistantModelAtom)
const { version } = useGetAppVersion(); const { version } = useGetAppVersion()
const { ram, cpu } = useGetSystemResources(); const { ram, cpu } = useGetSystemResources()
const modelDownloadStates = useAtomValue(modelDownloadStateAtom); const modelDownloadStates = useAtomValue(modelDownloadStateAtom)
const downloadStates: DownloadState[] = []; const downloadStates: DownloadState[] = []
for (const [, value] of Object.entries(modelDownloadStates)) { for (const [, value] of Object.entries(modelDownloadStates)) {
downloadStates.push(value); downloadStates.push(value)
} }
return ( return (
@ -26,7 +26,7 @@ const MonitorBar: React.FC = () => {
{progress && progress >= 0 ? ( {progress && progress >= 0 ? (
<ProgressBar total={100} used={progress} /> <ProgressBar total={100} used={progress} />
) : null} ) : 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 && ( {downloadStates.length > 0 && (
<SystemItem <SystemItem
name="Downloading" name="Downloading"
@ -38,12 +38,12 @@ const MonitorBar: React.FC = () => {
<SystemItem name="CPU" value={`${cpu}%`} /> <SystemItem name="CPU" value={`${cpu}%`} />
<SystemItem name="Mem" value={`${ram}%`} /> <SystemItem name="Mem" value={`${ram}%`} />
{activeModel && ( {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>
</div> </div>
); )
}; }
export default MonitorBar; export default MonitorBar

View File

@ -1,17 +1,17 @@
import HeaderTitle from "../HeaderTitle"; import HeaderTitle from '../HeaderTitle'
import DownloadedModelTable from "../DownloadedModelTable"; import DownloadedModelTable from '../DownloadedModelTable'
import ActiveModelTable from "../ActiveModelTable"; import ActiveModelTable from '../ActiveModelTable'
import DownloadingModelTable from "../DownloadingModelTable"; import DownloadingModelTable from '../DownloadingModelTable'
const MyModelContainer: React.FC = () => ( const MyModelContainer: React.FC = () => (
<div className="flex flex-col flex-1 pt-[60px]"> <div className="flex flex-1 flex-col pt-[60px]">
<HeaderTitle title="My Models" className="pl-[63px] pr-[89px]" /> <HeaderTitle title="My Models" className="pl-[63px] pr-[89px]" />
<div className="pb-6 overflow-y-auto scroll"> <div className="scroll overflow-y-auto pb-6">
<ActiveModelTable /> <ActiveModelTable />
<DownloadingModelTable /> <DownloadingModelTable />
<DownloadedModelTable /> <DownloadedModelTable />
</div> </div>
</div> </div>
); )
export default MyModelContainer; export default MyModelContainer

View File

@ -1,45 +1,45 @@
"use client"; 'use client'
import React from "react"; import React from 'react'
import SecondaryButton from "../SecondaryButton"; import SecondaryButton from '../SecondaryButton'
import { useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from 'jotai'
import { import {
MainViewState, MainViewState,
setMainViewStateAtom, setMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom"; } from '@/_helpers/atoms/MainView.atom'
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from '@/_hooks/useCreateConversation'
import useInitModel from "@/_hooks/useInitModel"; import useInitModel from '@/_hooks/useInitModel'
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from '@heroicons/react/24/outline'
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom"; import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
const NewChatButton: React.FC = () => { const NewChatButton: React.FC = () => {
const activeModel = useAtomValue(activeAssistantModelAtom); const activeModel = useAtomValue(activeAssistantModelAtom)
const setMainView = useSetAtom(setMainViewStateAtom); const setMainView = useSetAtom(setMainViewStateAtom)
const { requestCreateConvo } = useCreateConversation(); const { requestCreateConvo } = useCreateConversation()
const { initModel } = useInitModel(); const { initModel } = useInitModel()
const onClick = () => { const onClick = () => {
if (!activeModel) { if (!activeModel) {
setMainView(MainViewState.ConversationEmptyModel); setMainView(MainViewState.ConversationEmptyModel)
} else { } else {
createConversationAndInitModel(activeModel); createConversationAndInitModel(activeModel)
}
} }
};
const createConversationAndInitModel = async (model: AssistantModel) => { const createConversationAndInitModel = async (model: AssistantModel) => {
await requestCreateConvo(model); await requestCreateConvo(model)
await initModel(model); await initModel(model)
}; }
return ( return (
<SecondaryButton <SecondaryButton
title={"New Chat"} title={'New Chat'}
onClick={onClick} onClick={onClick}
className="my-5 mx-3" className="mx-3 my-5"
icon={<PlusIcon width={16} height={16} />} icon={<PlusIcon width={16} height={16} />}
/> />
); )
}; }
export default React.memo(NewChatButton); export default React.memo(NewChatButton)

View File

@ -1,25 +1,32 @@
"use client"; 'use client'
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from 'react'
import { plugins, extensionPoints } from "@/../../electron/core/plugin-manager/execution/index"; import {
import { ChartPieIcon, CommandLineIcon, PlayIcon } from "@heroicons/react/24/outline"; 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 { MagnifyingGlassIcon } from '@heroicons/react/20/solid'
import classNames from "classnames"; import classNames from 'classnames'
import { PluginService, preferences } from "@janhq/core"; import { PluginService, preferences } from '@janhq/core'
import { execute } from "../../../electron/core/plugin-manager/execution/extension-manager"; import { execute } from '../../../electron/core/plugin-manager/execution/extension-manager'
import LoadingIndicator from "./LoadingIndicator"; import LoadingIndicator from './LoadingIndicator'
export const Preferences = () => { export const Preferences = () => {
const [search, setSearch] = useState<string>(""); const [search, setSearch] = useState<string>('')
const [activePlugins, setActivePlugins] = useState<any[]>([]); const [activePlugins, setActivePlugins] = useState<any[]>([])
const [preferenceItems, setPreferenceItems] = useState<any[]>([]); const [preferenceItems, setPreferenceItems] = useState<any[]>([])
const [preferenceValues, setPreferenceValues] = useState<any[]>([]); const [preferenceValues, setPreferenceValues] = useState<any[]>([])
const [isTestAvailable, setIsTestAvailable] = useState(false); const [isTestAvailable, setIsTestAvailable] = useState(false)
const [fileName, setFileName] = useState(""); const [fileName, setFileName] = useState('')
const [pluginCatalog, setPluginCatalog] = useState<any[]>([]); const [pluginCatalog, setPluginCatalog] = useState<any[]>([])
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false)
const experimentRef = useRef(null); const experimentRef = useRef(null)
const preferenceRef = useRef(null); const preferenceRef = useRef(null)
/** /**
* Loads the plugin catalog module from a CDN and sets it as the plugin catalog state. * Loads the plugin catalog module from a CDN and sets it as the plugin catalog state.
@ -28,10 +35,10 @@ export const Preferences = () => {
useEffect(() => { useEffect(() => {
// @ts-ignore // @ts-ignore
import(/* webpackIgnore: true */ PLUGIN_CATALOGS).then((module) => { import(/* webpackIgnore: true */ PLUGIN_CATALOGS).then((module) => {
console.log(module); console.log(module)
setPluginCatalog(module.default); setPluginCatalog(module.default)
}); })
}, []); }, [])
/** /**
* Fetches the active plugins and their preferences from the `plugins` and `preferences` modules. * Fetches the active plugins and their preferences from the `plugins` and `preferences` modules.
@ -42,36 +49,42 @@ export const Preferences = () => {
*/ */
useEffect(() => { useEffect(() => {
const getActivePlugins = async () => { const getActivePlugins = async () => {
const plgs = await plugins.getActive(); const plgs = await plugins.getActive()
setActivePlugins(plgs); setActivePlugins(plgs)
if (extensionPoints.get("experimentComponent")) { if (extensionPoints.get('experimentComponent')) {
const components = await Promise.all(extensionPoints.execute("experimentComponent")); const components = await Promise.all(
extensionPoints.execute('experimentComponent')
)
if (components.length > 0) { if (components.length > 0) {
setIsTestAvailable(true); setIsTestAvailable(true)
} }
components.forEach((e) => { components.forEach((e) => {
if (experimentRef.current) { if (experimentRef.current) {
// @ts-ignore // @ts-ignore
experimentRef.current.appendChild(e); experimentRef.current.appendChild(e)
} }
}); })
} }
if (extensionPoints.get("PluginPreferences")) { if (extensionPoints.get('PluginPreferences')) {
const data = await Promise.all(extensionPoints.execute("PluginPreferences")); const data = await Promise.all(
setPreferenceItems(Array.isArray(data) ? data : []); extensionPoints.execute('PluginPreferences')
)
setPreferenceItems(Array.isArray(data) ? data : [])
Promise.all( Promise.all(
(Array.isArray(data) ? data : []).map((e) => (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) => { ).then((data) => {
setPreferenceValues(data); setPreferenceValues(data)
}); })
} }
}; }
getActivePlugins(); getActivePlugins()
}, []); }, [])
/** /**
* Installs a plugin by calling the `plugins.install` function with the plugin file path. * 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. * @param e - The event object.
*/ */
const install = async (e: any) => { const install = async (e: any) => {
e.preventDefault(); e.preventDefault()
//@ts-ignore //@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 // Send the filename of the to be installed plugin
// to the main process for installation // to the main process for installation
const installed = await plugins.install([pluginFile]); const installed = await plugins.install([pluginFile])
if (installed) window.coreAPI?.relaunch(); if (installed) window.coreAPI?.relaunch()
}; }
/** /**
* Uninstalls a plugin by calling the `plugins.uninstall` function with the plugin name. * 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) => { const uninstall = async (name: string) => {
// Send the filename of the to be uninstalled plugin // Send the filename of the to be uninstalled plugin
// to the main process for removal // to the main process for removal
const res = await plugins.uninstall([name]); const res = await plugins.uninstall([name])
if (res) window.coreAPI?.relaunch(); if (res) window.coreAPI?.relaunch()
}; }
/** /**
* Updates a plugin by calling the `window.pluggableElectronIpc.update` function with the plugin name. * 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. * @param plugin - The name of the plugin to update.
*/ */
const update = async (plugin: string) => { const update = async (plugin: string) => {
if (typeof window !== "undefined") { if (typeof window !== 'undefined') {
// @ts-ignore // @ts-ignore
await window.pluggableElectronIpc.update([plugin], true); await window.pluggableElectronIpc.update([plugin], true)
window.coreAPI?.relaunch(); window.coreAPI?.relaunch()
}
} }
};
/** /**
* Downloads a remote plugin tarball and installs it using the `plugins.install` function. * 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. * @param pluginName - The name of the remote plugin to download and install.
*/ */
const downloadTarball = async (pluginName: string) => { const downloadTarball = async (pluginName: string) => {
setIsLoading(true); setIsLoading(true)
const pluginPath = await window.coreAPI?.installRemotePlugin(pluginName); const pluginPath = await window.coreAPI?.installRemotePlugin(pluginName)
const installed = await plugins.install([pluginPath]); const installed = await plugins.install([pluginPath])
setIsLoading(false); setIsLoading(false)
if (installed) window.coreAPI.relaunch(); if (installed) window.coreAPI.relaunch()
}; }
/** /**
* Notifies plugins of a preference update by executing the `PluginService.OnPreferencesUpdate` event. * 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. * 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() { function notifyPreferenceUpdate() {
if (timeout) { 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. * @param event - The change event object.
*/ */
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0]
if (file) { if (file) {
setFileName(file.name); setFileName(file.name)
} else { } else {
setFileName(""); setFileName('')
}
} }
};
return ( return (
<div className="w-full h-screen overflow-scroll"> <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 shadow-sm sm:gap-x-6 sm:px-6 px-8"> <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 */} {/* 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"> <form className="relative flex flex-1" action="#" method="GET">
<label htmlFor="search-field" className="sr-only"> <label htmlFor="search-field" className="sr-only">
Search Search
@ -182,22 +195,25 @@ export const Preferences = () => {
</div> </div>
<main className="py-5"> <main className="py-5">
<div className="sm:px-6 px-8"> <div className="px-8 sm:px-6">
{/* Content */} {/* Content */}
<div className="flex flex-row items-center my-4"> <div className="my-4 flex flex-row items-center">
<ChartPieIcon width={30} /> <ChartPieIcon width={30} />
Install Plugin Install Plugin
</div> </div>
<form id="plugin-file" onSubmit={install}> <form id="plugin-file" onSubmit={install}>
<div className="flex flex-row items-center space-x-10"> <div className="flex flex-row items-center space-x-10">
<div className="flex items-center justify-center w-[300px]"> <div className="flex w-[300px] items-center justify-center">
<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"> <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 ? ( {!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"> <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>
<p className="text-xs text-gray-500 dark:text-gray-400">TGZ (MAX 50MB)</p>
</div> </div>
) : ( ) : (
<>{fileName}</> <>{fileName}</>
@ -216,8 +232,8 @@ export const Preferences = () => {
<button <button
type="submit" type="submit"
className={classNames( 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", '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" fileName ? 'bg-blue-500 hover:bg-blue-300' : 'bg-gray-500'
)} )}
> >
Install Plugin Install Plugin
@ -225,10 +241,10 @@ export const Preferences = () => {
<button <button
className={classNames( 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={() => { onClick={() => {
window.coreAPI?.reloadPlugins(); window.coreAPI?.reloadPlugins()
}} }}
> >
Reload Plugins Reload Plugins
@ -237,37 +253,49 @@ export const Preferences = () => {
</div> </div>
</form> </form>
<div className="flex flex-row items-center my-4"> <div className="my-4 flex flex-row items-center">
<CommandLineIcon width={30} /> <CommandLineIcon width={30} />
Installed Plugins Installed Plugins
</div> </div>
<div className="grid grid-cols-2 items-stretch gap-4"> <div className="grid grid-cols-2 items-stretch gap-4">
{activePlugins {activePlugins
.filter((e) => search.trim() === "" || e.name.toLowerCase().includes(search.toLowerCase())) .filter(
(e) =>
search.trim() === '' ||
e.name.toLowerCase().includes(search.toLowerCase())
)
.map((e) => ( .map((e) => (
<div <div
key={e.name} key={e.name}
data-testid="plugin-item" 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"> <div className="flex flex-row items-center space-x-2">
<span className="relative inline-block mt-1"> <span className="relative mt-1 inline-block">
<img className="h-14 w-14 rounded-md" src={e.icon ?? "icons/app_icon.svg"} alt="" /> <img
className="h-14 w-14 rounded-md"
src={e.icon ?? 'icons/app_icon.svg'}
alt=""
/>
</span> </span>
<div className="flex flex-col"> <div className="flex flex-col">
<p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white">{e.name}</p> <p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
<p className="font-normal text-gray-700 dark:text-gray-400">Version: {e.version}</p> {e.name}
</p>
<p className="font-normal text-gray-700 dark:text-gray-400">
Version: {e.version}
</p>
</div> </div>
</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"} {e.description ?? "Jan's Plugin"}
</p> </p>
<div className="flex flex-row space-x-5"> <div className="flex flex-row space-x-5">
<button <button
type="submit" type="submit"
onClick={() => { 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" 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 <button
type="submit" type="submit"
onClick={() => { 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" 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>
<div className="flex flex-row items-center my-4"> <div className="my-4 flex flex-row items-center">
<CommandLineIcon width={30} /> <CommandLineIcon width={30} />
Explore Plugins Explore Plugins
</div> </div>
<div className="grid grid-cols-2 items-stretch gap-4"> <div className="grid grid-cols-2 items-stretch gap-4">
{pluginCatalog {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) => ( .map((e: any) => (
<div <div
key={e.name} key={e.name}
data-testid="plugin-item" 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"> <div className="flex flex-row items-center space-x-2">
<span className="relative inline-block mt-1"> <span className="relative mt-1 inline-block">
<img className="h-14 w-14 rounded-md" src={e.icon ?? "icons/app_icon.svg"} alt="" /> <img
className="h-14 w-14 rounded-md"
src={e.icon ?? 'icons/app_icon.svg'}
alt=""
/>
</span> </span>
<div className="flex flex-col"> <div className="flex flex-col">
<p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white">{e.name}</p> <p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
<p className="font-normal text-gray-700 dark:text-gray-400">Version: {e.version}</p> {e.name}
</p>
<p className="font-normal text-gray-700 dark:text-gray-400">
Version: {e.version}
</p>
</div> </div>
</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"} {e.description ?? "Jan's Plugin"}
</p> </p>
<div className="flex flex-row space-x-5"> <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 <button
type="submit" type="submit"
onClick={() => downloadTarball(e.name)} onClick={() => downloadTarball(e.name)}
className={classNames( 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) activePlugins.some((p) => p.name === e.name)
? "bg-blue-500 hover:bg-blue-600" ? 'bg-blue-500 hover:bg-blue-600'
: "bg-red-500 hover:bg-red-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> </button>
)} )}
</div> </div>
@ -334,33 +378,41 @@ export const Preferences = () => {
))} ))}
</div> </div>
{activePlugins.length > 0 && isTestAvailable && ( {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} /> <PlayIcon width={30} />
Test Plugins Test Plugins
</div> </div>
)} )}
<div className="h-full w-full" ref={experimentRef}></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} /> <PlayIcon width={30} />
Preferences Preferences
</div> </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) => ( {preferenceItems?.map((e) => (
<div key={e.preferenceKey} className="flex flex-col mb-4"> <div key={e.preferenceKey} className="mb-4 flex flex-col">
<div> <div>
<span className="text-[16px] text-gray-600">Setting:</span>{" "} <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-900">
{e.preferenceName}
</span>
</div> </div>
<span className="text-[14px] text-gray-400">{e.preferenceDescription}</span> <span className="text-[14px] text-gray-400">
<div className="flex flex-row space-x-4 items-center mt-2"> {e.preferenceDescription}
</span>
<div className="mt-2 flex flex-row items-center space-x-4">
<input <input
className="text-gray-500 w-1/3 rounded-sm border-gray-300 border-[1px] h-8" 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} defaultValue={
preferenceValues.filter(
(v) => v.key === e.preferenceKey
)[0]?.value
}
onChange={(event) => { onChange={(event) => {
preferences preferences
.set(e.pluginName, e.preferenceKey, event.target.value) .set(e.pluginName, e.preferenceKey, event.target.value)
.then(() => notifyPreferenceUpdate()); .then(() => notifyPreferenceUpdate())
}} }}
></input> ></input>
</div> </div>
@ -370,11 +422,11 @@ export const Preferences = () => {
</div> </div>
</main> </main>
{isLoading && ( {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 /> <LoadingIndicator />
Installing... Installing...
</div> </div>
)} )}
</div> </div>
); )
}; }

View File

@ -1,11 +1,11 @@
import React from "react"; import React from 'react'
type Props = { type Props = {
title: string; title: string
onClick: () => void; onClick: () => void
fullWidth?: boolean; fullWidth?: boolean
className?: string; className?: string
}; }
const PrimaryButton: React.FC<Props> = ({ const PrimaryButton: React.FC<Props> = ({
title, title,
@ -16,12 +16,12 @@ const PrimaryButton: React.FC<Props> = ({
<button <button
onClick={onClick} onClick={onClick}
type="button" 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} ${ 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 " : "" fullWidth ? 'flex-1 ' : ''
}}`} }}`}
> >
{title} {title}
</button> </button>
); )
export default PrimaryButton; export default PrimaryButton

View File

@ -1,19 +1,19 @@
import Image from "next/image"; import Image from 'next/image'
type Props = { type Props = {
total: number; total: number
used: number; used: number
}; }
const ProgressBar: React.FC<Props> = ({ used, total }) => ( const ProgressBar: React.FC<Props> = ({ used, total }) => (
<div className="flex gap-2.5 items-center p-[10px]"> <div className="flex items-center gap-2.5 p-[10px]">
<div className="text-xs leading-[18px] gap-0.5 flex items-center"> <div className="flex items-center gap-0.5 text-xs leading-[18px]">
<Image src={"icons/app_icon.svg"} width={18} height={18} alt="" /> <Image src={'icons/app_icon.svg'} width={18} height={18} alt="" />
Updating Updating
</div> </div>
<div className="w-[150px] relative bg-blue-200 h-1 rounded-md flex"> <div className="relative flex h-1 w-[150px] rounded-md bg-blue-200">
<div <div
className="absolute top-0 left-0 h-full rounded-md bg-blue-600" className="absolute left-0 top-0 h-full rounded-md bg-blue-600"
style={{ width: `${((used / total) * 100).toFixed(2)}%` }} style={{ width: `${((used / total) * 100).toFixed(2)}%` }}
></div> ></div>
</div> </div>
@ -21,6 +21,6 @@ const ProgressBar: React.FC<Props> = ({ used, total }) => (
{((used / total) * 100).toFixed(0)}% {((used / total) * 100).toFixed(0)}%
</div> </div>
</div> </div>
); )
export default ProgressBar; export default ProgressBar

View File

@ -1,12 +1,12 @@
import { Fragment } from "react"; import { Fragment } from 'react'
import MainView from "../MainView"; import MainView from '../MainView'
import MonitorBar from "../MonitorBar"; import MonitorBar from '../MonitorBar'
const RightContainer = () => ( const RightContainer = () => (
<Fragment> <Fragment>
<MainView /> <MainView />
<MonitorBar /> <MonitorBar />
</Fragment> </Fragment>
); )
export default RightContainer; export default RightContainer

View File

@ -1,28 +1,28 @@
import { modelSearchAtom } from "@/_helpers/JotaiWrapper"; import { modelSearchAtom } from '@/_helpers/JotaiWrapper'
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from 'use-debounce'
export enum SearchType { export enum SearchType {
Model = "model", Model = 'model',
} }
type Props = { type Props = {
type?: SearchType; type?: SearchType
placeholder?: string; placeholder?: string
}; }
const SearchBar: React.FC<Props> = ({ type, placeholder }) => { const SearchBar: React.FC<Props> = ({ type, placeholder }) => {
const setModelSearch = useSetAtom(modelSearchAtom); const setModelSearch = useSetAtom(modelSearchAtom)
let placeholderText = placeholder ? placeholder : "Search (⌘K)"; let placeholderText = placeholder ? placeholder : 'Search (⌘K)'
const debounced = useDebouncedCallback((value) => { const debounced = useDebouncedCallback((value) => {
setModelSearch(value); setModelSearch(value)
}, 300); }, 300)
return ( return (
<div className="relative mt-3 flex items-center"> <div className="relative mt-3 flex items-center">
<div className="absolute top-0 left-2 h-full flex items-center"> <div className="absolute left-2 top-0 flex h-full items-center">
<MagnifyingGlassIcon <MagnifyingGlassIcon
width={16} width={16}
height={16} height={16}
@ -39,7 +39,7 @@ const SearchBar: React.FC<Props> = ({ type, placeholder }) => {
className="block w-full rounded-md border-0 py-1.5 pl-8 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" className="block w-full rounded-md border-0 py-1.5 pl-8 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/> />
</div> </div>
); )
}; }
export default SearchBar; export default SearchBar

View File

@ -1,12 +1,12 @@
import React from "react"; import React from 'react'
type Props = { type Props = {
title: string; title: string
onClick?: () => void; onClick?: () => void
disabled?: boolean; disabled?: boolean
className?: string; className?: string
icon?: React.ReactNode; icon?: React.ReactNode
}; }
const SecondaryButton: React.FC<Props> = ({ const SecondaryButton: React.FC<Props> = ({
title, title,
@ -19,11 +19,11 @@ const SecondaryButton: React.FC<Props> = ({
disabled={disabled} disabled={disabled}
type="button" type="button"
onClick={onClick} 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} {icon}
{title} {title}
</button> </button>
); )
export default React.memo(SecondaryButton); export default React.memo(SecondaryButton)

View File

@ -1,19 +1,19 @@
import { currentPromptAtom } from "@/_helpers/JotaiWrapper"; import { currentPromptAtom } from '@/_helpers/JotaiWrapper'
import { currentConvoStateAtom } from "@/_helpers/atoms/Conversation.atom"; import { currentConvoStateAtom } from '@/_helpers/atoms/Conversation.atom'
import useSendChatMessage from "@/_hooks/useSendChatMessage"; import useSendChatMessage from '@/_hooks/useSendChatMessage'
import { useAtom, useAtomValue } from "jotai"; import { useAtom, useAtomValue } from 'jotai'
const SendButton: React.FC = () => { const SendButton: React.FC = () => {
const [currentPrompt] = useAtom(currentPromptAtom); const [currentPrompt] = useAtom(currentPromptAtom)
const currentConvoState = useAtomValue(currentConvoStateAtom); const currentConvoState = useAtomValue(currentConvoStateAtom)
const { sendChatMessage } = useSendChatMessage(); const { sendChatMessage } = useSendChatMessage()
const isWaitingForResponse = currentConvoState?.waitingForResponse ?? false; const isWaitingForResponse = currentConvoState?.waitingForResponse ?? false
const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse; const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse
const disabledStyle = { const disabledStyle = {
backgroundColor: "#F3F4F6", backgroundColor: '#F3F4F6',
}; }
return ( return (
<button <button
@ -24,7 +24,7 @@ const SendButton: React.FC = () => {
> >
Send Send
</button> </button>
); )
}; }
export default SendButton; export default SendButton

View File

@ -1,11 +1,11 @@
"use client"; 'use client'
import React, { ReactNode } from "react"; import React, { ReactNode } from 'react'
import { SessionProvider } from "next-auth/react"; import { SessionProvider } from 'next-auth/react'
const SessionProviderWrapper: React.FC<{ children: ReactNode }> = ({ const SessionProviderWrapper: React.FC<{ children: ReactNode }> = ({
children, children,
}) => { }) => {
return <SessionProvider>{children}</SessionProvider>; return <SessionProvider>{children}</SessionProvider>
}; }
export default SessionProviderWrapper; export default SessionProviderWrapper

View File

@ -1,73 +1,73 @@
import Image from "next/image"; import Image from 'next/image'
import useCreateConversation from "@/_hooks/useCreateConversation"; import useCreateConversation from '@/_hooks/useCreateConversation'
import PrimaryButton from "../PrimaryButton"; import PrimaryButton from '../PrimaryButton'
import { useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react'
import { import {
MainViewState, MainViewState,
setMainViewStateAtom, setMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom"; } from '@/_helpers/atoms/MainView.atom'
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom"; import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import useInitModel from "@/_hooks/useInitModel"; import useInitModel from '@/_hooks/useInitModel'
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels"; import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import { AssistantModel } from "@/_models/AssistantModel"; import { AssistantModel } from '@/_models/AssistantModel'
enum ActionButton { enum ActionButton {
DownloadModel = "Download a Model", DownloadModel = 'Download a Model',
StartChat = "Start a Conversation", StartChat = 'Start a Conversation',
} }
const SidebarEmptyHistory: React.FC = () => { const SidebarEmptyHistory: React.FC = () => {
const { downloadedModels } = useGetDownloadedModels(); const { downloadedModels } = useGetDownloadedModels()
const activeModel = useAtomValue(activeAssistantModelAtom); const activeModel = useAtomValue(activeAssistantModelAtom)
const setMainView = useSetAtom(setMainViewStateAtom); const setMainView = useSetAtom(setMainViewStateAtom)
const { requestCreateConvo } = useCreateConversation(); const { requestCreateConvo } = useCreateConversation()
const [action, setAction] = useState(ActionButton.DownloadModel); const [action, setAction] = useState(ActionButton.DownloadModel)
const { initModel } = useInitModel(); const { initModel } = useInitModel()
useEffect(() => { useEffect(() => {
if (downloadedModels.length > 0) { if (downloadedModels.length > 0) {
setAction(ActionButton.StartChat); setAction(ActionButton.StartChat)
} else { } else {
setAction(ActionButton.DownloadModel); setAction(ActionButton.DownloadModel)
} }
}, [downloadedModels]); }, [downloadedModels])
const onClick = () => { const onClick = () => {
if (action === ActionButton.DownloadModel) { if (action === ActionButton.DownloadModel) {
setMainView(MainViewState.ExploreModel); setMainView(MainViewState.ExploreModel)
} else { } else {
if (!activeModel) { if (!activeModel) {
setMainView(MainViewState.ConversationEmptyModel); setMainView(MainViewState.ConversationEmptyModel)
} else { } else {
createConversationAndInitModel(activeModel); createConversationAndInitModel(activeModel)
}
} }
} }
};
const createConversationAndInitModel = async (model: AssistantModel) => { const createConversationAndInitModel = async (model: AssistantModel) => {
await requestCreateConvo(model); await requestCreateConvo(model)
await initModel(model); await initModel(model)
}; }
return ( return (
<div className="flex flex-col items-center py-10 gap-3"> <div className="flex flex-col items-center gap-3 py-10">
<Image <Image
src={"icons/chat-bubble-oval-left.svg"} src={'icons/chat-bubble-oval-left.svg'}
width={32} width={32}
height={32} height={32}
alt="" alt=""
/> />
<div className="flex flex-col items-center gap-6"> <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-sm text-gray-900">No Chat History</div>
<div className="text-center text-gray-500 text-sm"> <div className="text-center text-sm text-gray-500">
Get started by creating a new chat. Get started by creating a new chat.
</div> </div>
<PrimaryButton title={action} onClick={onClick} /> <PrimaryButton title={action} onClick={onClick} />
</div> </div>
</div> </div>
); )
}; }
export default SidebarEmptyHistory; export default SidebarEmptyHistory

View File

@ -1,23 +1,23 @@
import React from "react"; import React from 'react'
import SecondaryButton from "../SecondaryButton"; import SecondaryButton from '../SecondaryButton'
const SidebarFooter: React.FC = () => ( 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 <SecondaryButton
title={"Discord"} title={'Discord'}
onClick={() => onClick={() =>
window.electronAPI?.openExternalUrl("https://discord.gg/AsJ8krTT3N") window.electronAPI?.openExternalUrl('https://discord.gg/AsJ8krTT3N')
} }
className="flex-1" className="flex-1"
/> />
<SecondaryButton <SecondaryButton
title={"Twitter"} title={'Twitter'}
onClick={() => onClick={() =>
window.electronAPI?.openExternalUrl("https://twitter.com/janhq_") window.electronAPI?.openExternalUrl('https://twitter.com/janhq_')
} }
className="flex-1" className="flex-1"
/> />
</div> </div>
); )
export default React.memo(SidebarFooter); export default React.memo(SidebarFooter)

View File

@ -1,10 +1,10 @@
import React from "react"; import React from 'react'
import Image from "next/image"; import Image from 'next/image'
const SidebarHeader: React.FC = () => ( const SidebarHeader: React.FC = () => (
<div className="flex flex-col gap-2.5 px-3"> <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> </div>
); )
export default React.memo(SidebarHeader); export default React.memo(SidebarHeader)

View File

@ -1,27 +1,27 @@
import React from "react"; import React from 'react'
import SidebarMenuItem from "../SidebarMenuItem"; import SidebarMenuItem from '../SidebarMenuItem'
import { MainViewState } from "@/_helpers/atoms/MainView.atom"; import { MainViewState } from '@/_helpers/atoms/MainView.atom'
const menu = [ const menu = [
{ {
name: "Explore Models", name: 'Explore Models',
icon: "Search_gray", icon: 'Search_gray',
state: MainViewState.ExploreModel, state: MainViewState.ExploreModel,
}, },
{ {
name: "My Models", name: 'My Models',
icon: "ViewGrid", icon: 'ViewGrid',
state: MainViewState.MyModel, state: MainViewState.MyModel,
}, },
{ {
name: "Settings", name: 'Settings',
icon: "Cog", icon: 'Cog',
state: MainViewState.Setting, state: MainViewState.Setting,
}, },
]; ]
const SidebarMenu: React.FC = () => ( 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) => ( {menu.map((item) => (
<SidebarMenuItem <SidebarMenuItem
title={item.name} title={item.name}
@ -31,6 +31,6 @@ const SidebarMenu: React.FC = () => (
/> />
))} ))}
</ul> </ul>
); )
export default React.memo(SidebarMenu); export default React.memo(SidebarMenu)

View File

@ -1,32 +1,32 @@
import React from "react"; import React from 'react'
import { useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from 'jotai'
import Image from "next/image"; import Image from 'next/image'
import { import {
MainViewState, MainViewState,
getMainViewStateAtom, getMainViewStateAtom,
setMainViewStateAtom, setMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom"; } from '@/_helpers/atoms/MainView.atom'
type Props = { type Props = {
title: string; title: string
viewState: MainViewState; viewState: MainViewState
iconName: string; iconName: string
}; }
const SidebarMenuItem: React.FC<Props> = ({ title, viewState, iconName }) => { const SidebarMenuItem: React.FC<Props> = ({ title, viewState, iconName }) => {
const currentState = useAtomValue(getMainViewStateAtom); const currentState = useAtomValue(getMainViewStateAtom)
const setMainViewState = useSetAtom(setMainViewStateAtom); const setMainViewState = useSetAtom(setMainViewStateAtom)
let className = 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) { if (currentState == viewState) {
className = 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 = () => { const onClick = () => {
setMainViewState(viewState); setMainViewState(viewState)
}; }
return ( return (
<li key={title}> <li key={title}>
@ -35,7 +35,7 @@ const SidebarMenuItem: React.FC<Props> = ({ title, viewState, iconName }) => {
<span className="truncate">{title}</span> <span className="truncate">{title}</span>
</button> </button>
</li> </li>
); )
}; }
export default SidebarMenuItem; export default SidebarMenuItem

View File

@ -1,6 +1,6 @@
type Props = { type Props = {
name: string; name: string
}; }
const SimpleCheckbox: React.FC<Props> = ({ name }) => ( const SimpleCheckbox: React.FC<Props> = ({ name }) => (
<div className="relative flex items-center gap-[11px]"> <div className="relative flex items-center gap-[11px]">
@ -17,6 +17,6 @@ const SimpleCheckbox: React.FC<Props> = ({ name }) => (
<label htmlFor="offers">{name}</label> <label htmlFor="offers">{name}</label>
</div> </div>
</div> </div>
); )
export default SimpleCheckbox; export default SimpleCheckbox

View File

@ -1,19 +1,19 @@
import { displayDate } from "@/_utils/datetime"; import { displayDate } from '@/_utils/datetime'
import Link from "next/link"; import Link from 'next/link'
import React from "react"; import React from 'react'
import JanImage from "./JanImage"; import JanImage from './JanImage'
import Image from "next/image"; import Image from 'next/image'
type Props = { type Props = {
avatarUrl?: string; avatarUrl?: string
senderName: string; senderName: string
text: string; text: string
createdAt: number; createdAt: number
imageUrls: string[]; imageUrls: string[]
}; }
const SimpleControlNetMessage: React.FC<Props> = ({ const SimpleControlNetMessage: React.FC<Props> = ({
avatarUrl = "", avatarUrl = '',
senderName, senderName,
imageUrls, imageUrls,
text, text,
@ -29,30 +29,30 @@ const SimpleControlNetMessage: React.FC<Props> = ({
alt="" alt=""
/> />
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex gap-1 justify-start items-baseline"> <div className="flex items-baseline justify-start gap-1">
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px]"> <div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B]">
{senderName} {senderName}
</div> </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)} {displayDate(createdAt)}
</div> </div>
</div> </div>
<div className="flex gap-3 flex-col"> <div className="flex flex-col gap-3">
<p className="leading-[20px] whitespace-break-spaces text-sm font-normal dark:text-[#d1d5db]"> <p className="whitespace-break-spaces text-sm font-normal leading-[20px] dark:text-[#d1d5db]">
{text} {text}
</p> </p>
<JanImage <JanImage
imageUrl={imageUrls[0]} 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 <Link
href={imageUrls[0] || "#"} href={imageUrls[0] || '#'}
target="_blank_" 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="" /> <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 Download
</span> </span>
</Link> </Link>
@ -60,7 +60,7 @@ const SimpleControlNetMessage: React.FC<Props> = ({
</div> </div>
</div> </div>
</div> </div>
); )
}; }
export default SimpleControlNetMessage; export default SimpleControlNetMessage

View File

@ -1,18 +1,18 @@
import Image from "next/image"; import Image from 'next/image'
import JanImage from "../JanImage"; import JanImage from '../JanImage'
import { displayDate } from "@/_utils/datetime"; import { displayDate } from '@/_utils/datetime'
import Link from "next/link"; import Link from 'next/link'
type Props = { type Props = {
avatarUrl?: string; avatarUrl?: string
senderName: string; senderName: string
text?: string; text?: string
createdAt: number; createdAt: number
imageUrls: string[]; imageUrls: string[]
}; }
const SimpleImageMessage: React.FC<Props> = ({ const SimpleImageMessage: React.FC<Props> = ({
avatarUrl = "", avatarUrl = '',
senderName, senderName,
imageUrls, imageUrls,
text, text,
@ -29,36 +29,36 @@ const SimpleImageMessage: React.FC<Props> = ({
alt="" alt=""
/> />
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex gap-1 justify-start items-baseline"> <div className="flex items-baseline justify-start gap-1">
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px]"> <div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B]">
{senderName} {senderName}
</div> </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)} {displayDate(createdAt)}
</div> </div>
</div> </div>
<div className="flex items-center gap-3 flex-col"> <div className="flex flex-col items-center gap-3">
<JanImage <JanImage
imageUrl={imageUrls[0]} 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 <Link
href={imageUrls[0] || "#"} href={imageUrls[0] || '#'}
target="_blank_" 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="" /> <Image src="icons/download.svg" width={16} height={16} alt="" />
<span className="leading-[20px] text-sm text-[#111928]"> <span className="text-sm leading-[20px] text-[#111928]">
Download Download
</span> </span>
</Link> </Link>
<button <button
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"
// onClick={() => sendChatMessage()} // onClick={() => sendChatMessage()}
> >
<Image src="icons/refresh.svg" width={16} height={16} alt="" /> <Image src="icons/refresh.svg" width={16} height={16} alt="" />
<span className="leading-[20px] text-sm text-[#111928]"> <span className="text-sm leading-[20px] text-[#111928]">
Re-generate Re-generate
</span> </span>
</button> </button>
@ -66,7 +66,7 @@ const SimpleImageMessage: React.FC<Props> = ({
</div> </div>
</div> </div>
</div> </div>
); )
}; }
export default SimpleImageMessage; export default SimpleImageMessage

View File

@ -1,21 +1,21 @@
import { TagType } from "./TagType"; import { TagType } from './TagType'
export const tagStyleMapper: Record<TagType, string> = { export const tagStyleMapper: Record<TagType, string> = {
GGUF: "bg-yellow-100 text-yellow-800", GGUF: 'bg-yellow-100 text-yellow-800',
PerformancePositive: 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: 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: PerformanceNegative:
"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", HardwareCompatible: 'bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10',
HardwareIncompatible: HardwareIncompatible:
"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',
FreeStyle: "bg-gray-100 text-gray-800", FreeStyle: 'bg-gray-100 text-gray-800',
ExpectPerformanceMedium: "bg-yellow-100 text-yellow-800", ExpectPerformanceMedium: 'bg-yellow-100 text-yellow-800',
Version: "bg-red-100 text-yellow-800", Version: 'bg-red-100 text-yellow-800',
Default: "bg-blue-100 text-blue-800", Default: 'bg-blue-100 text-blue-800',
RamDefault: "bg-green-50 text-green-700", RamDefault: 'bg-green-50 text-green-700',
UsecaseDefault: "bg-orange-100 text-yellow-800", UsecaseDefault: 'bg-orange-100 text-yellow-800',
MiscellanousDefault: "bg-blue-100 text-blue-800", MiscellanousDefault: 'bg-blue-100 text-blue-800',
}; }

View File

@ -1,51 +1,51 @@
export enum ModelPerformance { export enum ModelPerformance {
PerformancePositive = "PerformancePositive", PerformancePositive = 'PerformancePositive',
PerformanceNeutral = "PerformanceNeutral", PerformanceNeutral = 'PerformanceNeutral',
PerformanceNegative = "PerformanceNegative", PerformanceNegative = 'PerformanceNegative',
} }
export enum HardwareCompatibility { export enum HardwareCompatibility {
HardwareCompatible = "HardwareCompatible", HardwareCompatible = 'HardwareCompatible',
HardwareIncompatible = "HardwareIncompatible", HardwareIncompatible = 'HardwareIncompatible',
} }
export enum ExpectedPerformance { export enum ExpectedPerformance {
ExpectPerformanceMedium = "ExpectPerformanceMedium", ExpectPerformanceMedium = 'ExpectPerformanceMedium',
} }
export enum ModelFormat { export enum ModelFormat {
GGUF = "GGUF", GGUF = 'GGUF',
} }
export enum FreestyleTag { export enum FreestyleTag {
FreeStyle = "FreeStyle", FreeStyle = 'FreeStyle',
} }
export enum VersionTag { export enum VersionTag {
Version = "Version", Version = 'Version',
} }
export enum QuantMethodTag { export enum QuantMethodTag {
Default = "Default", Default = 'Default',
} }
export enum NumOfBit { export enum NumOfBit {
Default = "Default", Default = 'Default',
} }
export enum RamRequired { export enum RamRequired {
RamDefault = "RamDefault", RamDefault = 'RamDefault',
} }
export enum UsecaseTag { export enum UsecaseTag {
UsecaseDefault = "UsecaseDefault", UsecaseDefault = 'UsecaseDefault',
} }
export enum MiscellanousTag { export enum MiscellanousTag {
MiscellanousDefault = "MiscellanousDefault", MiscellanousDefault = 'MiscellanousDefault',
} }
export type TagType = export type TagType =
@ -59,4 +59,4 @@ export type TagType =
| NumOfBit | NumOfBit
| RamRequired | RamRequired
| UsecaseTag | UsecaseTag
| MiscellanousTag; | MiscellanousTag

View File

@ -1,13 +1,13 @@
import React from "react"; import React from 'react'
import { TagType } from "./TagType"; import { TagType } from './TagType'
import { tagStyleMapper } from "./TagStyleMapper"; import { tagStyleMapper } from './TagStyleMapper'
type Props = { type Props = {
title: string; title: string
type: TagType; type: TagType
clickable?: boolean; clickable?: boolean
onClick?: () => void; onClick?: () => void
}; }
const SimpleTag: React.FC<Props> = ({ const SimpleTag: React.FC<Props> = ({
onClick, onClick,
@ -15,25 +15,25 @@ const SimpleTag: React.FC<Props> = ({
title, title,
type, type,
}) => { }) => {
if (!title || title.length === 0) return null; if (!title || title.length === 0) return null
if (!clickable) { if (!clickable) {
return ( return (
<div <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} {title}
</div> </div>
); )
} }
return ( return (
<button <button
onClick={onClick} 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 {title} x
</button> </button>
); )
}; }
export default React.memo(SimpleTag); export default React.memo(SimpleTag)

View File

@ -1,52 +1,52 @@
import React from "react"; import React from 'react'
import { displayDate } from "@/_utils/datetime"; import { displayDate } from '@/_utils/datetime'
import Image from "next/image"; import Image from 'next/image'
import { MessageSenderType } from "@/_models/ChatMessage"; import { MessageSenderType } from '@/_models/ChatMessage'
import LoadingIndicator from "../LoadingIndicator"; import LoadingIndicator from '../LoadingIndicator'
import { Marked } from "marked"; import { Marked } from 'marked'
import { markedHighlight } from "marked-highlight"; import { markedHighlight } from 'marked-highlight'
import hljs from "highlight.js"; import hljs from 'highlight.js'
type Props = { type Props = {
avatarUrl: string; avatarUrl: string
senderName: string; senderName: string
createdAt: number; createdAt: number
senderType: MessageSenderType; senderType: MessageSenderType
text?: string; text?: string
}; }
const marked = new Marked( const marked = new Marked(
markedHighlight({ markedHighlight({
langPrefix: "hljs", langPrefix: 'hljs',
highlight(code, lang) { highlight(code, lang) {
if (lang === undefined || lang === "") { if (lang === undefined || lang === '') {
return hljs.highlightAuto(code).value; return hljs.highlightAuto(code).value
} }
return hljs.highlight(code, { language: lang }).value; return hljs.highlight(code, { language: lang }).value
}, },
}), }),
{ {
renderer: { renderer: {
code(code, lang, escaped) { code(code, lang, escaped) {
return `<pre class="hljs"><code class="language-${escape( return `<pre class="hljs"><code class="language-${escape(
lang ?? "" lang ?? ''
)}">${escaped ? code : escape(code)}</code></pre>`; )}">${escaped ? code : escape(code)}</code></pre>`
}, },
}, },
} }
); )
const SimpleTextMessage: React.FC<Props> = ({ const SimpleTextMessage: React.FC<Props> = ({
senderName, senderName,
createdAt, createdAt,
senderType, senderType,
avatarUrl = "", avatarUrl = '',
text = "", text = '',
}) => { }) => {
const backgroundColor = const backgroundColor =
senderType === MessageSenderType.User ? "" : "bg-gray-100"; senderType === MessageSenderType.User ? '' : 'bg-gray-100'
const parsedText = marked.parse(text); const parsedText = marked.parse(text)
return ( return (
<div <div
@ -59,26 +59,26 @@ const SimpleTextMessage: React.FC<Props> = ({
height={32} height={32}
alt="" alt=""
/> />
<div className="flex flex-col gap-1 w-full"> <div className="flex w-full flex-col gap-1">
<div className="flex gap-1 justify-start items-baseline"> <div className="flex items-baseline justify-start gap-1">
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px] dark:text-[#d1d5db]"> <div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B] dark:text-[#d1d5db]">
{senderName} {senderName}
</div> </div>
<div className="text-xs leading-[13.2px] font-medium text-gray-400"> <div className="text-xs font-medium leading-[13.2px] text-gray-400">
{displayDate(createdAt)} {displayDate(createdAt)}
</div> </div>
</div> </div>
{text === "" ? ( {text === '' ? (
<LoadingIndicator /> <LoadingIndicator />
) : ( ) : (
<span <span
className="text-sm leading-loose font-normal" className="text-sm font-normal leading-loose"
dangerouslySetInnerHTML={{ __html: parsedText }} dangerouslySetInnerHTML={{ __html: parsedText }}
/> />
)} )}
</div> </div>
</div> </div>
); )
}; }
export default React.memo(SimpleTextMessage); export default React.memo(SimpleTextMessage)

View File

@ -1,15 +1,15 @@
type Props = { type Props = {
name: string; name: string
value: string; value: string
}; }
const SystemItem: React.FC<Props> = ({ name, value }) => ( const SystemItem: React.FC<Props> = ({ name, value }) => (
<div className="flex gap-2 pl-4 my-1"> <div className="my-1 flex gap-2 pl-4">
<div className="flex gap-2.5 w-max font-bold text-gray-900 text-sm"> <div className="flex w-max gap-2.5 text-sm font-bold text-gray-900">
{name} {name}
</div> </div>
<span className="text-gray-900 text-sm">{value}</span> <span className="text-sm text-gray-900">{value}</span>
</div> </div>
); )
export default SystemItem; export default SystemItem

View File

@ -1,29 +1,29 @@
import Image from "next/image"; import Image from 'next/image'
type Props = { type Props = {
onTabClick: (clickedTab: "description" | "api") => void; onTabClick: (clickedTab: 'description' | 'api') => void
tab: string; tab: string
}; }
export const TabModelDetail: React.FC<Props> = ({ onTabClick, tab }) => { export const TabModelDetail: React.FC<Props> = ({ onTabClick, tab }) => {
const btns = [ const btns = [
{ {
name: "api", name: 'api',
icon: "icons/unicorn_arrow.svg", icon: 'icons/unicorn_arrow.svg',
}, },
{ {
name: "description", name: 'description',
icon: "icons/unicorn_exclamation-circle.svg", icon: 'icons/unicorn_exclamation-circle.svg',
}, },
]; ]
return ( return (
<div className="flex gap-0.5 rounded p-1 w-full bg-gray-200"> <div className="flex w-full gap-0.5 rounded bg-gray-200 p-1">
{btns.map((item, index) => ( {btns.map((item, index) => (
<button <button
key={index} key={index}
onClick={() => onTabClick(item.name as "description" | "api")} 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 ${ className={`relative flex w-1/2 items-center justify-center gap-2 px-3 py-[6px] text-sm capitalize leading-5 ${
tab !== item.name ? "" : "bg-white rounded shadow-sm" tab !== item.name ? '' : 'rounded bg-white shadow-sm'
}`} }`}
> >
<Image src={item.icon} width={20} height={20} alt="" /> <Image src={item.icon} width={20} height={20} alt="" />
@ -31,5 +31,5 @@ export const TabModelDetail: React.FC<Props> = ({ onTabClick, tab }) => {
</button> </button>
))} ))}
</div> </div>
); )
}; }

View File

@ -1,13 +1,13 @@
"use client"; 'use client'
import React from "react"; import React from 'react'
import { Popover } from "@headlessui/react"; import { Popover } from '@headlessui/react'
import { MenuHeader } from "../MenuHeader"; import { MenuHeader } from '../MenuHeader'
// import useGetCurrentUser from "@/_hooks/useGetCurrentUser"; // import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
const UserProfileDropDown: React.FC = () => { const UserProfileDropDown: React.FC = () => {
// const { loading, user } = useGetCurrentUser(); // const { loading, user } = useGetCurrentUser();
return <div></div>; return <div></div>
// if (loading || !user) { // if (loading || !user) {
// return <div></div>; // return <div></div>;
// } // }
@ -31,6 +31,6 @@ const UserProfileDropDown: React.FC = () => {
// </Popover> // </Popover>
// </Popover.Group> // </Popover.Group>
// ); // );
}; }
export default UserProfileDropDown; export default UserProfileDropDown

View File

@ -1,29 +1,29 @@
"use client"; 'use client'
import { currentConversationAtom } from "@/_helpers/atoms/Conversation.atom"; import { currentConversationAtom } from '@/_helpers/atoms/Conversation.atom'
import { useAtomValue } from "jotai"; import { useAtomValue } from 'jotai'
import Image from "next/image"; import Image from 'next/image'
const UserToolbar: React.FC = () => { const UserToolbar: React.FC = () => {
const currentConvo = useAtomValue(currentConversationAtom); const currentConvo = useAtomValue(currentConversationAtom)
const avatarUrl = currentConvo?.image; const avatarUrl = currentConvo?.image
const title = currentConvo?.summary ?? currentConvo?.name ?? ""; const title = currentConvo?.summary ?? currentConvo?.name ?? ''
return ( return (
<div className="flex items-center gap-3 p-1"> <div className="flex items-center gap-3 p-1">
<Image <Image
className="rounded-full aspect-square w-8 h-8" className="aspect-square h-8 w-8 rounded-full"
src={avatarUrl ?? "icons/app_icon.svg"} src={avatarUrl ?? 'icons/app_icon.svg'}
alt="" alt=""
width={36} width={36}
height={36} height={36}
/> />
<span className="flex gap-0.5 leading-6 text-base font-semibold"> <span className="flex gap-0.5 text-base font-semibold leading-6">
{title} {title}
</span> </span>
</div> </div>
); )
}; }
export default UserToolbar; export default UserToolbar

View File

@ -1,21 +1,21 @@
import { ChevronDownIcon } from "@heroicons/react/24/outline"; import { ChevronDownIcon } from '@heroicons/react/24/outline'
type Props = { type Props = {
callback: () => void; callback: () => void
}; }
const ViewModelDetailButton: React.FC<Props> = ({ callback }) => { const ViewModelDetailButton: React.FC<Props> = ({ callback }) => {
return ( return (
<div className="px-4 pb-4"> <div className="px-4 pb-4">
<button <button
onClick={callback} 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> <span className="text-xs leading-[18px]">View Details</span>
<ChevronDownIcon width={18} height={18} /> <ChevronDownIcon width={18} height={18} />
</button> </button>
</div> </div>
); )
}; }
export default ViewModelDetailButton; export default ViewModelDetailButton

View File

@ -1,30 +1,30 @@
import Image from "next/image"; import Image from 'next/image'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { import {
setMainViewStateAtom, setMainViewStateAtom,
MainViewState, MainViewState,
} from "@/_helpers/atoms/MainView.atom"; } from '@/_helpers/atoms/MainView.atom'
import SecondaryButton from "../SecondaryButton"; import SecondaryButton from '../SecondaryButton'
const Welcome: React.FC = () => { const Welcome: React.FC = () => {
const setMainViewState = useSetAtom(setMainViewStateAtom); const setMainViewState = useSetAtom(setMainViewStateAtom)
return ( return (
<div className="flex flex-col h-full"> <div className="flex h-full flex-col">
<div className="px-[200px] flex-1 flex flex-col gap-5 justify-center items-start"> <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="" /> <Image src={'icons/Jan_AppIcon.svg'} width={44} height={45} alt="" />
<span className="font-semibold text-gray-500 text-5xl"> <span className="text-5xl font-semibold text-gray-500">
Welcome, Welcome,
<br /> <br />
lets download your first model lets download your first model
</span> </span>
<SecondaryButton <SecondaryButton
title={"Explore models"} title={'Explore models'}
onClick={() => setMainViewState(MainViewState.ExploreModel)} onClick={() => setMainViewState(MainViewState.ExploreModel)}
/> />
</div> </div>
</div> </div>
); )
}; }
export default Welcome; export default Welcome

View File

@ -1,34 +1,47 @@
import { addNewMessageAtom, updateMessageAtom } from "@/_helpers/atoms/ChatMessage.atom"; import {
import { toChatMessage } from "@/_models/ChatMessage"; addNewMessageAtom,
import { events, EventName, NewMessageResponse } from "@janhq/core"; updateMessageAtom,
import { useSetAtom } from "jotai"; } from '@/_helpers/atoms/ChatMessage.atom'
import { ReactNode, useEffect } from "react"; 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 }) { export default function EventHandler({ children }: { children: ReactNode }) {
const addNewMessage = useSetAtom(addNewMessageAtom); const addNewMessage = useSetAtom(addNewMessageAtom)
const updateMessage = useSetAtom(updateMessageAtom); const updateMessage = useSetAtom(updateMessageAtom)
function handleNewMessageResponse(message: NewMessageResponse) { function handleNewMessageResponse(message: NewMessageResponse) {
const newResponse = toChatMessage(message); const newResponse = toChatMessage(message)
addNewMessage(newResponse); addNewMessage(newResponse)
} }
async function handleMessageResponseUpdate(messageResponse: NewMessageResponse) { async function handleMessageResponseUpdate(
if (messageResponse.conversationId && messageResponse._id && messageResponse.message) messageResponse: NewMessageResponse
updateMessage(messageResponse._id, messageResponse.conversationId, messageResponse.message); ) {
if (
messageResponse.conversationId &&
messageResponse._id &&
messageResponse.message
)
updateMessage(
messageResponse._id,
messageResponse.conversationId,
messageResponse.message
)
} }
useEffect(() => { useEffect(() => {
if (window.corePlugin.events) { if (window.corePlugin.events) {
events.on(EventName.OnNewMessageResponse, handleNewMessageResponse); events.on(EventName.OnNewMessageResponse, handleNewMessageResponse)
events.on(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate); events.on(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate)
} }
}, []); }, [])
useEffect(() => { useEffect(() => {
return () => { return () => {
events.off(EventName.OnNewMessageResponse, handleNewMessageResponse); events.off(EventName.OnNewMessageResponse, handleNewMessageResponse)
events.off(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate); events.off(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate)
}; }
}, []); }, [])
return <> {children}</>; return <> {children}</>
} }

View File

@ -1,68 +1,86 @@
"use client"; 'use client'
import { useSetAtom } from "jotai"; import { useSetAtom } from 'jotai'
import { ReactNode, useEffect } from "react"; import { ReactNode, useEffect } from 'react'
import { appDownloadProgress } from "./JotaiWrapper"; import { appDownloadProgress } from './JotaiWrapper'
import { DownloadState } from "@/_models/DownloadState"; import { DownloadState } from '@/_models/DownloadState'
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager"; import { executeSerial } from '../../../electron/core/plugin-manager/execution/extension-manager'
import { ModelManagementService } from "@janhq/core"; import { ModelManagementService } from '@janhq/core'
import { setDownloadStateAtom, setDownloadStateSuccessAtom } from "./atoms/DownloadState.atom"; import {
import { getDownloadedModels } from "@/_hooks/useGetDownloadedModels"; setDownloadStateAtom,
import { downloadedModelAtom } from "./atoms/DownloadedModel.atom"; setDownloadStateSuccessAtom,
import EventHandler from "./EventHandler"; } from './atoms/DownloadState.atom'
import { getDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import { downloadedModelAtom } from './atoms/DownloadedModel.atom'
import EventHandler from './EventHandler'
type Props = { type Props = {
children: ReactNode; children: ReactNode
}; }
export default function EventListenerWrapper({ children }: Props) { export default function EventListenerWrapper({ children }: Props) {
const setDownloadState = useSetAtom(setDownloadStateAtom); const setDownloadState = useSetAtom(setDownloadStateAtom)
const setDownloadStateSuccess = useSetAtom(setDownloadStateSuccessAtom); const setDownloadStateSuccess = useSetAtom(setDownloadStateSuccessAtom)
const setProgress = useSetAtom(appDownloadProgress); const setProgress = useSetAtom(appDownloadProgress)
const setDownloadedModels = useSetAtom(downloadedModelAtom); const setDownloadedModels = useSetAtom(downloadedModelAtom)
useEffect(() => { useEffect(() => {
if (window && window.electronAPI) { if (window && window.electronAPI) {
window.electronAPI.onFileDownloadUpdate((_event: string, state: DownloadState | undefined) => { window.electronAPI.onFileDownloadUpdate(
if (!state) return; (_event: string, state: DownloadState | undefined) => {
setDownloadState(state); if (!state) return
}); setDownloadState(state)
}
)
window.electronAPI.onFileDownloadError((_event: string, callback: any) => { window.electronAPI.onFileDownloadError(
console.log("Download error", callback); (_event: string, callback: any) => {
}); console.log('Download error', callback)
}
)
window.electronAPI.onFileDownloadSuccess((_event: string, callback: any) => { window.electronAPI.onFileDownloadSuccess(
(_event: string, callback: any) => {
if (callback && callback.fileName) { if (callback && callback.fileName) {
setDownloadStateSuccess(callback.fileName); setDownloadStateSuccess(callback.fileName)
executeSerial(ModelManagementService.UpdateFinishedDownloadAt, callback.fileName).then(() => { executeSerial(
ModelManagementService.UpdateFinishedDownloadAt,
callback.fileName
).then(() => {
getDownloadedModels().then((models) => { getDownloadedModels().then((models) => {
setDownloadedModels(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);
});
} }
}, []); )
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 ( return (
<div id="eventlistener"> <div id="eventlistener">
<EventHandler>{children}</EventHandler> <EventHandler>{children}</EventHandler>
</div> </div>
); )
} }

View File

@ -1,21 +1,21 @@
"use client"; 'use client'
import { Provider, atom } from "jotai"; import { Provider, atom } from 'jotai'
import { ReactNode } from "react"; import { ReactNode } from 'react'
type Props = { type Props = {
children: ReactNode; children: ReactNode
};
export default function JotaiWrapper({ children }: Props) {
return <Provider>{children}</Provider>;
} }
export const currentPromptAtom = atom<string>(""); export default function JotaiWrapper({ children }: Props) {
return <Provider>{children}</Provider>
}
export const appDownloadProgress = atom<number>(-1); export const currentPromptAtom = atom<string>('')
export const searchingModelText = 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>('')

View File

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

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