Setup prettier

This commit is contained in:
Faisal Amir 2023-10-20 11:29:10 +07:00
parent 5f536d8d3b
commit 03d560bde6
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 React from "react";
import ModelTable from "../ModelTable";
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom";
import { useAtomValue } from 'jotai'
import React from 'react'
import ModelTable from '../ModelTable'
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
const ActiveModelTable: React.FC = () => {
const activeModel = useAtomValue(activeAssistantModelAtom);
const activeModel = useAtomValue(activeAssistantModelAtom)
if (!activeModel) return null;
if (!activeModel) return null
return (
<div className="pl-[63px] pr-[89px]">
<h3 className="text-xl leading-[25px] mb-[13px]">Active Model(s)</h3>
<h3 className="mb-[13px] text-xl leading-[25px]">Active Model(s)</h3>
<ModelTable models={[activeModel]} />
</div>
);
};
)
}
export default ActiveModelTable;
export default ActiveModelTable

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,43 +1,43 @@
import React from "react";
import Image from "next/image";
import useCreateConversation from "@/_hooks/useCreateConversation";
import { AssistantModel } from "@/_models/AssistantModel";
import React from 'react'
import Image from 'next/image'
import useCreateConversation from '@/_hooks/useCreateConversation'
import { AssistantModel } from '@/_models/AssistantModel'
type Props = {
model: AssistantModel;
};
model: AssistantModel
}
const ConversationalCard: React.FC<Props> = ({ model }) => {
const { requestCreateConvo } = useCreateConversation();
const { requestCreateConvo } = useCreateConversation()
const { name, avatarUrl, shortDescription } = model;
const { name, avatarUrl, shortDescription } = model
return (
<button
onClick={() => requestCreateConvo(model)}
className="flex flex-col justify-between flex-shrink-0 gap-3 bg-white p-4 w-52 rounded-lg text-left dark:bg-gray-700 hover:opacity-20"
className="flex w-52 flex-shrink-0 flex-col justify-between gap-3 rounded-lg bg-white p-4 text-left hover:opacity-20 dark:bg-gray-700"
>
<div className="flex flex-col gap-2 box-border">
<div className="box-border flex flex-col gap-2">
<Image
width={32}
height={32}
src={avatarUrl ?? ""}
src={avatarUrl ?? ''}
className="rounded-full"
alt=""
/>
<h2 className="text-gray-900 font-semibold dark:text-white line-clamp-1 mt-2">
<h2 className="mt-2 line-clamp-1 font-semibold text-gray-900 dark:text-white">
{name}
</h2>
<span className="text-gray-600 mt-1 font-normal line-clamp-2">
<span className="mt-1 line-clamp-2 font-normal text-gray-600">
{shortDescription}
</span>
</div>
<span className="flex text-xs leading-5 text-gray-500 items-center gap-0.5">
<Image src={"icons/play.svg"} width={16} height={16} alt="" />
<span className="flex items-center gap-0.5 text-xs leading-5 text-gray-500">
<Image src={'icons/play.svg'} width={16} height={16} alt="" />
32.2k runs
</span>
</button>
);
};
)
}
export default React.memo(ConversationalCard);
export default React.memo(ConversationalCard)

View File

@ -1,25 +1,25 @@
import { AssistantModel } from "@/_models/AssistantModel";
import ConversationalCard from "../ConversationalCard";
import { ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline";
import { AssistantModel } from '@/_models/AssistantModel'
import ConversationalCard from '../ConversationalCard'
import { ChatBubbleBottomCenterTextIcon } from '@heroicons/react/24/outline'
type Props = {
models: AssistantModel[];
};
models: AssistantModel[]
}
const ConversationalList: React.FC<Props> = ({ models }) => (
<>
<div className="flex items-center gap-3 mt-8 mb-2">
<div className="mb-2 mt-8 flex items-center gap-3">
<ChatBubbleBottomCenterTextIcon width={24} height={24} className="ml-6" />
<span className="font-semibold text-gray-900 dark:text-white">
Conversational
</span>
</div>
<div className="mt-2 pl-6 flex w-full gap-2 overflow-x-scroll scroll overflow-hidden">
<div className="scroll mt-2 flex w-full gap-2 overflow-hidden overflow-x-scroll pl-6">
{models.map((item) => (
<ConversationalCard key={item._id} model={item} />
))}
</div>
</>
);
)
export default ConversationalList;
export default ConversationalList

View File

@ -1,18 +1,18 @@
import React from "react";
import Link from "next/link";
import Image from "next/image";
import React from 'react'
import Link from 'next/link'
import Image from 'next/image'
const DiscordContainer = () => (
<div className="border-t border-gray-200 p-3 gap-3 flex items-center justify-between">
<div className="flex items-center justify-between gap-3 border-t border-gray-200 p-3">
<Link
className="flex items-center rounded-lg text-purple-700 text-xs leading-[18px] font-semibold gap-2"
href={process.env.NEXT_PUBLIC_DISCORD_INVITATION_URL ?? "#"}
className="flex items-center gap-2 rounded-lg text-xs font-semibold leading-[18px] text-purple-700"
href={process.env.NEXT_PUBLIC_DISCORD_INVITATION_URL ?? '#'}
target="_blank_"
>
<Image src={"icons/ico_Discord.svg"} width={20} height={20} alt="" />
<Image src={'icons/ico_Discord.svg'} width={20} height={20} alt="" />
Discord
</Link>
</div>
);
)
export default React.memo(DiscordContainer);
export default React.memo(DiscordContainer)

