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