fix: reduce the number of api call (#1896)
Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai>
This commit is contained in:
parent
ccbe18e5b8
commit
01fec49798
@ -12,6 +12,8 @@ import {
|
|||||||
import { JanApiRouteConfiguration } from '../common/configuration'
|
import { JanApiRouteConfiguration } from '../common/configuration'
|
||||||
import { startModel, stopModel } from '../common/startStopModel'
|
import { startModel, stopModel } from '../common/startStopModel'
|
||||||
import { ModelSettingParams } from '../../../types'
|
import { ModelSettingParams } from '../../../types'
|
||||||
|
import { getJanDataFolderPath } from '../../utils'
|
||||||
|
import { normalizeFilePath } from '../../path'
|
||||||
|
|
||||||
export const commonRouter = async (app: HttpServer) => {
|
export const commonRouter = async (app: HttpServer) => {
|
||||||
// Common Routes
|
// Common Routes
|
||||||
@ -52,7 +54,14 @@ export const commonRouter = async (app: HttpServer) => {
|
|||||||
// App Routes
|
// App Routes
|
||||||
app.post(`/app/${AppRoute.joinPath}`, async (request: any, reply: any) => {
|
app.post(`/app/${AppRoute.joinPath}`, async (request: any, reply: any) => {
|
||||||
const args = JSON.parse(request.body) as any[]
|
const args = JSON.parse(request.body) as any[]
|
||||||
reply.send(JSON.stringify(join(...args[0])))
|
|
||||||
|
const paths = args[0].map((arg: string) =>
|
||||||
|
typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`))
|
||||||
|
? join(getJanDataFolderPath(), normalizeFilePath(arg))
|
||||||
|
: arg
|
||||||
|
)
|
||||||
|
|
||||||
|
reply.send(JSON.stringify(join(...paths)))
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post(`/app/${AppRoute.baseName}`, async (request: any, reply: any) => {
|
app.post(`/app/${AppRoute.baseName}`, async (request: any, reply: any) => {
|
||||||
|
|||||||
@ -4,55 +4,55 @@ import { DownloadManager } from '../../download'
|
|||||||
import { HttpServer } from '../HttpServer'
|
import { HttpServer } from '../HttpServer'
|
||||||
import { createWriteStream } from 'fs'
|
import { createWriteStream } from 'fs'
|
||||||
import { getJanDataFolderPath } from '../../utils'
|
import { getJanDataFolderPath } from '../../utils'
|
||||||
import { normalizeFilePath } from "../../path";
|
import { normalizeFilePath } from '../../path'
|
||||||
|
|
||||||
export const downloadRouter = async (app: HttpServer) => {
|
export const downloadRouter = async (app: HttpServer) => {
|
||||||
app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => {
|
app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => {
|
||||||
const strictSSL = !(req.query.ignoreSSL === "true");
|
const strictSSL = !(req.query.ignoreSSL === 'true')
|
||||||
const proxy = req.query.proxy?.startsWith("http") ? req.query.proxy : undefined;
|
const proxy = req.query.proxy?.startsWith('http') ? req.query.proxy : undefined
|
||||||
const body = JSON.parse(req.body as any);
|
const body = JSON.parse(req.body as any)
|
||||||
const normalizedArgs = body.map((arg: any) => {
|
const normalizedArgs = body.map((arg: any) => {
|
||||||
if (typeof arg === "string") {
|
if (typeof arg === 'string' && arg.startsWith('file:')) {
|
||||||
return join(getJanDataFolderPath(), normalizeFilePath(arg));
|
return join(getJanDataFolderPath(), normalizeFilePath(arg))
|
||||||
}
|
}
|
||||||
return arg;
|
return arg
|
||||||
});
|
})
|
||||||
|
|
||||||
const localPath = normalizedArgs[1];
|
const localPath = normalizedArgs[1]
|
||||||
const fileName = localPath.split("/").pop() ?? "";
|
const fileName = localPath.split('/').pop() ?? ''
|
||||||
|
|
||||||
const request = require("request");
|
const request = require('request')
|
||||||
const progress = require("request-progress");
|
const progress = require('request-progress')
|
||||||
|
|
||||||
const rq = request({ url: normalizedArgs[0], strictSSL, proxy });
|
const rq = request({ url: normalizedArgs[0], strictSSL, proxy })
|
||||||
progress(rq, {})
|
progress(rq, {})
|
||||||
.on("progress", function (state: any) {
|
.on('progress', function (state: any) {
|
||||||
console.log("download onProgress", state);
|
console.log('download onProgress', state)
|
||||||
})
|
})
|
||||||
.on("error", function (err: Error) {
|
.on('error', function (err: Error) {
|
||||||
console.log("download onError", err);
|
console.log('download onError', err)
|
||||||
})
|
})
|
||||||
.on("end", function () {
|
.on('end', function () {
|
||||||
console.log("download onEnd");
|
console.log('download onEnd')
|
||||||
})
|
})
|
||||||
.pipe(createWriteStream(normalizedArgs[1]));
|
.pipe(createWriteStream(normalizedArgs[1]))
|
||||||
|
|
||||||
DownloadManager.instance.setRequest(fileName, rq);
|
DownloadManager.instance.setRequest(fileName, rq)
|
||||||
});
|
})
|
||||||
|
|
||||||
app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => {
|
app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => {
|
||||||
const body = JSON.parse(req.body as any);
|
const body = JSON.parse(req.body as any)
|
||||||
const normalizedArgs = body.map((arg: any) => {
|
const normalizedArgs = body.map((arg: any) => {
|
||||||
if (typeof arg === "string") {
|
if (typeof arg === 'string' && arg.startsWith('file:')) {
|
||||||
return join(getJanDataFolderPath(), normalizeFilePath(arg));
|
return join(getJanDataFolderPath(), normalizeFilePath(arg))
|
||||||
}
|
}
|
||||||
return arg;
|
return arg
|
||||||
});
|
})
|
||||||
|
|
||||||
const localPath = normalizedArgs[0];
|
const localPath = normalizedArgs[0]
|
||||||
const fileName = localPath.split("/").pop() ?? "";
|
const fileName = localPath.split('/').pop() ?? ''
|
||||||
const rq = DownloadManager.instance.networkRequests[fileName];
|
const rq = DownloadManager.instance.networkRequests[fileName]
|
||||||
DownloadManager.instance.networkRequests[fileName] = undefined;
|
DownloadManager.instance.networkRequests[fileName] = undefined
|
||||||
rq?.abort();
|
rq?.abort()
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
* functionality for managing threads.
|
* functionality for managing threads.
|
||||||
*/
|
*/
|
||||||
export default class JSONConversationalExtension extends ConversationalExtension {
|
export default class JSONConversationalExtension extends ConversationalExtension {
|
||||||
private static readonly _homeDir = 'file://threads'
|
private static readonly _threadFolder = 'file://threads'
|
||||||
private static readonly _threadInfoFileName = 'thread.json'
|
private static readonly _threadInfoFileName = 'thread.json'
|
||||||
private static readonly _threadMessagesFileName = 'messages.jsonl'
|
private static readonly _threadMessagesFileName = 'messages.jsonl'
|
||||||
|
|
||||||
@ -20,8 +20,8 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
* Called when the extension is loaded.
|
* Called when the extension is loaded.
|
||||||
*/
|
*/
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
if (!(await fs.existsSync(JSONConversationalExtension._homeDir)))
|
if (!(await fs.existsSync(JSONConversationalExtension._threadFolder)))
|
||||||
await fs.mkdirSync(JSONConversationalExtension._homeDir)
|
await fs.mkdirSync(JSONConversationalExtension._threadFolder)
|
||||||
console.debug('JSONConversationalExtension loaded')
|
console.debug('JSONConversationalExtension loaded')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
async saveThread(thread: Thread): Promise<void> {
|
async saveThread(thread: Thread): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const threadDirPath = await joinPath([
|
const threadDirPath = await joinPath([
|
||||||
JSONConversationalExtension._homeDir,
|
JSONConversationalExtension._threadFolder,
|
||||||
thread.id,
|
thread.id,
|
||||||
])
|
])
|
||||||
const threadJsonPath = await joinPath([
|
const threadJsonPath = await joinPath([
|
||||||
@ -92,7 +92,7 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
*/
|
*/
|
||||||
async deleteThread(threadId: string): Promise<void> {
|
async deleteThread(threadId: string): Promise<void> {
|
||||||
const path = await joinPath([
|
const path = await joinPath([
|
||||||
JSONConversationalExtension._homeDir,
|
JSONConversationalExtension._threadFolder,
|
||||||
`${threadId}`,
|
`${threadId}`,
|
||||||
])
|
])
|
||||||
try {
|
try {
|
||||||
@ -109,7 +109,7 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
async addNewMessage(message: ThreadMessage): Promise<void> {
|
async addNewMessage(message: ThreadMessage): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const threadDirPath = await joinPath([
|
const threadDirPath = await joinPath([
|
||||||
JSONConversationalExtension._homeDir,
|
JSONConversationalExtension._threadFolder,
|
||||||
message.thread_id,
|
message.thread_id,
|
||||||
])
|
])
|
||||||
const threadMessagePath = await joinPath([
|
const threadMessagePath = await joinPath([
|
||||||
@ -177,7 +177,7 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const threadDirPath = await joinPath([
|
const threadDirPath = await joinPath([
|
||||||
JSONConversationalExtension._homeDir,
|
JSONConversationalExtension._threadFolder,
|
||||||
threadId,
|
threadId,
|
||||||
])
|
])
|
||||||
const threadMessagePath = await joinPath([
|
const threadMessagePath = await joinPath([
|
||||||
@ -205,7 +205,7 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
private async readThread(threadDirName: string): Promise<any> {
|
private async readThread(threadDirName: string): Promise<any> {
|
||||||
return fs.readFileSync(
|
return fs.readFileSync(
|
||||||
await joinPath([
|
await joinPath([
|
||||||
JSONConversationalExtension._homeDir,
|
JSONConversationalExtension._threadFolder,
|
||||||
threadDirName,
|
threadDirName,
|
||||||
JSONConversationalExtension._threadInfoFileName,
|
JSONConversationalExtension._threadInfoFileName,
|
||||||
]),
|
]),
|
||||||
@ -219,14 +219,14 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
*/
|
*/
|
||||||
private async getValidThreadDirs(): Promise<string[]> {
|
private async getValidThreadDirs(): Promise<string[]> {
|
||||||
const fileInsideThread: string[] = await fs.readdirSync(
|
const fileInsideThread: string[] = await fs.readdirSync(
|
||||||
JSONConversationalExtension._homeDir
|
JSONConversationalExtension._threadFolder
|
||||||
)
|
)
|
||||||
|
|
||||||
const threadDirs: string[] = []
|
const threadDirs: string[] = []
|
||||||
for (let i = 0; i < fileInsideThread.length; i++) {
|
for (let i = 0; i < fileInsideThread.length; i++) {
|
||||||
if (fileInsideThread[i].includes('.DS_Store')) continue
|
if (fileInsideThread[i].includes('.DS_Store')) continue
|
||||||
const path = await joinPath([
|
const path = await joinPath([
|
||||||
JSONConversationalExtension._homeDir,
|
JSONConversationalExtension._threadFolder,
|
||||||
fileInsideThread[i],
|
fileInsideThread[i],
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
async getAllMessages(threadId: string): Promise<ThreadMessage[]> {
|
async getAllMessages(threadId: string): Promise<ThreadMessage[]> {
|
||||||
try {
|
try {
|
||||||
const threadDirPath = await joinPath([
|
const threadDirPath = await joinPath([
|
||||||
JSONConversationalExtension._homeDir,
|
JSONConversationalExtension._threadFolder,
|
||||||
threadId,
|
threadId,
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -263,22 +263,17 @@ export default class JSONConversationalExtension extends ConversationalExtension
|
|||||||
JSONConversationalExtension._threadMessagesFileName,
|
JSONConversationalExtension._threadMessagesFileName,
|
||||||
])
|
])
|
||||||
|
|
||||||
const result = await fs
|
let readResult = await fs.readFileSync(messageFilePath, 'utf-8')
|
||||||
.readFileSync(messageFilePath, 'utf-8')
|
|
||||||
.then((content) =>
|
if (typeof readResult === 'object') {
|
||||||
content
|
readResult = JSON.stringify(readResult)
|
||||||
.toString()
|
}
|
||||||
.split('\n')
|
|
||||||
.filter((line) => line !== '')
|
const result = readResult.split('\n').filter((line) => line !== '')
|
||||||
)
|
|
||||||
|
|
||||||
const messages: ThreadMessage[] = []
|
const messages: ThreadMessage[] = []
|
||||||
result.forEach((line: string) => {
|
result.forEach((line: string) => {
|
||||||
try {
|
messages.push(JSON.parse(line))
|
||||||
messages.push(JSON.parse(line) as ThreadMessage)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return messages
|
return messages
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -26,11 +26,12 @@ import { MainViewState } from '@/constants/screens'
|
|||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
|
|
||||||
import { useDownloadState } from '@/hooks/useDownloadState'
|
import { useDownloadState } from '@/hooks/useDownloadState'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const menuLinks = [
|
const menuLinks = [
|
||||||
{
|
{
|
||||||
@ -49,7 +50,8 @@ const BottomBar = () => {
|
|||||||
const { activeModel, stateModel } = useActiveModel()
|
const { activeModel, stateModel } = useActiveModel()
|
||||||
const { ram, cpu } = useGetSystemResources()
|
const { ram, cpu } = useGetSystemResources()
|
||||||
const progress = useAtomValue(appDownloadProgress)
|
const progress = useAtomValue(appDownloadProgress)
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
|
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
const { downloadStates } = useDownloadState()
|
const { downloadStates } = useDownloadState()
|
||||||
const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom)
|
const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom, useAtomValue } from 'jotai'
|
||||||
import { DatabaseIcon, CpuIcon } from 'lucide-react'
|
import { DatabaseIcon, CpuIcon } from 'lucide-react'
|
||||||
|
|
||||||
import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener'
|
import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener'
|
||||||
@ -19,14 +19,14 @@ import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener'
|
|||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
export default function CommandListDownloadedModel() {
|
export default function CommandListDownloadedModel() {
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
const { activeModel, startModel, stopModel } = useActiveModel()
|
const { activeModel, startModel, stopModel } = useActiveModel()
|
||||||
const [serverEnabled] = useAtom(serverEnabledAtom)
|
const [serverEnabled] = useAtom(serverEnabledAtom)
|
||||||
const [showSelectModelModal, setShowSelectModelModal] = useAtom(
|
const [showSelectModelModal, setShowSelectModelModal] = useAtom(
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import { MainViewState } from '@/constants/screens'
|
|||||||
|
|
||||||
import { useClickOutside } from '@/hooks/useClickOutside'
|
import { useClickOutside } from '@/hooks/useClickOutside'
|
||||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||||
import useGetAssistants, { getAssistants } from '@/hooks/useGetAssistants'
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { usePath } from '@/hooks/usePath'
|
import { usePath } from '@/hooks/usePath'
|
||||||
@ -29,13 +28,14 @@ import { showRightSideBarAtom } from '@/screens/Chat/Sidebar'
|
|||||||
|
|
||||||
import { openFileTitle } from '@/utils/titleUtils'
|
import { openFileTitle } from '@/utils/titleUtils'
|
||||||
|
|
||||||
|
import { assistantsAtom } from '@/helpers/atoms/Assistant.atom'
|
||||||
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
const TopBar = () => {
|
const TopBar = () => {
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
const { mainViewState } = useMainViewState()
|
const { mainViewState } = useMainViewState()
|
||||||
const { requestCreateNewThread } = useCreateNewThread()
|
const { requestCreateNewThread } = useCreateNewThread()
|
||||||
const { assistants } = useGetAssistants()
|
const assistants = useAtomValue(assistantsAtom)
|
||||||
const [showRightSideBar, setShowRightSideBar] = useAtom(showRightSideBarAtom)
|
const [showRightSideBar, setShowRightSideBar] = useAtom(showRightSideBarAtom)
|
||||||
const [showLeftSideBar, setShowLeftSideBar] = useAtom(showLeftSideBarAtom)
|
const [showLeftSideBar, setShowLeftSideBar] = useAtom(showLeftSideBarAtom)
|
||||||
const showing = useAtomValue(showRightSideBarAtom)
|
const showing = useAtomValue(showRightSideBarAtom)
|
||||||
@ -61,12 +61,7 @@ const TopBar = () => {
|
|||||||
|
|
||||||
const onCreateConversationClick = async () => {
|
const onCreateConversationClick = async () => {
|
||||||
if (assistants.length === 0) {
|
if (assistants.length === 0) {
|
||||||
const res = await getAssistants()
|
alert('No assistant available')
|
||||||
if (res.length === 0) {
|
|
||||||
alert('No assistant available')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
requestCreateNewThread(res[0])
|
|
||||||
} else {
|
} else {
|
||||||
requestCreateNewThread(assistants[0])
|
requestCreateNewThread(assistants[0])
|
||||||
}
|
}
|
||||||
|
|||||||
21
web/containers/Providers/DataLoader.tsx
Normal file
21
web/containers/Providers/DataLoader.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Fragment, ReactNode } from 'react'
|
||||||
|
|
||||||
|
import useAssistants from '@/hooks/useAssistants'
|
||||||
|
import useModels from '@/hooks/useModels'
|
||||||
|
import useThreads from '@/hooks/useThreads'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const DataLoader: React.FC<Props> = ({ children }) => {
|
||||||
|
useModels()
|
||||||
|
useThreads()
|
||||||
|
useAssistants()
|
||||||
|
|
||||||
|
return <Fragment>{children}</Fragment>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DataLoader
|
||||||
@ -18,7 +18,6 @@ import {
|
|||||||
loadModelErrorAtom,
|
loadModelErrorAtom,
|
||||||
stateModelAtom,
|
stateModelAtom,
|
||||||
} from '@/hooks/useActiveModel'
|
} from '@/hooks/useActiveModel'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
|
|
||||||
import { queuedMessageAtom } from '@/hooks/useSendChatMessage'
|
import { queuedMessageAtom } from '@/hooks/useSendChatMessage'
|
||||||
|
|
||||||
@ -29,6 +28,7 @@ import {
|
|||||||
addNewMessageAtom,
|
addNewMessageAtom,
|
||||||
updateMessageAtom,
|
updateMessageAtom,
|
||||||
} from '@/helpers/atoms/ChatMessage.atom'
|
} from '@/helpers/atoms/ChatMessage.atom'
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
import {
|
import {
|
||||||
updateThreadWaitingForResponseAtom,
|
updateThreadWaitingForResponseAtom,
|
||||||
threadsAtom,
|
threadsAtom,
|
||||||
@ -38,7 +38,7 @@ import {
|
|||||||
export default function EventHandler({ children }: { children: ReactNode }) {
|
export default function EventHandler({ children }: { children: ReactNode }) {
|
||||||
const addNewMessage = useSetAtom(addNewMessageAtom)
|
const addNewMessage = useSetAtom(addNewMessageAtom)
|
||||||
const updateMessage = useSetAtom(updateMessageAtom)
|
const updateMessage = useSetAtom(updateMessageAtom)
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
const setActiveModel = useSetAtom(activeModelAtom)
|
const setActiveModel = useSetAtom(activeModelAtom)
|
||||||
const setStateModel = useSetAtom(stateModelAtom)
|
const setStateModel = useSetAtom(stateModelAtom)
|
||||||
const setQueuedMessage = useSetAtom(queuedMessageAtom)
|
const setQueuedMessage = useSetAtom(queuedMessageAtom)
|
||||||
@ -143,7 +143,7 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
?.addNewMessage(message)
|
?.addNewMessage(message)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[updateMessage, updateThreadWaiting]
|
[updateMessage, updateThreadWaiting, setIsGeneratingResponse]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -3,10 +3,9 @@
|
|||||||
import { PropsWithChildren, useEffect, useRef } from 'react'
|
import { PropsWithChildren, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import { baseName } from '@janhq/core'
|
import { baseName } from '@janhq/core'
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { useDownloadState } from '@/hooks/useDownloadState'
|
import { useDownloadState } from '@/hooks/useDownloadState'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
|
|
||||||
import { modelBinFileName } from '@/utils/model'
|
import { modelBinFileName } from '@/utils/model'
|
||||||
|
|
||||||
@ -14,14 +13,17 @@ import EventHandler from './EventHandler'
|
|||||||
|
|
||||||
import { appDownloadProgress } from './Jotai'
|
import { appDownloadProgress } from './Jotai'
|
||||||
|
|
||||||
import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom'
|
import {
|
||||||
|
downloadedModelsAtom,
|
||||||
|
downloadingModelsAtom,
|
||||||
|
} from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
||||||
const setProgress = useSetAtom(appDownloadProgress)
|
const setProgress = useSetAtom(appDownloadProgress)
|
||||||
const models = useAtomValue(downloadingModelsAtom)
|
const models = useAtomValue(downloadingModelsAtom)
|
||||||
const modelsRef = useRef(models)
|
const modelsRef = useRef(models)
|
||||||
|
|
||||||
const { setDownloadedModels, downloadedModels } = useGetDownloadedModels()
|
const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom)
|
||||||
const {
|
const {
|
||||||
setDownloadState,
|
setDownloadState,
|
||||||
setDownloadStateSuccess,
|
setDownloadStateSuccess,
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import Umami from '@/utils/umami'
|
|||||||
|
|
||||||
import Loader from '../Loader'
|
import Loader from '../Loader'
|
||||||
|
|
||||||
|
import DataLoader from './DataLoader'
|
||||||
|
|
||||||
import KeyListener from './KeyListener'
|
import KeyListener from './KeyListener'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension'
|
import { extensionManager } from '@/extension'
|
||||||
@ -81,7 +83,9 @@ const Providers = (props: PropsWithChildren) => {
|
|||||||
<KeyListener>
|
<KeyListener>
|
||||||
<FeatureToggleWrapper>
|
<FeatureToggleWrapper>
|
||||||
<EventListenerWrapper>
|
<EventListenerWrapper>
|
||||||
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<DataLoader>{children}</DataLoader>
|
||||||
|
</TooltipProvider>
|
||||||
{!isMac && <GPUDriverPrompt />}
|
{!isMac && <GPUDriverPrompt />}
|
||||||
</EventListenerWrapper>
|
</EventListenerWrapper>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
4
web/helpers/atoms/Assistant.atom.ts
Normal file
4
web/helpers/atoms/Assistant.atom.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Assistant } from '@janhq/core/.'
|
||||||
|
import { atom } from 'jotai'
|
||||||
|
|
||||||
|
export const assistantsAtom = atom<Assistant[]>([])
|
||||||
@ -24,3 +24,7 @@ export const removeDownloadingModelAtom = atom(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const downloadedModelsAtom = atom<Model[]>([])
|
||||||
|
|
||||||
|
export const configuredModelsAtom = atom<Model[]>([])
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
|||||||
|
|
||||||
import { toaster } from '@/containers/Toast'
|
import { toaster } from '@/containers/Toast'
|
||||||
|
|
||||||
import { useGetDownloadedModels } from './useGetDownloadedModels'
|
|
||||||
import { LAST_USED_MODEL_ID } from './useRecommendedModel'
|
import { LAST_USED_MODEL_ID } from './useRecommendedModel'
|
||||||
|
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
export const activeModelAtom = atom<Model | undefined>(undefined)
|
export const activeModelAtom = atom<Model | undefined>(undefined)
|
||||||
@ -21,7 +21,7 @@ export function useActiveModel() {
|
|||||||
const [activeModel, setActiveModel] = useAtom(activeModelAtom)
|
const [activeModel, setActiveModel] = useAtom(activeModelAtom)
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
const [stateModel, setStateModel] = useAtom(stateModelAtom)
|
const [stateModel, setStateModel] = useAtom(stateModelAtom)
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
const setLoadModelError = useSetAtom(loadModelErrorAtom)
|
const setLoadModelError = useSetAtom(loadModelErrorAtom)
|
||||||
|
|
||||||
const startModel = async (modelId: string) => {
|
const startModel = async (modelId: string) => {
|
||||||
|
|||||||
28
web/hooks/useAssistants.ts
Normal file
28
web/hooks/useAssistants.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
import { Assistant, AssistantExtension, ExtensionTypeEnum } from '@janhq/core'
|
||||||
|
|
||||||
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { extensionManager } from '@/extension'
|
||||||
|
import { assistantsAtom } from '@/helpers/atoms/Assistant.atom'
|
||||||
|
|
||||||
|
const useAssistants = () => {
|
||||||
|
const setAssistants = useSetAtom(assistantsAtom)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getAssistants = async () => {
|
||||||
|
const assistants = await getLocalAssistants()
|
||||||
|
setAssistants(assistants)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssistants()
|
||||||
|
}, [setAssistants])
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLocalAssistants = async (): Promise<Assistant[]> =>
|
||||||
|
extensionManager
|
||||||
|
.get<AssistantExtension>(ExtensionTypeEnum.Assistant)
|
||||||
|
?.getAssistants() ?? []
|
||||||
|
|
||||||
|
export default useAssistants
|
||||||
@ -1,13 +1,14 @@
|
|||||||
import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core'
|
import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core'
|
||||||
|
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
|
||||||
import { toaster } from '@/containers/Toast'
|
import { toaster } from '@/containers/Toast'
|
||||||
|
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
export default function useDeleteModel() {
|
export default function useDeleteModel() {
|
||||||
const { setDownloadedModels, downloadedModels } = useGetDownloadedModels()
|
const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom)
|
||||||
|
|
||||||
const deleteModel = async (model: Model) => {
|
const deleteModel = async (model: Model) => {
|
||||||
await extensionManager
|
await extensionManager
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import { Assistant, ExtensionTypeEnum, AssistantExtension } from '@janhq/core'
|
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
|
||||||
|
|
||||||
export const getAssistants = async (): Promise<Assistant[]> =>
|
|
||||||
extensionManager
|
|
||||||
.get<AssistantExtension>(ExtensionTypeEnum.Assistant)
|
|
||||||
?.getAssistants() ?? []
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hooks for get assistants
|
|
||||||
*
|
|
||||||
* @returns assistants
|
|
||||||
*/
|
|
||||||
export default function useGetAssistants() {
|
|
||||||
const [assistants, setAssistants] = useState<Assistant[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getAssistants()
|
|
||||||
.then((data) => setAssistants(data))
|
|
||||||
.catch((err) => console.error(err))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { assistants }
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core'
|
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
|
||||||
|
|
||||||
export function useGetConfiguredModels() {
|
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
|
||||||
const [models, setModels] = useState<Model[]>([])
|
|
||||||
|
|
||||||
const fetchModels = useCallback(async () => {
|
|
||||||
setLoading(true)
|
|
||||||
const models = await getConfiguredModels()
|
|
||||||
setLoading(false)
|
|
||||||
setModels(models)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchModels()
|
|
||||||
}, [fetchModels])
|
|
||||||
|
|
||||||
return { loading, models }
|
|
||||||
}
|
|
||||||
|
|
||||||
const getConfiguredModels = async (): Promise<Model[]> => {
|
|
||||||
const models = await extensionManager
|
|
||||||
.get<ModelExtension>(ExtensionTypeEnum.Model)
|
|
||||||
?.getConfiguredModels()
|
|
||||||
return models ?? []
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core'
|
|
||||||
|
|
||||||
import { atom, useAtom } from 'jotai'
|
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
|
||||||
|
|
||||||
export const downloadedModelsAtom = atom<Model[]>([])
|
|
||||||
|
|
||||||
export function useGetDownloadedModels() {
|
|
||||||
const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getDownloadedModels().then((downloadedModels) => {
|
|
||||||
setDownloadedModels(downloadedModels)
|
|
||||||
})
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { downloadedModels, setDownloadedModels }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDownloadedModels = async (): Promise<Model[]> =>
|
|
||||||
extensionManager
|
|
||||||
.get<ModelExtension>(ExtensionTypeEnum.Model)
|
|
||||||
?.getDownloadedModels() ?? []
|
|
||||||
@ -58,7 +58,7 @@ export default function useGetSystemResources() {
|
|||||||
// There is a possibility that this will be removed and replaced by the process event hook?
|
// There is a possibility that this will be removed and replaced by the process event hook?
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
getSystemResources()
|
getSystemResources()
|
||||||
}, 500)
|
}, 5000)
|
||||||
|
|
||||||
// clean up interval
|
// clean up interval
|
||||||
return () => clearInterval(intervalId)
|
return () => clearInterval(intervalId)
|
||||||
|
|||||||
46
web/hooks/useModels.ts
Normal file
46
web/hooks/useModels.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
import { ExtensionTypeEnum, Model, ModelExtension } from '@janhq/core'
|
||||||
|
|
||||||
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { extensionManager } from '@/extension'
|
||||||
|
import {
|
||||||
|
configuredModelsAtom,
|
||||||
|
downloadedModelsAtom,
|
||||||
|
} from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
|
const useModels = () => {
|
||||||
|
const setDownloadedModels = useSetAtom(downloadedModelsAtom)
|
||||||
|
const setConfiguredModels = useSetAtom(configuredModelsAtom)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getDownloadedModels = async () => {
|
||||||
|
const models = await getLocalDownloadedModels()
|
||||||
|
setDownloadedModels(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDownloadedModels()
|
||||||
|
}, [setDownloadedModels])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getConfiguredModels = async () => {
|
||||||
|
const models = await getLocalConfiguredModels()
|
||||||
|
setConfiguredModels(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfiguredModels()
|
||||||
|
}, [setConfiguredModels])
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLocalConfiguredModels = async (): Promise<Model[]> =>
|
||||||
|
extensionManager
|
||||||
|
.get<ModelExtension>(ExtensionTypeEnum.Model)
|
||||||
|
?.getConfiguredModels() ?? []
|
||||||
|
|
||||||
|
const getLocalDownloadedModels = async (): Promise<Model[]> =>
|
||||||
|
extensionManager
|
||||||
|
.get<ModelExtension>(ExtensionTypeEnum.Model)
|
||||||
|
?.getDownloadedModels() ?? []
|
||||||
|
|
||||||
|
export default useModels
|
||||||
@ -5,9 +5,9 @@ import { Model, InferenceEngine } from '@janhq/core'
|
|||||||
import { atom, useAtomValue } from 'jotai'
|
import { atom, useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import { activeModelAtom } from './useActiveModel'
|
import { activeModelAtom } from './useActiveModel'
|
||||||
import { getDownloadedModels } from './useGetDownloadedModels'
|
|
||||||
|
|
||||||
import { activeThreadAtom, threadStatesAtom } from '@/helpers/atoms/Thread.atom'
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
export const lastUsedModel = atom<Model | undefined>(undefined)
|
export const lastUsedModel = atom<Model | undefined>(undefined)
|
||||||
|
|
||||||
@ -24,19 +24,20 @@ export const LAST_USED_MODEL_ID = 'last-used-model-id'
|
|||||||
*/
|
*/
|
||||||
export default function useRecommendedModel() {
|
export default function useRecommendedModel() {
|
||||||
const activeModel = useAtomValue(activeModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
const [downloadedModels, setDownloadedModels] = useState<Model[]>([])
|
const [sortedModels, setSortedModels] = useState<Model[]>([])
|
||||||
const [recommendedModel, setRecommendedModel] = useState<Model | undefined>()
|
const [recommendedModel, setRecommendedModel] = useState<Model | undefined>()
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
|
|
||||||
const getAndSortDownloadedModels = useCallback(async (): Promise<Model[]> => {
|
const getAndSortDownloadedModels = useCallback(async (): Promise<Model[]> => {
|
||||||
const models = (await getDownloadedModels()).sort((a, b) =>
|
const models = downloadedModels.sort((a, b) =>
|
||||||
a.engine !== InferenceEngine.nitro && b.engine === InferenceEngine.nitro
|
a.engine !== InferenceEngine.nitro && b.engine === InferenceEngine.nitro
|
||||||
? 1
|
? 1
|
||||||
: -1
|
: -1
|
||||||
)
|
)
|
||||||
setDownloadedModels(models)
|
setSortedModels(models)
|
||||||
return models
|
return models
|
||||||
}, [])
|
}, [downloadedModels])
|
||||||
|
|
||||||
const getRecommendedModel = useCallback(async (): Promise<
|
const getRecommendedModel = useCallback(async (): Promise<
|
||||||
Model | undefined
|
Model | undefined
|
||||||
@ -98,5 +99,5 @@ export default function useRecommendedModel() {
|
|||||||
getRecommendedModel()
|
getRecommendedModel()
|
||||||
}, [getRecommendedModel])
|
}, [getRecommendedModel])
|
||||||
|
|
||||||
return { recommendedModel, downloadedModels }
|
return { recommendedModel, downloadedModels: sortedModels }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InferenceEvent,
|
InferenceEvent,
|
||||||
ExtensionTypeEnum,
|
ExtensionTypeEnum,
|
||||||
@ -6,7 +8,7 @@ import {
|
|||||||
ConversationalExtension,
|
ConversationalExtension,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
|
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { loadModelErrorAtom } from './useActiveModel'
|
import { loadModelErrorAtom } from './useActiveModel'
|
||||||
|
|
||||||
@ -14,43 +16,46 @@ import { extensionManager } from '@/extension'
|
|||||||
import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import {
|
import {
|
||||||
ModelParams,
|
ModelParams,
|
||||||
getActiveThreadIdAtom,
|
|
||||||
isGeneratingResponseAtom,
|
isGeneratingResponseAtom,
|
||||||
setActiveThreadIdAtom,
|
setActiveThreadIdAtom,
|
||||||
setThreadModelParamsAtom,
|
setThreadModelParamsAtom,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
export default function useSetActiveThread() {
|
export default function useSetActiveThread() {
|
||||||
const activeThreadId = useAtomValue(getActiveThreadIdAtom)
|
|
||||||
const setActiveThreadId = useSetAtom(setActiveThreadIdAtom)
|
const setActiveThreadId = useSetAtom(setActiveThreadIdAtom)
|
||||||
const setThreadMessage = useSetAtom(setConvoMessagesAtom)
|
const setThreadMessage = useSetAtom(setConvoMessagesAtom)
|
||||||
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
||||||
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
|
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
|
||||||
const setLoadModelError = useSetAtom(loadModelErrorAtom)
|
const setLoadModelError = useSetAtom(loadModelErrorAtom)
|
||||||
|
|
||||||
const setActiveThread = async (thread: Thread) => {
|
const setActiveThread = useCallback(
|
||||||
if (activeThreadId === thread.id) {
|
async (thread: Thread) => {
|
||||||
console.debug('Thread already active')
|
setIsGeneratingResponse(false)
|
||||||
return
|
events.emit(InferenceEvent.OnInferenceStopped, thread.id)
|
||||||
}
|
|
||||||
|
|
||||||
setIsGeneratingResponse(false)
|
// load the corresponding messages
|
||||||
setLoadModelError(undefined)
|
const messages = await getLocalThreadMessage(thread.id)
|
||||||
events.emit(InferenceEvent.OnInferenceStopped, thread.id)
|
setThreadMessage(thread.id, messages)
|
||||||
|
|
||||||
// load the corresponding messages
|
setActiveThreadId(thread.id)
|
||||||
const messages = await extensionManager
|
const modelParams: ModelParams = {
|
||||||
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
...thread.assistants[0]?.model?.parameters,
|
||||||
?.getAllMessages(thread.id)
|
...thread.assistants[0]?.model?.settings,
|
||||||
setThreadMessage(thread.id, messages ?? [])
|
}
|
||||||
|
setThreadModelParams(thread.id, modelParams)
|
||||||
|
},
|
||||||
|
[
|
||||||
|
setActiveThreadId,
|
||||||
|
setThreadMessage,
|
||||||
|
setThreadModelParams,
|
||||||
|
setIsGeneratingResponse,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
setActiveThreadId(thread.id)
|
return { setActiveThread }
|
||||||
const modelParams: ModelParams = {
|
|
||||||
...thread.assistants[0]?.model?.parameters,
|
|
||||||
...thread.assistants[0]?.model?.settings,
|
|
||||||
}
|
|
||||||
setThreadModelParams(thread.id, modelParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { activeThreadId, setActiveThread }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getLocalThreadMessage = async (threadId: string) =>
|
||||||
|
extensionManager
|
||||||
|
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
||||||
|
?.getAllMessages(threadId) ?? []
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExtensionTypeEnum,
|
ExtensionTypeEnum,
|
||||||
Thread,
|
Thread,
|
||||||
@ -5,14 +7,13 @@ import {
|
|||||||
ConversationalExtension,
|
ConversationalExtension,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
|
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import useSetActiveThread from './useSetActiveThread'
|
import useSetActiveThread from './useSetActiveThread'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
import {
|
import {
|
||||||
ModelParams,
|
ModelParams,
|
||||||
activeThreadAtom,
|
|
||||||
threadModelParamsAtom,
|
threadModelParamsAtom,
|
||||||
threadStatesAtom,
|
threadStatesAtom,
|
||||||
threadsAtom,
|
threadsAtom,
|
||||||
@ -22,11 +23,10 @@ const useThreads = () => {
|
|||||||
const setThreadStates = useSetAtom(threadStatesAtom)
|
const setThreadStates = useSetAtom(threadStatesAtom)
|
||||||
const setThreads = useSetAtom(threadsAtom)
|
const setThreads = useSetAtom(threadsAtom)
|
||||||
const setThreadModelRuntimeParams = useSetAtom(threadModelParamsAtom)
|
const setThreadModelRuntimeParams = useSetAtom(threadModelParamsAtom)
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
|
||||||
const { setActiveThread } = useSetActiveThread()
|
const { setActiveThread } = useSetActiveThread()
|
||||||
|
|
||||||
const getThreads = async () => {
|
useEffect(() => {
|
||||||
try {
|
const getThreads = async () => {
|
||||||
const localThreads = await getLocalThreads()
|
const localThreads = await getLocalThreads()
|
||||||
const localThreadStates: Record<string, ThreadState> = {}
|
const localThreadStates: Record<string, ThreadState> = {}
|
||||||
const threadModelParams: Record<string, ModelParams> = {}
|
const threadModelParams: Record<string, ModelParams> = {}
|
||||||
@ -54,17 +54,19 @@ const useThreads = () => {
|
|||||||
setThreadStates(localThreadStates)
|
setThreadStates(localThreadStates)
|
||||||
setThreads(localThreads)
|
setThreads(localThreads)
|
||||||
setThreadModelRuntimeParams(threadModelParams)
|
setThreadModelRuntimeParams(threadModelParams)
|
||||||
if (localThreads.length && !activeThread) {
|
|
||||||
|
if (localThreads.length > 0) {
|
||||||
setActiveThread(localThreads[0])
|
setActiveThread(localThreads[0])
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
getThreads()
|
||||||
getThreads,
|
}, [
|
||||||
}
|
setActiveThread,
|
||||||
|
setThreadModelRuntimeParams,
|
||||||
|
setThreadStates,
|
||||||
|
setThreads,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLocalThreads = async (): Promise<Thread[]> =>
|
const getLocalThreads = async (): Promise<Thread[]> =>
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import LogoMark from '@/containers/Brand/Logo/Mark'
|
|||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { loadModelErrorAtom } from '@/hooks/useActiveModel'
|
import { loadModelErrorAtom } from '@/hooks/useActiveModel'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
@ -20,10 +19,13 @@ import ChatItem from '../ChatItem'
|
|||||||
import ErrorMessage from '../ErrorMessage'
|
import ErrorMessage from '../ErrorMessage'
|
||||||
|
|
||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const ChatBody: React.FC = () => {
|
const ChatBody: React.FC = () => {
|
||||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
|
||||||
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
|
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
|
|
||||||
if (downloadedModels.length === 0)
|
if (downloadedModels.length === 0)
|
||||||
|
|||||||
65
web/screens/Chat/CleanThreadModal/index.tsx
Normal file
65
web/screens/Chat/CleanThreadModal/index.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React, { useCallback } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalClose,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalPortal,
|
||||||
|
ModalTitle,
|
||||||
|
ModalTrigger,
|
||||||
|
} from '@janhq/uikit'
|
||||||
|
import { Paintbrush } from 'lucide-react'
|
||||||
|
|
||||||
|
import useDeleteThread from '@/hooks/useDeleteThread'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
threadId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const CleanThreadModal: React.FC<Props> = ({ threadId }) => {
|
||||||
|
const { cleanThread } = useDeleteThread()
|
||||||
|
const onCleanThreadClick = useCallback(
|
||||||
|
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
cleanThread(threadId)
|
||||||
|
},
|
||||||
|
[cleanThread, threadId]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal>
|
||||||
|
<ModalTrigger asChild onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
||||||
|
<Paintbrush size={16} className="text-muted-foreground" />
|
||||||
|
<span className="text-bold text-black dark:text-muted-foreground">
|
||||||
|
Clean thread
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ModalTrigger>
|
||||||
|
<ModalPortal />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalTitle>Clean Thread</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
<p>Are you sure you want to clean this thread?</p>
|
||||||
|
<ModalFooter>
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<ModalClose asChild onClick={(e) => e.stopPropagation()}>
|
||||||
|
<Button themes="ghost">No</Button>
|
||||||
|
</ModalClose>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button themes="danger" onClick={onCleanThreadClick} autoFocus>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(CleanThreadModal)
|
||||||
68
web/screens/Chat/DeleteThreadModal/index.tsx
Normal file
68
web/screens/Chat/DeleteThreadModal/index.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React, { useCallback } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalTrigger,
|
||||||
|
ModalPortal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalTitle,
|
||||||
|
ModalFooter,
|
||||||
|
ModalClose,
|
||||||
|
Button,
|
||||||
|
} from '@janhq/uikit'
|
||||||
|
import { Trash2Icon } from 'lucide-react'
|
||||||
|
|
||||||
|
import useDeleteThread from '@/hooks/useDeleteThread'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
threadId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteThreadModal: React.FC<Props> = ({ threadId }) => {
|
||||||
|
const { deleteThread } = useDeleteThread()
|
||||||
|
const onDeleteThreadClick = useCallback(
|
||||||
|
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
deleteThread(threadId)
|
||||||
|
},
|
||||||
|
[deleteThread, threadId]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal>
|
||||||
|
<ModalTrigger asChild onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
||||||
|
<Trash2Icon size={16} className="text-red-600 dark:text-red-300" />
|
||||||
|
<span className="text-bold text-red-600 dark:text-red-300">
|
||||||
|
Delete thread
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ModalTrigger>
|
||||||
|
<ModalPortal />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalTitle>Delete Thread</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
<p>
|
||||||
|
Are you sure you want to delete this thread? This action cannot be
|
||||||
|
undone.
|
||||||
|
</p>
|
||||||
|
<ModalFooter>
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<ModalClose asChild onClick={(e) => e.stopPropagation()}>
|
||||||
|
<Button themes="ghost">No</Button>
|
||||||
|
</ModalClose>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button autoFocus themes="danger" onClick={onDeleteThreadClick}>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(DeleteThreadModal)
|
||||||
@ -2,15 +2,18 @@ import React, { Fragment, useCallback } from 'react'
|
|||||||
|
|
||||||
import { Button } from '@janhq/uikit'
|
import { Button } from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const RequestDownloadModel: React.FC = () => {
|
const RequestDownloadModel: React.FC = () => {
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
|
|||||||
@ -1,76 +1,39 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
import {
|
import { Thread } from '@janhq/core/'
|
||||||
Modal,
|
|
||||||
ModalTrigger,
|
|
||||||
ModalClose,
|
|
||||||
ModalFooter,
|
|
||||||
ModalPortal,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalTitle,
|
|
||||||
Button,
|
|
||||||
} from '@janhq/uikit'
|
|
||||||
|
|
||||||
import { motion as m } from 'framer-motion'
|
import { motion as m } from 'framer-motion'
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
import {
|
import { GalleryHorizontalEndIcon, MoreVerticalIcon } from 'lucide-react'
|
||||||
GalleryHorizontalEndIcon,
|
|
||||||
MoreVerticalIcon,
|
|
||||||
Trash2Icon,
|
|
||||||
Paintbrush,
|
|
||||||
} from 'lucide-react'
|
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
|
||||||
import useDeleteThread from '@/hooks/useDeleteThread'
|
|
||||||
|
|
||||||
import useGetAssistants from '@/hooks/useGetAssistants'
|
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
import useSetActiveThread from '@/hooks/useSetActiveThread'
|
import useSetActiveThread from '@/hooks/useSetActiveThread'
|
||||||
|
|
||||||
import useThreads from '@/hooks/useThreads'
|
|
||||||
|
|
||||||
import { displayDate } from '@/utils/datetime'
|
import { displayDate } from '@/utils/datetime'
|
||||||
|
|
||||||
|
import CleanThreadModal from '../CleanThreadModal'
|
||||||
|
|
||||||
|
import DeleteThreadModal from '../DeleteThreadModal'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
getActiveThreadIdAtom,
|
||||||
threadStatesAtom,
|
threadStatesAtom,
|
||||||
threadsAtom,
|
threadsAtom,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
export default function ThreadList() {
|
export default function ThreadList() {
|
||||||
const threads = useAtomValue(threadsAtom)
|
|
||||||
const threadStates = useAtomValue(threadStatesAtom)
|
const threadStates = useAtomValue(threadStatesAtom)
|
||||||
const { getThreads } = useThreads()
|
const threads = useAtomValue(threadsAtom)
|
||||||
const { assistants } = useGetAssistants()
|
const activeThreadId = useAtomValue(getActiveThreadIdAtom)
|
||||||
const { requestCreateNewThread } = useCreateNewThread()
|
const { setActiveThread } = useSetActiveThread()
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
|
||||||
const { deleteThread, cleanThread } = useDeleteThread()
|
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
|
||||||
const [isThreadsReady, setIsThreadsReady] = useState(false)
|
|
||||||
|
|
||||||
const { activeThreadId, setActiveThread: onThreadClick } =
|
const onThreadClick = useCallback(
|
||||||
useSetActiveThread()
|
(thread: Thread) => {
|
||||||
|
setActiveThread(thread)
|
||||||
useEffect(() => {
|
},
|
||||||
getThreads().then(() => setIsThreadsReady(true))
|
[setActiveThread]
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
)
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
isThreadsReady &&
|
|
||||||
downloadedModels.length !== 0 &&
|
|
||||||
threads.length === 0 &&
|
|
||||||
assistants.length !== 0 &&
|
|
||||||
!activeThread
|
|
||||||
) {
|
|
||||||
requestCreateNewThread(assistants[0])
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [assistants, threads, downloadedModels, activeThread, isThreadsReady])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3 py-4">
|
<div className="px-3 py-4">
|
||||||
@ -83,133 +46,44 @@ export default function ThreadList() {
|
|||||||
<h2 className="font-semibold">No Thread History</h2>
|
<h2 className="font-semibold">No Thread History</h2>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
threads.map((thread, i) => {
|
threads.map((thread) => (
|
||||||
const lastMessage =
|
<div
|
||||||
threadStates[thread.id]?.lastMessage ?? 'No new message'
|
key={thread.id}
|
||||||
return (
|
className={twMerge(
|
||||||
<div
|
`group/message relative mb-1 flex cursor-pointer flex-col transition-all hover:rounded-lg hover:bg-gray-100 hover:dark:bg-secondary/50`
|
||||||
key={i}
|
)}
|
||||||
className={twMerge(
|
onClick={() => {
|
||||||
`group/message relative mb-1 flex cursor-pointer flex-col transition-all hover:rounded-lg hover:bg-gray-100 hover:dark:bg-secondary/50`
|
onThreadClick(thread)
|
||||||
)}
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
onThreadClick(thread)
|
<div className="relative z-10 p-4 py-4">
|
||||||
}}
|
<p className="line-clamp-1 text-xs leading-5 text-muted-foreground">
|
||||||
>
|
{thread.updated && displayDate(thread.updated)}
|
||||||
<div className="relative z-10 p-4 py-4">
|
</p>
|
||||||
<p className="line-clamp-1 text-xs leading-5 text-muted-foreground">
|
<h2 className="line-clamp-1 font-bold">{thread.title}</h2>
|
||||||
{thread.updated && displayDate(thread.updated)}
|
<p className="mt-1 line-clamp-1 text-xs text-gray-700 group-hover/message:max-w-[160px] dark:text-gray-300">
|
||||||
</p>
|
{threadStates[thread.id]?.lastMessage ?? 'No new message'}
|
||||||
<h2 className="line-clamp-1 font-bold">{thread.title}</h2>
|
</p>
|
||||||
<p className="mt-1 line-clamp-1 text-xs text-gray-700 group-hover/message:max-w-[160px] dark:text-gray-300">
|
|
||||||
{lastMessage || 'No new message'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
`group/icon invisible absolute bottom-2 right-2 z-20 rounded-lg p-1 text-muted-foreground hover:bg-gray-200 group-hover/message:visible hover:dark:bg-secondary`
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<MoreVerticalIcon />
|
|
||||||
<div className="invisible absolute right-0 z-20 w-40 overflow-hidden rounded-lg border border-border bg-background shadow-lg group-hover/icon:visible">
|
|
||||||
<Modal>
|
|
||||||
<ModalTrigger asChild onClick={(e) => e.stopPropagation()}>
|
|
||||||
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
|
||||||
<Paintbrush
|
|
||||||
size={16}
|
|
||||||
className="text-muted-foreground"
|
|
||||||
/>
|
|
||||||
<span className="text-bold text-black dark:text-muted-foreground">
|
|
||||||
Clean thread
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</ModalTrigger>
|
|
||||||
<ModalPortal />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>
|
|
||||||
<ModalTitle>Clean Thread</ModalTitle>
|
|
||||||
</ModalHeader>
|
|
||||||
<p>Are you sure you want to clean this thread?</p>
|
|
||||||
<ModalFooter>
|
|
||||||
<div className="flex gap-x-2">
|
|
||||||
<ModalClose
|
|
||||||
asChild
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Button themes="ghost">No</Button>
|
|
||||||
</ModalClose>
|
|
||||||
<ModalClose asChild>
|
|
||||||
<Button
|
|
||||||
themes="danger"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
cleanThread(thread.id)
|
|
||||||
}}
|
|
||||||
autoFocus
|
|
||||||
>
|
|
||||||
Yes
|
|
||||||
</Button>
|
|
||||||
</ModalClose>
|
|
||||||
</div>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
<Modal>
|
|
||||||
<ModalTrigger asChild onClick={(e) => e.stopPropagation()}>
|
|
||||||
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
|
||||||
<Trash2Icon
|
|
||||||
size={16}
|
|
||||||
className="text-red-600 dark:text-red-300"
|
|
||||||
/>
|
|
||||||
<span className="text-bold text-red-600 dark:text-red-300">
|
|
||||||
Delete thread
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</ModalTrigger>
|
|
||||||
<ModalPortal />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>
|
|
||||||
<ModalTitle>Delete Thread</ModalTitle>
|
|
||||||
</ModalHeader>
|
|
||||||
<p>
|
|
||||||
Are you sure you want to delete this thread? This action
|
|
||||||
cannot be undone.
|
|
||||||
</p>
|
|
||||||
<ModalFooter>
|
|
||||||
<div className="flex gap-x-2">
|
|
||||||
<ModalClose
|
|
||||||
asChild
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Button themes="ghost">No</Button>
|
|
||||||
</ModalClose>
|
|
||||||
<ModalClose asChild>
|
|
||||||
<Button
|
|
||||||
autoFocus
|
|
||||||
themes="danger"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
deleteThread(thread.id)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Yes
|
|
||||||
</Button>
|
|
||||||
</ModalClose>
|
|
||||||
</div>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{activeThreadId === thread.id && (
|
|
||||||
<m.div
|
|
||||||
className="absolute inset-0 left-0 h-full w-full rounded-lg bg-gray-100 p-4 dark:bg-secondary/50"
|
|
||||||
layoutId="active-thread"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
<div
|
||||||
})
|
className={twMerge(
|
||||||
|
`group/icon invisible absolute bottom-2 right-2 z-20 rounded-lg p-1 text-muted-foreground hover:bg-gray-200 group-hover/message:visible hover:dark:bg-secondary`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<MoreVerticalIcon />
|
||||||
|
<div className="invisible absolute right-0 z-20 w-40 overflow-hidden rounded-lg border border-border bg-background shadow-lg group-hover/icon:visible">
|
||||||
|
<CleanThreadModal threadId={thread.id} />
|
||||||
|
<DeleteThreadModal threadId={thread.id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{activeThreadId === thread.id && (
|
||||||
|
<m.div
|
||||||
|
className="absolute inset-0 left-0 h-full w-full rounded-lg bg-gray-100 p-4 dark:bg-secondary/50"
|
||||||
|
layoutId="active-thread"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -27,14 +27,14 @@ import useDownloadModel from '@/hooks/useDownloadModel'
|
|||||||
|
|
||||||
import { useDownloadState } from '@/hooks/useDownloadState'
|
import { useDownloadState } from '@/hooks/useDownloadState'
|
||||||
|
|
||||||
import { getAssistants } from '@/hooks/useGetAssistants'
|
|
||||||
import { downloadedModelsAtom } from '@/hooks/useGetDownloadedModels'
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { toGibibytes } from '@/utils/converter'
|
import { toGibibytes } from '@/utils/converter'
|
||||||
|
|
||||||
|
import { assistantsAtom } from '@/helpers/atoms/Assistant.atom'
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
|
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom'
|
import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -49,7 +49,9 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
|||||||
const { modelDownloadStateAtom } = useDownloadState()
|
const { modelDownloadStateAtom } = useDownloadState()
|
||||||
const { requestCreateNewThread } = useCreateNewThread()
|
const { requestCreateNewThread } = useCreateNewThread()
|
||||||
const totalRam = useAtomValue(totalRamAtom)
|
const totalRam = useAtomValue(totalRamAtom)
|
||||||
|
|
||||||
const serverEnabled = useAtomValue(serverEnabledAtom)
|
const serverEnabled = useAtomValue(serverEnabledAtom)
|
||||||
|
const assistants = useAtomValue(assistantsAtom)
|
||||||
|
|
||||||
const downloadAtom = useMemo(
|
const downloadAtom = useMemo(
|
||||||
() => atom((get) => get(modelDownloadStateAtom)[model.id]),
|
() => atom((get) => get(modelDownloadStateAtom)[model.id]),
|
||||||
@ -60,7 +62,6 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
|||||||
|
|
||||||
const onDownloadClick = useCallback(() => {
|
const onDownloadClick = useCallback(() => {
|
||||||
downloadModel(model)
|
downloadModel(model)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [model])
|
}, [model])
|
||||||
|
|
||||||
const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null
|
const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null
|
||||||
@ -70,7 +71,6 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const onUseModelClick = useCallback(async () => {
|
const onUseModelClick = useCallback(async () => {
|
||||||
const assistants = await getAssistants()
|
|
||||||
if (assistants.length === 0) {
|
if (assistants.length === 0) {
|
||||||
alert('No assistant available')
|
alert('No assistant available')
|
||||||
return
|
return
|
||||||
|
|||||||
@ -10,9 +10,11 @@ import { MainViewState } from '@/constants/screens'
|
|||||||
|
|
||||||
import useDownloadModel from '@/hooks/useDownloadModel'
|
import useDownloadModel from '@/hooks/useDownloadModel'
|
||||||
import { useDownloadState } from '@/hooks/useDownloadState'
|
import { useDownloadState } from '@/hooks/useDownloadState'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: Model
|
model: Model
|
||||||
isRecommended: boolean
|
isRecommended: boolean
|
||||||
@ -20,7 +22,7 @@ type Props = {
|
|||||||
|
|
||||||
const ModelVersionItem: React.FC<Props> = ({ model }) => {
|
const ModelVersionItem: React.FC<Props> = ({ model }) => {
|
||||||
const { downloadModel } = useDownloadModel()
|
const { downloadModel } = useDownloadModel()
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
const isDownloaded =
|
const isDownloaded =
|
||||||
downloadedModels.find(
|
downloadedModels.find(
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
import { openExternalUrl } from '@janhq/core'
|
import { openExternalUrl } from '@janhq/core'
|
||||||
import {
|
import {
|
||||||
@ -12,24 +12,24 @@ import {
|
|||||||
SelectItem,
|
SelectItem,
|
||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { SearchIcon } from 'lucide-react'
|
import { SearchIcon } from 'lucide-react'
|
||||||
|
|
||||||
import Loader from '@/containers/Loader'
|
|
||||||
|
|
||||||
import { useGetConfiguredModels } from '@/hooks/useGetConfiguredModels'
|
|
||||||
|
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
|
|
||||||
import ExploreModelList from './ExploreModelList'
|
import ExploreModelList from './ExploreModelList'
|
||||||
|
|
||||||
|
import {
|
||||||
|
configuredModelsAtom,
|
||||||
|
downloadedModelsAtom,
|
||||||
|
} from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const ExploreModelsScreen = () => {
|
const ExploreModelsScreen = () => {
|
||||||
const { loading, models } = useGetConfiguredModels()
|
const configuredModels = useAtomValue(configuredModelsAtom)
|
||||||
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
const [searchValue, setsearchValue] = useState('')
|
const [searchValue, setsearchValue] = useState('')
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
|
||||||
const [sortSelected, setSortSelected] = useState('All Models')
|
const [sortSelected, setSortSelected] = useState('All Models')
|
||||||
const sortMenu = ['All Models', 'Recommended', 'Downloaded']
|
const sortMenu = ['All Models', 'Recommended', 'Downloaded']
|
||||||
|
|
||||||
const filteredModels = models.filter((x) => {
|
const filteredModels = configuredModels.filter((x) => {
|
||||||
if (sortSelected === 'Downloaded') {
|
if (sortSelected === 'Downloaded') {
|
||||||
return (
|
return (
|
||||||
x.name.toLowerCase().includes(searchValue.toLowerCase()) &&
|
x.name.toLowerCase().includes(searchValue.toLowerCase()) &&
|
||||||
@ -45,11 +45,9 @@ const ExploreModelsScreen = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const onHowToImportModelClick = () => {
|
const onHowToImportModelClick = useCallback(() => {
|
||||||
openExternalUrl('https://jan.ai/guides/using-models/import-manually/')
|
openExternalUrl('https://jan.ai/guides/using-models/import-manually/')
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
if (loading) return <Loader description="loading ..." />
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -2,16 +2,17 @@ import { useState } from 'react'
|
|||||||
|
|
||||||
import { Input } from '@janhq/uikit'
|
import { Input } from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { SearchIcon } from 'lucide-react'
|
import { SearchIcon } from 'lucide-react'
|
||||||
|
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
|
||||||
|
|
||||||
import RowModel from './Row'
|
import RowModel from './Row'
|
||||||
|
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const Column = ['Name', 'Model ID', 'Size', 'Version', 'Status', '']
|
const Column = ['Name', 'Model ID', 'Size', 'Version', 'Status', '']
|
||||||
|
|
||||||
export default function Models() {
|
export default function Models() {
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
const [searchValue, setsearchValue] = useState('')
|
const [searchValue, setsearchValue] = useState('')
|
||||||
|
|
||||||
const filteredDownloadedModels = downloadedModels.filter((x) => {
|
const filteredDownloadedModels = downloadedModels.filter((x) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user