View File

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

View File

@ -1,13 +1,13 @@
type Props = {
title: string;
};
title: string
}
export const DownloadModelTitle: React.FC<Props> = ({ title }) => (
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
<span className="text-xs leading-[18px] font-medium text-purple-800">
<div className="rounded-md bg-purple-100 px-2.5 py-0.5 text-center">
<span className="text-xs font-medium leading-[18px] text-purple-800">
{title}
</span>
</div>
);
)
export default DownloadModelTitle;
export default DownloadModelTitle

View File

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

View File

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

View File

@ -1,29 +1,29 @@
import React, { Fragment } from "react";
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
import { useAtomValue } from "jotai";
import ModelDownloadingTable from "../ModelDownloadingTable";
import { DownloadState } from "@/_models/DownloadState";
import React, { Fragment } from 'react'
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { useAtomValue } from 'jotai'
import ModelDownloadingTable from '../ModelDownloadingTable'
import { DownloadState } from '@/_models/DownloadState'
const DownloadingModelTable: React.FC = () => {
const modelDownloadState = useAtomValue(modelDownloadStateAtom);
const modelDownloadState = useAtomValue(modelDownloadStateAtom)
const isAnyModelDownloading = Object.values(modelDownloadState).length > 0;
const isAnyModelDownloading = Object.values(modelDownloadState).length > 0
if (!isAnyModelDownloading) return null;
if (!isAnyModelDownloading) return null
const downloadStates: DownloadState[] = [];
const downloadStates: DownloadState[] = []
for (const [, value] of Object.entries(modelDownloadState)) {
downloadStates.push(value);
downloadStates.push(value)
}
return (
<div className="pl-[63px] pr-[89px]">
<h3 className="text-xl leading-[25px] mt-[50px] mb-4">
<h3 className="mb-4 mt-[50px] text-xl leading-[25px]">
Downloading Models
</h3>
<ModelDownloadingTable downloadStates={downloadStates} />
</div>
);
};
)
}
export default DownloadingModelTable;
export default DownloadingModelTable

View File

@ -1,64 +1,64 @@
import { Fragment, useState } from "react";
import { Menu, Transition } from "@headlessui/react";
import Image from "next/image";
function classNames(...classes: any) {
return classes.filter(Boolean).join(" ");
}
type Props = {
title: string;
data: string[];
};
export const DropdownsList: React.FC<Props> = ({ data, title }) => {
const [checked, setChecked] = useState(data[0]);
return (
<Menu as="div" className="relative w-full text-left">
<div className="pt-2 gap-2 flex flex-col">
<h2 className="text-[#111928] text-sm">{title}</h2>
<Menu.Button className="inline-flex w-full items-center justify-between gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
{checked}
<Image
src={"icons/unicorn_angle-down.svg"}
width={12}
height={12}
alt=""
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-full origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
{data.map((item, index) => (
<Menu.Item key={index}>
{({ active }) => (
<a
onClick={() => setChecked(item)}
href="#"
className={classNames(
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
"block px-4 py-2 text-sm"
)}
>
{item}
</a>
)}
</Menu.Item>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
);
};
import { Fragment, useState } from 'react'
import { Menu, Transition } from '@headlessui/react'
import Image from 'next/image'
function classNames(...classes: any) {
return classes.filter(Boolean).join(' ')
}
type Props = {
title: string
data: string[]
}
export const DropdownsList: React.FC<Props> = ({ data, title }) => {
const [checked, setChecked] = useState(data[0])
return (
<Menu as="div" className="relative w-full text-left">
<div className="flex flex-col gap-2 pt-2">
<h2 className="text-sm text-[#111928]">{title}</h2>
<Menu.Button className="inline-flex w-full items-center justify-between gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
{checked}
<Image
src={'icons/unicorn_angle-down.svg'}
width={12}
height={12}
alt=""
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-full origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
{data.map((item, index) => (
<Menu.Item key={index}>
{({ active }) => (
<a
onClick={() => setChecked(item)}
href="#"
className={classNames(
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
'block px-4 py-2 text-sm'
)}
>
{item}
</a>
)}
</Menu.Item>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
)
}

View File

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

View File

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

View File

@ -1,20 +1,20 @@
import HeaderTitle from "../HeaderTitle";
import SearchBar, { SearchType } from "../SearchBar";
import ExploreModelList from "../ExploreModelList";
import ExploreModelFilter from "../ExploreModelFilter";
import HeaderTitle from '../HeaderTitle'
import SearchBar, { SearchType } from '../SearchBar'
import ExploreModelList from '../ExploreModelList'
import ExploreModelFilter from '../ExploreModelFilter'
const ExploreModelContainer: React.FC = () => (
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
<div className="flex flex-1 flex-col overflow-hidden px-16 pt-14">
<HeaderTitle title="Explore Models" />
{/* <SearchBar
type={SearchType.Model}
placeholder="Owner name like TheBloke, bhlim etc.."
/> */}
<div className="flex flex-1 gap-x-10 mt-9 overflow-hidden">
<div className="mt-9 flex flex-1 gap-x-10 overflow-hidden">
<ExploreModelFilter />
<ExploreModelList />
</div>
</div>
);
)
export default ExploreModelContainer;
export default ExploreModelContainer

View File

@ -1,29 +1,29 @@
import React from "react";
import SearchBar from "../SearchBar";
import SimpleCheckbox from "../SimpleCheckbox";
import SimpleTag from "../SimpleTag";
import { TagType } from "../SimpleTag/TagType";
import React from 'react'
import SearchBar from '../SearchBar'
import SimpleCheckbox from '../SimpleCheckbox'
import SimpleTag from '../SimpleTag'
import { TagType } from '../SimpleTag/TagType'
const tags = [
"Roleplay",
"Llama",
"Story",
"Casual",
"Professional",
"CodeLlama",
"Coding",
];
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
'Roleplay',
'Llama',
'Story',
'Casual',
'Professional',
'CodeLlama',
'Coding',
]
const checkboxs = ['GGUF', 'TensorRT', 'Meow', 'JigglyPuff']
const ExploreModelFilter: React.FC = () => {
const enabled = false;
if (!enabled) return null;
const enabled = false
if (!enabled) return null
return (
<div className="w-64">
<h2 className="font-semibold text-xs mb-[15px]">Tags</h2>
<h2 className="mb-[15px] text-xs font-semibold">Tags</h2>
<SearchBar placeholder="Filter by tags" />
<div className="flex flex-wrap gap-[9px] mt-[14px]">
<div className="mt-[14px] flex flex-wrap gap-[9px]">
{tags.map((item) => (
<SimpleTag key={item} title={item} type={item as TagType} />
))}
@ -35,7 +35,7 @@ const ExploreModelFilter: React.FC = () => {
))}
</fieldset>
</div>
);
};
)
}
export default ExploreModelFilter;
export default ExploreModelFilter

