ui: interface revamp (#429)

* feat: adding create bot functionality

Signed-off-by: James <james@jan.ai>

* update the temperature progress bar

Signed-off-by: James <james@jan.ai>

* WIP baselayout

* Mapping plugins with available preferences

* Added loader component

* WIP working another screen

* Cleanup types and avoid import one by one

* Prepare bottom bar

* Add css variables colors to enable user select the accent

* Enable change accent color

* Seperate css variable

* Fix conflict

* Add blank state of my model empty

* Restyle explore models page

* Enable user config left sidebar

* Restyle my models page

* WIP styling chat page

* Restyling chat message

* Fix conflict

* Adde form preferences setting plugins

* Fixed form bot info

* Sidebar bot chat

* Showing rightbar for both setting when user created bot

* Fix style bot info

* Using overflow auto intead of scroll

* Remove script built UI from root package

* Fix missig import

* Resolve error linter

* fix e2e tests

Signed-off-by: James <james@jan.ai>

---------

Signed-off-by: James <james@jan.ai>
Co-authored-by: James <james@jan.ai>
This commit is contained in:
Faisal Amir 2023-10-24 10:59:12 +07:00 committed by GitHub
parent e332a7c0bd
commit 539b467141
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
209 changed files with 3286 additions and 1913 deletions

View File

@ -1,4 +1,11 @@
import { app, BrowserWindow, ipcMain, dialog, shell } from "electron";
import {
app,
BrowserWindow,
ipcMain,
dialog,
shell,
nativeTheme,
} from "electron";
import { readdirSync, writeFileSync } from "fs";
import { resolve, join, extname } from "path";
import { rmdir, unlink, createWriteStream } from "fs";
@ -36,12 +43,30 @@ app.on("window-all-closed", () => {
app.quit();
});
ipcMain.handle("setNativeThemeLight", () => {
nativeTheme.themeSource = "light";
});
ipcMain.handle("setNativeThemeDark", () => {
nativeTheme.themeSource = "dark";
});
ipcMain.handle("setNativeThemeSystem", () => {
nativeTheme.themeSource = "system";
});
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
frame: false,
show: false,
backgroundColor: "white",
trafficLightPosition: {
x: 16,
y: 10,
},
titleBarStyle: "hidden",
vibrancy: "sidebar",
webPreferences: {
nodeIntegration: true,
preload: join(__dirname, "preload.js"),
@ -118,11 +143,13 @@ function handleIPCs() {
ipcMain.handle(
"invokePluginFunc",
async (_event, modulePath, method, ...args) => {
const module = require(/* webpackIgnore: true */ join(
app.getPath("userData"),
"plugins",
modulePath
));
const module = require(
/* webpackIgnore: true */ join(
app.getPath("userData"),
"plugins",
modulePath
)
);
requiredModules[modulePath] = module;
if (typeof module[method] === "function") {

View File

@ -9,6 +9,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
invokePluginFunc: (plugin: any, method: any, ...args: any[]) =>
ipcRenderer.invoke("invokePluginFunc", plugin, method, ...args),
setNativeThemeLight: () => ipcRenderer.invoke("setNativeThemeLight"),
setNativeThemeDark: () => ipcRenderer.invoke("setNativeThemeDark"),
setNativeThemeSystem: () => ipcRenderer.invoke("setNativeThemeSystem"),
basePlugins: () => ipcRenderer.invoke("basePlugins"),
pluginPath: () => ipcRenderer.invoke("pluginPath"),
@ -23,19 +29,27 @@ contextBridge.exposeInMainWorld("electronAPI", {
deleteFile: (filePath: string) => ipcRenderer.invoke("deleteFile", filePath),
installRemotePlugin: (pluginName: string) => ipcRenderer.invoke("installRemotePlugin", pluginName),
installRemotePlugin: (pluginName: string) =>
ipcRenderer.invoke("installRemotePlugin", pluginName),
downloadFile: (url: string, path: string) => ipcRenderer.invoke("downloadFile", url, path),
downloadFile: (url: string, path: string) =>
ipcRenderer.invoke("downloadFile", url, path),
onFileDownloadUpdate: (callback: any) => ipcRenderer.on("FILE_DOWNLOAD_UPDATE", callback),
onFileDownloadUpdate: (callback: any) =>
ipcRenderer.on("FILE_DOWNLOAD_UPDATE", callback),
onFileDownloadError: (callback: any) => ipcRenderer.on("FILE_DOWNLOAD_ERROR", callback),
onFileDownloadError: (callback: any) =>
ipcRenderer.on("FILE_DOWNLOAD_ERROR", callback),
onFileDownloadSuccess: (callback: any) => ipcRenderer.on("FILE_DOWNLOAD_COMPLETE", callback),
onFileDownloadSuccess: (callback: any) =>
ipcRenderer.on("FILE_DOWNLOAD_COMPLETE", callback),
onAppUpdateDownloadUpdate: (callback: any) => ipcRenderer.on("APP_UPDATE_PROGRESS", callback),
onAppUpdateDownloadUpdate: (callback: any) =>
ipcRenderer.on("APP_UPDATE_PROGRESS", callback),
onAppUpdateDownloadError: (callback: any) => ipcRenderer.on("APP_UPDATE_ERROR", callback),
onAppUpdateDownloadError: (callback: any) =>
ipcRenderer.on("APP_UPDATE_ERROR", callback),
onAppUpdateDownloadSuccess: (callback: any) => ipcRenderer.on("APP_UPDATE_COMPLETE", callback),
onAppUpdateDownloadSuccess: (callback: any) =>
ipcRenderer.on("APP_UPDATE_COMPLETE", callback),
});

View File

@ -48,10 +48,8 @@ test("renders the home page", async () => {
// Welcome text is available
const welcomeText = await page
.locator(".text-5xl", {
hasText: "Welcome,lets download your first model",
})
.getByTestId("testid-welcome-title")
.first()
.isDisabled();
.isVisible();
expect(welcomeText).toBe(false);
});

View File

@ -40,7 +40,7 @@ test("shows my models", async () => {
.getByRole("heading")
.filter({ hasText: "My Models" })
.first()
.isDisabled();
.isVisible();
expect(header).toBe(false);
// More test cases here...
});

View File

@ -35,37 +35,13 @@ test.afterAll(async () => {
});
test("renders left navigation panel", async () => {
// Chat History section is available
const chatSection = await page
.getByRole("heading")
.filter({ hasText: "CHAT HISTORY" })
.first()
.isDisabled();
// Chat section should be there
const chatSection = await page.getByTestId("Chat").first().isVisible();
expect(chatSection).toBe(false);
// Home actions
const createBotBtn = await page
.getByRole("button", { name: "Create bot" })
.first()
.isEnabled();
const exploreBtn = await page
.getByRole("button", { name: "Explore Models" })
.first()
.isEnabled();
const myModelsBtn = await page
.getByTestId("My Models")
.first()
.isEnabled();
const settingsBtn = await page
.getByTestId("Settings")
.first()
.isEnabled();
expect(
[
createBotBtn,
exploreBtn,
myModelsBtn,
settingsBtn,
].filter((e) => !e).length
).toBe(0);
const botBtn = await page.getByTestId("Bot").first().isEnabled();
const myModelsBtn = await page.getByTestId("My Models").first().isEnabled();
const settingsBtn = await page.getByTestId("Settings").first().isEnabled();
expect([botBtn, myModelsBtn, settingsBtn].filter((e) => !e).length).toBe(0);
});

View File

@ -36,7 +36,5 @@ test.afterAll(async () => {
test("shows settings", async () => {
await page.getByTestId("Settings").first().click();
const pluginList = await page.getByTestId("plugin-item").count();
expect(pluginList).toBe(4);
await page.getByTestId("testid-setting-description").isVisible();
});

View File

@ -43,9 +43,5 @@
"@janhq/core": "^0.1.6",
"pouchdb-find": "^8.0.1",
"pouchdb-node": "^8.0.1"
},
"bundleDependencies": [
"pouchdb-node",
"pouchdb-find"
]
}
}

View File

@ -9,13 +9,17 @@ import {
} from "@janhq/core";
import { Observable } from "rxjs";
const initModel = async (product) => invokePluginFunc(MODULE_PATH, "initModel", product);
const initModel = async (product) =>
invokePluginFunc(MODULE_PATH, "initModel", product);
const stopModel = () => {
invokePluginFunc(MODULE_PATH, "killSubprocess");
};
function requestInference(recentMessages: any[], bot?: any): Observable<string> {
function requestInference(
recentMessages: any[],
bot?: any
): Observable<string> {
return new Observable((subscriber) => {
const requestBody = JSON.stringify({
messages: recentMessages,
@ -69,10 +73,15 @@ function requestInference(recentMessages: any[], bot?: any): Observable<string>
async function retrieveLastTenMessages(conversationId: string, bot?: any) {
// TODO: Common collections should be able to access via core functions instead of store
const messageHistory = (await store.findMany("messages", { conversationId }, [{ createdAt: "asc" }])) ?? [];
const messageHistory =
(await store.findMany("messages", { conversationId }, [
{ createdAt: "asc" },
])) ?? [];
let recentMessages = messageHistory
.filter((e) => e.message !== "" && (e.user === "user" || e.user === "assistant"))
.filter(
(e) => e.message !== "" && (e.user === "user" || e.user === "assistant")
)
.slice(-9)
.map((message) => ({
content: message.message.trim(),
@ -81,10 +90,13 @@ async function retrieveLastTenMessages(conversationId: string, bot?: any) {
if (bot && bot.systemPrompt) {
// append bot's system prompt
recentMessages = [{
content: `[INST] ${bot.systemPrompt}`,
role: 'system'
},...recentMessages];
recentMessages = [
{
content: `[INST] ${bot.systemPrompt}`,
role: "system",
},
...recentMessages,
];
}
console.debug(`Last 10 messages: ${JSON.stringify(recentMessages, null, 2)}`);
@ -93,13 +105,19 @@ async function retrieveLastTenMessages(conversationId: string, bot?: any) {
}
async function handleMessageRequest(data: NewMessageRequest) {
const conversation = await store.findOne("conversations", data.conversationId);
const conversation = await store.findOne(
"conversations",
data.conversationId
);
let bot = undefined;
if (conversation.botId != null) {
bot = await store.findOne("bots", conversation.botId);
}
const recentMessages = await retrieveLastTenMessages(data.conversationId, bot);
const recentMessages = await retrieveLastTenMessages(
data.conversationId,
bot
);
const message = {
...data,
message: "",
@ -124,7 +142,8 @@ async function handleMessageRequest(data: NewMessageRequest) {
await store.updateOne("messages", message._id, message);
},
error: async (err) => {
message.message = message.message.trim() + "\n" + "Error occurred: " + err;
message.message =
message.message.trim() + "\n" + "Error occurred: " + err;
// TODO: Common collections should be able to access via core functions instead of store
await store.updateOne("messages", message._id, message);
},
@ -140,7 +159,10 @@ async function inferenceRequest(data: NewMessageRequest): Promise<any> {
};
return new Promise(async (resolve, reject) => {
const recentMessages = await retrieveLastTenMessages(data.conversationId);
requestInference([...recentMessages, { role: "user", content: data.message }]).subscribe({
requestInference([
...recentMessages,
{ role: "user", content: data.message },
]).subscribe({
next: (content) => {
message.message = content;
},
@ -166,5 +188,9 @@ export function init({ register }) {
register(PluginService.OnStart, PLUGIN_NAME, onStart);
register(InferenceService.InitModel, initModel.name, initModel);
register(InferenceService.StopModel, stopModel.name, stopModel);
register(InferenceService.InferenceRequest, inferenceRequest.name, inferenceRequest);
register(
InferenceService.InferenceRequest,
inferenceRequest.name,
inferenceRequest
);
}

View File

@ -1,7 +1,7 @@
import { useAtomValue } from 'jotai'
import React from 'react'
import ModelTable from '../ModelTable'
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
const ActiveModelTable: React.FC = () => {
const activeModel = useAtomValue(activeAssistantModelAtom)

View File

@ -2,8 +2,7 @@ import DownloadModelContent from '../DownloadModelContent'
import ModelDownloadButton from '../ModelDownloadButton'
import ModelDownloadingButton from '../ModelDownloadingButton'
import { useAtomValue } from 'jotai'
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { AssistantModel } from '@/_models/AssistantModel'
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
type Props = {
model: AssistantModel

View File

@ -7,7 +7,7 @@ type Props = {
const Avatar: React.FC<Props> = ({ allowEdit = false }) => (
<div className="mx-auto flex flex-col gap-5">
<span className="mx-auto inline-block h-14 w-14 overflow-hidden rounded-full bg-gray-100">
<span className="mx-auto inline-block h-10 w-10 overflow-hidden rounded-full bg-gray-100">
<svg
className="mx-auto h-full w-full text-gray-300"
fill="currentColor"

View File

@ -3,7 +3,7 @@
import { useSetAtom } from 'jotai'
import { InformationCircleIcon } from '@heroicons/react/24/outline'
import SendButton from '../SendButton'
import { showingAdvancedPromptAtom } from '@/_helpers/atoms/Modal.atom'
import { showingAdvancedPromptAtom } from '@helpers/atoms/Modal.atom'
const BasicPromptAccessories: React.FC = () => {
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom)
@ -11,7 +11,7 @@ const BasicPromptAccessories: React.FC = () => {
const shouldShowAdvancedPrompt = false
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 p-3">
{/* Add future accessories here, e.g upload a file */}
<div className="flex items-center space-x-5">
<div className="flex items-center">

View File

@ -1,7 +1,7 @@
import React from 'react'
import { useSetAtom } from 'jotai'
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 setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom)

View File

@ -1,11 +1,11 @@
'use client'
import { currentPromptAtom } from '@/_helpers/JotaiWrapper'
import { getActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
import { selectedModelAtom } from '@/_helpers/atoms/Model.atom'
import useCreateConversation from '@/_hooks/useCreateConversation'
import useInitModel from '@/_hooks/useInitModel'
import useSendChatMessage from '@/_hooks/useSendChatMessage'
import { currentPromptAtom } from '@helpers/JotaiWrapper'
import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom'
import { selectedModelAtom } from '@helpers/atoms/Model.atom'
import useCreateConversation from '@hooks/useCreateConversation'
import useInitModel from '@hooks/useInitModel'
import useSendChatMessage from '@hooks/useSendChatMessage'
import { useAtom, useAtomValue } from 'jotai'
import { ChangeEvent, useEffect, useRef } from 'react'
@ -68,7 +68,7 @@ const BasicPromptInput: React.FC = () => {
}
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=" border-border rounded-lg border shadow-sm">
<textarea
ref={textareaRef}
onKeyDown={handleKeyDown}
@ -76,7 +76,7 @@ const BasicPromptInput: React.FC = () => {
onChange={handleMessageChange}
name="comment"
id="comment"
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="text-background-reverse block w-full resize-none border-0 bg-transparent py-1.5 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
placeholder="Message ..."
rows={1}
style={{ overflow: 'auto' }}

View File

@ -1,56 +1,58 @@
import { activeBotAtom } from "@/_helpers/atoms/Bot.atom";
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import {
MainViewState,
setMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom";
import useCreateConversation from "@/_hooks/useCreateConversation";
import useDeleteBot from "@/_hooks/useDeleteBot";
import { useAtomValue, useSetAtom } from "jotai";
import React from "react";
import PrimaryButton from "../PrimaryButton";
import ExpandableHeader from "../ExpandableHeader";
} from '@helpers/atoms/MainView.atom'
import useCreateConversation from '@hooks/useCreateConversation'
import useDeleteBot from '@hooks/useDeleteBot'
import { useAtomValue, useSetAtom } from 'jotai'
import React from 'react'
import PrimaryButton from '../PrimaryButton'
import ExpandableHeader from '../ExpandableHeader'
const BotInfo: React.FC = () => {
const { deleteBot } = useDeleteBot();
const { createConvoByBot } = useCreateConversation();
const setMainView = useSetAtom(setMainViewStateAtom);
const botInfo = useAtomValue(activeBotAtom);
if (!botInfo) return null;
const { deleteBot } = useDeleteBot()
const { createConvoByBot } = useCreateConversation()
const setMainView = useSetAtom(setMainViewStateAtom)
const botInfo = useAtomValue(activeBotAtom)
if (!botInfo) return null
const onNewChatClicked = () => {
if (!botInfo) {
alert("No bot selected");
return;
alert('No bot selected')
return
}
createConvoByBot(botInfo);
};
createConvoByBot(botInfo)
}
const onDeleteBotClick = async () => {
// TODO: display confirmation diaglog
const result = await deleteBot(botInfo._id);
if (result === "success") {
setMainView(MainViewState.Welcome);
const result = await deleteBot(botInfo._id)
if (result === 'success') {
setMainView(MainViewState.Welcome)
}
};
}
return (
<div className="flex flex-col gap-2 mx-1 my-1">
<ExpandableHeader title="BOT INFO" expanded={true} onClick={() => {}} />
<div className="mx-1 my-1 flex flex-col gap-2">
<ExpandableHeader title="BOT INFO" />
<div className="flex flex-col">
<label>{botInfo.name}</label>
<PrimaryButton onClick={onNewChatClicked} title="New chat" />
<span>{botInfo.description}</span>
<label className="mb-2">{botInfo.name}</label>
<span className="text-muted-foreground">{botInfo.description}</span>
</div>
<PrimaryButton
title="Delete bot"
onClick={onDeleteBotClick}
className="bg-red-500 hover:bg-red-400"
/>
<div className="flex w-full flex-col space-y-2">
<PrimaryButton onClick={onNewChatClicked} title="New chat" />
<PrimaryButton
title="Delete bot"
onClick={onDeleteBotClick}
className="bg-red-500 hover:bg-red-400"
/>
</div>
</div>
);
};
)
}
export default BotInfo;
export default BotInfo

View File

@ -1,14 +1,14 @@
import { activeBotAtom } from '@/_helpers/atoms/Bot.atom'
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import { useAtomValue, useSetAtom } from 'jotai'
import React from 'react'
import Avatar from '../Avatar'
import PrimaryButton from '../PrimaryButton'
import useCreateConversation from '@/_hooks/useCreateConversation'
import useDeleteBot from '@/_hooks/useDeleteBot'
import useCreateConversation from '@hooks/useCreateConversation'
import useDeleteBot from '@hooks/useDeleteBot'
import {
setMainViewStateAtom,
MainViewState,
} from '@/_helpers/atoms/MainView.atom'
} from '@helpers/atoms/MainView.atom'
const BotInfoContainer: React.FC = () => {
const activeBot = useAtomValue(activeBotAtom)
@ -44,9 +44,7 @@ const BotInfoContainer: React.FC = () => {
<div className="flex h-full w-full pt-4">
<div className="mx-auto flex w-[672px] min-w-max flex-col gap-4">
<Avatar />
<h1 className="text-center text-2xl font-bold">
{activeBot?.name}
</h1>
<h1 className="text-center text-2xl font-bold">{activeBot?.name}</h1>
<div className="flex gap-4">
<PrimaryButton
fullWidth
@ -55,7 +53,7 @@ const BotInfoContainer: React.FC = () => {
/>
<PrimaryButton
fullWidth
className='bg-red-500 hover:bg-red-400'
className="bg-red-500 hover:bg-red-400"
title="Delete bot"
onClick={onDeleteBotClick}
/>

View File

@ -1,14 +1,14 @@
import { activeBotAtom } from '@/_helpers/atoms/Bot.atom'
import { showingBotListModalAtom } from '@/_helpers/atoms/Modal.atom'
import useGetBots from '@/_hooks/useGetBots'
import { Bot } from '@/_models/Bot'
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import { showingBotListModalAtom } from '@helpers/atoms/Modal.atom'
import useGetBots from '@hooks/useGetBots'
import { useAtom, useSetAtom } from 'jotai'
import { rightSideBarExpandStateAtom } from '@helpers/atoms/SideBarExpand.atom'
import React, { useEffect, useState } from 'react'
import Avatar from '../Avatar'
import {
MainViewState,
setMainViewStateAtom,
} from '@/_helpers/atoms/MainView.atom'
} from '@helpers/atoms/MainView.atom'
const BotListContainer: React.FC = () => {
const [open, setOpen] = useAtom(showingBotListModalAtom)
@ -16,6 +16,7 @@ const BotListContainer: React.FC = () => {
const [activeBot, setActiveBot] = useAtom(activeBotAtom)
const [bots, setBots] = useState<Bot[]>([])
const { getAllBots } = useGetBots()
const setRightSideBarVisibility = useSetAtom(rightSideBarExpandStateAtom)
useEffect(() => {
if (open) {
@ -23,31 +24,34 @@ const BotListContainer: React.FC = () => {
setBots(res)
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open])
const onBotSelected = (bot: Bot) => {
if (bot._id !== activeBot?._id) {
setMainView(MainViewState.BotInfo)
setActiveBot(bot)
setRightSideBarVisibility(true)
}
setOpen(false)
}
return (
<div className="overflow-hidden bg-white shadow sm:rounded-md">
<div className="bg-background/50 border-border overflow-hidden border sm:rounded-md">
<ul role="list" className="divide-y divide-gray-200">
{bots.map((bot) => (
{bots.map((bot, i) => (
<li
role="button"
key={bot._id}
className="flex gap-4 p-4 hover:bg-hover-light sm:px-6"
key={i}
className="flex items-center gap-4 p-4 hover:bg-hover-light sm:px-6"
onClick={() => onBotSelected(bot)}
>
<Avatar />
<div className="flex flex-1 flex-col">
<p className="line-clamp-1">{bot.name}</p>
<p className="line-clamp-1 text-ellipsis">{bot._id}</p>
<p className="text-muted-foreground mt-1 line-clamp-1 text-ellipsis">
{bot._id}
</p>
</div>
</li>
))}

View File

@ -1,4 +1,4 @@
import { showingBotListModalAtom } from '@/_helpers/atoms/Modal.atom'
import { showingBotListModalAtom } from '@helpers/atoms/Modal.atom'
import { Dialog, Transition } from '@headlessui/react'
import { useAtom } from 'jotai'
import React, { Fragment } from 'react'
@ -19,10 +19,10 @@ const BotListModal: React.FC = () => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
<div className="fixed inset-0 z-40 h-full bg-gray-950/90 transition-opacity dark:backdrop-blur-sm" />
</Transition.Child>
<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
<div className="fixed inset-0 z-50 w-screen overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
@ -33,8 +33,8 @@ const BotListModal: React.FC = () => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6">
<h1 className="mb-4 text-lg text-black font-bold">Your bots</h1>
<Dialog.Panel className="border-border bg-background/90 relative transform overflow-hidden rounded-lg border px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<h1 className="mb-4 font-bold">Your bots</h1>
<BotListContainer />
</Dialog.Panel>
</Transition.Child>

View File

@ -1,26 +1,26 @@
import Image from "next/image";
import Image from 'next/image'
const BotPreview: React.FC = () => {
return (
<div className="flex pb-2 flex-col border border-gray-400 min-h-[235px] gap-2 overflow-hidden rounded-lg">
<div className="flex items-center justify-center p-2 bg-gray-400">
<div className="flex min-h-[235px] flex-col gap-2 overflow-hidden rounded-lg border border-gray-400 pb-2">
<div className="flex items-center justify-center bg-gray-400 p-2">
<Image
className="rounded-md"
src={
"https://i.pinimg.com/564x/52/b1/6f/52b16f96f52221d48bea716795ccc89a.jpg"
'https://i.pinimg.com/564x/52/b1/6f/52b16f96f52221d48bea716795ccc89a.jpg'
}
width={32}
height={32}
alt=""
/>
</div>
<div className="flex items-center text-xs text-gray-400 gap-1 px-1">
<div className="flex-grow mx-1 border-b border-gray-400"></div>
<div className="flex items-center gap-1 px-1 text-xs text-gray-400">
<div className="mx-1 flex-grow border-b border-gray-400"></div>
Context cleared
<div className="flex-grow mx-1 border-b border-gray-400"></div>
<div className="mx-1 flex-grow border-b border-gray-400"></div>
</div>
</div>
);
};
)
}
export default BotPreview;
export default BotPreview

View File

@ -1,28 +1,26 @@
import { activeBotAtom } from '@/_helpers/atoms/Bot.atom'
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import { useAtomValue } from 'jotai'
import React, { useEffect, useState } from 'react'
import React, { useState } from 'react'
import ExpandableHeader from '../ExpandableHeader'
import { useDebouncedCallback } from 'use-debounce'
import useUpdateBot from '@/_hooks/useUpdateBot'
import ProgressSetting from '../ProgressSetting'
import { set } from 'react-hook-form'
import useUpdateBot from '@hooks/useUpdateBot'
import { formatTwoDigits } from '@utils/converter'
const delayBeforeUpdateInMs = 1000
const BotSetting: React.FC = () => {
const activeBot = useAtomValue(activeBotAtom)
const [temperature, setTemperature] = useState(0)
const [maxTokens, setMaxTokens] = useState(0)
const [frequencyPenalty, setFrequencyPenalty] = useState(0)
const [presencePenalty, setPresencePenalty] = useState(0)
const [temperature, setTemperature] = useState(
activeBot?.customTemperature ?? 0
)
useEffect(() => {
if (!activeBot) return
setMaxTokens(activeBot.maxTokens ?? 0)
setTemperature(activeBot.customTemperature ?? 0)
setFrequencyPenalty(activeBot.frequencyPenalty ?? 0)
setPresencePenalty(activeBot.presencePenalty ?? 0)
}, [activeBot?._id])
const [maxTokens, setMaxTokens] = useState(activeBot?.maxTokens ?? 0)
const [frequencyPenalty, setFrequencyPenalty] = useState(
activeBot?.frequencyPenalty ?? 0
)
const [presencePenalty, setPresencePenalty] = useState(
activeBot?.presencePenalty ?? 0
)
const { updateBot } = useUpdateBot()
@ -60,71 +58,109 @@ const BotSetting: React.FC = () => {
return (
<div className="my-3 flex flex-col">
<ExpandableHeader
title="BOT SETTINGS"
expanded={true}
onClick={() => {}}
/>
<ExpandableHeader title="BOT SETTINGS" />
<div className="mx-2 mt-3 flex flex-shrink-0 flex-col gap-4">
{/* System prompt */}
<div>
<label
htmlFor="comment"
className="block text-sm font-medium leading-6 text-gray-900"
>
<label htmlFor="comment" className="block">
System prompt
</label>
<div className="mt-2">
<div className="mt-1">
<textarea
rows={4}
name="comment"
id="comment"
className="block w-full rounded-md border-0 py-1.5 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="bg-background/80 text-background-reverse ring-border placeholder:text-muted-foreground focus:ring-accent/50 block w-full resize-none rounded-md border-0 py-1.5 text-xs leading-relaxed shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset"
defaultValue={activeBot.systemPrompt}
onChange={(e) => debouncedSystemPrompt(e.target.value)}
/>
</div>
</div>
<ProgressSetting
title="Max tokens"
min={0}
max={4096}
step={1}
value={maxTokens}
onValueChanged={(value) => debouncedMaxToken(value)}
/>
{/* TODO: clean up this code */}
{/* Max temp */}
<p>Max tokens</p>
<div className="mt-2 flex items-center gap-2">
<input
className="flex-1"
type="range"
defaultValue={activeBot.maxTokens ?? 0}
min={0}
max={4096}
step={1}
onChange={(e) => {
const value = Number(e.target.value)
setMaxTokens(value)
debouncedMaxToken(value)
}}
/>
<span className="border-accent rounded-md border px-2 py-1">
{formatTwoDigits(maxTokens)}
</span>
</div>
<ProgressSetting
min={0}
max={1}
step={0.01}
title="Temperature"
value={temperature}
onValueChanged={(value) => debouncedTemperature(value)}
/>
<p>Frequency penalty</p>
<div className="mt-2 flex items-center gap-2">
<input
className="flex-1"
type="range"
defaultValue={activeBot.frequencyPenalty ?? 0}
min={0}
max={1}
step={0.01}
onChange={(e) => {
const value = Number(e.target.value)
setFrequencyPenalty(value)
debouncedFreqPenalty(value)
}}
/>
<span className="border-accent rounded-md border px-2 py-1">
{formatTwoDigits(frequencyPenalty)}
</span>
</div>
<ProgressSetting
title="Frequency penalty"
value={frequencyPenalty}
min={0}
max={1}
step={0.01}
onValueChanged={(value) => debouncedFreqPenalty(value)}
/>
<p>Presence penalty</p>
<div className="mt-2 flex items-center gap-2">
<input
className="flex-1"
type="range"
defaultValue={activeBot.maxTokens ?? 0}
min={0}
max={1}
step={0.01}
onChange={(e) => {
const value = Number(e.target.value)
setPresencePenalty(value)
debouncedPresencePenalty(value)
}}
/>
<span className="border-accent rounded-md border px-2 py-1">
{formatTwoDigits(presencePenalty)}
</span>
</div>
<ProgressSetting
min={0}
max={1}
step={0.01}
title="Presence penalty"
value={presencePenalty}
onValueChanged={(value) => {
setPresencePenalty(value)
debouncedPresencePenalty(value)
}}
/>
{/* Custom temp */}
<p>Temperature</p>
<div className="mt-2 flex items-center gap-2">
<input
className="flex-1"
type="range"
id="volume"
name="volume"
defaultValue={activeBot.customTemperature ?? 0}
min="0"
max="1"
step="0.01"
onChange={(e) => {
const newTemp = Number(e.target.value)
setTemperature(newTemp)
debouncedTemperature(Number(e.target.value))
}}
/>
<span className="border-accent rounded-md border px-2 py-1">
{formatTwoDigits(temperature)}
</span>
</div>
</div>
</div>
)

View File

@ -1,12 +1,12 @@
import React from "react";
import MainHeader from "../MainHeader";
import MainView from "../MainView";
import React from 'react'
import MainHeader from '../MainHeader'
import MainView from '../MainView'
const CenterContainer: React.FC = () => (
<div className="flex-1 flex flex-col">
<div className="flex flex-1 flex-col dark:bg-gray-950/50">
<MainHeader />
<MainView />
</div>
);
)
export default React.memo(CenterContainer);
export default React.memo(CenterContainer)

View File

@ -2,12 +2,11 @@
import React, { useCallback, useRef, useState, useEffect } from 'react'
import ChatItem from '../ChatItem'
import { ChatMessage } from '@/_models/ChatMessage'
import useChatMessages from '@/_hooks/useChatMessages'
import useChatMessages from '@hooks/useChatMessages'
import { useAtomValue } from 'jotai'
import { selectAtom } from 'jotai/utils'
import { getActiveConvoIdAtom } from '@/_helpers/atoms/Conversation.atom'
import { chatMessages } from '@/_helpers/atoms/ChatMessage.atom'
import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom'
import { chatMessages } from '@helpers/atoms/ChatMessage.atom'
const ChatBody: React.FC = () => {
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? ''
@ -54,7 +53,7 @@ const ChatBody: React.FC = () => {
}, [messageList, lastPostRef])
return (
<div className="scroll flex flex-1 flex-col-reverse overflow-y-auto py-4">
<div className="[&>*:nth-child(odd)]:bg-background flex h-full flex-1 flex-col-reverse overflow-y-auto">
{content}
</div>
)

View File

@ -1,7 +1,8 @@
import SimpleControlNetMessage from '../SimpleControlNetMessage'
import SimpleImageMessage from '../SimpleImageMessage'
import SimpleTextMessage from '../SimpleTextMessage'
import { ChatMessage, MessageType } from '@/_models/ChatMessage'
import { ChatMessage, MessageType } from '@models/ChatMessage'
export default function renderChatMessage({
id,

View File

@ -1,7 +1,6 @@
/* eslint-disable react/display-name */
import React, { forwardRef } from 'react'
import renderChatMessage from '../ChatBody/renderChatMessage'
import { ChatMessage } from '@/_models/ChatMessage'
type Props = {
message: ChatMessage

View File

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

View File

@ -1,5 +1,5 @@
import { showConfirmDeleteConversationModalAtom } from '@/_helpers/atoms/Modal.atom'
import useDeleteConversation from '@/_hooks/useDeleteConversation'
import { showConfirmDeleteConversationModalAtom } from '@helpers/atoms/Modal.atom'
import useDeleteConversation from '@hooks/useDeleteConversation'
import { Dialog, Transition } from '@headlessui/react'
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
import { useAtom } from 'jotai'
@ -31,10 +31,10 @@ const ConfirmDeleteConversationModal: React.FC = () => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
<div className="fixed inset-0 z-40 h-full bg-gray-950/90 transition-opacity dark:backdrop-blur-sm" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
@ -45,7 +45,7 @@ const ConfirmDeleteConversationModal: React.FC = () => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-border bg-background/90 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon
@ -54,14 +54,11 @@ const ConfirmDeleteConversationModal: React.FC = () => {
/>
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title
as="h3"
className="text-base font-semibold leading-6 text-gray-900"
>
<Dialog.Title as="h3" className="font-semibold leading-6">
Delete Conversation
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
<p className="text-muted-foreground">
Are you sure you want to delete this conversation? All
of messages will be permanently removed. This action
cannot be undone.
@ -72,14 +69,14 @@ const ConfirmDeleteConversationModal: React.FC = () => {
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-xs font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
onClick={() => onConfirmDelete()}
>
Delete
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-xs font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => setShow(false)}
ref={cancelButtonRef}
>

View File

@ -2,7 +2,7 @@ import React, { Fragment } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline'
import { useAtom } from 'jotai'
import { showConfirmDeleteModalAtom } from '@/_helpers/atoms/Modal.atom'
import { showConfirmDeleteModalAtom } from '@helpers/atoms/Modal.atom'
const ConfirmDeleteModelModal: React.FC = () => {
const [show, setShow] = useAtom(showConfirmDeleteModalAtom)

View File

@ -2,8 +2,8 @@ import React, { Fragment } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline'
import { useAtom } from 'jotai'
import useSignOut from '@/_hooks/useSignOut'
import { showConfirmSignOutModalAtom } from '@/_helpers/atoms/Modal.atom'
import useSignOut from '@hooks/useSignOut'
import { showConfirmSignOutModalAtom } from '@helpers/atoms/Modal.atom'
const ConfirmSignOutModal: React.FC = () => {
const [show, setShow] = useAtom(showConfirmSignOutModalAtom)

View File

@ -1,8 +1,7 @@
import React from 'react'
import Image from 'next/image'
import useCreateConversation from '@/_hooks/useCreateConversation'
import { AssistantModel } from '@/_models/AssistantModel'
import { PlayIcon } from "@heroicons/react/24/outline"
import useCreateConversation from '@hooks/useCreateConversation'
import { PlayIcon } from '@heroicons/react/24/outline'
type Props = {
model: AssistantModel

View File

@ -1,4 +1,3 @@
import { AssistantModel } from '@/_models/AssistantModel'
import ConversationalCard from '../ConversationalCard'
import { ChatBubbleBottomCenterTextIcon } from '@heroicons/react/24/outline'

View File

@ -5,18 +5,21 @@ import DropdownBox from '../DropdownBox'
import PrimaryButton from '../PrimaryButton'
import ToggleSwitch from '../ToggleSwitch'
import CreateBotPromptInput from '../CreateBotPromptInput'
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import { Bot } from '@/_models/Bot'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
import { SubmitHandler, useForm } from 'react-hook-form'
import Avatar from '../Avatar'
import { v4 as uuidv4 } from 'uuid'
import DraggableProgressBar from '../DraggableProgressBar'
import { useSetAtom } from 'jotai'
import { activeBotAtom } from '@/_helpers/atoms/Bot.atom'
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import {
leftSideBarExpandStateAtom,
rightSideBarExpandStateAtom,
} from '@helpers/atoms/SideBarExpand.atom'
import {
MainViewState,
setMainViewStateAtom,
} from '@/_helpers/atoms/MainView.atom'
} from '@helpers/atoms/MainView.atom'
import { executeSerial } from '../../../../electron/core/plugin-manager/execution/extension-manager'
import { DataService } from '@janhq/core'
@ -24,16 +27,18 @@ const CreateBotContainer: React.FC = () => {
const { downloadedModels } = useGetDownloadedModels()
const setActiveBot = useSetAtom(activeBotAtom)
const setMainViewState = useSetAtom(setMainViewStateAtom)
const setRightSideBarVisibility = useSetAtom(rightSideBarExpandStateAtom)
const createBot = async (bot: Bot) => {
try {
await executeSerial(DataService.CreateBot, bot).then(async () => {
setActiveBot(bot)
setMainViewState(MainViewState.BotInfo)
})
await executeSerial(DataService.CreateBot, bot)
} catch (err) {
alert(err)
console.error(err)
} finally {
setMainViewState(MainViewState.BotInfo)
setActiveBot(bot)
setRightSideBarVisibility(true)
}
}
@ -73,9 +78,10 @@ const CreateBotContainer: React.FC = () => {
createBot(bot)
}
let models = downloadedModels.map((model) => {
let models = downloadedModels.map((model: { _id: any }) => {
return model._id
})
models = ['Select a model', ...models]
return (
@ -84,7 +90,7 @@ const CreateBotContainer: React.FC = () => {
onSubmit={handleSubmit(onSubmit)}
>
<div className="mx-6 mt-3 flex items-center justify-between gap-3">
<span className="text-3xl font-bold text-gray-900">Create Bot</span>
<span className="text-lg font-bold">Create Bot</span>
<div className="flex gap-3">
<PrimaryButton isSubmit title="Create" />
</div>
@ -108,7 +114,7 @@ const CreateBotContainer: React.FC = () => {
control={control}
/>
<div className="flex flex-col gap-4 pb-2">
<div className="flex flex-col pb-2">
<DropdownBox
id="modelId"
title="Model"
@ -116,30 +122,23 @@ const CreateBotContainer: React.FC = () => {
control={control}
required={true}
/>
</div>
<CreateBotPromptInput
id="systemPrompt"
<CreateBotPromptInput id="systemPrompt" control={control} required />
<div className="flex flex-col gap-0.5">
<label className="block">Bot access</label>
<span className="mb-4 mt-1 text-muted-foreground">
If this setting is enabled, the bot will be added to your profile
and will be publicly accessible. Turning this off will make the
bot private.
</span>
<ToggleSwitch
id="publiclyAccessible"
title="Bot publicly accessible"
control={control}
required
/>
<div className="flex flex-col gap-0.5">
<label className="block text-base font-bold text-gray-900">
Bot access
</label>
<span className="pb-2 text-sm text-[#737d7d]">
If this setting is enabled, the bot will be added to your
profile and will be publicly accessible. Turning this off will
make the bot private.
</span>
<ToggleSwitch
id="publiclyAccessible"
title="Bot publicly accessible"
control={control}
/>
</div>
<p>Max tokens</p>
<DraggableProgressBar
id="maxTokens"
control={control}

View File

@ -0,0 +1,98 @@
import React, { Fragment, useState } from 'react'
import ToggleSwitch from '../ToggleSwitch'
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'
import CutomBotTemperature from '../CustomBotTemperature'
import DraggableProgressBar from '../DraggableProgressBar'
type Props = {
control?: any
}
const CreateBotInAdvance: React.FC<Props> = ({ control }) => {
const [showAdvanced, setShowAdvanced] = useState(true)
const handleShowAdvanced = (e: React.MouseEvent<HTMLButtonElement>) => {
setShowAdvanced(!showAdvanced)
}
return (
<div className="flex flex-col gap-2">
<div className="flex items-start">
<button
className="mb-2 flex items-center justify-center font-bold"
onClick={handleShowAdvanced}
>
Advanced
{showAdvanced ? (
<ChevronDownIcon width={16} className="ml-2" />
) : (
<ChevronUpIcon width={16} className="ml-2" />
)}
</button>
</div>
{showAdvanced && (
<>
<div>
<p className="text-bold">Max tokens</p>
<DraggableProgressBar
id="maxTokens"
control={control}
min={0}
max={4096}
step={1}
/>
</div>
<div>
<p className="text-bold">Custom temperature</p>
<DraggableProgressBar
id="customTemperature"
control={control}
min={0}
max={1}
step={0.01}
/>
</div>
<div>
<p className="text-bold">Frequency penalty</p>
<DraggableProgressBar
id="frequencyPenalty"
control={control}
min={0}
max={1}
step={0.01}
/>
</div>
<div>
<p className="text-bold">Presence penalty</p>
<DraggableProgressBar
id="presencePenalty"
control={control}
min={0}
max={1}
step={0.01}
/>
</div>
</>
)}
{/* {showAdvanced && (
<Fragment>
<ToggleSwitch
id="suggestReplies"
title="Suggest replies"
control={control}
/>
<ToggleSwitch
id="renderMarkdownContent"
title="Render markdown content"
control={control}
/>
<CutomBotTemperature control={control} />
</Fragment>
)} */}
</div>
)
}
export default CreateBotInAdvance

View File

@ -1,30 +1,27 @@
import React, { Fragment, use } from "react";
import ToggleSwitch from "../ToggleSwitch";
import { useController } from "react-hook-form";
import React, { Fragment, use } from 'react'
import ToggleSwitch from '../ToggleSwitch'
import { useController } from 'react-hook-form'
type Props = {
id: string;
control?: any;
required?: boolean;
};
id: string
control?: any
required?: boolean
}
const CreateBotPromptInput: React.FC<Props> = ({ id, control, required }) => {
const { field } = useController({
name: id,
control: control,
rules: { required: required },
});
})
return (
<Fragment>
<div className="flex flex-col gap-2">
<label
htmlFor="comment"
className="block text-base text-gray-900 font-bold"
>
<label htmlFor="comment" className="block font-bold ">
Prompt
</label>
<p className="text-sm text-gray-400 font-normal">
<p className="mt-1 font-normal text-gray-400">
All conversations with this bot will start with your prompt but it
will not be visible to the user in the chat. If you would like the
prompt message to be visible to the user, consider using an intro
@ -32,18 +29,18 @@ const CreateBotPromptInput: React.FC<Props> = ({ id, control, required }) => {
</p>
<ToggleSwitch
id="visibleFromBotProfile"
title={"Prompt visible from bot profile"}
title={'Prompt visible from bot profile'}
control={control}
/>
<textarea
rows={4}
className="block w-full resize-none rounded-md border-0 py-1.5 bg-transparent shadow-sm ring-1 ring-inset text-gray-900 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 resize-none rounded-md border-0 bg-background/80 py-1.5 text-xs leading-relaxed text-background-reverse shadow-sm ring-1 ring-inset ring-border placeholder:text-muted-foreground focus:ring-2 focus:ring-inset focus:ring-accent/50"
placeholder="Talk to me like a pirate"
{...field}
/>
</div>
</Fragment>
);
};
)
}
export default CreateBotPromptInput;
export default CreateBotPromptInput

View File

@ -1,10 +1,10 @@
import ToggleSwitch from "../ToggleSwitch";
import DraggableProgressBar from "../DraggableProgressBar";
import { Controller } from "react-hook-form";
import ToggleSwitch from '../ToggleSwitch'
import DraggableProgressBar from '../DraggableProgressBar'
import { Controller } from 'react-hook-form'
type Props = {
control?: any;
};
control?: any
}
const CutomBotTemperature: React.FC<Props> = ({ control }) => (
<div className="flex flex-col gap-2">
@ -13,23 +13,29 @@ const CutomBotTemperature: React.FC<Props> = ({ control }) => (
title="Custom temperature"
control={control}
/>
<div className="text-gray-500 mt-1 text-[0.8em]">
Controls the creativity of the bot&apos;s responses. Higher values produce more
<div className="mt-1 text-[0.8em] text-gray-500">
{`Controls the creativity of the bot's responses. Higher values produce more
varied but unpredictable replies, lower values generate more consistent
responses.
responses.`}
</div>
<span className="text-gray-900">default: 0.7</span>
<Controller
name="enableCustomTemperature"
control={control}
render={({ field: { value } }) => {
if (!value) return <div />;
if (!value) return <div />
return (
<DraggableProgressBar id="customTemperature" control={control} min={0} max={1} step={0.01} />
);
<DraggableProgressBar
id="customTemperature"
control={control}
min={0}
max={1}
step={0.01}
/>
)
}}
/>
</div>
);
)
export default CutomBotTemperature;
export default CutomBotTemperature

View File

@ -1,4 +1,3 @@
import { AssistantModel } from '@/_models/AssistantModel'
import DownloadModelContent from '../DownloadModelContent'
type Props = {

View File

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

View File

@ -1,8 +1,7 @@
import React, { Fragment } from 'react'
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
import { useAtomValue } from 'jotai'
import ModelDownloadingTable from '../ModelDownloadingTable'
import { DownloadState } from '@/_models/DownloadState'
const DownloadingModelTable: React.FC = () => {
const modelDownloadState = useAtomValue(modelDownloadStateAtom)

View File

@ -1,23 +1,29 @@
import { formatTwoDigits } from "@/_utils/converter";
import React from "react";
import { Controller, useController } from "react-hook-form";
import { formatTwoDigits } from '@utils/converter'
import React from 'react'
import { Controller, useController } from 'react-hook-form'
type Props = {
id: string;
control: any;
min: number;
max: number;
step: number;
};
id: string
control: any
min: number
max: number
step: number
}
const DraggableProgressBar: React.FC<Props> = ({ id, control, min, max, step }) => {
const DraggableProgressBar: React.FC<Props> = ({
id,
control,
min,
max,
step,
}) => {
const { field } = useController({
name: id,
control: control,
});
})
return (
<div className="flex items-center gap-2 mt-2">
<div className="flex items-center gap-2">
<input
{...field}
className="flex-1"
@ -30,13 +36,13 @@ const DraggableProgressBar: React.FC<Props> = ({ id, control, min, max, step })
name={id}
control={control}
render={({ field: { value } }) => (
<span className="border border-[#737d7d] rounded-md py-1 px-2 text-gray-900">
<span className="rounded-md border border-border px-2 py-1 text-accent">
{formatTwoDigits(value)}
</span>
)}
/>
</div>
);
};
)
}
export default DraggableProgressBar;
export default DraggableProgressBar

View File

@ -24,9 +24,9 @@ const DropdownBox: React.FC<Props> = ({
return (
<Fragment>
<label className="block text-base font-bold text-gray-900">{title}</label>
<label className="block font-bold">{title}</label>
<select
className="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6"
className="bg-background/80 ring-border focus:ring-accent/50 mt-1 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-xs ring-1 ring-inset focus:ring-2 sm:leading-6"
{...field}
>
{data.map((option) => (

View File

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

View File

@ -1,5 +1,5 @@
import HeaderTitle from '../HeaderTitle'
import ExploreModelList from '../ExploreModelList'
import ExploreModelList from '../../../screens/ExploreModels/ExploreModelList'
import ExploreModelFilter from '../ExploreModelFilter'
const ExploreModelContainer: React.FC = () => (

View File

@ -16,7 +16,7 @@ const tags = [
const checkboxs = ['GGUF', 'TensorRT', 'Meow', 'JigglyPuff']
const ExploreModelFilter: React.FC = () => {
const enabled = false
const enabled = true
if (!enabled) return null
return (

View File

@ -3,6 +3,7 @@
'use client'
import ExploreModelItemHeader from '../ExploreModelItemHeader'
import { Button } from '@uikit'
import ModelVersionList from '../ModelVersionList'
import { Fragment, forwardRef, useEffect, useState } from 'react'
import SimpleTag from '../SimpleTag'
@ -14,10 +15,9 @@ import {
UsecaseTag,
VersionTag,
} from '@/_components/SimpleTag/TagType'
import { displayDate } from '@/_utils/datetime'
import { Product } from '@/_models/Product'
import useGetMostSuitableModelVersion from '@/_hooks/useGetMostSuitableModelVersion'
import { toGigabytes } from '@/_utils/converter'
import { displayDate } from '@utils/datetime'
import useGetMostSuitableModelVersion from '@hooks/useGetMostSuitableModelVersion'
import { toGigabytes } from '@utils/converter'
type Props = {
model: Product
@ -32,6 +32,7 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
useEffect(() => {
getMostSuitableModelVersion(availableVersions)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [availableVersions])
if (!suitableModel) {
@ -43,25 +44,30 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
return (
<div
ref={ref}
className="mb-4 flex flex-col rounded-md border border-gray-200"
className="border-border bg-background/60 mb-4 flex flex-col rounded-md border"
>
<ExploreModelItemHeader
suitableModel={suitableModel}
exploreModel={model}
/>
<div className="flex flex-col px-[26px] py-[22px]">
<div className="flex flex-col p-4">
<div className="mb-4 flex flex-col gap-1">
<span className="font-semibold">About</span>
<span className="text-muted-foreground leading-relaxed">
{model.longDescription}
</span>
</div>
<div className="flex justify-between">
<div className="flex flex-1 flex-col gap-8">
<div className="flex flex-1 flex-col gap-y-4">
<div className="flex flex-col gap-1">
<div className="text-sm font-medium text-gray-500">
Release Date
</div>
<div className="text-sm font-normal text-gray-900">
<div className="font-semibold">Release Date</div>
<p className="text-muted-foreground mt-1">
{displayDate(model.releaseDate)}
</div>
</p>
</div>
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-gray-500">Version</div>
<div className="font-semibold">Version</div>
<div className="flex gap-2">
<SimpleTag
title={model.version}
@ -81,17 +87,14 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
</div>
</div>
</div>
<div className="flex flex-1 flex-col gap-8">
<div className="flex flex-1 flex-col gap-y-4">
<div>
<div className="text-sm font-medium text-gray-500">Author</div>
<div className="text-sm font-normal text-gray-900">
{model.author}
</div>
<div className="font-semibold">Author</div>
<p className="text-muted-foreground mt-1">{model.author}</p>
</div>
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-gray-500">
Compatibility
</div>
<div className="font-semibold">Compatibility</div>
<div className="flex gap-2">
<SimpleTag
title={usecase}
@ -106,44 +109,42 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
</div>
</div>
</div>
</div>
<div className="mt-[26px] flex flex-col gap-1">
<span className="text-sm font-medium text-gray-500">About</span>
<span className="text-sm font-normal text-gray-500">
{model.longDescription}
</span>
</div>
<div className="mt-5 flex flex-col gap-2">
<span className="text-sm font-medium text-gray-500">Tags</span>
<div className="flex flex-wrap gap-2">
{model.tags.map((tag) => (
<SimpleTag
key={tag}
title={tag}
type={MiscellanousTag.MiscellanousDefault}
clickable={false}
/>
))}
<div className="flex flex-1 flex-col gap-y-4">
<div>
<div className="font-medium">Tags</div>
<div className="mt-1 flex flex-wrap gap-2">
{model.tags.map((tag) => (
<SimpleTag
key={tag}
title={tag}
type={MiscellanousTag.MiscellanousDefault}
clickable={false}
/>
))}
</div>
</div>
</div>
</div>
{model.availableVersions?.length > 0 && (
<div className="border-border bg-background mt-5 w-full rounded-md border p-2">
<button onClick={() => setShow(!show)} className="w-full">
{!show
? '+ Show Available Versions'
: '- Collapse Available Versions'}
</button>
{show && (
<ModelVersionList
model={model}
versions={model.availableVersions}
recommendedVersion={suitableModel?._id ?? ''}
/>
)}
</div>
)}
</div>
{model.availableVersions?.length > 0 && (
<Fragment>
{show && (
<ModelVersionList
model={model}
versions={model.availableVersions}
recommendedVersion={suitableModel?._id ?? ''}
/>
)}
<button
onClick={() => setShow(!show)}
className="border-t border-gray-200 bg-[#FBFBFB] px-4 py-2 text-left text-sm text-gray-500"
>
{!show ? '+ Show Available Versions' : '- Collapse'}
</button>
</Fragment>
)}
</div>
)
})

View File

@ -1,19 +1,18 @@
import SimpleTag from '../SimpleTag'
import PrimaryButton from '../PrimaryButton'
import { formatDownloadPercentage, toGigabytes } from '@/_utils/converter'
import { formatDownloadPercentage, toGigabytes } from '@utils/converter'
import SecondaryButton from '../SecondaryButton'
import { Product } from '@/_models/Product'
import { useCallback, useEffect, useMemo } from 'react'
import { ModelVersion } from '@/_models/ModelVersion'
import useGetPerformanceTag from '@/_hooks/useGetPerformanceTag'
import useDownloadModel from '@/_hooks/useDownloadModel'
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import useGetPerformanceTag from '@hooks/useGetPerformanceTag'
import useDownloadModel from '@hooks/useDownloadModel'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
import { atom, useAtomValue, useSetAtom } from 'jotai'
import { Button } from '@uikit'
import {
MainViewState,
setMainViewStateAtom,
} from '@/_helpers/atoms/MainView.atom'
} from '@helpers/atoms/MainView.atom'
type Props = {
suitableModel: ModelVersion
@ -37,35 +36,36 @@ const ExploreModelItemHeader: React.FC<Props> = ({
useEffect(() => {
getPerformanceForModel(suitableModel)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [suitableModel])
const onDownloadClick = useCallback(() => {
downloadModel(exploreModel, suitableModel)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [exploreModel, suitableModel])
const isDownloaded =
downloadedModels.find((model) => model._id === suitableModel._id) != null
let downloadButton = (
<PrimaryButton
title={
suitableModel.size
? `Download (${toGigabytes(suitableModel.size)})`
: 'Download'
}
onClick={() => onDownloadClick()}
/>
<Button themes="accent" onClick={() => onDownloadClick()}>
{suitableModel.size
? `Download (${toGigabytes(suitableModel.size)})`
: 'Download'}
</Button>
)
if (isDownloaded) {
downloadButton = (
<PrimaryButton
title="View Downloaded Model"
<Button
size="sm"
themes="accent"
onClick={() => {
setMainViewState(MainViewState.MyModel)
}}
className="bg-green-500 hover:bg-green-400"
/>
>
View Downloaded Model
</Button>
)
}
@ -82,7 +82,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({
}
return (
<div className="flex items-center justify-between border-b border-gray-200 p-4">
<div className="border-border bg-background/50 flex items-center justify-between rounded-t-md border-b px-4 py-2">
<div className="flex items-center gap-2">
<span>{exploreModel.name}</span>
{performanceTag && (

View File

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

View File

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

View File

@ -1,6 +1,6 @@
'use client'
import { showingMobilePaneAtom } from '@/_helpers/atoms/Modal.atom'
import { showingMobilePaneAtom } from '@helpers/atoms/Modal.atom'
import { Bars3Icon } from '@heroicons/react/24/outline'
import { useSetAtom } from 'jotai'
import React from 'react'

View File

@ -1,7 +1,6 @@
import React from 'react'
import { useAtomValue, useSetAtom } from 'jotai'
import Image from 'next/image'
import { Conversation } from '@/_models/Conversation'
import { ModelManagementService } from '@janhq/core'
import { executeSerial } from '../../../../electron/core/plugin-manager/execution/extension-manager'
import {
@ -9,13 +8,14 @@ import {
setActiveConvoIdAtom,
updateConversationErrorAtom,
updateConversationWaitingForResponseAtom,
} from '@/_helpers/atoms/Conversation.atom'
} from '@helpers/atoms/Conversation.atom'
import {
setMainViewStateAtom,
MainViewState,
} from '@/_helpers/atoms/MainView.atom'
import useInitModel from '@/_hooks/useInitModel'
import { displayDate } from '@/_utils/datetime'
} from '@helpers/atoms/MainView.atom'
import useInitModel from '@hooks/useInitModel'
import { displayDate } from '@utils/datetime'
import { twMerge } from 'tailwind-merge'
type Props = {
conversation: Conversation
@ -35,9 +35,7 @@ const HistoryItem: React.FC<Props> = ({
const setMainViewState = useSetAtom(setMainViewStateAtom)
const activeConvoId = useAtomValue(getActiveConvoIdAtom)
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
const updateConvWaiting = useSetAtom(
updateConversationWaitingForResponseAtom
)
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
const updateConvError = useSetAtom(updateConversationErrorAtom)
const isSelected = activeConvoId === conversation._id
@ -62,43 +60,33 @@ const HistoryItem: React.FC<Props> = ({
setMainViewState(MainViewState.Conversation)
setActiveConvoId(conversation._id)
}
};
}
const backgroundColor = isSelected
? "bg-gray-100 dark:bg-gray-700"
: "bg-white dark:bg-gray-500"
const description = conversation?.lastMessage ?? "No new message"
const backgroundColor = isSelected ? 'bg-background/80' : 'bg-background/20'
const description = conversation?.lastMessage ?? 'No new message'
return (
<li
role="button"
className={`ml-3 mr-2 flex flex-row rounded p-3 ${backgroundColor} hover:bg-hover-light`}
className={twMerge(
'flex flex-row rounded-md border border-border p-3',
backgroundColor
)}
onClick={onClick}
>
<div className="h-8 w-8">
<Image
width={32}
height={32}
src={avatarUrl ?? 'icons/app_icon.svg'}
className="aspect-square rounded-full"
alt=""
/>
</div>
<div className="ml-2 flex flex-1 flex-col">
<div className="flex flex-1 flex-col">
{/* title */}
<div className="flex">
<span className="line-clamp-1 flex-1 text-gray-900">
{summary ?? name}
</span>
<span className="line-clamp-1 text-xs leading-5 text-gray-500">
{updatedAt && displayDate(new Date(updatedAt).getTime())}
</span>
</div>
<span className="mb-1 line-clamp-1 leading-5 text-muted-foreground">
{updatedAt && displayDate(new Date(updatedAt).getTime())}
</span>
<span className="line-clamp-1">{summary ?? name}</span>
{/* description */}
<span className="mt-1 line-clamp-2 text-gray-400">{description}</span>
<span className="mt-1 line-clamp-2 text-muted-foreground">
{description}
</span>
</div>
</li>
)

View File

@ -2,15 +2,15 @@ import HistoryItem from '../HistoryItem'
import { useEffect, useState } from 'react'
import ExpandableHeader from '../ExpandableHeader'
import { useAtomValue } from 'jotai'
import { searchAtom } from '@/_helpers/JotaiWrapper'
import useGetUserConversations from '@/_hooks/useGetUserConversations'
import { searchAtom } from '@helpers/JotaiWrapper'
import useGetUserConversations from '@hooks/useGetUserConversations'
import SidebarEmptyHistory from '../SidebarEmptyHistory'
import { userConversationsAtom } from '@/_helpers/atoms/Conversation.atom'
import { userConversationsAtom } from '@helpers/atoms/Conversation.atom'
import { twMerge } from 'tailwind-merge'
const HistoryList: React.FC = () => {
const conversations = useAtomValue(userConversationsAtom)
const searchText = useAtomValue(searchAtom)
const [expand, setExpand] = useState<boolean>(true)
const { getUserConversations } = useGetUserConversations()
useEffect(() => {
@ -18,17 +18,9 @@ const HistoryList: React.FC = () => {
}, [])
return (
<div className="flex flex-grow flex-col gap-2 overflow-hidden pt-3">
<ExpandableHeader
title="CHAT HISTORY"
expanded={expand}
onClick={() => setExpand(!expand)}
/>
<ul
className={`scroll mt-1 flex flex-col gap-1 overflow-y-auto ${
!expand ? 'hidden ' : 'block'
}`}
>
<div className="flex flex-grow flex-col gap-2">
<ExpandableHeader title="CHAT HISTORY" />
<ul className={twMerge('mt-1 flex flex-col gap-y-3 overflow-y-auto')}>
{conversations.length > 0 ? (
conversations
.filter(

View File

@ -1,21 +1,22 @@
/* eslint-disable react-hooks/rules-of-hooks */
'use client'
import BasicPromptInput from '../BasicPromptInput'
import BasicPromptAccessories from '../BasicPromptAccessories'
import { useAtomValue, useSetAtom } from 'jotai'
import { showingAdvancedPromptAtom } from '@/_helpers/atoms/Modal.atom'
import { showingAdvancedPromptAtom } from '@helpers/atoms/Modal.atom'
import SecondaryButton from '../SecondaryButton'
import { Fragment, useEffect, useState } from 'react'
import { PlusIcon } from '@heroicons/react/24/outline'
import useCreateConversation from '@/_hooks/useCreateConversation'
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import useCreateConversation from '@hooks/useCreateConversation'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import {
currentConversationAtom,
currentConvoStateAtom,
} from '@/_helpers/atoms/Conversation.atom'
import useGetBots from '@/_hooks/useGetBots'
import { activeBotAtom } from '@/_helpers/atoms/Bot.atom'
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
} from '@helpers/atoms/Conversation.atom'
import useGetBots from '@hooks/useGetBots'
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
const InputToolbar: React.FC = () => {
const showingAdvancedPrompt = useAtomValue(showingAdvancedPromptAtom)
@ -62,6 +63,7 @@ const InputToolbar: React.FC = () => {
}
}
getReplyState()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentConvo])
const onNewConversationClick = () => {
@ -70,26 +72,19 @@ const InputToolbar: React.FC = () => {
}
}
if (showingAdvancedPrompt) {
return <div />
}
if (inputState === 'loading') {
return <div>Loading..</div>
}
if (inputState === 'disabled') {
// text italic
if (inputState === 'loading') return <div>Loading..</div>
if (inputState === 'disabled')
return (
<p className="mx-auto my-5 line-clamp-2 text-ellipsis text-center text-sm italic text-gray-600">
{error}
</p>
<div className="bg-background/90 sticky bottom-0 flex items-center justify-center">
<p className="mx-auto my-5 line-clamp-2 text-ellipsis text-center italic text-gray-600">
{error}
</p>
</div>
)
}
return (
<Fragment>
<div className="bg-background/90 sticky bottom-0 w-full px-5 py-0">
{currentConvoState?.error && (
<div className="flex flex-row justify-center">
<span className="mx-5 my-2 text-sm text-red-500">
@ -105,13 +100,13 @@ const InputToolbar: React.FC = () => {
/>
</div>
{/* My text input */}
<div className="mx-12 mb-5 flex items-start space-x-4 md:mx-32 2xl:mx-64">
<div className="mb-5 flex items-start space-x-4">
<div className="relative min-w-0 flex-1">
<BasicPromptInput />
<BasicPromptAccessories />
</div>
</div>
</Fragment>
</div>
)
}

View File

@ -1,9 +1,9 @@
import React, { Fragment } from "react"
import HistoryList from "../HistoryList"
import LeftHeaderAction from "../LeftHeaderAction"
import { leftSideBarExpandStateAtom } from "@/_helpers/atoms/LeftSideBarExpand.atom"
import { useAtomValue } from "jotai"
import { Variants, motion } from "framer-motion"
import React, { Fragment } from 'react'
import HistoryList from '../HistoryList'
import LeftHeaderAction from '../LeftHeaderAction'
import { leftSideBarExpandStateAtom } from '@helpers/atoms/SideBarExpand.atom'
import { useAtomValue } from 'jotai'
import { Variants, motion } from 'framer-motion'
const leftSideBarVariants: Variants = {
show: {
@ -13,7 +13,7 @@ const leftSideBarVariants: Variants = {
transition: { duration: 0.3 },
},
hide: {
x: "-100%",
x: '-100%',
width: 0,
opacity: 0,
transition: { duration: 0.3 },
@ -26,13 +26,13 @@ const LeftContainer: React.FC = () => {
return (
<motion.div
initial={false}
animate={isVisible ? "show" : "hide"}
animate={isVisible ? 'show' : 'hide'}
variants={leftSideBarVariants}
className="flex flex-col w-80 flex-shrink-0 border-r border-gray-200"
className="flex w-80 flex-shrink-0 flex-col dark:bg-gray-950/50"
>
{isVisible && (
<Fragment>
<LeftHeaderAction />
{/* <LeftHeaderAction /> */}
<HistoryList />
</Fragment>
)}

View File

@ -1,47 +1,47 @@
"use client";
'use client'
import React from "react";
import SecondaryButton from "../SecondaryButton";
import { useSetAtom } from "jotai";
import React from 'react'
import SecondaryButton from '../SecondaryButton'
import { useSetAtom } from 'jotai'
import {
MainViewState,
setMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom";
import { MagnifyingGlassIcon, PlusIcon } from "@heroicons/react/24/outline";
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
} from '@helpers/atoms/MainView.atom'
import { MagnifyingGlassIcon, PlusIcon } from '@heroicons/react/24/outline'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
const LeftHeaderAction: React.FC = () => {
const setMainView = useSetAtom(setMainViewStateAtom);
const { downloadedModels } = useGetDownloadedModels();
const setMainView = useSetAtom(setMainViewStateAtom)
const { downloadedModels } = useGetDownloadedModels()
const onExploreClick = () => {
setMainView(MainViewState.ExploreModel);
};
setMainView(MainViewState.ExploreModel)
}
const onCreateBotClicked = () => {
if (downloadedModels.length === 0) {
alert("You need to download at least one model to create a bot.");
return;
alert('You need to download at least one model to create a bot.')
return
}
setMainView(MainViewState.CreateBot);
};
setMainView(MainViewState.CreateBot)
}
return (
<div className="flex flex-row gap-2 mx-3 my-3">
<div className="sticky top-0 mb-4 flex flex-row gap-2">
<SecondaryButton
title={"Explore"}
title={'Explore'}
onClick={onExploreClick}
className="flex-1"
icon={<MagnifyingGlassIcon width={16} height={16} />}
/>
<SecondaryButton
title={"Create bot"}
title={'Create bot'}
onClick={onCreateBotClicked}
className="flex-1"
icon={<PlusIcon width={16} height={16} />}
/>
</div>
);
};
)
}
export default React.memo(LeftHeaderAction);
export default React.memo(LeftHeaderAction)

View File

@ -2,9 +2,9 @@ import {
MainViewState,
getMainViewStateAtom,
setMainViewStateAtom,
} from '@/_helpers/atoms/MainView.atom'
import Image from 'next/image'
import CompactLogo from '../CompactLogo'
} from '@helpers/atoms/MainView.atom'
import CompactLogo from '../../../containers/Logo/CompactLogo'
import {
ChatBubbleOvalLeftEllipsisIcon,
Cog8ToothIcon,
@ -13,24 +13,26 @@ import {
Squares2X2Icon,
} from '@heroicons/react/24/outline'
import { useAtomValue, useSetAtom } from 'jotai'
import { showingBotListModalAtom } from '@/_helpers/atoms/Modal.atom'
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import useGetBots from '@/_hooks/useGetBots'
import { showingBotListModalAtom } from '@helpers/atoms/Modal.atom'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
import useGetBots from '@hooks/useGetBots'
import { Icons, Toggle } from '@uikit'
const menu = [
{
name: 'Explore Models',
iconComponent: <CpuChipIcon width={24} height={24} color="#C7D2FE" />,
state: MainViewState.ExploreModel,
},
// {
// name: 'Explore Models',
// icon: <CpuChipIcon />,
// state: MainViewState.ExploreModel,
// },
{
name: 'My Models',
iconComponent: <Squares2X2Icon width={24} height={24} color="#C7D2FE" />,
icon: <Icons name="layout-grid" />,
state: MainViewState.MyModel,
},
{
name: 'Settings',
iconComponent: <Cog8ToothIcon width={24} height={24} color="#C7D2FE" />,
icon: <Icons name="settings" />,
state: MainViewState.Setting,
},
]
@ -51,7 +53,7 @@ const LeftRibbonNav: React.FC = () => {
const bgColor = isConversationView ? 'bg-gray-500' : ''
const onConversationClick = () => {
if (currentState === MainViewState.Conversation) return
// if (currentState === MainViewState.Conversation) return
setMainViewState(MainViewState.Conversation)
}
@ -71,52 +73,42 @@ const LeftRibbonNav: React.FC = () => {
}
return (
<nav className="flex h-screen w-20 flex-col bg-gray-900">
<div className='mx-auto mt-4'>
<nav className="flex h-screen flex-shrink-0 flex-col items-center pt-10">
<CompactLogo />
</div>
<div className="flex w-full flex-1 flex-col items-center justify-between px-3 py-6">
<div className="flex flex-1 flex-col gap-2">
<button
onClick={onConversationClick}
className={`rounded-lg p-4 ${bgColor} hover:bg-gray-400`}
>
<div className="flex w-full flex-1 flex-col items-center justify-between">
<div className="flex flex-col pt-4">
<button onClick={onConversationClick}>
<ChatBubbleOvalLeftEllipsisIcon
width={24}
height={24}
color="#C7D2FE"
color="text-white"
/>
</button>
<button
onClick={onBotListClick}
className={`rounded-lg p-4 hover:bg-gray-400`}
>
<CubeTransparentIcon width={24} height={24} color="#C7D2FE" />
<button onClick={onBotListClick}>
<CubeTransparentIcon />
</button>
</div>
<ul className="flex flex-col gap-3">
<ul className="flex flex-col gap-3 py-8">
{menu.map((item) => {
const bgColor = currentState === item.state ? 'bg-gray-500' : ''
return (
<li
role="button"
data-testid={item.name}
key={item.name}
className={`rounded-lg p-4 ${bgColor} hover:bg-gray-400`}
className="item-center flex gap-x-2"
onClick={() => onMenuClick(item.state)}
>
{item.iconComponent}
{item.icon}
<span className="text-xs">{item.name}</span>
</li>
)
})}
</ul>
</div>
{/* User avatar */}
<div className="flex items-center justify-center pb-5">
<Image src={'/icons/avatar.svg'} width={40} height={40} alt="" />
</div>
{/* <div className="pb-5 flex items-center justify-center">
<Image src={"/icons/avatar.svg"} width={40} height={40} alt="" />
</div> */}
</nav>
)
}

View File

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

View File

@ -1,21 +1,19 @@
import React from "react"
import LeftContainer from "../LeftContainer"
import LeftRibbonNav from "../LeftRibbonNav"
import MonitorBar from "../MonitorBar"
import RightContainer from "../RightContainer"
import CenterContainer from "../CenterContainer"
import React from 'react'
import LeftContainer from '../LeftContainer'
import LeftRibbonNav from '../LeftRibbonNav'
import MonitorBar from '../MonitorBar'
import RightContainer from '../RightContainer'
import CenterContainer from '../CenterContainer'
const MainContainer: React.FC = () => (
<div className="flex h-screen">
<LeftRibbonNav />
<div className="flex flex-1 flex-col h-full">
<div className="flex flex-1 flex-col ">
<div className="flex flex-1 overflow-hidden">
<LeftContainer />
<CenterContainer />
<RightContainer />
</div>
<MonitorBar />
</div>
</div>

View File

@ -1,19 +1,17 @@
import { currentConversationAtom } from '@/_helpers/atoms/Conversation.atom'
import { currentConversationAtom } from '@helpers/atoms/Conversation.atom'
import {
leftSideBarExpandStateAtom,
rightSideBarExpandStateAtom,
showRightSideBarToggleAtom,
} from '@/_helpers/atoms/LeftSideBarExpand.atom'
import { TrashIcon } from '@heroicons/react/24/outline'
import { showConfirmDeleteConversationModalAtom } from '@/_helpers/atoms/Modal.atom'
} from '@helpers/atoms/SideBarExpand.atom'
import { showConfirmDeleteConversationModalAtom } from '@helpers/atoms/Modal.atom'
import { ChartPieIcon, TrashIcon } from '@heroicons/react/24/outline'
import { useAtomValue, useSetAtom } from 'jotai'
import React from 'react'
import Image from 'next/image'
import { Trash2 } from 'lucide-react'
const MainHeader: React.FC = () => {
const setLeftSideBarVisibility = useSetAtom(leftSideBarExpandStateAtom)
const setRightSideBarVisibility = useSetAtom(rightSideBarExpandStateAtom)
const showRightSideBarToggle = useAtomValue(showRightSideBarToggleAtom)
const setShowConfirmDeleteConversationModal = useSetAtom(
showConfirmDeleteConversationModalAtom
)
@ -22,43 +20,19 @@ const MainHeader: React.FC = () => {
const currentConvo = useAtomValue(currentConversationAtom)
let title = currentConvo?.name ?? ''
return (
<div className="flex justify-between bg-gray-200 px-2 py-3">
<Image
role="button"
alt=""
src="icons/ic_sidebar_off.svg"
width={24}
onClick={() => setLeftSideBarVisibility((prev) => !prev)}
height={24}
/>
if (!activeConversation) return null
<span className="flex gap-0.5 text-base font-semibold leading-6">
{title}
</span>
return (
<div className="sticky top-0 border-b border-border bg-background/90 px-4 py-2">
<span className="font-semibold text-muted-foreground">{title}</span>
{/* right most */}
<div className="flex gap-4">
{activeConversation != null && (
<TrashIcon
role="button"
width={24}
height={24}
color="#9CA3AF"
onClick={() => setShowConfirmDeleteConversationModal(true)}
/>
)}
{showRightSideBarToggle && (
<Image
role="button"
alt=""
src="icons/ic_sidebar_off.svg"
width={24}
onClick={() => setRightSideBarVisibility((prev) => !prev)}
height={24}
/>
)}
<div className="absolute right-4 top-2">
<Trash2
role="button"
size={16}
onClick={() => setShowConfirmDeleteConversationModal(true)}
/>
</div>
</div>
)

View File

@ -8,11 +8,10 @@ import ExploreModelContainer from '../ExploreModelContainer'
import {
MainViewState,
getMainViewStateAtom,
} from '@/_helpers/atoms/MainView.atom'
} from '@helpers/atoms/MainView.atom'
import EmptyChatContainer from '../EmptyChatContainer'
import MainChat from '../MainChat'
import CreateBotContainer from '../CreateBotContainer'
import BotInfoContainer from '../BotInfoContainer'
const MainView: React.FC = () => {
const viewState = useAtomValue(getMainViewStateAtom)
@ -38,9 +37,6 @@ const MainView: React.FC = () => {
case MainViewState.Welcome:
children = <Welcome />
break
case MainViewState.BotInfo:
children = <BotInfoContainer />
break
default:
children = <MainChat />
break

View File

@ -3,7 +3,7 @@ import { Popover, Transition } from '@headlessui/react'
import { Fragment } from 'react'
// import useGetCurrentUser from "@/_hooks/useGetCurrentUser";
import { useSetAtom } from 'jotai'
import { showConfirmSignOutModalAtom } from '@/_helpers/atoms/Modal.atom'
import { showConfirmSignOutModalAtom } from '@helpers/atoms/Modal.atom'
export const MenuHeader: React.FC = () => {
const setShowConfirmSignOutModal = useSetAtom(showConfirmSignOutModalAtom)

View File

@ -3,7 +3,7 @@ import { Dialog } from '@headlessui/react'
import { XMarkIcon } from '@heroicons/react/24/outline'
import Image from 'next/image'
import { useAtom } from 'jotai'
import { showingMobilePaneAtom } from '@/_helpers/atoms/Modal.atom'
import { showingMobilePaneAtom } from '@helpers/atoms/Modal.atom'
const MobileMenuPane: React.FC = () => {
const [show, setShow] = useAtom(showingMobilePaneAtom)

View File

@ -1,5 +1,6 @@
import React from 'react'
import PrimaryButton from '../PrimaryButton'
import { Button } from '@uikit'
import ModelActionMenu from '../ModelActionMenu'
export enum ModelActionType {
Start = 'Start',
@ -8,41 +9,45 @@ export enum ModelActionType {
type ModelActionStyle = {
title: string
backgroundColor: string
textColor: string
}
const modelActionMapper: Record<ModelActionType, ModelActionStyle> = {
[ModelActionType.Start]: {
title: 'Start',
backgroundColor: 'bg-blue-500 hover:bg-blue-600',
textColor: 'text-white',
},
[ModelActionType.Stop]: {
title: 'Stop',
backgroundColor: 'bg-red-500 hover:bg-red-600',
textColor: 'text-white',
},
}
type Props = {
type: ModelActionType
onActionClick: (type: ModelActionType) => void
onDeleteClick: () => void
}
const ModelActionButton: React.FC<Props> = ({ type, onActionClick }) => {
const ModelActionButton: React.FC<Props> = ({
type,
onActionClick,
onDeleteClick,
}) => {
const styles = modelActionMapper[type]
const onClick = () => {
onActionClick(type)
}
return (
<td className="whitespace-nowrap px-6 py-4 text-sm">
<PrimaryButton
title={styles.title}
onClick={onClick}
className={styles.backgroundColor}
/>
<td className="whitespace-nowrap px-3 py-2 text-right">
<div className="flex items-center justify-end gap-x-4">
<ModelActionMenu onDeleteClick={onDeleteClick} />
<Button
size="sm"
themes={styles.title === 'Start' ? 'accent' : 'default'}
onClick={onClick}
>
{styles.title} Model
</Button>
</div>
</td>
)
}

View File

@ -7,36 +7,9 @@ type Props = {
}
const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => (
<Menu as="div" className="relative flex-none">
<Menu.Button className="block text-gray-500 hover:text-gray-900">
<span className="sr-only">Open options</span>
<EllipsisVerticalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-50 mt-2 w-32 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-gray-900/5 focus:outline-none">
<Menu.Item>
{({ active }) => (
<button
className={`${
active ? 'bg-violet-500 text-white' : 'text-gray-900'
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
onClick={onDeleteClick}
>
Delete
</button>
)}
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
<button className="text-muted-foreground text-xs" onClick={onDeleteClick}>
Delete
</button>
)
export default ModelActionMenu

View File

@ -1,4 +1,4 @@
import { toGigabytes } from '@/_utils/converter'
import { toGigabytes } from '@utils/converter'
type Props = {
total: number

View File

@ -1,10 +1,10 @@
import React from 'react'
import { DownloadState } from '@/_models/DownloadState'
import {
formatDownloadPercentage,
formatDownloadSpeed,
toGigabytes,
} from '@/_utils/converter'
} from '@utils/converter'
type Props = {
downloadState: DownloadState

View File

@ -1,6 +1,5 @@
import React from 'react'
import ModelTableHeader from '../ModelTableHeader'
import { DownloadState } from '@/_models/DownloadState'
import ModelDownloadingRow from '../ModelDownloadingRow'
type Props = {

View File

@ -1,13 +1,11 @@
import React, { useCallback } from 'react'
import { ModelStatus, ModelStatusComponent } from '../ModelStatusComponent'
import ModelActionMenu from '../ModelActionMenu'
import { useAtomValue } from 'jotai'
import ModelActionButton, { ModelActionType } from '../ModelActionButton'
import useStartStopModel from '@/_hooks/useStartStopModel'
import useDeleteModel from '@/_hooks/useDeleteModel'
import { AssistantModel } from '@/_models/AssistantModel'
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import { toGigabytes } from '@/_utils/converter'
import useStartStopModel from '@hooks/useStartStopModel'
import useDeleteModel from '@hooks/useDeleteModel'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import { toGigabytes } from '@utils/converter'
type Props = {
model: AssistantModel
@ -38,32 +36,31 @@ const ModelRow: React.FC<Props> = ({ model }) => {
const onDeleteClick = useCallback(() => {
deleteModel(model)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [model])
return (
<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">
<tr className="bg-background/50 border-border border-b last:rounded-lg last:border-b-0">
<td className="text-muted-foreground whitespace-nowrap px-3 font-semibold">
{model.name}
<span className="font-normal text-gray-500">{model.version}</span>
<span className="ml-2 font-semibold">v{model.version}</span>
</td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
<td className="text-muted-foreground whitespace-nowrap px-3">
<div className="flex flex-col justify-start">
<span>GGUF</span>
</div>
</td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
<td className="text-muted-foreground whitespace-nowrap px-3">
{toGigabytes(model.size)}
</td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
<td className="text-muted-foreground whitespace-nowrap px-3">
<ModelStatusComponent status={status} />
</td>
<ModelActionButton
type={actionButtonType}
onActionClick={onModelActionClick}
onDeleteClick={onDeleteClick}
/>
<td className="relative w-fit whitespace-nowrap px-6 py-4 text-right text-sm font-medium">
<ModelActionMenu onDeleteClick={onDeleteClick} />
</td>
</tr>
)
}

View File

@ -1,6 +1,6 @@
'use client'
import { searchingModelText } from '@/_helpers/JotaiWrapper'
import { searchingModelText } from '@helpers/JotaiWrapper'
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
import { useSetAtom } from 'jotai'
import { useEffect, useState } from 'react'

View File

@ -2,9 +2,8 @@ import { Fragment, useEffect } from 'react'
import { Listbox, Transition } from '@headlessui/react'
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'
import { useAtom, useAtomValue } from 'jotai'
import { selectedModelAtom } from '@/_helpers/atoms/Model.atom'
import { downloadedModelAtom } from '@/_helpers/atoms/DownloadedModel.atom'
import { AssistantModel } from '@/_models/AssistantModel'
import { selectedModelAtom } from '@helpers/atoms/Model.atom'
import { downloadedModelAtom } from '@helpers/atoms/DownloadedModel.atom'
function classNames(...classes: any) {
return classes.filter(Boolean).join(' ')

View File

@ -16,17 +16,17 @@ export const ModelStatusMapper: Record<ModelStatus, ModelStatusType> = {
[ModelStatus.Installed]: {
title: 'Installed',
textColor: 'text-black',
backgroundColor: 'bg-gray-100',
backgroundColor: 'bg-gray-200 text-gray-600',
},
[ModelStatus.Active]: {
title: 'Active',
textColor: 'text-black',
backgroundColor: 'bg-green-100',
textColor: 'text-green-800',
backgroundColor: 'bg-green-100 dark:bg-green-300',
},
[ModelStatus.RunningInNitro]: {
title: 'Running in Nitro',
textColor: 'text-black',
backgroundColor: 'bg-green-100',
textColor: 'text-green-800',
backgroundColor: 'bg-green-100 dark:bg-green-300',
},
}
@ -38,7 +38,7 @@ export const ModelStatusComponent: React.FC<Props> = ({ status }) => {
const statusType = ModelStatusMapper[status]
return (
<div
className={`w-fit rounded-[10px] px-2.5 py-0.5 text-xs font-medium ${statusType.backgroundColor}`}
className={`w-fit rounded-full px-2.5 py-0.5 text-xs font-medium ${statusType.backgroundColor}`}
>
{statusType.title}
</div>

View File

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

View File

@ -7,7 +7,7 @@ type Props = {
const ModelTableHeader: React.FC<Props> = ({ title }) => (
<th
scope="col"
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"
className="text-muted-foreground border-border border-b p-3 text-left text-xs font-semibold uppercase first:rounded-tl-lg last:rounded-tr-lg last:text-right"
>
{title}
</th>

View File

@ -1,12 +1,10 @@
import React, { useMemo } from 'react'
import { formatDownloadPercentage, toGigabytes } from '@/_utils/converter'
import { formatDownloadPercentage, toGigabytes } from '@utils/converter'
import Image from 'next/image'
import { Product } from '@/_models/Product'
import useDownloadModel from '@/_hooks/useDownloadModel'
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import useDownloadModel from '@hooks/useDownloadModel'
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
import { atom, useAtomValue } from 'jotai'
import { ModelVersion } from '@/_models/ModelVersion'
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
import SimpleTag from '../SimpleTag'
import { RamRequired, UsecaseTag } from '../SimpleTag/TagType'
@ -56,10 +54,9 @@ const ModelVersionItem: React.FC<Props> = ({
const { maxRamRequired, usecase } = modelVersion
return (
<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="border-border flex items-center justify-between gap-4 border-t pb-3 pl-3 pr-4 pt-3 first:border-t-0">
<div className="flex items-center gap-2">
<Image src={'/icons/app_icon.svg'} width={14} height={20} alt="" />
<span className="font-sm flex-1 text-gray-900">
<span className="font-sm text-muted-foreground mb-4 line-clamp-1 flex-1">
{modelVersion.name}
</span>
</div>
@ -75,9 +72,9 @@ const ModelVersionItem: React.FC<Props> = ({
type={RamRequired.RamDefault}
clickable={false}
/>
</div>
<div className="rounded bg-gray-200 px-2.5 py-0.5 text-xs font-medium">
{toGigabytes(modelVersion.size)}
<div className="bg-background border-border rounded-full border px-2.5 py-0.5 font-medium">
{toGigabytes(modelVersion.size)}
</div>
</div>
{downloadButton}
</div>

View File

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

View File

@ -1,13 +1,12 @@
import ProgressBar from '../ProgressBar'
import SystemItem from '../SystemItem'
import ProgressBar from '@/_components/ProgressBar'
import SystemItem from '@containers/SystemItem'
import { useAtomValue } from 'jotai'
import { appDownloadProgress } from '@/_helpers/JotaiWrapper'
import useGetAppVersion from '@/_hooks/useGetAppVersion'
import useGetSystemResources from '@/_hooks/useGetSystemResources'
import { modelDownloadStateAtom } from '@/_helpers/atoms/DownloadState.atom'
import { DownloadState } from '@/_models/DownloadState'
import { formatDownloadPercentage } from '@/_utils/converter'
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import { appDownloadProgress } from '@helpers/JotaiWrapper'
import useGetAppVersion from '@hooks/useGetAppVersion'
import useGetSystemResources from '@hooks/useGetSystemResources'
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
import { formatDownloadPercentage } from '@utils/converter'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
const MonitorBar: React.FC = () => {
const progress = useAtomValue(appDownloadProgress)
@ -22,7 +21,7 @@ const MonitorBar: React.FC = () => {
}
return (
<div className="flex flex-row items-center justify-between border-t border-gray-200">
<div className="flex flex-row items-center justify-between">
{progress && progress >= 0 ? (
<ProgressBar total={100} used={progress} />
) : null}
@ -40,7 +39,7 @@ const MonitorBar: React.FC = () => {
{activeModel && (
<SystemItem name={`Active model: ${activeModel.name}`} value={''} />
)}
<span className="text-sm text-gray-900">v{version}</span>
<span className="text-xs">v{version}</span>
</div>
</div>
)

View File

@ -35,7 +35,6 @@ export const Preferences = () => {
useEffect(() => {
// @ts-ignore
import(/* webpackIgnore: true */ PLUGIN_CATALOGS).then((module) => {
console.log(module)
setPluginCatalog(module.default)
})
}, [])
@ -149,8 +148,11 @@ export const Preferences = () => {
if (timeout) {
clearTimeout(timeout)
}
if(extensionPoints.get(PluginService.OnPreferencesUpdate))
timeout = setTimeout(() => execute(PluginService.OnPreferencesUpdate), 100)
if (extensionPoints.get(PluginService.OnPreferencesUpdate))
timeout = setTimeout(
() => execute(PluginService.OnPreferencesUpdate),
100
)
}
/**

View File

@ -16,9 +16,9 @@ const PrimaryButton: React.FC<Props> = ({
className,
}) => (
<button
onClick={() => onClick?.()}
type={isSubmit ? "submit" : "button"}
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} ${
onClick={onClick}
type={isSubmit ? 'submit' : 'button'}
className={`line-clamp-1 flex-shrink-0 rounded-md bg-accent px-3 py-1 font-semibold text-white shadow-sm hover:bg-accent/80 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 ${className} ${
fullWidth ? 'flex-1 ' : ''
}}`}
>

View File

@ -1,4 +1,4 @@
import { formatTwoDigits } from '@/_utils/converter'
import { formatTwoDigits } from '@utils/converter'
import React from 'react'
type Props = {

View File

@ -1,9 +1,9 @@
import { rightSideBarExpandStateAtom } from "@/_helpers/atoms/LeftSideBarExpand.atom"
import { Variants, motion } from "framer-motion"
import { useAtomValue } from "jotai"
import { Fragment } from "react"
import BotSetting from "../BotSetting"
import BotInfo from "../BotInfo"
import { rightSideBarExpandStateAtom } from '@helpers/atoms/SideBarExpand.atom'
import { Variants, motion } from 'framer-motion'
import { useAtomValue } from 'jotai'
import { Fragment } from 'react'
import BotSetting from '../BotSetting'
import BotInfo from '../BotInfo'
const variants: Variants = {
show: {
@ -13,7 +13,7 @@ const variants: Variants = {
transition: { duration: 0.3 },
},
hide: {
x: "100%",
x: '100%',
width: 0,
opacity: 0,
transition: { duration: 0.3 },
@ -26,9 +26,9 @@ const RightContainer = () => {
return (
<motion.div
initial={false}
animate={isVisible ? "show" : "hide"}
animate={isVisible ? 'show' : 'hide'}
variants={variants}
className="flex flex-col w-80 flex-shrink-0 py-3 border-l border-gray-200 overflow-y-auto scroll"
className="scroll flex w-80 flex-shrink-0 flex-col overflow-y-auto border-l border-gray-200 py-3"
>
{isVisible && (
<Fragment>

View File

@ -1,4 +1,4 @@
import { modelSearchAtom } from '@/_helpers/JotaiWrapper'
import { modelSearchAtom } from '@helpers/JotaiWrapper'
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
import { useSetAtom } from 'jotai'
import { useDebouncedCallback } from 'use-debounce'

View File

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

View File

@ -1,7 +1,8 @@
import { currentPromptAtom } from '@/_helpers/JotaiWrapper'
import { currentConvoStateAtom } from '@/_helpers/atoms/Conversation.atom'
import useSendChatMessage from '@/_hooks/useSendChatMessage'
import { currentPromptAtom } from '@helpers/JotaiWrapper'
import { currentConvoStateAtom } from '@helpers/atoms/Conversation.atom'
import useSendChatMessage from '@hooks/useSendChatMessage'
import { useAtom, useAtomValue } from 'jotai'
import { Button } from '@uikit'
const SendButton: React.FC = () => {
const [currentPrompt] = useAtom(currentPromptAtom)
@ -11,19 +12,15 @@ const SendButton: React.FC = () => {
const isWaitingForResponse = currentConvoState?.waitingForResponse ?? false
const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse
const disabledStyle = {
backgroundColor: '#F3F4F6',
}
return (
<button
<Button
themes="accent"
onClick={sendChatMessage}
style={disabled ? disabledStyle : {}}
disabled={disabled}
type="submit"
className="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Send
</button>
</Button>
)
}

View File

@ -1,16 +1,17 @@
import useCreateConversation from '@/_hooks/useCreateConversation'
import useCreateConversation from '@hooks/useCreateConversation'
import PrimaryButton from '../PrimaryButton'
import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect, useState } from 'react'
import {
MainViewState,
setMainViewStateAtom,
} from '@/_helpers/atoms/MainView.atom'
import { activeAssistantModelAtom } from '@/_helpers/atoms/Model.atom'
import useInitModel from '@/_hooks/useInitModel'
import { useGetDownloadedModels } from '@/_hooks/useGetDownloadedModels'
import { AssistantModel } from '@/_models/AssistantModel'
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline"
} from '@helpers/atoms/MainView.atom'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import useInitModel from '@hooks/useInitModel'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
import { Button } from '@uikit'
import {MessageCircle} from "lucide-react"
enum ActionButton {
DownloadModel = 'Download a Model',
@ -53,13 +54,15 @@ const SidebarEmptyHistory: React.FC = () => {
return (
<div className="flex flex-col items-center gap-3 py-10">
<ChatBubbleOvalLeftEllipsisIcon width={32} height={32} />
<div className="flex flex-col items-center gap-6">
<div className="text-center text-sm text-gray-900">No Chat History</div>
<div className="text-center text-sm text-gray-500">
<MessageCircle size={32} />
<div className="flex flex-col items-center gap-y-2">
<h6 className="text-center text-base">No Chat History</h6>
<p className="text-center text-muted-foreground mb-6">
Get started by creating a new chat.
</div>
<PrimaryButton title={action} onClick={onClick} />
</p>
<Button onClick={onClick} themes="accent" >
{action}
</Button>
</div>
</div>
)

View File

@ -5,7 +5,7 @@ import {
MainViewState,
getMainViewStateAtom,
setMainViewStateAtom,
} from '@/_helpers/atoms/MainView.atom'
} from '@helpers/atoms/MainView.atom'
type Props = {
title: string

View File

@ -1,9 +1,9 @@
import { displayDate } from '@/_utils/datetime'
import { displayDate } from '../../utils/datetime'
import Link from 'next/link'
import React from 'react'
import JanImage from './JanImage'
import JanImage from '../../containers/JanImage'
import Image from 'next/image'
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline"
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'
type Props = {
avatarUrl?: string

View File

@ -1,6 +1,6 @@
import Image from 'next/image'
import JanImage from '../JanImage'
import { displayDate } from '@/_utils/datetime'
import JanImage from '../../../containers/JanImage'
import { displayDate } from '@utils/datetime'
import Link from 'next/link'
type Props = {

View File

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

View File

@ -19,7 +19,7 @@ const SimpleTag: React.FC<Props> = ({
if (!clickable) {
return (
<div
className={`line-clamp-1 max-w-[40%] items-center rounded px-2.5 py-0.5 text-xs font-medium ${tagStyleMapper[type]}`}
className={`line-clamp-1 max-w-[70%] items-center rounded-full px-2 py-0.5 text-xs dark:bg-opacity-10 ${tagStyleMapper[type]}`}
>
{title}
</div>
@ -29,7 +29,7 @@ const SimpleTag: React.FC<Props> = ({
return (
<button
onClick={onClick}
className={`line-clamp-1 max-w-[40%] items-center rounded px-2.5 py-0.5 text-xs font-medium ${tagStyleMapper[type]}`}
className={`line-clamp-1 max-w-[70%] items-center rounded-full px-2 py-0.5 text-xs dark:bg-opacity-10 ${tagStyleMapper[type]}`}
>
{title} x
</button>

View File

@ -1,11 +1,12 @@
import React from 'react'
import { displayDate } from '@/_utils/datetime'
import { displayDate } from '@utils/datetime'
import Image from 'next/image'
import { MessageSenderType } from '@/_models/ChatMessage'
import LoadingIndicator from '../LoadingIndicator'
import { Marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import hljs from 'highlight.js'
import { MessageSenderType } from '@models/ChatMessage'
type Props = {
avatarUrl: string
@ -50,7 +51,7 @@ const SimpleTextMessage: React.FC<Props> = ({
return (
<div
className={`flex items-start gap-2 px-12 md:px-32 2xl:px-64 ${backgroundColor} py-5`}
className={`border-border/50 flex items-start gap-x-4 gap-y-2 border-b px-4 py-5 last:border-none`}
>
<Image
className="rounded-full"
@ -61,10 +62,10 @@ const SimpleTextMessage: React.FC<Props> = ({
/>
<div className="flex w-full flex-col gap-1">
<div className="flex items-baseline justify-start gap-1">
<div className="text-sm font-extrabold leading-[15.2px] text-[#1B1B1B] dark:text-[#d1d5db]">
<div className="text-sm font-extrabold leading-[15.2px] ">
{senderName}
</div>
<div className="text-xs font-medium leading-[13.2px] text-gray-400">
<div className="text-xs font-medium leading-[13.2px]">
{displayDate(createdAt)}
</div>
</div>
@ -72,7 +73,7 @@ const SimpleTextMessage: React.FC<Props> = ({
<LoadingIndicator />
) : (
<span
className="text-sm font-normal leading-loose"
className="text-muted-foreground text-xs font-normal leading-loose"
dangerouslySetInnerHTML={{ __html: parsedText }}
/>
)}

View File

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

View File

@ -1,14 +1,14 @@
import React from "react";
import { useController } from "react-hook-form";
import React from 'react'
import { useController } from 'react-hook-form'
type Props = {
id: string;
title: string;
placeholder: string;
description?: string;
control?: any;
required?: boolean;
};
id: string
title: string
placeholder: string
description?: string
control?: any
required?: boolean
}
const TextAreaWithTitle: React.FC<Props> = ({
id,
@ -22,27 +22,24 @@ const TextAreaWithTitle: React.FC<Props> = ({
name: id,
control: control,
rules: { required: required },
});
})
return (
<div className="flex flex-col gap-2">
<label
htmlFor="comment"
className="block text-base text-gray-900 font-bold"
>
<label htmlFor="comment" className="block font-bold">
{title}
</label>
{description && (
<p className="text-sm text-gray-400 font-normal">{description}</p>
<p className="mt-1 font-normal text-muted-foreground">{description}</p>
)}
<textarea
rows={4}
className="block w-full resize-none rounded-md border-0 py-1.5 bg-transparent shadow-sm ring-1 ring-inset text-gray-900 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 resize-none rounded-md border-0 bg-background/80 py-1.5 text-xs leading-relaxed text-background-reverse shadow-sm ring-1 ring-inset ring-border placeholder:text-muted-foreground focus:ring-2 focus:ring-inset focus:ring-accent/50"
placeholder={placeholder}
{...field}
/>
</div>
);
};
)
}
export default TextAreaWithTitle;
export default TextAreaWithTitle

View File

@ -1,14 +1,14 @@
import React from "react";
import { useController } from "react-hook-form";
import React from 'react'
import { useController } from 'react-hook-form'
type Props = {
id: string;
title: string;
description: string;
placeholder?: string;
control?: any;
required?: boolean;
};
id: string
title: string
description: string
placeholder?: string
control?: any
required?: boolean
}
const TextInputWithTitle: React.FC<Props> = ({
id,
@ -22,19 +22,19 @@ const TextInputWithTitle: React.FC<Props> = ({
name: id,
control: control,
rules: { required: required },
});
})
return (
<div className="flex flex-col gap-2">
<div className="text-gray-900 font-bold">{title}</div>
<div className="text-sm pb-2 text-[#737d7d]">{description}</div>
<div className="font-bold">{title}</div>
<div className="pb-2 text-muted-foreground">{description}</div>
<input
className="block w-full rounded-md border-0 py-1.5 bg-transparent shadow-sm ring-1 ring-inset text-gray-900 ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
className="block w-full rounded-md border-0 bg-background/80 py-1.5 text-xs shadow-sm ring-1 ring-inset ring-border placeholder:text-muted-foreground focus:ring-2 focus:ring-inset focus:ring-accent/50 sm:leading-6"
placeholder={placeholder}
{...field}
/>
</div>
);
};
)
}
export default TextInputWithTitle;
export default TextInputWithTitle

View File

@ -1,17 +1,17 @@
import React from "react";
import { Switch } from "@headlessui/react";
import { Controller } from "react-hook-form";
import React from 'react'
import { Switch } from '@headlessui/react'
import { Controller } from 'react-hook-form'
function classNames(...classes: any) {
return classes.filter(Boolean).join(" ");
return classes.filter(Boolean).join(' ')
}
type Props = {
id: string;
title: string;
control: any;
required?: boolean;
};
id: string
title: string
control: any
required?: boolean
}
const ToggleSwitch: React.FC<Props> = ({
id,
@ -20,7 +20,7 @@ const ToggleSwitch: React.FC<Props> = ({
required = false,
}) => (
<div className="flex items-center justify-between">
<div className="text-gray-900 text-base">{title}</div>
<div className="text-bold">{title}</div>
<Controller
name={id}
control={control}
@ -30,22 +30,22 @@ const ToggleSwitch: React.FC<Props> = ({
checked={value}
onChange={onChange}
className={classNames(
value ? "bg-indigo-600" : "bg-gray-200",
"relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
value ? 'bg-accent' : 'bg-gray-200',
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2'
)}
>
<span className="sr-only">Use setting</span>
<span
aria-hidden="true"
className={classNames(
value ? "translate-x-5" : "translate-x-0",
"pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
value ? 'translate-x-5' : 'translate-x-0',
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'
)}
/>
</Switch>
)}
/>
</div>
);
)
export default ToggleSwitch;
export default ToggleSwitch

View File

@ -3,9 +3,11 @@ import { useSetAtom } from 'jotai'
import {
setMainViewStateAtom,
MainViewState,
} from '@/_helpers/atoms/MainView.atom'
} from '@helpers/atoms/MainView.atom'
import SecondaryButton from '../SecondaryButton'
import { Button, Icons } from '@uikit'
const Welcome: React.FC = () => {
const setMainViewState = useSetAtom(setMainViewStateAtom)
@ -18,6 +20,8 @@ const Welcome: React.FC = () => {
<br />
lets download your first model
</span>
<Button>Button component</Button>
<Icons name="panel-left" />
<SecondaryButton
title={'Explore models'}
onClick={() => setMainViewState(MainViewState.ExploreModel)}

View File

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

View File

@ -1,15 +0,0 @@
'use client'
import { ThemeProvider } from 'next-themes'
import { ReactNode } from 'react'
type Props = {
children: ReactNode
}
// consider to use next-themes or not. This caused the error Warning: Extra attributes from the server: class,style at html after hydration
export const ThemeWrapper: React.FC<Props> = ({ children }) => (
<ThemeProvider enableSystem={false} attribute="class">
{children}
</ThemeProvider>
)

View File

@ -1,4 +0,0 @@
import { Bot } from "@/_models/Bot";
import { atom } from "jotai";
export const activeBotAtom = atom<Bot | undefined>(undefined);

View File

@ -1,22 +1,9 @@
import { Bot } from '@/_models/Bot'
import { executeSerial } from '../../../electron/core/plugin-manager/execution/extension-manager'
import { DataService } from '@janhq/core'
import {
MainViewState,
setMainViewStateAtom,
} from '@/_helpers/atoms/MainView.atom'
import { useSetAtom } from 'jotai'
import { activeBotAtom } from '@/_helpers/atoms/Bot.atom'
export default function useCreateBot() {
const setActiveBot = useSetAtom(activeBotAtom)
const setMainViewState = useSetAtom(setMainViewStateAtom)
const createBot = async (bot: Bot) => {
try {
await executeSerial(DataService.CreateBot, bot)
setActiveBot(bot)
setMainViewState(MainViewState.BotInfo)
await executeSerial('createBot', bot)
} catch (err) {
alert(err)
console.error(err)

View File

@ -1,25 +1,24 @@
import { useSetAtom } from "jotai";
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
import { activeBotAtom } from "@/_helpers/atoms/Bot.atom";
import { rightSideBarExpandStateAtom } from "@/_helpers/atoms/LeftSideBarExpand.atom";
import { DataService } from "@janhq/core";
import { useSetAtom } from 'jotai'
import { executeSerial } from '../../../electron/core/plugin-manager/execution/extension-manager'
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import { rightSideBarExpandStateAtom } from '@helpers/atoms/SideBarExpand.atom'
export default function useDeleteBot() {
const setActiveBot = useSetAtom(activeBotAtom);
const setRightPanelVisibility = useSetAtom(rightSideBarExpandStateAtom);
const setActiveBot = useSetAtom(activeBotAtom)
const setRightPanelVisibility = useSetAtom(rightSideBarExpandStateAtom)
const deleteBot = async (botId: string): Promise<"success" | "failed"> => {
const deleteBot = async (botId: string): Promise<'success' | 'failed'> => {
try {
await executeSerial(DataService.DeleteBot, botId);
setRightPanelVisibility(false);
setActiveBot(undefined);
return "success";
await executeSerial('deleteBot', botId)
setRightPanelVisibility(false)
setActiveBot(undefined)
return 'success'
} catch (err) {
alert(`Failed to delete bot ${botId}: ${err}`);
console.error(err);
return "failed";
alert(`Failed to delete bot ${botId}: ${err}`)
console.error(err)
return 'failed'
}
};
}
return { deleteBot };
return { deleteBot }
}

View File

@ -1,29 +1,27 @@
import { Bot } from "@/_models/Bot";
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
import { DataService } from "@janhq/core";
import { executeSerial } from '../../../electron/core/plugin-manager/execution/extension-manager'
export default function useGetBots() {
const getAllBots = async (): Promise<Bot[]> => {
try {
const bots = await executeSerial(DataService.GetBots);
return bots;
const bots = await executeSerial('getBots')
return bots
} catch (err) {
alert(`Failed to get bots: ${err}`);
console.error(err);
return [];
alert(`Failed to get bots: ${err}`)
console.error(err)
return []
}
};
}
const getBotById = async (botId: string): Promise<Bot | undefined> => {
try {
const bot: Bot = await executeSerial(DataService.GetBotById, botId);
return bot;
const bot: Bot = await executeSerial('getBotById', botId)
return bot
} catch (err) {
alert(`Failed to get bot ${botId}: ${err}`);
console.error(err);
return undefined;
alert(`Failed to get bot ${botId}: ${err}`)
console.error(err)
return undefined
}
};
}
return { getBotById, getAllBots };
return { getBotById, getAllBots }
}

View File

@ -1,6 +1,4 @@
import { Bot } from "@/_models/Bot";
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
import { DataService } from "@janhq/core";
import { executeSerial } from '../../../electron/core/plugin-manager/execution/extension-manager'
export default function useUpdateBot() {
const updateBot = async (
@ -13,26 +11,26 @@ export default function useUpdateBot() {
for (const [key, value] of Object.entries(updatableField)) {
if (value !== undefined) {
//@ts-ignore
bot[key] = value;
bot[key] = value
}
}
await executeSerial(DataService.UpdateBot, bot);
console.debug("Bot updated", JSON.stringify(bot, null, 2));
await executeSerial('updateBot', bot)
console.debug('Bot updated', JSON.stringify(bot, null, 2))
} catch (err) {
alert(`Update bot error: ${err}`);
console.error(err);
return;
alert(`Update bot error: ${err}`)
console.error(err)
return
}
};
}
return { updateBot };
return { updateBot }
}
export type UpdatableField = {
presencePenalty?: number;
frequencyPenalty?: number;
maxTokens?: number;
customTemperature?: number;
systemPrompt?: number;
};
presencePenalty?: number
frequencyPenalty?: number
maxTokens?: number
customTemperature?: number
systemPrompt?: number
}

View File

@ -1,32 +1,32 @@
export type Bot = {
_id: string;
name: string;
description: string;
visibleFromBotProfile: boolean;
systemPrompt: string;
welcomeMessage: string;
publiclyAccessible: boolean;
suggestReplies: boolean;
renderMarkdownContent: boolean;
_id: string
name: string
description: string
visibleFromBotProfile: boolean
systemPrompt: string
welcomeMessage: string
publiclyAccessible: boolean
suggestReplies: boolean
renderMarkdownContent: boolean
/**
* If true, the bot will use the custom temperature value instead of the
* default temperature value.
*/
enableCustomTemperature: boolean;
enableCustomTemperature: boolean
/**
* Default is 0.7.
*/
customTemperature: number;
customTemperature: number
maxTokens: number;
maxTokens: number
frequencyPenalty: number;
frequencyPenalty: number
presencePenalty: number;
presencePenalty: number
modelId: string;
createdAt?: number;
updatedAt?: number;
};
modelId: string
createdAt?: number
updatedAt?: number
}

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