View File

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

View File

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

View File

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

View File

@ -1,29 +1,23 @@
import Image from "next/image";
import Link from "next/link";
import Image from 'next/image'
import Link from 'next/link'
// DEPRECATED
export default function Footer() {
return (
<div className="flex items-center justify-between container m-auto">
<div className="container m-auto flex items-center justify-between">
<div className="flex items-center gap-3">
<Image src={"icons/app_icon.svg"} width={32} height={32} alt="" />
<Image src={'icons/app_icon.svg'} width={32} height={32} alt="" />
<span>Jan</span>
</div>
<div className="flex gap-4 my-6">
<Link
href="/privacy"
className="cursor-pointer"
>
<div className="my-6 flex gap-4">
<Link href="/privacy" className="cursor-pointer">
Privacy
</Link>
<span>&#8226;</span>
<Link
href="/support"
className="cursor-pointer"
>
<Link href="/support" className="cursor-pointer">
Support
</Link>
</div>
</div>
);
)
}

View File

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

View File

@ -1,19 +1,19 @@
import React from "react";
import UserProfileDropDown from "../UserProfileDropDown";
import LoginButton from "../LoginButton";
import HamburgerButton from "../HamburgerButton";
const Header: React.FC = () => {
return (
<header className="flex border-b-[1px] border-gray-200 p-3 dark:bg-gray-800">
<nav className="flex-1 justify-center">
<HamburgerButton />
</nav>
<div className="h-[30px]" />
<LoginButton />
<UserProfileDropDown />
</header>
);
};
export default Header;
import React from 'react'
import UserProfileDropDown from '../UserProfileDropDown'
import LoginButton from '../LoginButton'
import HamburgerButton from '../HamburgerButton'
const Header: React.FC = () => {
return (
<header className="flex border-b-[1px] border-gray-200 p-3 dark:bg-gray-800">
<nav className="flex-1 justify-center">
<HamburgerButton />
</nav>
<div className="h-[30px]" />
<LoginButton />
<UserProfileDropDown />
</header>
)
}
export default Header

View File

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

View File

@ -1,16 +1,16 @@
import React from "react";
import React from 'react'
type Props = {
title: string;
className?: string;
};
title: string
className?: string
}
const HeaderTitle: React.FC<Props> = ({ title, className }) => (
<h2
className={`my-5 font-semibold text-[34px] tracking-[-0.4px] leading-[41px] ${className}`}
className={`my-5 text-[34px] font-semibold leading-[41px] tracking-[-0.4px] ${className}`}
>
{title}
</h2>
);
)
export default React.memo(HeaderTitle);
export default React.memo(HeaderTitle)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,53 +1,53 @@
import Link from "next/link";
import { Popover, Transition } from "@headlessui/react";
import { Fragment } from "react";
// import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import { useSetAtom } from "jotai";
import { showConfirmSignOutModalAtom } from "@/_helpers/atoms/Modal.atom";
export const MenuHeader: React.FC = () => {
const setShowConfirmSignOutModal = useSetAtom(showConfirmSignOutModalAtom);
// const { user } = useGetCurrentUser();
return <div></div>;
// return (
// <Transition
// as={Fragment}
// enter="transition ease-out duration-200"
// enterFrom="opacity-0 translate-y-1"
// enterTo="opacity-100 translate-y-0"
// leave="transition ease-in duration-150"
// leaveFrom="opacity-100 translate-y-0"
// leaveTo="opacity-0 translate-y-1"
// >
// <Popover.Panel className="absolute shadow-profile -right-2 top-full z-10 mt-3 w-[224px] overflow-hidden rounded-[6px] bg-white shadow-lg ring-1 ring-gray-200">
// <div className="py-3 px-4 gap-2 flex flex-col">
// <h2 className="text-[20px] leading-[25px] tracking-[-0.4px] font-bold text-[#111928]">
// {user.displayName}
// </h2>
// <span className="text-[#6B7280] leading-[17.5px] text-sm">
// {user.email}
// </span>
// </div>
// <hr />
// <button
// onClick={() => setShowConfirmSignOutModal(true)}
// className="px-4 py-3 text-sm w-full text-left text-gray-700"
// >
// Sign Out
// </button>
// <hr />
// <div className="flex gap-2 px-4 py-2 justify-center items-center">
// <Link href="/privacy">
// <span className="text-[#6B7280] text-xs">Privacy</span>
// </Link>
// <div className="w-1 h-1 bg-[#D9D9D9] rounded-lg" />
// <Link href="/support">
// <span className="text-[#6B7280] text-xs">Support</span>
// </Link>
// </div>
// </Popover.Panel>
// </Transition>
// );
};
import Link from 'next/link'
import { Popover, Transition } from '@headlessui/react'
import { Fragment } from 'react'
// import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import { useSetAtom } from 'jotai'
import { showConfirmSignOutModalAtom } from '@/_helpers/atoms/Modal.atom'
export const MenuHeader: React.FC = () => {
const setShowConfirmSignOutModal = useSetAtom(showConfirmSignOutModalAtom)
// const { user } = useGetCurrentUser();
return <div></div>
// return (
// <Transition
// as={Fragment}
// enter="transition ease-out duration-200"
// enterFrom="opacity-0 translate-y-1"
// enterTo="opacity-100 translate-y-0"
// leave="transition ease-in duration-150"
// leaveFrom="opacity-100 translate-y-0"
// leaveTo="opacity-0 translate-y-1"
// >
// <Popover.Panel className="absolute shadow-profile -right-2 top-full z-10 mt-3 w-[224px] overflow-hidden rounded-[6px] bg-white shadow-lg ring-1 ring-gray-200">
// <div className="py-3 px-4 gap-2 flex flex-col">
// <h2 className="text-[20px] leading-[25px] tracking-[-0.4px] font-bold text-[#111928]">
// {user.displayName}
// </h2>
// <span className="text-[#6B7280] leading-[17.5px] text-sm">
// {user.email}
// </span>
// </div>
// <hr />
// <button
// onClick={() => setShowConfirmSignOutModal(true)}
// className="px-4 py-3 text-sm w-full text-left text-gray-700"
// >
// Sign Out
// </button>
// <hr />
// <div className="flex gap-2 px-4 py-2 justify-center items-center">
// <Link href="/privacy">
// <span className="text-[#6B7280] text-xs">Privacy</span>
// </Link>
// <div className="w-1 h-1 bg-[#D9D9D9] rounded-lg" />
// <Link href="/support">
// <span className="text-[#6B7280] text-xs">Support</span>
// </Link>
// </div>
// </Popover.Panel>
// </Transition>
// );
}

View File

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

View File

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

View File

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

View File

@ -1,21 +1,21 @@
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'
type Props = {
callback: () => void;
};
callback: () => void
}
const ModelDownloadButton: React.FC<Props> = ({ callback }) => {
return (
<button
className="bg-[#1A56DB] rounded-lg py-2 px-3 flex items-center gap-2"
className="flex items-center gap-2 rounded-lg bg-[#1A56DB] px-3 py-2"
onClick={callback}
>
<ArrowDownTrayIcon width={16} height={16} color="#FFFFFF" />
<span className="text-xs leading-[18px] text-[#fff] font-medium">
<span className="text-xs font-medium leading-[18px] text-[#fff]">
Download
</span>
</button>
);
};
)
}
export default ModelDownloadButton;
export default ModelDownloadButton

View File

@ -1,23 +1,23 @@
import { toGigabytes } from "@/_utils/converter";
import { toGigabytes } from '@/_utils/converter'
type Props = {
total: number;
value: number;
};
total: number
value: number
}
const ModelDownloadingButton: React.FC<Props> = ({ total, value }) => {
return (
<div className="flex flex-col gap-1">
<button className="py-2 px-3 flex gap-2 border text-xs leading-[18px] border-gray-200 rounded-lg">
<button className="flex gap-2 rounded-lg border border-gray-200 px-3 py-2 text-xs leading-[18px]">
Downloading...
</button>
<div className="py-0.5 px-2.5 bg-gray-200 rounded">
<div className="rounded bg-gray-200 px-2.5 py-0.5">
<span className="text-xs font-medium text-gray-800">
{toGigabytes(value)} / {toGigabytes(total)}
</span>
</div>
</div>
);
};
)
}
export default ModelDownloadingButton;
export default ModelDownloadingButton

View File

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

View File

@ -1,23 +1,23 @@
import React from "react";
import ModelTableHeader from "../ModelTableHeader";
import { DownloadState } from "@/_models/DownloadState";
import ModelDownloadingRow from "../ModelDownloadingRow";
import React from 'react'
import ModelTableHeader from '../ModelTableHeader'
import { DownloadState } from '@/_models/DownloadState'
import ModelDownloadingRow from '../ModelDownloadingRow'
type Props = {
downloadStates: DownloadState[];
};
downloadStates: DownloadState[]
}
const tableHeaders = ["MODEL", "TRANSFERRED", "SIZE", "PERCENTAGE", "SPEED"];
const tableHeaders = ['MODEL', 'TRANSFERRED', 'SIZE', 'PERCENTAGE', 'SPEED']
const ModelDownloadingTable: React.FC<Props> = ({ downloadStates }) => (
<div className="flow-root border rounded-lg border-gray-200 min-w-full align-middle shadow-lg">
<div className="flow-root min-w-full rounded-lg border border-gray-200 align-middle shadow-lg">
<table className="min-w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<thead className="border-b border-gray-200 bg-gray-50">
<tr className="rounded-t-lg">
{tableHeaders.map((item) => (
<ModelTableHeader key={item} title={item} />
))}
<th scope="col" className="relative px-6 py-3 w-fit">
<th scope="col" className="relative w-fit px-6 py-3">
<span className="sr-only">Edit</span>
</th>
</tr>
@ -29,6 +29,6 @@ const ModelDownloadingTable: React.FC<Props> = ({ downloadStates }) => (
</tbody>
</table>
</div>
);
)
export default React.memo(ModelDownloadingTable);
export default React.memo(ModelDownloadingTable)

View File

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

View File

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

View File

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

View File

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

View File

@ -1,31 +1,31 @@
"use client";
'use client'
import { searchingModelText } from "@/_helpers/JotaiWrapper";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { useSetAtom } from "jotai";
import { useEffect, useState } from "react";
import { searchingModelText } from '@/_helpers/JotaiWrapper'
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
import { useSetAtom } from 'jotai'
import { useEffect, useState } from 'react'
const ModelSearchBar: React.FC = () => {
const setSearchtext = useSetAtom(searchingModelText);
const [text, setText] = useState("");
const setSearchtext = useSetAtom(searchingModelText)
const [text, setText] = useState('')
useEffect(() => {
setSearchtext(text);
}, [text, setSearchtext]);
setSearchtext(text)
}, [text, setSearchtext])
return (
<div className="py-[27px] flex items-center justify-center">
<div className="w-[520px] h-[42px] flex items-center">
<div className="flex items-center justify-center py-[27px]">
<div className="flex h-[42px] w-[520px] items-center">
<input
className="outline-none bg-gray-300 text-sm h-full rounded-tl-lg rounded-bl-lg leading-[17.5px] border border-gray-300 py-3 px-4 flex-1"
className="h-full flex-1 rounded-bl-lg rounded-tl-lg border border-gray-300 bg-gray-300 px-4 py-3 text-sm leading-[17.5px] outline-none"
placeholder="Search model"
value={text}
onChange={(text) => setText(text.currentTarget.value)}
/>
<button className="flex items-center justify-center bg-gray-800 border border-gray-800 p-2 w-[42px] rounded-tr-lg rounded-br-lg h-[42px]">
<button className="flex h-[42px] w-[42px] items-center justify-center rounded-br-lg rounded-tr-lg border border-gray-800 bg-gray-800 p-2">
<MagnifyingGlassIcon width={20} height={20} color="#FFFFFF" />
</button>
</div>
</div>
);
};
)
}
export default ModelSearchBar;
export default ModelSearchBar

View File

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

View File

@ -1,10 +1,10 @@
import React from "react";
import React from 'react'
export type ModelStatusType = {
title: string;
textColor: string;
backgroundColor: string;
};
title: string
textColor: string
backgroundColor: string
}
export enum ModelStatus {
Installed,
@ -14,33 +14,33 @@ export enum ModelStatus {
export const ModelStatusMapper: Record<ModelStatus, ModelStatusType> = {
[ModelStatus.Installed]: {
title: "Installed",
textColor: "text-black",
backgroundColor: "bg-gray-100",
title: 'Installed',
textColor: 'text-black',
backgroundColor: 'bg-gray-100',
},
[ModelStatus.Active]: {
title: "Active",
textColor: "text-black",
backgroundColor: "bg-green-100",
title: 'Active',
textColor: 'text-black',
backgroundColor: 'bg-green-100',
},
[ModelStatus.RunningInNitro]: {
title: "Running in Nitro",
textColor: "text-black",
backgroundColor: "bg-green-100",
title: 'Running in Nitro',
textColor: 'text-black',
backgroundColor: 'bg-green-100',
},
};
}
type Props = {
status: ModelStatus;
};
status: ModelStatus
}
export const ModelStatusComponent: React.FC<Props> = ({ status }) => {
const statusType = ModelStatusMapper[status];
const statusType = ModelStatusMapper[status]
return (
<div
className={`rounded-[10px] py-0.5 px-2.5 w-fit text-xs font-medium ${statusType.backgroundColor}`}
className={`w-fit rounded-[10px] px-2.5 py-0.5 text-xs font-medium ${statusType.backgroundColor}`}
>
{statusType.title}
</div>
);
};
)
}

View File

@ -1,23 +1,23 @@
import React from "react";
import ModelRow from "../ModelRow";
import ModelTableHeader from "../ModelTableHeader";
import { AssistantModel } from "@/_models/AssistantModel";
import React from 'react'
import ModelRow from '../ModelRow'
import ModelTableHeader from '../ModelTableHeader'
import { AssistantModel } from '@/_models/AssistantModel'
type Props = {
models: AssistantModel[];
};
models: AssistantModel[]
}
const tableHeaders = ["MODEL", "FORMAT", "SIZE", "STATUS", "ACTIONS"];
const tableHeaders = ['MODEL', 'FORMAT', 'SIZE', 'STATUS', 'ACTIONS']
const ModelTable: React.FC<Props> = ({ models }) => (
<div className="flow-root border rounded-lg border-gray-200 min-w-full align-middle shadow-lg">
<div className="flow-root min-w-full rounded-lg border border-gray-200 align-middle shadow-lg">
<table className="min-w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<thead className="border-b border-gray-200 bg-gray-50">
<tr className="rounded-t-lg">
{tableHeaders.map((item) => (
<ModelTableHeader key={item} title={item} />
))}
<th scope="col" className="relative px-6 py-3 w-fit">
<th scope="col" className="relative w-fit px-6 py-3">
<span className="sr-only">Edit</span>
</th>
</tr>
@ -29,6 +29,6 @@ const ModelTable: React.FC<Props> = ({ models }) => (
</tbody>
</table>
</div>
);
)
export default React.memo(ModelTable);
export default React.memo(ModelTable)

View File

@ -1,16 +1,16 @@
import React from "react";
import React from 'react'
type Props = {
title: string;
};
title: string
}
const ModelTableHeader: React.FC<Props> = ({ title }) => (
<th
scope="col"
className="px-6 py-3 text-left first:rounded-tl-lg last:rounded-tr-lg text-xs font-medium uppercase tracking-wide text-gray-500"
className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500 first:rounded-tl-lg last:rounded-tr-lg"
>
{title}
</th>
);
)
export default React.memo(ModelTableHeader);
export default React.memo(ModelTableHeader)

View File

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

View File

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

View File

@ -1,24 +1,24 @@
import ProgressBar from "../ProgressBar";
import SystemItem from "../SystemItem";
import { useAtomValue } from "jotai";
import { appDownloadProgress } from "@/_helpers/JotaiWrapper";
import useGetAppVersion from "@/_hooks/useGetAppVersion";
import useGetSystemResources from "@/_hooks/useGetSystemResources";
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
import { DownloadState } from "@/_models/DownloadState";
import { formatDownloadPercentage } from "@/_utils/converter";
import { activeAssistantModelAtom } from "@/_helpers/atoms/Model.atom";
import ProgressBar from '../ProgressBar'
import SystemItem from '../SystemItem'
import { useAtomValue } from 'jotai'
import { appDownloadProgress } from '@/_helpers/JotaiWrapper'
import useGetAppVersion from '@/_hooks/useGetAppVersion'
import useGetSystemResources from '@/_hooks/useGetSystemResources'
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { DownloadState } from '@/_models/DownloadState'
import { formatDownloadPercentage } from '@/_utils/converter'
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
const MonitorBar: React.FC = () => {
const progress = useAtomValue(appDownloadProgress);
const activeModel = useAtomValue(activeAssistantModelAtom);
const { version } = useGetAppVersion();
const { ram, cpu } = useGetSystemResources();
const modelDownloadStates = useAtomValue(modelDownloadStateAtom);
const progress = useAtomValue(appDownloadProgress)
const activeModel = useAtomValue(activeAssistantModelAtom)
const { version } = useGetAppVersion()
const { ram, cpu } = useGetSystemResources()
const modelDownloadStates = useAtomValue(modelDownloadStateAtom)
const downloadStates: DownloadState[] = [];
const downloadStates: DownloadState[] = []
for (const [, value] of Object.entries(modelDownloadStates)) {
downloadStates.push(value);
downloadStates.push(value)
}
return (
@ -26,7 +26,7 @@ const MonitorBar: React.FC = () => {
{progress && progress >= 0 ? (
<ProgressBar total={100} used={progress} />
) : null}
<div className="flex-1 justify-end flex items-center gap-8 px-2">
<div className="flex flex-1 items-center justify-end gap-8 px-2">
{downloadStates.length > 0 && (
<SystemItem
name="Downloading"
@ -38,12 +38,12 @@ const MonitorBar: React.FC = () => {
<SystemItem name="CPU" value={`${cpu}%`} />
<SystemItem name="Mem" value={`${ram}%`} />
{activeModel && (
<SystemItem name={`Active model: ${activeModel.name}`} value={""} />
<SystemItem name={`Active model: ${activeModel.name}`} value={''} />
)}
<span className="text-gray-900 text-sm">v{version}</span>
<span className="text-sm text-gray-900">v{version}</span>
</div>
</div>
);
};
)
}
export default MonitorBar;
export default MonitorBar

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
import React from "react";
import React from 'react'
type Props = {
title: string;
onClick?: () => void;
disabled?: boolean;
className?: string;
icon?: React.ReactNode;
};
title: string
onClick?: () => void
disabled?: boolean
className?: string
icon?: React.ReactNode
}
const SecondaryButton: React.FC<Props> = ({
title,
@ -19,11 +19,11 @@ const SecondaryButton: React.FC<Props> = ({
disabled={disabled}
type="button"
onClick={onClick}
className={`flex items-center justify-center gap-1 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 ${className} flex-shrink-0 line-clamp-1`}
className={`flex items-center justify-center gap-1 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 ${className} line-clamp-1 flex-shrink-0`}
>
{icon}
{title}
</button>
);
)
export default React.memo(SecondaryButton);
export default React.memo(SecondaryButton)

View File

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

View File

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

View File

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

View File

@ -1,23 +1,23 @@
import React from "react";
import SecondaryButton from "../SecondaryButton";
import React from 'react'
import SecondaryButton from '../SecondaryButton'
const SidebarFooter: React.FC = () => (
<div className="flex justify-between items-center gap-2 mx-3">
<div className="mx-3 flex items-center justify-between gap-2">
<SecondaryButton
title={"Discord"}
title={'Discord'}
onClick={() =>
window.electronAPI?.openExternalUrl("https://discord.gg/AsJ8krTT3N")
window.electronAPI?.openExternalUrl('https://discord.gg/AsJ8krTT3N')
}
className="flex-1"
/>
<SecondaryButton
title={"Twitter"}
title={'Twitter'}
onClick={() =>
window.electronAPI?.openExternalUrl("https://twitter.com/janhq_")
window.electronAPI?.openExternalUrl('https://twitter.com/janhq_')
}
className="flex-1"
/>
</div>
);
)
export default React.memo(SidebarFooter);
export default React.memo(SidebarFooter)

View File

@ -1,10 +1,10 @@
import React from "react";
import Image from "next/image";
import React from 'react'
import Image from 'next/image'
const SidebarHeader: React.FC = () => (
<div className="flex flex-col gap-2.5 px-3">
<Image src={"icons/Jan_AppIcon.svg"} width={68} height={28} alt="" />
<Image src={'icons/Jan_AppIcon.svg'} width={68} height={28} alt="" />
</div>
);
)
export default React.memo(SidebarHeader);
export default React.memo(SidebarHeader)

View File

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

View File

@ -1,32 +1,32 @@
import React from "react";
import { useAtomValue, useSetAtom } from "jotai";
import Image from "next/image";
import React from 'react'
import { useAtomValue, useSetAtom } from 'jotai'
import Image from 'next/image'
import {
MainViewState,
getMainViewStateAtom,
setMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom";
} from '@/_helpers/atoms/MainView.atom'
type Props = {
title: string;
viewState: MainViewState;
iconName: string;
};
title: string
viewState: MainViewState
iconName: string
}
const SidebarMenuItem: React.FC<Props> = ({ title, viewState, iconName }) => {
const currentState = useAtomValue(getMainViewStateAtom);
const setMainViewState = useSetAtom(setMainViewStateAtom);
const currentState = useAtomValue(getMainViewStateAtom)
const setMainViewState = useSetAtom(setMainViewStateAtom)
let className =
"text-gray-600 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md text-base py-2 px-3 w-full";
'text-gray-600 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md text-base py-2 px-3 w-full'
if (currentState == viewState) {
className =
"bg-gray-100 text-indigo-600 group flex gap-x-3 rounded-md text-base py-2 px-3 w-full";
'bg-gray-100 text-indigo-600 group flex gap-x-3 rounded-md text-base py-2 px-3 w-full'
}
const onClick = () => {
setMainViewState(viewState);
};
setMainViewState(viewState)
}
return (
<li key={title}>
@ -35,7 +35,7 @@ const SidebarMenuItem: React.FC<Props> = ({ title, viewState, iconName }) => {
<span className="truncate">{title}</span>
</button>
</li>
);
};
)
}
export default SidebarMenuItem;
export default SidebarMenuItem

View File

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

View File

@ -1,19 +1,19 @@
import { displayDate } from "@/_utils/datetime";
import Link from "next/link";
import React from "react";
import JanImage from "./JanImage";
import Image from "next/image";
import { displayDate } from '@/_utils/datetime'
import Link from 'next/link'
import React from 'react'
import JanImage from './JanImage'
import Image from 'next/image'
type Props = {
avatarUrl?: string;
senderName: string;
text: string;
createdAt: number;
imageUrls: string[];
};
avatarUrl?: string
senderName: string
text: string
createdAt: number
imageUrls: string[]
}
const SimpleControlNetMessage: React.FC<Props> = ({
avatarUrl = "",
avatarUrl = '',
senderName,
imageUrls,
text,
@ -29,30 +29,30 @@ const SimpleControlNetMessage: React.FC<Props> = ({
alt=""
/>
<div className="flex flex-col gap-1">
<div className="flex gap-1 justify-start items-baseline">
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px]">
<div className="flex items-baseline justify-start gap-1">
<div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B]">
{senderName}
</div>
<div className="text-xs leading-[13.2px] font-medium text-gray-400 ml-2">
<div className="ml-2 text-xs font-medium leading-[13.2px] text-gray-400">
{displayDate(createdAt)}
</div>
</div>
<div className="flex gap-3 flex-col">
<p className="leading-[20px] whitespace-break-spaces text-sm font-normal dark:text-[#d1d5db]">
<div className="flex flex-col gap-3">
<p className="whitespace-break-spaces text-sm font-normal leading-[20px] dark:text-[#d1d5db]">
{text}
</p>
<JanImage
imageUrl={imageUrls[0]}
className="w-72 aspect-square rounded-lg"
className="aspect-square w-72 rounded-lg"
/>
<div className="flex flex-row justify-start items-start w-full gap-2">
<div className="flex w-full flex-row items-start justify-start gap-2">
<Link
href={imageUrls[0] || "#"}
href={imageUrls[0] || '#'}
target="_blank_"
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
className="flex items-center gap-1 rounded-xl bg-[#F3F4F6] px-2 py-1"
>
<Image src="icons/download.svg" width={16} height={16} alt="" />
<span className="leading-[20px] text-[14px] text-[#111928]">
<span className="text-[14px] leading-[20px] text-[#111928]">
Download
</span>
</Link>
@ -60,7 +60,7 @@ const SimpleControlNetMessage: React.FC<Props> = ({
</div>
</div>
</div>
);
};
)
}
export default SimpleControlNetMessage;
export default SimpleControlNetMessage

View File

@ -1,72 +1,72 @@
import Image from "next/image";
import JanImage from "../JanImage";
import { displayDate } from "@/_utils/datetime";
import Link from "next/link";
type Props = {
avatarUrl?: string;
senderName: string;
text?: string;
createdAt: number;
imageUrls: string[];
};
const SimpleImageMessage: React.FC<Props> = ({
avatarUrl = "",
senderName,
imageUrls,
text,
createdAt,
}) => {
// TODO handle regenerate image case
return (
<div className="flex items-start gap-2">
<img
className="rounded-full"
src={avatarUrl}
width={32}
height={32}
alt=""
/>
<div className="flex flex-col gap-1">
<div className="flex gap-1 justify-start items-baseline">
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px]">
{senderName}
</div>
<div className="text-xs leading-[13.2px] font-medium text-gray-400 ml-2">
{displayDate(createdAt)}
</div>
</div>
<div className="flex items-center gap-3 flex-col">
<JanImage
imageUrl={imageUrls[0]}
className="w-72 aspect-square rounded-lg"
/>
<div className="flex flex-row justify-start items-start w-full gap-2">
<Link
href={imageUrls[0] || "#"}
target="_blank_"
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
>
<Image src="icons/download.svg" width={16} height={16} alt="" />
<span className="leading-[20px] text-sm text-[#111928]">
Download
</span>
</Link>
<button
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
// onClick={() => sendChatMessage()}
>
<Image src="icons/refresh.svg" width={16} height={16} alt="" />
<span className="leading-[20px] text-sm text-[#111928]">
Re-generate
</span>
</button>
</div>
</div>
</div>
</div>
);
};
export default SimpleImageMessage;
import Image from 'next/image'
import JanImage from '../JanImage'
import { displayDate } from '@/_utils/datetime'
import Link from 'next/link'
type Props = {
avatarUrl?: string
senderName: string
text?: string
createdAt: number
imageUrls: string[]
}
const SimpleImageMessage: React.FC<Props> = ({
avatarUrl = '',
senderName,
imageUrls,
text,
createdAt,
}) => {
// TODO handle regenerate image case
return (
<div className="flex items-start gap-2">
<img
className="rounded-full"
src={avatarUrl}
width={32}
height={32}
alt=""
/>
<div className="flex flex-col gap-1">
<div className="flex items-baseline justify-start gap-1">
<div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B]">
{senderName}
</div>
<div className="ml-2 text-xs font-medium leading-[13.2px] text-gray-400">
{displayDate(createdAt)}
</div>
</div>
<div className="flex flex-col items-center gap-3">
<JanImage
imageUrl={imageUrls[0]}
className="aspect-square w-72 rounded-lg"
/>
<div className="flex w-full flex-row items-start justify-start gap-2">
<Link
href={imageUrls[0] || '#'}
target="_blank_"
className="flex items-center gap-1 rounded-xl bg-[#F3F4F6] px-2 py-1"
>
<Image src="icons/download.svg" width={16} height={16} alt="" />
<span className="text-sm leading-[20px] text-[#111928]">
Download
</span>
</Link>
<button
className="flex items-center gap-1 rounded-xl bg-[#F3F4F6] px-2 py-1"
// onClick={() => sendChatMessage()}
>
<Image src="icons/refresh.svg" width={16} height={16} alt="" />
<span className="text-sm leading-[20px] text-[#111928]">
Re-generate
</span>
</button>
</div>
</div>
</div>
</div>
)
}
export default SimpleImageMessage

View File

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

View File

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

View File

@ -1,13 +1,13 @@
import React from "react";
import { TagType } from "./TagType";
import { tagStyleMapper } from "./TagStyleMapper";
import React from 'react'
import { TagType } from './TagType'
import { tagStyleMapper } from './TagStyleMapper'
type Props = {
title: string;
type: TagType;
clickable?: boolean;
onClick?: () => void;
};
title: string
type: TagType
clickable?: boolean
onClick?: () => void
}
const SimpleTag: React.FC<Props> = ({
onClick,
@ -15,25 +15,25 @@ const SimpleTag: React.FC<Props> = ({
title,
type,
}) => {
if (!title || title.length === 0) return null;
if (!title || title.length === 0) return null
if (!clickable) {
return (
<div
className={`px-2.5 py-0.5 rounded text-xs font-medium items-center line-clamp-1 max-w-[40%] ${tagStyleMapper[type]}`}
className={`line-clamp-1 max-w-[40%] items-center rounded px-2.5 py-0.5 text-xs font-medium ${tagStyleMapper[type]}`}
>
{title}
</div>
);
)
}
return (
<button
onClick={onClick}
className={`px-2.5 py-0.5 rounded text-xs font-medium items-center line-clamp-1 max-w-[40%] ${tagStyleMapper[type]}`}
className={`line-clamp-1 max-w-[40%] items-center rounded px-2.5 py-0.5 text-xs font-medium ${tagStyleMapper[type]}`}
>
{title} x
</button>
);
};
)
}
export default React.memo(SimpleTag);
export default React.memo(SimpleTag)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,21 +1,21 @@
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { ChevronDownIcon } from '@heroicons/react/24/outline'
type Props = {
callback: () => void;
};
callback: () => void
}
const ViewModelDetailButton: React.FC<Props> = ({ callback }) => {
return (
<div className="px-4 pb-4">
<button
onClick={callback}
className="bg-gray-100 py-1 px-2.5 w-full flex items-center justify-center gap-1 rounded-lg"
className="flex w-full items-center justify-center gap-1 rounded-lg bg-gray-100 px-2.5 py-1"
>
<span className="text-xs leading-[18px]">View Details</span>
<ChevronDownIcon width={18} height={18} />
</button>
</div>
);
};
)
}
export default ViewModelDetailButton;
export default ViewModelDetailButton

View File

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

View File

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

View File

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

View File

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

View File

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

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