refactor: move file to jan root (#598)
* feat: move necessary files to jan root Signed-off-by: James <james@jan.ai> * chore: check model dir --------- Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
parent
9c5c03b6bc
commit
52d56a8ae1
@ -20,7 +20,7 @@ export type MessageHistory = {
|
|||||||
* The `NewMessageRequest` type defines the shape of a new message request object.
|
* The `NewMessageRequest` type defines the shape of a new message request object.
|
||||||
*/
|
*/
|
||||||
export type NewMessageRequest = {
|
export type NewMessageRequest = {
|
||||||
_id?: string;
|
id?: string;
|
||||||
conversationId?: string;
|
conversationId?: string;
|
||||||
user?: string;
|
user?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
@ -34,7 +34,7 @@ export type NewMessageRequest = {
|
|||||||
* The `NewMessageRequest` type defines the shape of a new message request object.
|
* The `NewMessageRequest` type defines the shape of a new message request object.
|
||||||
*/
|
*/
|
||||||
export type NewMessageResponse = {
|
export type NewMessageResponse = {
|
||||||
_id?: string;
|
id?: string;
|
||||||
conversationId?: string;
|
conversationId?: string;
|
||||||
user?: string;
|
user?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
|||||||
@ -8,6 +8,21 @@ const writeFile: (path: string, data: string) => Promise<any> = (path, data) =>
|
|||||||
window.coreAPI?.writeFile(path, data) ??
|
window.coreAPI?.writeFile(path, data) ??
|
||||||
window.electronAPI?.writeFile(path, data);
|
window.electronAPI?.writeFile(path, data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user space path.
|
||||||
|
* @returns {Promise<any>} A Promise that resolves with the user space path.
|
||||||
|
*/
|
||||||
|
const getUserSpace = (): Promise<string> =>
|
||||||
|
window.coreAPI?.getUserSpace() ?? window.electronAPI?.getUserSpace();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the path is a directory.
|
||||||
|
* @param path - The path to check.
|
||||||
|
* @returns {boolean} A boolean indicating whether the path is a directory.
|
||||||
|
*/
|
||||||
|
const isDirectory = (path: string): Promise<boolean> =>
|
||||||
|
window.coreAPI?.isDirectory(path) ?? window.electronAPI?.isDirectory(path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the contents of a file at the specified path.
|
* Reads the contents of a file at the specified path.
|
||||||
* @param {string} path - The path of the file to read.
|
* @param {string} path - The path of the file to read.
|
||||||
@ -48,6 +63,8 @@ const deleteFile: (path: string) => Promise<any> = (path) =>
|
|||||||
window.coreAPI?.deleteFile(path) ?? window.electronAPI?.deleteFile(path);
|
window.coreAPI?.deleteFile(path) ?? window.electronAPI?.deleteFile(path);
|
||||||
|
|
||||||
export const fs = {
|
export const fs = {
|
||||||
|
isDirectory,
|
||||||
|
getUserSpace,
|
||||||
writeFile,
|
writeFile,
|
||||||
readFile,
|
readFile,
|
||||||
listFiles,
|
listFiles,
|
||||||
|
|||||||
@ -20,12 +20,9 @@ export { events } from "./events";
|
|||||||
* Events types exports.
|
* Events types exports.
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
export {
|
export * from "./events";
|
||||||
EventName,
|
|
||||||
NewMessageRequest,
|
export * from "./types/index";
|
||||||
NewMessageResponse,
|
|
||||||
MessageHistory,
|
|
||||||
} from "./events";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filesystem module exports.
|
* Filesystem module exports.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export interface Conversation {
|
export interface Conversation {
|
||||||
_id: string;
|
id: string;
|
||||||
modelId?: string;
|
modelId?: string;
|
||||||
botId?: string;
|
botId?: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -8,11 +8,23 @@ export interface Conversation {
|
|||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
|
lastMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
|
id: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
user?: string;
|
user?: string;
|
||||||
_id: string;
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RawMessage {
|
||||||
|
id?: string;
|
||||||
|
conversationId?: string;
|
||||||
|
user?: string;
|
||||||
|
avatar?: string;
|
||||||
|
message?: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
}
|
}
|
||||||
@ -22,7 +34,7 @@ export interface Model {
|
|||||||
* Combination of owner and model name.
|
* Combination of owner and model name.
|
||||||
* Being used as file name. MUST be unique.
|
* Being used as file name. MUST be unique.
|
||||||
*/
|
*/
|
||||||
_id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
quantMethod: string;
|
quantMethod: string;
|
||||||
bits: number;
|
bits: number;
|
||||||
@ -51,7 +63,7 @@ export interface Model {
|
|||||||
tags: string[];
|
tags: string[];
|
||||||
}
|
}
|
||||||
export interface ModelCatalog {
|
export interface ModelCatalog {
|
||||||
_id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
shortDescription: string;
|
shortDescription: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
@ -74,7 +86,7 @@ export type ModelVersion = {
|
|||||||
* Combination of owner and model name.
|
* Combination of owner and model name.
|
||||||
* Being used as file name. Should be unique.
|
* Being used as file name. Should be unique.
|
||||||
*/
|
*/
|
||||||
_id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
quantMethod: string;
|
quantMethod: string;
|
||||||
bits: number;
|
bits: number;
|
||||||
@ -89,3 +101,40 @@ export type ModelVersion = {
|
|||||||
startDownloadAt?: number;
|
startDownloadAt?: number;
|
||||||
finishDownloadAt?: number;
|
finishDownloadAt?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ChatMessage {
|
||||||
|
id: string;
|
||||||
|
conversationId: string;
|
||||||
|
messageType: MessageType;
|
||||||
|
messageSenderType: MessageSenderType;
|
||||||
|
senderUid: string;
|
||||||
|
senderName: string;
|
||||||
|
senderAvatarUrl: string;
|
||||||
|
text: string | undefined;
|
||||||
|
imageUrls?: string[] | undefined;
|
||||||
|
createdAt: number;
|
||||||
|
status: MessageStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MessageType {
|
||||||
|
Text = "Text",
|
||||||
|
Image = "Image",
|
||||||
|
ImageWithText = "ImageWithText",
|
||||||
|
Error = "Error",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MessageSenderType {
|
||||||
|
Ai = "assistant",
|
||||||
|
User = "user",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MessageStatus {
|
||||||
|
Ready = "ready",
|
||||||
|
Pending = "pending",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConversationState = {
|
||||||
|
hasMore: boolean;
|
||||||
|
waitingForResponse: boolean;
|
||||||
|
error?: Error;
|
||||||
|
};
|
||||||
|
|||||||
8
electron/.prettierrc
Normal file
8
electron/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { app, ipcMain } from "electron";
|
import { app, ipcMain } from 'electron'
|
||||||
import { DownloadManager } from "../managers/download";
|
import { DownloadManager } from '../managers/download'
|
||||||
import { resolve, join } from "path";
|
import { resolve, join } from 'path'
|
||||||
import { WindowManager } from "../managers/window";
|
import { WindowManager } from '../managers/window'
|
||||||
import request from "request";
|
import request from 'request'
|
||||||
import { createWriteStream, unlink } from "fs";
|
import { createWriteStream, unlink } from 'fs'
|
||||||
const progress = require("request-progress");
|
const progress = require('request-progress')
|
||||||
|
|
||||||
export function handleDownloaderIPCs() {
|
export function handleDownloaderIPCs() {
|
||||||
/**
|
/**
|
||||||
@ -12,18 +12,18 @@ export function handleDownloaderIPCs() {
|
|||||||
* @param _event - The IPC event object.
|
* @param _event - The IPC event object.
|
||||||
* @param fileName - The name of the file being downloaded.
|
* @param fileName - The name of the file being downloaded.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle("pauseDownload", async (_event, fileName) => {
|
ipcMain.handle('pauseDownload', async (_event, fileName) => {
|
||||||
DownloadManager.instance.networkRequests[fileName]?.pause();
|
DownloadManager.instance.networkRequests[fileName]?.pause()
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the "resumeDownload" IPC message by resuming the download associated with the provided fileName.
|
* Handles the "resumeDownload" IPC message by resuming the download associated with the provided fileName.
|
||||||
* @param _event - The IPC event object.
|
* @param _event - The IPC event object.
|
||||||
* @param fileName - The name of the file being downloaded.
|
* @param fileName - The name of the file being downloaded.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle("resumeDownload", async (_event, fileName) => {
|
ipcMain.handle('resumeDownload', async (_event, fileName) => {
|
||||||
DownloadManager.instance.networkRequests[fileName]?.resume();
|
DownloadManager.instance.networkRequests[fileName]?.resume()
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the "abortDownload" IPC message by aborting the download associated with the provided fileName.
|
* Handles the "abortDownload" IPC message by aborting the download associated with the provided fileName.
|
||||||
@ -31,24 +31,26 @@ export function handleDownloaderIPCs() {
|
|||||||
* @param _event - The IPC event object.
|
* @param _event - The IPC event object.
|
||||||
* @param fileName - The name of the file being downloaded.
|
* @param fileName - The name of the file being downloaded.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle("abortDownload", async (_event, fileName) => {
|
ipcMain.handle('abortDownload', async (_event, fileName) => {
|
||||||
const rq = DownloadManager.instance.networkRequests[fileName];
|
const rq = DownloadManager.instance.networkRequests[fileName]
|
||||||
DownloadManager.instance.networkRequests[fileName] = undefined;
|
DownloadManager.instance.networkRequests[fileName] = undefined
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath('userData')
|
||||||
const fullPath = join(userDataPath, fileName);
|
const fullPath = join(userDataPath, fileName)
|
||||||
rq?.abort();
|
rq?.abort()
|
||||||
let result = "NULL";
|
let result = 'NULL'
|
||||||
unlink(fullPath, function (err) {
|
unlink(fullPath, function (err) {
|
||||||
if (err && err.code == "ENOENT") {
|
if (err && err.code == 'ENOENT') {
|
||||||
result = `File not exist: ${err}`;
|
result = `File not exist: ${err}`
|
||||||
} else if (err) {
|
} else if (err) {
|
||||||
result = `File delete error: ${err}`;
|
result = `File delete error: ${err}`
|
||||||
} else {
|
} else {
|
||||||
result = "File deleted successfully";
|
result = 'File deleted successfully'
|
||||||
}
|
}
|
||||||
console.log(`Delete file ${fileName} from ${fullPath} result: ${result}`);
|
console.debug(
|
||||||
});
|
`Delete file ${fileName} from ${fullPath} result: ${result}`
|
||||||
});
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads a file from a given URL.
|
* Downloads a file from a given URL.
|
||||||
@ -56,51 +58,51 @@ export function handleDownloaderIPCs() {
|
|||||||
* @param url - The URL to download the file from.
|
* @param url - The URL to download the file from.
|
||||||
* @param fileName - The name to give the downloaded file.
|
* @param fileName - The name to give the downloaded file.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle("downloadFile", async (_event, url, fileName) => {
|
ipcMain.handle('downloadFile', async (_event, url, fileName) => {
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = join(app.getPath('home'), 'jan')
|
||||||
const destination = resolve(userDataPath, fileName);
|
const destination = resolve(userDataPath, fileName)
|
||||||
const rq = request(url);
|
const rq = request(url)
|
||||||
|
|
||||||
progress(rq, {})
|
progress(rq, {})
|
||||||
.on("progress", function (state: any) {
|
.on('progress', function (state: any) {
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
WindowManager?.instance.currentWindow?.webContents.send(
|
||||||
"FILE_DOWNLOAD_UPDATE",
|
'FILE_DOWNLOAD_UPDATE',
|
||||||
{
|
{
|
||||||
...state,
|
...state,
|
||||||
fileName,
|
fileName,
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
})
|
})
|
||||||
.on("error", function (err: Error) {
|
.on('error', function (err: Error) {
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
WindowManager?.instance.currentWindow?.webContents.send(
|
||||||
"FILE_DOWNLOAD_ERROR",
|
'FILE_DOWNLOAD_ERROR',
|
||||||
{
|
{
|
||||||
fileName,
|
fileName,
|
||||||
err,
|
err,
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
})
|
})
|
||||||
.on("end", function () {
|
.on('end', function () {
|
||||||
if (DownloadManager.instance.networkRequests[fileName]) {
|
if (DownloadManager.instance.networkRequests[fileName]) {
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
WindowManager?.instance.currentWindow?.webContents.send(
|
||||||
"FILE_DOWNLOAD_COMPLETE",
|
'FILE_DOWNLOAD_COMPLETE',
|
||||||
{
|
{
|
||||||
fileName,
|
fileName,
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
DownloadManager.instance.setRequest(fileName, undefined);
|
DownloadManager.instance.setRequest(fileName, undefined)
|
||||||
} else {
|
} else {
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
WindowManager?.instance.currentWindow?.webContents.send(
|
||||||
"FILE_DOWNLOAD_ERROR",
|
'FILE_DOWNLOAD_ERROR',
|
||||||
{
|
{
|
||||||
fileName,
|
fileName,
|
||||||
err: "Download cancelled",
|
err: 'Download cancelled',
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.pipe(createWriteStream(destination));
|
.pipe(createWriteStream(destination))
|
||||||
|
|
||||||
DownloadManager.instance.setRequest(fileName, rq);
|
DownloadManager.instance.setRequest(fileName, rq)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,53 @@
|
|||||||
import { app, ipcMain } from "electron";
|
import { app, ipcMain } from 'electron'
|
||||||
import * as fs from "fs";
|
import * as fs from 'fs'
|
||||||
import { join } from "path";
|
import { join } from 'path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles file system operations.
|
* Handles file system operations.
|
||||||
*/
|
*/
|
||||||
export function handleFsIPCs() {
|
export function handleFsIPCs() {
|
||||||
|
const userSpacePath = join(app.getPath('home'), 'jan')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the path to the user data directory.
|
||||||
|
* @param event - The event object.
|
||||||
|
* @returns A promise that resolves with the path to the user data directory.
|
||||||
|
*/
|
||||||
|
ipcMain.handle(
|
||||||
|
'getUserSpace',
|
||||||
|
(): Promise<string> => Promise.resolve(userSpacePath)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the path is a directory.
|
||||||
|
* @param event - The event object.
|
||||||
|
* @param path - The path to check.
|
||||||
|
* @returns A promise that resolves with a boolean indicating whether the path is a directory.
|
||||||
|
*/
|
||||||
|
ipcMain.handle('isDirectory', (_event, path: string): Promise<boolean> => {
|
||||||
|
const fullPath = join(userSpacePath, path)
|
||||||
|
return Promise.resolve(
|
||||||
|
fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a file from the user data directory.
|
* Reads a file from the user data directory.
|
||||||
* @param event - The event object.
|
* @param event - The event object.
|
||||||
* @param path - The path of the file to read.
|
* @param path - The path of the file to read.
|
||||||
* @returns A promise that resolves with the contents of the file.
|
* @returns A promise that resolves with the contents of the file.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle("readFile", async (event, path: string): Promise<string> => {
|
ipcMain.handle('readFile', async (event, path: string): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.readFile(join(app.getPath("userData"), path), "utf8", (err, data) => {
|
fs.readFile(join(userSpacePath, path), 'utf8', (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
resolve(data);
|
resolve(data)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes data to a file in the user data directory.
|
* Writes data to a file in the user data directory.
|
||||||
@ -32,24 +57,19 @@ export function handleFsIPCs() {
|
|||||||
* @returns A promise that resolves when the file has been written.
|
* @returns A promise that resolves when the file has been written.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
"writeFile",
|
'writeFile',
|
||||||
async (event, path: string, data: string): Promise<void> => {
|
async (event, path: string, data: string): Promise<void> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.writeFile(
|
fs.writeFile(join(userSpacePath, path), data, 'utf8', (err) => {
|
||||||
join(app.getPath("userData"), path),
|
|
||||||
data,
|
|
||||||
"utf8",
|
|
||||||
(err) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a directory in the user data directory.
|
* Creates a directory in the user data directory.
|
||||||
@ -57,21 +77,17 @@ export function handleFsIPCs() {
|
|||||||
* @param path - The path of the directory to create.
|
* @param path - The path of the directory to create.
|
||||||
* @returns A promise that resolves when the directory has been created.
|
* @returns A promise that resolves when the directory has been created.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle("mkdir", async (event, path: string): Promise<void> => {
|
ipcMain.handle('mkdir', async (event, path: string): Promise<void> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.mkdir(
|
fs.mkdir(join(userSpacePath, path), { recursive: true }, (err) => {
|
||||||
join(app.getPath("userData"), path),
|
|
||||||
{ recursive: true },
|
|
||||||
(err) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
);
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a directory in the user data directory.
|
* Removes a directory in the user data directory.
|
||||||
@ -79,21 +95,17 @@ export function handleFsIPCs() {
|
|||||||
* @param path - The path of the directory to remove.
|
* @param path - The path of the directory to remove.
|
||||||
* @returns A promise that resolves when the directory is removed successfully.
|
* @returns A promise that resolves when the directory is removed successfully.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle("rmdir", async (event, path: string): Promise<void> => {
|
ipcMain.handle('rmdir', async (event, path: string): Promise<void> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.rmdir(
|
fs.rmdir(join(userSpacePath, path), { recursive: true }, (err) => {
|
||||||
join(app.getPath("userData"), path),
|
|
||||||
{ recursive: true },
|
|
||||||
(err) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
);
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists the files in a directory in the user data directory.
|
* Lists the files in a directory in the user data directory.
|
||||||
@ -102,19 +114,19 @@ export function handleFsIPCs() {
|
|||||||
* @returns A promise that resolves with an array of file names.
|
* @returns A promise that resolves with an array of file names.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
"listFiles",
|
'listFiles',
|
||||||
async (event, path: string): Promise<string[]> => {
|
async (event, path: string): Promise<string[]> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.readdir(join(app.getPath("userData"), path), (err, files) => {
|
fs.readdir(join(userSpacePath, path), (err, files) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
resolve(files);
|
resolve(files)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a file from the user data folder.
|
* Deletes a file from the user data folder.
|
||||||
@ -122,22 +134,23 @@ export function handleFsIPCs() {
|
|||||||
* @param filePath - The path to the file to delete.
|
* @param filePath - The path to the file to delete.
|
||||||
* @returns A string indicating the result of the operation.
|
* @returns A string indicating the result of the operation.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle("deleteFile", async (_event, filePath) => {
|
ipcMain.handle('deleteFile', async (_event, filePath) => {
|
||||||
const userDataPath = app.getPath("userData");
|
const fullPath = join(userSpacePath, filePath)
|
||||||
const fullPath = join(userDataPath, filePath);
|
|
||||||
|
|
||||||
let result = "NULL";
|
let result = 'NULL'
|
||||||
fs.unlink(fullPath, function (err) {
|
fs.unlink(fullPath, function (err) {
|
||||||
if (err && err.code == "ENOENT") {
|
if (err && err.code == 'ENOENT') {
|
||||||
result = `File not exist: ${err}`;
|
result = `File not exist: ${err}`
|
||||||
} else if (err) {
|
} else if (err) {
|
||||||
result = `File delete error: ${err}`;
|
result = `File delete error: ${err}`
|
||||||
} else {
|
} else {
|
||||||
result = "File deleted successfully";
|
result = 'File deleted successfully'
|
||||||
}
|
}
|
||||||
console.log(`Delete file ${filePath} from ${fullPath} result: ${result}`);
|
console.debug(
|
||||||
});
|
`Delete file ${filePath} from ${fullPath} result: ${result}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
return result;
|
return result
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export function handlePluginIPCs() {
|
|||||||
if (typeof module[method] === "function") {
|
if (typeof module[method] === "function") {
|
||||||
return module[method](...args);
|
return module[method](...args);
|
||||||
} else {
|
} else {
|
||||||
console.log(module[method]);
|
console.debug(module[method]);
|
||||||
console.error(`Function "${method}" does not exist in the module.`);
|
console.error(`Function "${method}" does not exist in the module.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ export function handlePluginIPCs() {
|
|||||||
const fullPath = join(userDataPath, "plugins");
|
const fullPath = join(userDataPath, "plugins");
|
||||||
|
|
||||||
rmdir(fullPath, { recursive: true }, function (err) {
|
rmdir(fullPath, { recursive: true }, function (err) {
|
||||||
if (err) console.log(err);
|
if (err) console.error(err);
|
||||||
ModuleManager.instance.clearImportedModules();
|
ModuleManager.instance.clearImportedModules();
|
||||||
|
|
||||||
// just relaunch if packaged, should launch manually in development mode
|
// just relaunch if packaged, should launch manually in development mode
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export function handleAppUpdates() {
|
|||||||
|
|
||||||
/* App Update Progress */
|
/* App Update Progress */
|
||||||
autoUpdater.on("download-progress", (progress: any) => {
|
autoUpdater.on("download-progress", (progress: any) => {
|
||||||
console.log("app update progress: ", progress.percent);
|
console.debug("app update progress: ", progress.percent);
|
||||||
WindowManager.instance.currentWindow?.webContents.send(
|
WindowManager.instance.currentWindow?.webContents.send(
|
||||||
"APP_UPDATE_PROGRESS",
|
"APP_UPDATE_PROGRESS",
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
import { app, BrowserWindow } from "electron";
|
import { app, BrowserWindow } from 'electron'
|
||||||
import { join } from "path";
|
import { join } from 'path'
|
||||||
import { setupMenu } from "./utils/menu";
|
import { setupMenu } from './utils/menu'
|
||||||
import { handleFsIPCs } from "./handlers/fs";
|
import { handleFsIPCs } from './handlers/fs'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Managers
|
* Managers
|
||||||
**/
|
**/
|
||||||
import { WindowManager } from "./managers/window";
|
import { WindowManager } from './managers/window'
|
||||||
import { ModuleManager } from "./managers/module";
|
import { ModuleManager } from './managers/module'
|
||||||
import { PluginManager } from "./managers/plugin";
|
import { PluginManager } from './managers/plugin'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IPC Handlers
|
* IPC Handlers
|
||||||
**/
|
**/
|
||||||
import { handleDownloaderIPCs } from "./handlers/download";
|
import { handleDownloaderIPCs } from './handlers/download'
|
||||||
import { handleThemesIPCs } from "./handlers/theme";
|
import { handleThemesIPCs } from './handlers/theme'
|
||||||
import { handlePluginIPCs } from "./handlers/plugin";
|
import { handlePluginIPCs } from './handlers/plugin'
|
||||||
import { handleAppIPCs } from "./handlers/app";
|
import { handleAppIPCs } from './handlers/app'
|
||||||
import { handleAppUpdates } from "./handlers/update";
|
import { handleAppUpdates } from './handlers/update'
|
||||||
|
|
||||||
app
|
app
|
||||||
.whenReady()
|
.whenReady()
|
||||||
@ -28,56 +28,56 @@ app
|
|||||||
.then(handleAppUpdates)
|
.then(handleAppUpdates)
|
||||||
.then(createMainWindow)
|
.then(createMainWindow)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
app.on("activate", () => {
|
app.on('activate', () => {
|
||||||
if (!BrowserWindow.getAllWindows().length) {
|
if (!BrowserWindow.getAllWindows().length) {
|
||||||
createMainWindow();
|
createMainWindow()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
app.on("window-all-closed", () => {
|
app.on('window-all-closed', () => {
|
||||||
ModuleManager.instance.clearImportedModules();
|
ModuleManager.instance.clearImportedModules()
|
||||||
app.quit();
|
app.quit()
|
||||||
});
|
})
|
||||||
|
|
||||||
app.on("quit", () => {
|
app.on('quit', () => {
|
||||||
ModuleManager.instance.clearImportedModules();
|
ModuleManager.instance.clearImportedModules()
|
||||||
app.quit();
|
app.quit()
|
||||||
});
|
})
|
||||||
|
|
||||||
function createMainWindow() {
|
function createMainWindow() {
|
||||||
/* Create main window */
|
/* Create main window */
|
||||||
const mainWindow = WindowManager.instance.createWindow({
|
const mainWindow = WindowManager.instance.createWindow({
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
preload: join(__dirname, "preload.js"),
|
preload: join(__dirname, 'preload.js'),
|
||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const startURL = app.isPackaged
|
const startURL = app.isPackaged
|
||||||
? `file://${join(__dirname, "../renderer/index.html")}`
|
? `file://${join(__dirname, '../renderer/index.html')}`
|
||||||
: "http://localhost:3000";
|
: 'http://localhost:3000'
|
||||||
|
|
||||||
/* Load frontend app to the window */
|
/* Load frontend app to the window */
|
||||||
mainWindow.loadURL(startURL);
|
mainWindow.loadURL(startURL)
|
||||||
|
|
||||||
mainWindow.once("ready-to-show", () => mainWindow?.show());
|
mainWindow.once('ready-to-show', () => mainWindow?.show())
|
||||||
mainWindow.on("closed", () => {
|
mainWindow.on('closed', () => {
|
||||||
if (process.platform !== "darwin") app.quit();
|
if (process.platform !== 'darwin') app.quit()
|
||||||
});
|
})
|
||||||
|
|
||||||
/* Enable dev tools for development */
|
/* Enable dev tools for development */
|
||||||
if (!app.isPackaged) mainWindow.webContents.openDevTools();
|
if (!app.isPackaged) mainWindow.webContents.openDevTools()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles various IPC messages from the renderer process.
|
* Handles various IPC messages from the renderer process.
|
||||||
*/
|
*/
|
||||||
function handleIPCs() {
|
function handleIPCs() {
|
||||||
handleFsIPCs();
|
handleFsIPCs()
|
||||||
handleDownloaderIPCs();
|
handleDownloaderIPCs()
|
||||||
handleThemesIPCs();
|
handleThemesIPCs()
|
||||||
handlePluginIPCs();
|
handlePluginIPCs()
|
||||||
handleAppIPCs();
|
handleAppIPCs()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,14 +42,14 @@ export class PluginManager {
|
|||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const store = new Store();
|
const store = new Store();
|
||||||
if (store.get("migrated_version") !== app.getVersion()) {
|
if (store.get("migrated_version") !== app.getVersion()) {
|
||||||
console.log("start migration:", store.get("migrated_version"));
|
console.debug("start migration:", store.get("migrated_version"));
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const fullPath = join(userDataPath, "plugins");
|
const fullPath = join(userDataPath, "plugins");
|
||||||
|
|
||||||
rmdir(fullPath, { recursive: true }, function (err) {
|
rmdir(fullPath, { recursive: true }, function (err) {
|
||||||
if (err) console.log(err);
|
if (err) console.error(err);
|
||||||
store.set("migrated_version", app.getVersion());
|
store.set("migrated_version", app.getVersion());
|
||||||
console.log("migrate plugins done");
|
console.debug("migrate plugins done");
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -33,6 +33,8 @@
|
|||||||
* @property {Function} relaunch - Relaunches the app.
|
* @property {Function} relaunch - Relaunches the app.
|
||||||
* @property {Function} openAppDirectory - Opens the app directory.
|
* @property {Function} openAppDirectory - Opens the app directory.
|
||||||
* @property {Function} deleteFile - Deletes the file at the given path.
|
* @property {Function} deleteFile - Deletes the file at the given path.
|
||||||
|
* @property {Function} isDirectory - Returns true if the file at the given path is a directory.
|
||||||
|
* @property {Function} getUserSpace - Returns the user space.
|
||||||
* @property {Function} readFile - Reads the file at the given path.
|
* @property {Function} readFile - Reads the file at the given path.
|
||||||
* @property {Function} writeFile - Writes the given data to the file at the given path.
|
* @property {Function} writeFile - Writes the given data to the file at the given path.
|
||||||
* @property {Function} listFiles - Lists the files in the directory at the given path.
|
* @property {Function} listFiles - Lists the files in the directory at the given path.
|
||||||
@ -52,81 +54,85 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Make Pluggable Electron's facade available to the renderer on window.plugins
|
// Make Pluggable Electron's facade available to the renderer on window.plugins
|
||||||
import { useFacade } from "./core/plugin/facade";
|
import { useFacade } from './core/plugin/facade'
|
||||||
|
|
||||||
useFacade();
|
useFacade()
|
||||||
|
|
||||||
const { contextBridge, ipcRenderer } = require("electron");
|
const { contextBridge, ipcRenderer } = require('electron')
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("electronAPI", {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
invokePluginFunc: (plugin: any, method: any, ...args: any[]) =>
|
invokePluginFunc: (plugin: any, method: any, ...args: any[]) =>
|
||||||
ipcRenderer.invoke("invokePluginFunc", plugin, method, ...args),
|
ipcRenderer.invoke('invokePluginFunc', plugin, method, ...args),
|
||||||
|
|
||||||
setNativeThemeLight: () => ipcRenderer.invoke("setNativeThemeLight"),
|
setNativeThemeLight: () => ipcRenderer.invoke('setNativeThemeLight'),
|
||||||
|
|
||||||
setNativeThemeDark: () => ipcRenderer.invoke("setNativeThemeDark"),
|
setNativeThemeDark: () => ipcRenderer.invoke('setNativeThemeDark'),
|
||||||
|
|
||||||
setNativeThemeSystem: () => ipcRenderer.invoke("setNativeThemeSystem"),
|
setNativeThemeSystem: () => ipcRenderer.invoke('setNativeThemeSystem'),
|
||||||
|
|
||||||
basePlugins: () => ipcRenderer.invoke("basePlugins"),
|
basePlugins: () => ipcRenderer.invoke('basePlugins'),
|
||||||
|
|
||||||
pluginPath: () => ipcRenderer.invoke("pluginPath"),
|
pluginPath: () => ipcRenderer.invoke('pluginPath'),
|
||||||
|
|
||||||
appDataPath: () => ipcRenderer.invoke("appDataPath"),
|
appDataPath: () => ipcRenderer.invoke('appDataPath'),
|
||||||
|
|
||||||
reloadPlugins: () => ipcRenderer.invoke("reloadPlugins"),
|
reloadPlugins: () => ipcRenderer.invoke('reloadPlugins'),
|
||||||
|
|
||||||
appVersion: () => ipcRenderer.invoke("appVersion"),
|
appVersion: () => ipcRenderer.invoke('appVersion'),
|
||||||
|
|
||||||
openExternalUrl: (url: string) => ipcRenderer.invoke("openExternalUrl", url),
|
openExternalUrl: (url: string) => ipcRenderer.invoke('openExternalUrl', url),
|
||||||
|
|
||||||
relaunch: () => ipcRenderer.invoke("relaunch"),
|
relaunch: () => ipcRenderer.invoke('relaunch'),
|
||||||
|
|
||||||
openAppDirectory: () => ipcRenderer.invoke("openAppDirectory"),
|
openAppDirectory: () => ipcRenderer.invoke('openAppDirectory'),
|
||||||
|
|
||||||
deleteFile: (filePath: string) => ipcRenderer.invoke("deleteFile", filePath),
|
deleteFile: (filePath: string) => ipcRenderer.invoke('deleteFile', filePath),
|
||||||
|
|
||||||
readFile: (path: string) => ipcRenderer.invoke("readFile", path),
|
isDirectory: (filePath: string) => ipcRenderer.invoke('isDirectory', filePath),
|
||||||
|
|
||||||
|
getUserSpace: () => ipcRenderer.invoke('getUserSpace'),
|
||||||
|
|
||||||
|
readFile: (path: string) => ipcRenderer.invoke('readFile', path),
|
||||||
|
|
||||||
writeFile: (path: string, data: string) =>
|
writeFile: (path: string, data: string) =>
|
||||||
ipcRenderer.invoke("writeFile", path, data),
|
ipcRenderer.invoke('writeFile', path, data),
|
||||||
|
|
||||||
listFiles: (path: string) => ipcRenderer.invoke("listFiles", path),
|
listFiles: (path: string) => ipcRenderer.invoke('listFiles', path),
|
||||||
|
|
||||||
mkdir: (path: string) => ipcRenderer.invoke("mkdir", path),
|
mkdir: (path: string) => ipcRenderer.invoke('mkdir', path),
|
||||||
|
|
||||||
rmdir: (path: string) => ipcRenderer.invoke("rmdir", path),
|
rmdir: (path: string) => ipcRenderer.invoke('rmdir', path),
|
||||||
|
|
||||||
installRemotePlugin: (pluginName: string) =>
|
installRemotePlugin: (pluginName: string) =>
|
||||||
ipcRenderer.invoke("installRemotePlugin", pluginName),
|
ipcRenderer.invoke('installRemotePlugin', pluginName),
|
||||||
|
|
||||||
downloadFile: (url: string, path: string) =>
|
downloadFile: (url: string, path: string) =>
|
||||||
ipcRenderer.invoke("downloadFile", url, path),
|
ipcRenderer.invoke('downloadFile', url, path),
|
||||||
|
|
||||||
pauseDownload: (fileName: string) =>
|
pauseDownload: (fileName: string) =>
|
||||||
ipcRenderer.invoke("pauseDownload", fileName),
|
ipcRenderer.invoke('pauseDownload', fileName),
|
||||||
|
|
||||||
resumeDownload: (fileName: string) =>
|
resumeDownload: (fileName: string) =>
|
||||||
ipcRenderer.invoke("resumeDownload", fileName),
|
ipcRenderer.invoke('resumeDownload', fileName),
|
||||||
|
|
||||||
abortDownload: (fileName: string) =>
|
abortDownload: (fileName: string) =>
|
||||||
ipcRenderer.invoke("abortDownload", fileName),
|
ipcRenderer.invoke('abortDownload', fileName),
|
||||||
|
|
||||||
onFileDownloadUpdate: (callback: any) =>
|
onFileDownloadUpdate: (callback: any) =>
|
||||||
ipcRenderer.on("FILE_DOWNLOAD_UPDATE", callback),
|
ipcRenderer.on('FILE_DOWNLOAD_UPDATE', callback),
|
||||||
|
|
||||||
onFileDownloadError: (callback: any) =>
|
onFileDownloadError: (callback: any) =>
|
||||||
ipcRenderer.on("FILE_DOWNLOAD_ERROR", callback),
|
ipcRenderer.on('FILE_DOWNLOAD_ERROR', callback),
|
||||||
|
|
||||||
onFileDownloadSuccess: (callback: any) =>
|
onFileDownloadSuccess: (callback: any) =>
|
||||||
ipcRenderer.on("FILE_DOWNLOAD_COMPLETE", callback),
|
ipcRenderer.on('FILE_DOWNLOAD_COMPLETE', callback),
|
||||||
|
|
||||||
onAppUpdateDownloadUpdate: (callback: any) =>
|
onAppUpdateDownloadUpdate: (callback: any) =>
|
||||||
ipcRenderer.on("APP_UPDATE_PROGRESS", callback),
|
ipcRenderer.on('APP_UPDATE_PROGRESS', callback),
|
||||||
|
|
||||||
onAppUpdateDownloadError: (callback: any) =>
|
onAppUpdateDownloadError: (callback: any) =>
|
||||||
ipcRenderer.on("APP_UPDATE_ERROR", callback),
|
ipcRenderer.on('APP_UPDATE_ERROR', callback),
|
||||||
|
|
||||||
onAppUpdateDownloadSuccess: (callback: any) =>
|
onAppUpdateDownloadSuccess: (callback: any) =>
|
||||||
ipcRenderer.on("APP_UPDATE_COMPLETE", callback),
|
ipcRenderer.on('APP_UPDATE_COMPLETE', callback),
|
||||||
});
|
})
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "file:../../core",
|
"@janhq/core": "file:../../core",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
"ts-loader": "^9.5.0"
|
"ts-loader": "^9.5.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import { PluginType, fs } from '@janhq/core'
|
import { PluginType, fs } from '@janhq/core'
|
||||||
import { ConversationalPlugin } from '@janhq/core/lib/plugins'
|
import { ConversationalPlugin } from '@janhq/core/lib/plugins'
|
||||||
import { Conversation } from '@janhq/core/lib/types'
|
import { Conversation } from '@janhq/core/lib/types'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSONConversationalPlugin is a ConversationalPlugin implementation that provides
|
* JSONConversationalPlugin is a ConversationalPlugin implementation that provides
|
||||||
* functionality for managing conversations.
|
* functionality for managing conversations.
|
||||||
*/
|
*/
|
||||||
export default class JSONConversationalPlugin implements ConversationalPlugin {
|
export default class JSONConversationalPlugin implements ConversationalPlugin {
|
||||||
|
private static readonly _homeDir = 'threads'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of the plugin.
|
* Returns the type of the plugin.
|
||||||
*/
|
*/
|
||||||
@ -18,7 +21,7 @@ export default class JSONConversationalPlugin implements ConversationalPlugin {
|
|||||||
* Called when the plugin is loaded.
|
* Called when the plugin is loaded.
|
||||||
*/
|
*/
|
||||||
onLoad() {
|
onLoad() {
|
||||||
fs.mkdir('conversations')
|
fs.mkdir(JSONConversationalPlugin._homeDir)
|
||||||
console.debug('JSONConversationalPlugin loaded')
|
console.debug('JSONConversationalPlugin loaded')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,10 +68,14 @@ export default class JSONConversationalPlugin implements ConversationalPlugin {
|
|||||||
*/
|
*/
|
||||||
saveConversation(conversation: Conversation): Promise<void> {
|
saveConversation(conversation: Conversation): Promise<void> {
|
||||||
return fs
|
return fs
|
||||||
.mkdir(`conversations/${conversation._id}`)
|
.mkdir(`${JSONConversationalPlugin._homeDir}/${conversation.id}`)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
fs.writeFile(
|
fs.writeFile(
|
||||||
`conversations/${conversation._id}/${conversation._id}.json`,
|
join(
|
||||||
|
JSONConversationalPlugin._homeDir,
|
||||||
|
conversation.id,
|
||||||
|
`${conversation.id}.json`
|
||||||
|
),
|
||||||
JSON.stringify(conversation)
|
JSON.stringify(conversation)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -79,7 +86,9 @@ export default class JSONConversationalPlugin implements ConversationalPlugin {
|
|||||||
* @param conversationId The ID of the conversation to delete.
|
* @param conversationId The ID of the conversation to delete.
|
||||||
*/
|
*/
|
||||||
deleteConversation(conversationId: string): Promise<void> {
|
deleteConversation(conversationId: string): Promise<void> {
|
||||||
return fs.rmdir(`conversations/${conversationId}`)
|
return fs.rmdir(
|
||||||
|
join(JSONConversationalPlugin._homeDir, `${conversationId}`)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,7 +97,9 @@ export default class JSONConversationalPlugin implements ConversationalPlugin {
|
|||||||
* @returns data of the conversation
|
* @returns data of the conversation
|
||||||
*/
|
*/
|
||||||
private async readConvo(convoId: string): Promise<any> {
|
private async readConvo(convoId: string): Promise<any> {
|
||||||
return fs.readFile(`conversations/${convoId}/${convoId}.json`)
|
return fs.readFile(
|
||||||
|
join(JSONConversationalPlugin._homeDir, convoId, `${convoId}.json`)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,7 +108,9 @@ export default class JSONConversationalPlugin implements ConversationalPlugin {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private async getConversationDocs(): Promise<string[]> {
|
private async getConversationDocs(): Promise<string[]> {
|
||||||
return fs.listFiles(`conversations`).then((files: string[]) => {
|
return fs
|
||||||
|
.listFiles(JSONConversationalPlugin._homeDir)
|
||||||
|
.then((files: string[]) => {
|
||||||
return Promise.all(files.filter((file) => file.startsWith('jan-')))
|
return Promise.all(files.filter((file) => file.startsWith('jan-')))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,9 @@ module.exports = {
|
|||||||
plugins: [new webpack.DefinePlugin({})],
|
plugins: [new webpack.DefinePlugin({})],
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js"],
|
extensions: [".ts", ".js"],
|
||||||
|
fallback: {
|
||||||
|
path: require.resolve('path-browserify'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Do not minify the output, otherwise it breaks the class registration
|
// Do not minify the output, otherwise it breaks the class registration
|
||||||
optimization: {
|
optimization: {
|
||||||
|
|||||||
@ -83,15 +83,15 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
|
|||||||
*/
|
*/
|
||||||
private parseConversationMarkdown(markdown: string): Conversation {
|
private parseConversationMarkdown(markdown: string): Conversation {
|
||||||
const conversation: Conversation = {
|
const conversation: Conversation = {
|
||||||
_id: "",
|
id: "",
|
||||||
name: "",
|
name: "",
|
||||||
messages: [],
|
messages: [],
|
||||||
};
|
};
|
||||||
var currentMessage: Message | undefined = undefined;
|
var currentMessage: Message | undefined = undefined;
|
||||||
for (const line of markdown.split("\n")) {
|
for (const line of markdown.split("\n")) {
|
||||||
const trimmedLine = line.trim();
|
const trimmedLine = line.trim();
|
||||||
if (trimmedLine.startsWith("- _id:")) {
|
if (trimmedLine.startsWith("- id:")) {
|
||||||
conversation._id = trimmedLine.replace("- _id:", "").trim();
|
conversation.id = trimmedLine.replace("- id:", "").trim();
|
||||||
} else if (trimmedLine.startsWith("- modelId:")) {
|
} else if (trimmedLine.startsWith("- modelId:")) {
|
||||||
conversation.modelId = trimmedLine.replace("- modelId:", "").trim();
|
conversation.modelId = trimmedLine.replace("- modelId:", "").trim();
|
||||||
} else if (trimmedLine.startsWith("- name:")) {
|
} else if (trimmedLine.startsWith("- name:")) {
|
||||||
@ -128,7 +128,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
|
|||||||
if (currentMessage) {
|
if (currentMessage) {
|
||||||
conversation.messages.push(currentMessage);
|
conversation.messages.push(currentMessage);
|
||||||
}
|
}
|
||||||
currentMessage = { _id: messageMatch[1] };
|
currentMessage = { id: messageMatch[1] };
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
currentMessage?.message &&
|
currentMessage?.message &&
|
||||||
@ -170,7 +170,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
|
|||||||
private generateMarkdown(conversation: Conversation): string {
|
private generateMarkdown(conversation: Conversation): string {
|
||||||
// Generate the Markdown content based on the Conversation object
|
// Generate the Markdown content based on the Conversation object
|
||||||
const conversationMetadata = `
|
const conversationMetadata = `
|
||||||
- _id: ${conversation._id}
|
- id: ${conversation.id}
|
||||||
- modelId: ${conversation.modelId}
|
- modelId: ${conversation.modelId}
|
||||||
- name: ${conversation.name}
|
- name: ${conversation.name}
|
||||||
- lastMessage: ${conversation.message}
|
- lastMessage: ${conversation.message}
|
||||||
@ -182,7 +182,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
|
|||||||
|
|
||||||
const messages = conversation.messages.map(
|
const messages = conversation.messages.map(
|
||||||
(message) => `
|
(message) => `
|
||||||
- Message ${message._id}:
|
- Message ${message.id}:
|
||||||
- createdAt: ${message.createdAt}
|
- createdAt: ${message.createdAt}
|
||||||
- user: ${message.user}
|
- user: ${message.user}
|
||||||
- message: ${message.message?.trim()}
|
- message: ${message.message?.trim()}
|
||||||
@ -204,10 +204,10 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
|
|||||||
private async writeMarkdownToFile(conversation: Conversation) {
|
private async writeMarkdownToFile(conversation: Conversation) {
|
||||||
// Generate the Markdown content
|
// Generate the Markdown content
|
||||||
const markdownContent = this.generateMarkdown(conversation);
|
const markdownContent = this.generateMarkdown(conversation);
|
||||||
await fs.mkdir(`conversations/${conversation._id}`);
|
await fs.mkdir(`conversations/${conversation.id}`);
|
||||||
// Write the content to a Markdown file
|
// Write the content to a Markdown file
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
`conversations/${conversation._id}/${conversation._id}.md`,
|
`conversations/${conversation.id}/${conversation.id}.md`,
|
||||||
markdownContent
|
markdownContent
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { InferencePlugin } from "@janhq/core/lib/plugins";
|
|||||||
import { requestInference } from "./helpers/sse";
|
import { requestInference } from "./helpers/sse";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { appDataPath } from "@janhq/core";
|
import { fs } from "@janhq/core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that implements the InferencePlugin interface from the @janhq/core package.
|
* A class that implements the InferencePlugin interface from the @janhq/core package.
|
||||||
@ -54,8 +54,10 @@ export default class JanInferencePlugin implements InferencePlugin {
|
|||||||
* @returns {Promise<void>} A promise that resolves when the model is initialized.
|
* @returns {Promise<void>} A promise that resolves when the model is initialized.
|
||||||
*/
|
*/
|
||||||
async initModel(modelFileName: string): Promise<void> {
|
async initModel(modelFileName: string): Promise<void> {
|
||||||
const appPath = await appDataPath();
|
const userSpacePath = await fs.getUserSpace();
|
||||||
return executeOnMain(MODULE, "initModel", join(appPath, modelFileName));
|
const modelFullPath = join(userSpacePath, modelFileName);
|
||||||
|
|
||||||
|
return executeOnMain(MODULE, "initModel", modelFullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,7 +86,7 @@ export default class JanInferencePlugin implements InferencePlugin {
|
|||||||
content: data.message,
|
content: data.message,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const recentMessages = await (data.history ?? prompts);
|
const recentMessages = data.history ?? prompts;
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
requestInference([
|
requestInference([
|
||||||
@ -121,7 +123,7 @@ export default class JanInferencePlugin implements InferencePlugin {
|
|||||||
message: "",
|
message: "",
|
||||||
user: "assistant",
|
user: "assistant",
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
_id: ulid(),
|
id: ulid(),
|
||||||
};
|
};
|
||||||
events.emit(EventName.OnNewMessageResponse, message);
|
events.emit(EventName.OnNewMessageResponse, message);
|
||||||
|
|
||||||
|
|||||||
@ -124,7 +124,7 @@ function killSubprocess(): Promise<void> {
|
|||||||
if (subprocess) {
|
if (subprocess) {
|
||||||
subprocess.kill();
|
subprocess.kill();
|
||||||
subprocess = null;
|
subprocess = null;
|
||||||
console.log("Subprocess terminated.");
|
console.debug("Subprocess terminated.");
|
||||||
} else {
|
} else {
|
||||||
return kill(PORT, "tcp").then(console.log).catch(console.log);
|
return kill(PORT, "tcp").then(console.log).catch(console.log);
|
||||||
}
|
}
|
||||||
|
|||||||
8
plugins/model-plugin/.prettierrc
Normal file
8
plugins/model-plugin/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "file:../../core",
|
"@janhq/core": "file:../../core",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
"ts-loader": "^9.5.0"
|
"ts-loader": "^9.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
import { EventName, events } from "@janhq/core";
|
|
||||||
|
|
||||||
export async function pollDownloadProgress(fileName: string) {
|
|
||||||
if (
|
|
||||||
typeof window !== "undefined" &&
|
|
||||||
typeof (window as any).electronAPI === "undefined"
|
|
||||||
) {
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
notifyProgress(fileName, intervalId);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function notifyProgress(
|
|
||||||
fileName: string,
|
|
||||||
intervalId: NodeJS.Timeout
|
|
||||||
): Promise<string> {
|
|
||||||
const response = await fetch("/api/v1/downloadProgress", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ fileName: fileName }),
|
|
||||||
headers: { "Content-Type": "application/json", Authorization: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
events.emit(EventName.OnDownloadError, null);
|
|
||||||
clearInterval(intervalId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const json = await response.json();
|
|
||||||
if (isEmptyObject(json)) {
|
|
||||||
if (!fileName && intervalId) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
}
|
|
||||||
return Promise.resolve("");
|
|
||||||
}
|
|
||||||
if (json.success === true) {
|
|
||||||
events.emit(EventName.OnDownloadSuccess, json);
|
|
||||||
clearInterval(intervalId);
|
|
||||||
return Promise.resolve("");
|
|
||||||
} else {
|
|
||||||
events.emit(EventName.OnDownloadUpdate, json);
|
|
||||||
return Promise.resolve(json.fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEmptyObject(ojb: any): boolean {
|
|
||||||
return Object.keys(ojb).length === 0;
|
|
||||||
}
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
export const parseToModel = (model) => {
|
export const parseToModel = (model) => {
|
||||||
const modelVersions = [];
|
const modelVersions = []
|
||||||
model.versions.forEach((v) => {
|
model.versions.forEach((v) => {
|
||||||
const version = {
|
const version = {
|
||||||
_id: `${model.author}-${v.name}`,
|
id: `${model.author}-${v.name}`,
|
||||||
name: v.name,
|
name: v.name,
|
||||||
quantMethod: v.quantMethod,
|
quantMethod: v.quantMethod,
|
||||||
bits: v.bits,
|
bits: v.bits,
|
||||||
@ -11,12 +11,12 @@ export const parseToModel = (model) => {
|
|||||||
usecase: v.usecase,
|
usecase: v.usecase,
|
||||||
downloadLink: v.downloadLink,
|
downloadLink: v.downloadLink,
|
||||||
productId: model.id,
|
productId: model.id,
|
||||||
};
|
}
|
||||||
modelVersions.push(version);
|
modelVersions.push(version)
|
||||||
});
|
})
|
||||||
|
|
||||||
const product = {
|
const product = {
|
||||||
_id: model.id,
|
id: model.id,
|
||||||
name: model.name,
|
name: model.name,
|
||||||
shortDescription: model.shortDescription,
|
shortDescription: model.shortDescription,
|
||||||
avatarUrl: model.avatarUrl,
|
avatarUrl: model.avatarUrl,
|
||||||
@ -29,9 +29,9 @@ export const parseToModel = (model) => {
|
|||||||
type: model.type,
|
type: model.type,
|
||||||
createdAt: model.createdAt,
|
createdAt: model.createdAt,
|
||||||
longDescription: model.longDescription,
|
longDescription: model.longDescription,
|
||||||
status: "Downloadable",
|
status: 'Downloadable',
|
||||||
releaseDate: 0,
|
releaseDate: 0,
|
||||||
availableVersions: modelVersions,
|
availableVersions: modelVersions,
|
||||||
};
|
}
|
||||||
return product;
|
return product
|
||||||
};
|
}
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
import { PluginType, fs, downloadFile } from "@janhq/core";
|
import { PluginType, fs, downloadFile } from '@janhq/core'
|
||||||
import { ModelPlugin } from "@janhq/core/lib/plugins";
|
import { ModelPlugin } from '@janhq/core/lib/plugins'
|
||||||
import { Model, ModelCatalog } from "@janhq/core/lib/types";
|
import { Model, ModelCatalog } from '@janhq/core/lib/types'
|
||||||
import { pollDownloadProgress } from "./helpers/cloudNative";
|
import { parseToModel } from './helpers/modelParser'
|
||||||
import { parseToModel } from "./helpers/modelParser";
|
import { join } from 'path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A plugin for managing machine learning models.
|
* A plugin for managing machine learning models.
|
||||||
*/
|
*/
|
||||||
export default class JanModelPlugin implements ModelPlugin {
|
export default class JanModelPlugin implements ModelPlugin {
|
||||||
|
private static readonly _homeDir = 'models'
|
||||||
/**
|
/**
|
||||||
* Implements type from JanPlugin.
|
* Implements type from JanPlugin.
|
||||||
* @override
|
* @override
|
||||||
* @returns The type of the plugin.
|
* @returns The type of the plugin.
|
||||||
*/
|
*/
|
||||||
type(): PluginType {
|
type(): PluginType {
|
||||||
return PluginType.Model;
|
return PluginType.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,6 +26,7 @@ export default class JanModelPlugin implements ModelPlugin {
|
|||||||
/** Cloud Native
|
/** Cloud Native
|
||||||
* TODO: Fetch all downloading progresses?
|
* TODO: Fetch all downloading progresses?
|
||||||
**/
|
**/
|
||||||
|
fs.mkdir(JanModelPlugin._homeDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,12 +41,13 @@ export default class JanModelPlugin implements ModelPlugin {
|
|||||||
* @returns A Promise that resolves when the model is downloaded.
|
* @returns A Promise that resolves when the model is downloaded.
|
||||||
*/
|
*/
|
||||||
async downloadModel(model: Model): Promise<void> {
|
async downloadModel(model: Model): Promise<void> {
|
||||||
await fs.mkdir("models");
|
// create corresponding directory
|
||||||
downloadFile(model.downloadLink, `models/${model._id}`);
|
const directoryPath = join(JanModelPlugin._homeDir, model.productName)
|
||||||
/** Cloud Native
|
await fs.mkdir(directoryPath)
|
||||||
* MARK: Poll Downloading Progress
|
|
||||||
**/
|
// path to model binary
|
||||||
pollDownloadProgress(model._id);
|
const path = join(directoryPath, model.id)
|
||||||
|
downloadFile(model.downloadLink, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,10 +55,15 @@ export default class JanModelPlugin implements ModelPlugin {
|
|||||||
* @param filePath - The path to the model file to delete.
|
* @param filePath - The path to the model file to delete.
|
||||||
* @returns A Promise that resolves when the model is deleted.
|
* @returns A Promise that resolves when the model is deleted.
|
||||||
*/
|
*/
|
||||||
deleteModel(filePath: string): Promise<void> {
|
async deleteModel(filePath: string): Promise<void> {
|
||||||
return fs
|
try {
|
||||||
.deleteFile(`models/${filePath}`)
|
await Promise.allSettled([
|
||||||
.then(() => fs.deleteFile(`models/m-${filePath}.json`));
|
fs.deleteFile(filePath),
|
||||||
|
fs.deleteFile(`${filePath}.json`),
|
||||||
|
])
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,30 +72,46 @@ export default class JanModelPlugin implements ModelPlugin {
|
|||||||
* @returns A Promise that resolves when the model is saved.
|
* @returns A Promise that resolves when the model is saved.
|
||||||
*/
|
*/
|
||||||
async saveModel(model: Model): Promise<void> {
|
async saveModel(model: Model): Promise<void> {
|
||||||
await fs.writeFile(`models/m-${model._id}.json`, JSON.stringify(model));
|
const directoryPath = join(JanModelPlugin._homeDir, model.productName)
|
||||||
|
const jsonFilePath = join(directoryPath, `${model.id}.json`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.writeFile(jsonFilePath, JSON.stringify(model))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all downloaded models.
|
* Gets all downloaded models.
|
||||||
* @returns A Promise that resolves with an array of all models.
|
* @returns A Promise that resolves with an array of all models.
|
||||||
*/
|
*/
|
||||||
getDownloadedModels(): Promise<Model[]> {
|
async getDownloadedModels(): Promise<Model[]> {
|
||||||
return fs
|
const results: Model[] = []
|
||||||
.listFiles("models")
|
const allDirs: string[] = await fs.listFiles(JanModelPlugin._homeDir)
|
||||||
.then((files: string[]) => {
|
for (const dir of allDirs) {
|
||||||
return Promise.all(
|
const modelDirPath = join(JanModelPlugin._homeDir, dir)
|
||||||
files
|
const isModelDir = await fs.isDirectory(modelDirPath)
|
||||||
.filter((file) => /^m-.*\.json$/.test(file))
|
if (!isModelDir) {
|
||||||
.map(async (file) => {
|
// if not a directory, ignore
|
||||||
const model: Model = JSON.parse(
|
continue
|
||||||
await fs.readFile(`models/${file}`)
|
|
||||||
);
|
|
||||||
return model;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch((e) => fs.mkdir("models").then(() => []));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const jsonFiles: string[] = (await fs.listFiles(modelDirPath)).filter(
|
||||||
|
(file: string) => file.endsWith('.json')
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const json of jsonFiles) {
|
||||||
|
const model: Model = JSON.parse(
|
||||||
|
await fs.readFile(join(modelDirPath, json))
|
||||||
|
)
|
||||||
|
results.push(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all available models.
|
* Gets all available models.
|
||||||
* @returns A Promise that resolves with an array of all models.
|
* @returns A Promise that resolves with an array of all models.
|
||||||
@ -96,10 +120,6 @@ export default class JanModelPlugin implements ModelPlugin {
|
|||||||
// Add a timestamp to the URL to prevent caching
|
// Add a timestamp to the URL to prevent caching
|
||||||
return import(
|
return import(
|
||||||
/* webpackIgnore: true */ MODEL_CATALOG_URL + `?t=${Date.now()}`
|
/* webpackIgnore: true */ MODEL_CATALOG_URL + `?t=${Date.now()}`
|
||||||
).then((module) =>
|
).then((module) => module.default.map((e) => parseToModel(e)))
|
||||||
module.default.map((e) => {
|
|
||||||
return parseToModel(e);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
const path = require("path");
|
const path = require('path')
|
||||||
const webpack = require("webpack");
|
const webpack = require('webpack')
|
||||||
const packageJson = require("./package.json");
|
const packageJson = require('./package.json')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
entry: "./src/index.ts", // Adjust the entry point to match your project's main file
|
entry: './src/index.ts', // Adjust the entry point to match your project's main file
|
||||||
mode: "production",
|
mode: 'production',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
use: "ts-loader",
|
use: 'ts-loader',
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -20,20 +20,23 @@ module.exports = {
|
|||||||
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
||||||
MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
||||||
MODEL_CATALOG_URL: JSON.stringify(
|
MODEL_CATALOG_URL: JSON.stringify(
|
||||||
"https://cdn.jsdelivr.net/npm/@janhq/models@latest/dist/index.js"
|
'https://cdn.jsdelivr.net/npm/@janhq/models@latest/dist/index.js'
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
output: {
|
output: {
|
||||||
filename: "index.js", // Adjust the output file name as needed
|
filename: 'index.js', // Adjust the output file name as needed
|
||||||
path: path.resolve(__dirname, "dist"),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
library: { type: "module" }, // Specify ESM output format
|
library: { type: 'module' }, // Specify ESM output format
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js"],
|
extensions: ['.ts', '.js'],
|
||||||
|
fallback: {
|
||||||
|
path: require.resolve('path-browserify'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
minimize: false,
|
minimize: false,
|
||||||
},
|
},
|
||||||
// Add loaders and other configuration as needed for your project
|
// Add loaders and other configuration as needed for your project
|
||||||
};
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const BottomBar = () => {
|
|||||||
<SystemItem
|
<SystemItem
|
||||||
name="Active model:"
|
name="Active model:"
|
||||||
value={
|
value={
|
||||||
activeModel?._id || (
|
activeModel?.id || (
|
||||||
<Badge themes="secondary">⌘e to show your model</Badge>
|
<Badge themes="secondary">⌘e to show your model</Badge>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export default function CommandListDownloadedModel() {
|
|||||||
const { activeModel, startModel, stopModel } = useActiveModel()
|
const { activeModel, startModel, stopModel } = useActiveModel()
|
||||||
|
|
||||||
const onModelActionClick = (modelId: string) => {
|
const onModelActionClick = (modelId: string) => {
|
||||||
if (activeModel && activeModel._id === modelId) {
|
if (activeModel && activeModel.id === modelId) {
|
||||||
stopModel(modelId)
|
stopModel(modelId)
|
||||||
} else {
|
} else {
|
||||||
startModel(modelId)
|
startModel(modelId)
|
||||||
@ -62,7 +62,7 @@ export default function CommandListDownloadedModel() {
|
|||||||
<CommandItem
|
<CommandItem
|
||||||
key={i}
|
key={i}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
onModelActionClick(model._id)
|
onModelActionClick(model.id)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -72,7 +72,7 @@ export default function CommandListDownloadedModel() {
|
|||||||
/>
|
/>
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<span>{model.name}</span>
|
<span>{model.name}</span>
|
||||||
{activeModel && activeModel._id === model._id && (
|
{activeModel && activeModel.id === model.id && (
|
||||||
<Badge themes="secondary">Active</Badge>
|
<Badge themes="secondary">Active</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -32,9 +32,9 @@ export default function ModalCancelDownload({
|
|||||||
const { modelDownloadStateAtom } = useDownloadState()
|
const { modelDownloadStateAtom } = useDownloadState()
|
||||||
useGetPerformanceTag()
|
useGetPerformanceTag()
|
||||||
const downloadAtom = useMemo(
|
const downloadAtom = useMemo(
|
||||||
() => atom((get) => get(modelDownloadStateAtom)[suitableModel._id]),
|
() => atom((get) => get(modelDownloadStateAtom)[suitableModel.id]),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[suitableModel._id]
|
[suitableModel.id]
|
||||||
)
|
)
|
||||||
const downloadState = useAtomValue(downloadAtom)
|
const downloadState = useAtomValue(downloadAtom)
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,22 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { ReactNode, useEffect, useRef } from 'react'
|
import { ReactNode, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import { events, EventName, NewMessageResponse, PluginType } from '@janhq/core'
|
import {
|
||||||
|
events,
|
||||||
|
EventName,
|
||||||
|
NewMessageResponse,
|
||||||
|
PluginType,
|
||||||
|
ChatMessage,
|
||||||
|
} from '@janhq/core'
|
||||||
|
import { Conversation, Message, MessageStatus } from '@janhq/core'
|
||||||
import { ConversationalPlugin, ModelPlugin } from '@janhq/core/lib/plugins'
|
import { ConversationalPlugin, ModelPlugin } from '@janhq/core/lib/plugins'
|
||||||
import { Message } from '@janhq/core/lib/types'
|
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { useDownloadState } from '@/hooks/useDownloadState'
|
import { useDownloadState } from '@/hooks/useDownloadState'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||||
|
|
||||||
|
import { toChatMessage } from '@/utils/message'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addNewMessageAtom,
|
addNewMessageAtom,
|
||||||
chatMessages,
|
chatMessages,
|
||||||
@ -20,11 +27,8 @@ import {
|
|||||||
updateConversationWaitingForResponseAtom,
|
updateConversationWaitingForResponseAtom,
|
||||||
userConversationsAtom,
|
userConversationsAtom,
|
||||||
} from '@/helpers/atoms/Conversation.atom'
|
} from '@/helpers/atoms/Conversation.atom'
|
||||||
|
|
||||||
import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom'
|
import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
import { MessageStatus, toChatMessage } from '@/models/ChatMessage'
|
|
||||||
import { pluginManager } from '@/plugin'
|
import { pluginManager } from '@/plugin'
|
||||||
import { ChatMessage, Conversation } from '@/types/chatMessage'
|
|
||||||
|
|
||||||
let currentConversation: Conversation | undefined = undefined
|
let currentConversation: Conversation | undefined = undefined
|
||||||
|
|
||||||
@ -50,9 +54,7 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
async function handleNewMessageResponse(message: NewMessageResponse) {
|
async function handleNewMessageResponse(message: NewMessageResponse) {
|
||||||
if (message.conversationId) {
|
if (message.conversationId) {
|
||||||
const convo = convoRef.current.find(
|
const convo = convoRef.current.find((e) => e.id == message.conversationId)
|
||||||
(e) => e._id == message.conversationId
|
|
||||||
)
|
|
||||||
if (!convo) return
|
if (!convo) return
|
||||||
const newResponse = toChatMessage(message)
|
const newResponse = toChatMessage(message)
|
||||||
addNewMessage(newResponse)
|
addNewMessage(newResponse)
|
||||||
@ -63,11 +65,11 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
messageResponse.conversationId &&
|
messageResponse.conversationId &&
|
||||||
messageResponse._id &&
|
messageResponse.id &&
|
||||||
messageResponse.message
|
messageResponse.message
|
||||||
) {
|
) {
|
||||||
updateMessage(
|
updateMessage(
|
||||||
messageResponse._id,
|
messageResponse.id,
|
||||||
messageResponse.conversationId,
|
messageResponse.conversationId,
|
||||||
messageResponse.message,
|
messageResponse.message,
|
||||||
MessageStatus.Pending
|
MessageStatus.Pending
|
||||||
@ -77,11 +79,11 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
if (messageResponse.conversationId) {
|
if (messageResponse.conversationId) {
|
||||||
if (
|
if (
|
||||||
!currentConversation ||
|
!currentConversation ||
|
||||||
currentConversation._id !== messageResponse.conversationId
|
currentConversation.id !== messageResponse.conversationId
|
||||||
) {
|
) {
|
||||||
if (convoRef.current && messageResponse.conversationId)
|
if (convoRef.current && messageResponse.conversationId)
|
||||||
currentConversation = convoRef.current.find(
|
currentConversation = convoRef.current.find(
|
||||||
(e) => e._id == messageResponse.conversationId
|
(e) => e.id == messageResponse.conversationId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,11 +106,11 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
messageResponse.conversationId &&
|
messageResponse.conversationId &&
|
||||||
messageResponse._id &&
|
messageResponse.id &&
|
||||||
messageResponse.message
|
messageResponse.message
|
||||||
) {
|
) {
|
||||||
updateMessage(
|
updateMessage(
|
||||||
messageResponse._id,
|
messageResponse.id,
|
||||||
messageResponse.conversationId,
|
messageResponse.conversationId,
|
||||||
messageResponse.message,
|
messageResponse.message,
|
||||||
MessageStatus.Ready
|
MessageStatus.Ready
|
||||||
@ -116,27 +118,23 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const convo = convoRef.current.find(
|
const convo = convoRef.current.find(
|
||||||
(e) => e._id == messageResponse.conversationId
|
(e) => e.id == messageResponse.conversationId
|
||||||
)
|
)
|
||||||
if (convo) {
|
if (convo) {
|
||||||
const messagesData = (messagesRef.current ?? [])[convo._id].map<Message>(
|
const messagesData = (messagesRef.current ?? [])[convo.id].map<Message>(
|
||||||
(e: ChatMessage) => {
|
(e: ChatMessage) => ({
|
||||||
return {
|
id: e.id,
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
_id: e.id,
|
|
||||||
message: e.text,
|
message: e.text,
|
||||||
user: e.senderUid,
|
user: e.senderUid,
|
||||||
updatedAt: new Date(e.createdAt).toISOString(),
|
updatedAt: new Date(e.createdAt).toISOString(),
|
||||||
createdAt: new Date(e.createdAt).toISOString(),
|
createdAt: new Date(e.createdAt).toISOString(),
|
||||||
}
|
})
|
||||||
}
|
|
||||||
)
|
)
|
||||||
pluginManager
|
pluginManager
|
||||||
.get<ConversationalPlugin>(PluginType.Conversational)
|
.get<ConversationalPlugin>(PluginType.Conversational)
|
||||||
?.saveConversation({
|
?.saveConversation({
|
||||||
...convo,
|
...convo,
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
id: convo.id ?? '',
|
||||||
_id: convo._id ?? '',
|
|
||||||
name: convo.name ?? '',
|
name: convo.name ?? '',
|
||||||
message: convo.lastMessage ?? '',
|
message: convo.lastMessage ?? '',
|
||||||
messages: messagesData,
|
messages: messagesData,
|
||||||
@ -153,7 +151,7 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
if (state && state.fileName && state.success === true) {
|
if (state && state.fileName && state.success === true) {
|
||||||
state.fileName = state.fileName.replace('models/', '')
|
state.fileName = state.fileName.replace('models/', '')
|
||||||
setDownloadStateSuccess(state.fileName)
|
setDownloadStateSuccess(state.fileName)
|
||||||
const model = models.find((e) => e._id === state.fileName)
|
const model = models.find((e) => e.id === state.fileName)
|
||||||
if (model)
|
if (model)
|
||||||
pluginManager
|
pluginManager
|
||||||
.get<ModelPlugin>(PluginType.Model)
|
.get<ModelPlugin>(PluginType.Model)
|
||||||
|
|||||||
@ -33,14 +33,17 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
window.electronAPI.onFileDownloadUpdate(
|
window.electronAPI.onFileDownloadUpdate(
|
||||||
(_event: string, state: DownloadState | undefined) => {
|
(_event: string, state: DownloadState | undefined) => {
|
||||||
if (!state) return
|
if (!state) return
|
||||||
setDownloadState(state)
|
setDownloadState({
|
||||||
|
...state,
|
||||||
|
fileName: state.fileName.split('/').pop() ?? '',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
window.electronAPI.onFileDownloadError(
|
window.electronAPI.onFileDownloadError(
|
||||||
(_event: string, callback: any) => {
|
(_event: string, callback: any) => {
|
||||||
console.log('Download error', callback)
|
console.error('Download error', callback)
|
||||||
const fileName = callback.fileName.replace('models/', '')
|
const fileName = callback.fileName.split('/').pop() ?? ''
|
||||||
setDownloadStateFailed(fileName)
|
setDownloadStateFailed(fileName)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -48,10 +51,10 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
window.electronAPI.onFileDownloadSuccess(
|
window.electronAPI.onFileDownloadSuccess(
|
||||||
(_event: string, callback: any) => {
|
(_event: string, callback: any) => {
|
||||||
if (callback && callback.fileName) {
|
if (callback && callback.fileName) {
|
||||||
const fileName = callback.fileName.replace('models/', '')
|
const fileName = callback.fileName.split('/').pop() ?? ''
|
||||||
setDownloadStateSuccess(fileName)
|
setDownloadStateSuccess(fileName)
|
||||||
|
|
||||||
const model = modelsRef.current.find((e) => e._id === fileName)
|
const model = modelsRef.current.find((e) => e.id === fileName)
|
||||||
if (model)
|
if (model)
|
||||||
pluginManager
|
pluginManager
|
||||||
.get<ModelPlugin>(PluginType.Model)
|
.get<ModelPlugin>(PluginType.Model)
|
||||||
@ -66,13 +69,13 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
window.electronAPI.onAppUpdateDownloadUpdate(
|
window.electronAPI.onAppUpdateDownloadUpdate(
|
||||||
(_event: string, progress: any) => {
|
(_event: string, progress: any) => {
|
||||||
setProgress(progress.percent)
|
setProgress(progress.percent)
|
||||||
console.log('app update progress:', progress.percent)
|
console.debug('app update progress:', progress.percent)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
window.electronAPI.onAppUpdateDownloadError(
|
window.electronAPI.onAppUpdateDownloadError(
|
||||||
(_event: string, callback: any) => {
|
(_event: string, callback: any) => {
|
||||||
console.log('Download error', callback)
|
console.error('Download error', callback)
|
||||||
setProgress(-1)
|
setProgress(-1)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
|
import { ChatMessage, MessageStatus } from '@janhq/core'
|
||||||
import { atom } from 'jotai'
|
import { atom } from 'jotai'
|
||||||
|
|
||||||
import { getActiveConvoIdAtom } from './Conversation.atom'
|
import { getActiveConvoIdAtom } from './Conversation.atom'
|
||||||
|
|
||||||
import { ChatMessage, MessageStatus } from '@/models/ChatMessage'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores all chat messages for all conversations
|
* Stores all chat messages for all conversations
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import { Conversation, ConversationState } from '@/types/chatMessage'
|
import { Conversation, ConversationState } from '@janhq/core'
|
||||||
import { atom } from 'jotai'
|
import { atom } from 'jotai'
|
||||||
|
|
||||||
// import { MainViewState, setMainViewStateAtom } from './MainView.atom'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the current active conversation id.
|
* Stores the current active conversation id.
|
||||||
*/
|
*/
|
||||||
@ -78,13 +76,13 @@ export const updateConversationHasMoreAtom = atom(
|
|||||||
export const updateConversationAtom = atom(
|
export const updateConversationAtom = atom(
|
||||||
null,
|
null,
|
||||||
(get, set, conversation: Conversation) => {
|
(get, set, conversation: Conversation) => {
|
||||||
const id = conversation._id
|
const id = conversation.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
const convo = get(userConversationsAtom).find((c) => c._id === id)
|
const convo = get(userConversationsAtom).find((c) => c.id === id)
|
||||||
if (!convo) return
|
if (!convo) return
|
||||||
|
|
||||||
const newConversations: Conversation[] = get(userConversationsAtom).map(
|
const newConversations: Conversation[] = get(userConversationsAtom).map(
|
||||||
(c) => (c._id === id ? conversation : c)
|
(c) => (c.id === id ? conversation : c)
|
||||||
)
|
)
|
||||||
|
|
||||||
// sort new conversations based on updated at
|
// sort new conversations based on updated at
|
||||||
@ -103,5 +101,5 @@ export const updateConversationAtom = atom(
|
|||||||
*/
|
*/
|
||||||
export const userConversationsAtom = atom<Conversation[]>([])
|
export const userConversationsAtom = atom<Conversation[]>([])
|
||||||
export const currentConversationAtom = atom<Conversation | undefined>((get) =>
|
export const currentConversationAtom = atom<Conversation | undefined>((get) =>
|
||||||
get(userConversationsAtom).find((c) => c._id === get(getActiveConvoIdAtom))
|
get(userConversationsAtom).find((c) => c.id === get(getActiveConvoIdAtom))
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { toaster } from '@/containers/Toast'
|
|||||||
import { useGetDownloadedModels } from './useGetDownloadedModels'
|
import { useGetDownloadedModels } from './useGetDownloadedModels'
|
||||||
|
|
||||||
import { pluginManager } from '@/plugin'
|
import { pluginManager } from '@/plugin'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
const activeAssistantModelAtom = atom<Model | undefined>(undefined)
|
const activeAssistantModelAtom = atom<Model | undefined>(undefined)
|
||||||
|
|
||||||
@ -21,16 +22,16 @@ export function useActiveModel() {
|
|||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
|
|
||||||
const startModel = async (modelId: string) => {
|
const startModel = async (modelId: string) => {
|
||||||
if (activeModel && activeModel._id === modelId) {
|
if (activeModel && activeModel.id === modelId) {
|
||||||
console.debug(`Model ${modelId} is already init. Ignore..`)
|
console.debug(`Model ${modelId} is already init. Ignore..`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateModel({ state: 'start', loading: true, model: modelId })
|
setStateModel({ state: 'start', loading: true, model: modelId })
|
||||||
|
|
||||||
const model = await downloadedModels.find((e) => e._id === modelId)
|
const model = downloadedModels.find((e) => e.id === modelId)
|
||||||
|
|
||||||
if (!modelId) {
|
if (!model) {
|
||||||
alert(`Model ${modelId} not found! Please re-download the model first.`)
|
alert(`Model ${modelId} not found! Please re-download the model first.`)
|
||||||
setStateModel(() => ({
|
setStateModel(() => ({
|
||||||
state: 'start',
|
state: 'start',
|
||||||
@ -42,8 +43,8 @@ export function useActiveModel() {
|
|||||||
|
|
||||||
const currentTime = Date.now()
|
const currentTime = Date.now()
|
||||||
console.debug('Init model: ', modelId)
|
console.debug('Init model: ', modelId)
|
||||||
|
const path = join('models', model.productName, modelId)
|
||||||
const res = await initModel(`models/${modelId}`)
|
const res = await initModel(path)
|
||||||
if (res?.error) {
|
if (res?.error) {
|
||||||
const errorMessage = `${res.error}`
|
const errorMessage = `${res.error}`
|
||||||
alert(errorMessage)
|
alert(errorMessage)
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { PluginType } from '@janhq/core'
|
import { PluginType } from '@janhq/core'
|
||||||
|
import { Conversation, Model } from '@janhq/core'
|
||||||
import { ConversationalPlugin } from '@janhq/core/lib/plugins'
|
import { ConversationalPlugin } from '@janhq/core/lib/plugins'
|
||||||
import { Model } from '@janhq/core/lib/types'
|
|
||||||
|
|
||||||
import { useAtom, useSetAtom } from 'jotai'
|
import { useAtom, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { generateConversationId } from '@/utils/conversation'
|
import { generateConversationId } from '@/utils/conversation'
|
||||||
@ -12,7 +11,6 @@ import {
|
|||||||
addNewConversationStateAtom,
|
addNewConversationStateAtom,
|
||||||
} from '@/helpers/atoms/Conversation.atom'
|
} from '@/helpers/atoms/Conversation.atom'
|
||||||
import { pluginManager } from '@/plugin'
|
import { pluginManager } from '@/plugin'
|
||||||
import { Conversation } from '@/types/chatMessage'
|
|
||||||
|
|
||||||
export const useCreateConversation = () => {
|
export const useCreateConversation = () => {
|
||||||
const [userConversations, setUserConversations] = useAtom(
|
const [userConversations, setUserConversations] = useAtom(
|
||||||
@ -24,15 +22,15 @@ export const useCreateConversation = () => {
|
|||||||
const requestCreateConvo = async (model: Model) => {
|
const requestCreateConvo = async (model: Model) => {
|
||||||
const conversationName = model.name
|
const conversationName = model.name
|
||||||
const mappedConvo: Conversation = {
|
const mappedConvo: Conversation = {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
id: generateConversationId(),
|
||||||
_id: generateConversationId(),
|
modelId: model.id,
|
||||||
modelId: model._id,
|
|
||||||
name: conversationName,
|
name: conversationName,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
|
messages: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewConvoState(mappedConvo._id, {
|
addNewConvoState(mappedConvo.id, {
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
waitingForResponse: false,
|
waitingForResponse: false,
|
||||||
})
|
})
|
||||||
@ -45,7 +43,7 @@ export const useCreateConversation = () => {
|
|||||||
messages: [],
|
messages: [],
|
||||||
})
|
})
|
||||||
setUserConversations([mappedConvo, ...userConversations])
|
setUserConversations([mappedConvo, ...userConversations])
|
||||||
setActiveConvoId(mappedConvo._id)
|
setActiveConvoId(mappedConvo.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export default function useDeleteConversation() {
|
|||||||
.get<ConversationalPlugin>(PluginType.Conversational)
|
.get<ConversationalPlugin>(PluginType.Conversational)
|
||||||
?.deleteConversation(activeConvoId)
|
?.deleteConversation(activeConvoId)
|
||||||
const currentConversations = userConversations.filter(
|
const currentConversations = userConversations.filter(
|
||||||
(c) => c._id !== activeConvoId
|
(c) => c.id !== activeConvoId
|
||||||
)
|
)
|
||||||
setUserConversations(currentConversations)
|
setUserConversations(currentConversations)
|
||||||
deleteMessages(activeConvoId)
|
deleteMessages(activeConvoId)
|
||||||
@ -50,7 +50,7 @@ export default function useDeleteConversation() {
|
|||||||
description: `Delete chat with ${activeModel?.name} has been completed`,
|
description: `Delete chat with ${activeModel?.name} has been completed`,
|
||||||
})
|
})
|
||||||
if (currentConversations.length > 0) {
|
if (currentConversations.length > 0) {
|
||||||
setActiveConvoId(currentConversations[0]._id)
|
setActiveConvoId(currentConversations[0].id)
|
||||||
} else {
|
} else {
|
||||||
setActiveConvoId(undefined)
|
setActiveConvoId(undefined)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,20 +7,20 @@ import { toaster } from '@/containers/Toast'
|
|||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||||
|
|
||||||
import { pluginManager } from '@/plugin/PluginManager'
|
import { pluginManager } from '@/plugin/PluginManager'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
export default function useDeleteModel() {
|
export default function useDeleteModel() {
|
||||||
const { setDownloadedModels, downloadedModels } = useGetDownloadedModels()
|
const { setDownloadedModels, downloadedModels } = useGetDownloadedModels()
|
||||||
|
|
||||||
const deleteModel = async (model: Model) => {
|
const deleteModel = async (model: Model) => {
|
||||||
await pluginManager
|
const path = join('models', model.productName, model.id)
|
||||||
.get<ModelPlugin>(PluginType.Model)
|
await pluginManager.get<ModelPlugin>(PluginType.Model)?.deleteModel(path)
|
||||||
?.deleteModel(model._id)
|
|
||||||
|
|
||||||
// reload models
|
// reload models
|
||||||
setDownloadedModels(downloadedModels.filter((e) => e._id !== model._id))
|
setDownloadedModels(downloadedModels.filter((e) => e.id !== model.id))
|
||||||
toaster({
|
toaster({
|
||||||
title: 'Delete a Model',
|
title: 'Delete a Model',
|
||||||
description: `Model ${model._id} has been deleted.`,
|
description: `Model ${model.id} has been deleted.`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export default function useDownloadModel() {
|
|||||||
): Model => {
|
): Model => {
|
||||||
return {
|
return {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_id: modelVersion._id,
|
id: modelVersion.id,
|
||||||
name: modelVersion.name,
|
name: modelVersion.name,
|
||||||
quantMethod: modelVersion.quantMethod,
|
quantMethod: modelVersion.quantMethod,
|
||||||
bits: modelVersion.bits,
|
bits: modelVersion.bits,
|
||||||
@ -31,7 +31,7 @@ export default function useDownloadModel() {
|
|||||||
downloadLink: modelVersion.downloadLink,
|
downloadLink: modelVersion.downloadLink,
|
||||||
startDownloadAt: modelVersion.startDownloadAt,
|
startDownloadAt: modelVersion.startDownloadAt,
|
||||||
finishDownloadAt: modelVersion.finishDownloadAt,
|
finishDownloadAt: modelVersion.finishDownloadAt,
|
||||||
productId: model._id,
|
productId: model.id,
|
||||||
productName: model.name,
|
productName: model.name,
|
||||||
shortDescription: model.shortDescription,
|
shortDescription: model.shortDescription,
|
||||||
longDescription: model.longDescription,
|
longDescription: model.longDescription,
|
||||||
@ -53,7 +53,7 @@ export default function useDownloadModel() {
|
|||||||
) => {
|
) => {
|
||||||
// set an initial download state
|
// set an initial download state
|
||||||
setDownloadState({
|
setDownloadState({
|
||||||
modelId: modelVersion._id,
|
modelId: modelVersion.id,
|
||||||
time: {
|
time: {
|
||||||
elapsed: 0,
|
elapsed: 0,
|
||||||
remaining: 0,
|
remaining: 0,
|
||||||
@ -64,7 +64,7 @@ export default function useDownloadModel() {
|
|||||||
total: 0,
|
total: 0,
|
||||||
transferred: 0,
|
transferred: 0,
|
||||||
},
|
},
|
||||||
fileName: modelVersion._id,
|
fileName: modelVersion.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
modelVersion.startDownloadAt = Date.now()
|
modelVersion.startDownloadAt = Date.now()
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Model, Conversation } from '@janhq/core'
|
||||||
import { Model } from '@janhq/core/lib/types'
|
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import { useActiveModel } from './useActiveModel'
|
import { useActiveModel } from './useActiveModel'
|
||||||
import { useGetDownloadedModels } from './useGetDownloadedModels'
|
import { useGetDownloadedModels } from './useGetDownloadedModels'
|
||||||
|
|
||||||
import { currentConversationAtom } from '@/helpers/atoms/Conversation.atom'
|
import { currentConversationAtom } from '@/helpers/atoms/Conversation.atom'
|
||||||
import { Conversation } from '@/types/chatMessage'
|
|
||||||
|
|
||||||
export default function useGetInputState() {
|
export default function useGetInputState() {
|
||||||
const [inputState, setInputState] = useState<InputType>('loading')
|
const [inputState, setInputState] = useState<InputType>('loading')
|
||||||
@ -27,7 +23,7 @@ export default function useGetInputState() {
|
|||||||
|
|
||||||
// check if convo model id is in downloaded models
|
// check if convo model id is in downloaded models
|
||||||
const isModelAvailable = downloadedModels.some(
|
const isModelAvailable = downloadedModels.some(
|
||||||
(model) => model._id === convo.modelId
|
(model) => model.id === convo.modelId
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isModelAvailable) {
|
if (!isModelAvailable) {
|
||||||
@ -36,7 +32,7 @@ export default function useGetInputState() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convo.modelId !== currentModel._id) {
|
if (convo.modelId !== currentModel.id) {
|
||||||
// in case convo model and active model is different,
|
// in case convo model and active model is different,
|
||||||
// ask user to init the required model
|
// ask user to init the required model
|
||||||
setInputState('model-mismatch')
|
setInputState('model-mismatch')
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import { PluginType } from '@janhq/core'
|
import { PluginType, ChatMessage, ConversationState } from '@janhq/core'
|
||||||
import { ConversationalPlugin } from '@janhq/core/lib/plugins'
|
import { ConversationalPlugin } from '@janhq/core/lib/plugins'
|
||||||
import { Conversation } from '@janhq/core/lib/types'
|
import { Conversation } from '@janhq/core/lib/types'
|
||||||
import { useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { toChatMessage } from '@/utils/message'
|
||||||
|
|
||||||
import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import {
|
import {
|
||||||
conversationStatesAtom,
|
conversationStatesAtom,
|
||||||
userConversationsAtom,
|
userConversationsAtom,
|
||||||
} from '@/helpers/atoms/Conversation.atom'
|
} from '@/helpers/atoms/Conversation.atom'
|
||||||
import { toChatMessage } from '@/models/ChatMessage'
|
|
||||||
import { pluginManager } from '@/plugin/PluginManager'
|
import { pluginManager } from '@/plugin/PluginManager'
|
||||||
import { ChatMessage, ConversationState } from '@/types/chatMessage'
|
|
||||||
|
|
||||||
const useGetUserConversations = () => {
|
const useGetUserConversations = () => {
|
||||||
const setConversationStates = useSetAtom(conversationStatesAtom)
|
const setConversationStates = useSetAtom(conversationStatesAtom)
|
||||||
@ -24,19 +24,19 @@ const useGetUserConversations = () => {
|
|||||||
?.getConversations()
|
?.getConversations()
|
||||||
const convoStates: Record<string, ConversationState> = {}
|
const convoStates: Record<string, ConversationState> = {}
|
||||||
convos?.forEach((convo) => {
|
convos?.forEach((convo) => {
|
||||||
convoStates[convo._id ?? ''] = {
|
convoStates[convo.id ?? ''] = {
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
waitingForResponse: false,
|
waitingForResponse: false,
|
||||||
}
|
}
|
||||||
setConvoMessages(
|
setConvoMessages(
|
||||||
convo.messages.map<ChatMessage>((msg) => toChatMessage(msg)),
|
convo.messages.map<ChatMessage>((msg) => toChatMessage(msg)),
|
||||||
convo._id ?? ''
|
convo.id ?? ''
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
setConversationStates(convoStates)
|
setConversationStates(convoStates)
|
||||||
setConversations(convos ?? [])
|
setConversations(convos ?? [])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,13 +4,13 @@ import {
|
|||||||
NewMessageRequest,
|
NewMessageRequest,
|
||||||
PluginType,
|
PluginType,
|
||||||
events,
|
events,
|
||||||
|
ChatMessage,
|
||||||
|
Message,
|
||||||
|
Conversation,
|
||||||
|
MessageSenderType,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
|
|
||||||
import { ConversationalPlugin, InferencePlugin } from '@janhq/core/lib/plugins'
|
import { ConversationalPlugin, InferencePlugin } from '@janhq/core/lib/plugins'
|
||||||
|
|
||||||
import { Message } from '@janhq/core/lib/types'
|
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { currentPromptAtom } from '@/containers/Providers/Jotai'
|
import { currentPromptAtom } from '@/containers/Providers/Jotai'
|
||||||
import { ulid } from 'ulid'
|
import { ulid } from 'ulid'
|
||||||
import {
|
import {
|
||||||
@ -22,10 +22,8 @@ import {
|
|||||||
updateConversationAtom,
|
updateConversationAtom,
|
||||||
updateConversationWaitingForResponseAtom,
|
updateConversationWaitingForResponseAtom,
|
||||||
} from '@/helpers/atoms/Conversation.atom'
|
} from '@/helpers/atoms/Conversation.atom'
|
||||||
import { MessageSenderType, toChatMessage } from '@/models/ChatMessage'
|
|
||||||
|
|
||||||
import { pluginManager } from '@/plugin/PluginManager'
|
import { pluginManager } from '@/plugin/PluginManager'
|
||||||
import { ChatMessage, Conversation } from '@/types/chatMessage'
|
import { toChatMessage } from '@/utils/message'
|
||||||
|
|
||||||
export default function useSendChatMessage() {
|
export default function useSendChatMessage() {
|
||||||
const currentConvo = useAtomValue(currentConversationAtom)
|
const currentConvo = useAtomValue(currentConversationAtom)
|
||||||
@ -59,7 +57,7 @@ export default function useSendChatMessage() {
|
|||||||
if (
|
if (
|
||||||
result?.message &&
|
result?.message &&
|
||||||
result.message.split(' ').length <= 10 &&
|
result.message.split(' ').length <= 10 &&
|
||||||
conv?._id
|
conv?.id
|
||||||
) {
|
) {
|
||||||
const updatedConv = {
|
const updatedConv = {
|
||||||
...conv,
|
...conv,
|
||||||
@ -73,7 +71,7 @@ export default function useSendChatMessage() {
|
|||||||
name: updatedConv.name ?? '',
|
name: updatedConv.name ?? '',
|
||||||
message: updatedConv.lastMessage ?? '',
|
message: updatedConv.lastMessage ?? '',
|
||||||
messages: currentMessages.map<Message>((e: ChatMessage) => ({
|
messages: currentMessages.map<Message>((e: ChatMessage) => ({
|
||||||
_id: e.id,
|
id: e.id,
|
||||||
message: e.text,
|
message: e.text,
|
||||||
user: e.senderUid,
|
user: e.senderUid,
|
||||||
updatedAt: new Date(e.createdAt).toISOString(),
|
updatedAt: new Date(e.createdAt).toISOString(),
|
||||||
@ -87,7 +85,11 @@ export default function useSendChatMessage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sendChatMessage = async () => {
|
const sendChatMessage = async () => {
|
||||||
const convoId = currentConvo?._id as string
|
const convoId = currentConvo?.id
|
||||||
|
if (!convoId) {
|
||||||
|
console.error('No conversation id')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setCurrentPrompt('')
|
setCurrentPrompt('')
|
||||||
updateConvWaiting(convoId, true)
|
updateConvWaiting(convoId, true)
|
||||||
@ -106,8 +108,7 @@ export default function useSendChatMessage() {
|
|||||||
} as MessageHistory,
|
} as MessageHistory,
|
||||||
])
|
])
|
||||||
const newMessage: NewMessageRequest = {
|
const newMessage: NewMessageRequest = {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
id: ulid(),
|
||||||
_id: ulid(),
|
|
||||||
conversationId: convoId,
|
conversationId: convoId,
|
||||||
message: prompt,
|
message: prompt,
|
||||||
user: MessageSenderType.User,
|
user: MessageSenderType.User,
|
||||||
|
|||||||
@ -1,84 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
import { NewMessageResponse } from '@janhq/core'
|
|
||||||
import { Message } from '@janhq/core/lib/types'
|
|
||||||
|
|
||||||
export enum MessageType {
|
|
||||||
Text = 'Text',
|
|
||||||
Image = 'Image',
|
|
||||||
ImageWithText = 'ImageWithText',
|
|
||||||
Error = 'Error',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum MessageSenderType {
|
|
||||||
Ai = 'assistant',
|
|
||||||
User = 'user',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum MessageStatus {
|
|
||||||
Ready = 'ready',
|
|
||||||
Pending = 'pending',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
|
||||||
id: string
|
|
||||||
conversationId: string
|
|
||||||
messageType: MessageType
|
|
||||||
messageSenderType: MessageSenderType
|
|
||||||
senderUid: string
|
|
||||||
senderName: string
|
|
||||||
senderAvatarUrl: string
|
|
||||||
text: string | undefined
|
|
||||||
imageUrls?: string[] | undefined
|
|
||||||
createdAt: number
|
|
||||||
status: MessageStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RawMessage {
|
|
||||||
_id?: string
|
|
||||||
conversationId?: string
|
|
||||||
user?: string
|
|
||||||
avatar?: string
|
|
||||||
message?: string
|
|
||||||
createdAt?: string
|
|
||||||
updatedAt?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const toChatMessage = (
|
|
||||||
m: RawMessage | Message | NewMessageResponse,
|
|
||||||
|
|
||||||
conversationId?: string
|
|
||||||
): ChatMessage => {
|
|
||||||
const createdAt = new Date(m.createdAt ?? '').getTime()
|
|
||||||
const imageUrls: string[] = []
|
|
||||||
const imageUrl = undefined
|
|
||||||
if (imageUrl) {
|
|
||||||
imageUrls.push(imageUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageType = MessageType.Text
|
|
||||||
const messageSenderType =
|
|
||||||
m.user === 'user' ? MessageSenderType.User : MessageSenderType.Ai
|
|
||||||
|
|
||||||
const content = m.message ?? ''
|
|
||||||
|
|
||||||
const senderName = m.user === 'user' ? 'You' : 'Assistant'
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: (m._id ?? 0).toString(),
|
|
||||||
conversationId: (
|
|
||||||
(m as RawMessage | NewMessageResponse)?.conversationId ??
|
|
||||||
conversationId ??
|
|
||||||
0
|
|
||||||
).toString(),
|
|
||||||
messageType: messageType,
|
|
||||||
messageSenderType: messageSenderType,
|
|
||||||
senderUid: m.user?.toString() || '0',
|
|
||||||
senderName: senderName,
|
|
||||||
senderAvatarUrl:
|
|
||||||
m.user === 'user' ? 'icons/avatar.svg' : 'icons/app_icon.svg',
|
|
||||||
text: content,
|
|
||||||
imageUrls: imageUrls,
|
|
||||||
createdAt: createdAt,
|
|
||||||
status: MessageStatus.Ready,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import React, { forwardRef } from 'react'
|
import React, { forwardRef } from 'react'
|
||||||
|
import { ChatMessage } from '@janhq/core'
|
||||||
import SimpleTextMessage from '../SimpleTextMessage'
|
import SimpleTextMessage from '../SimpleTextMessage'
|
||||||
import { ChatMessage } from '@/types/chatMessage'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
message: ChatMessage
|
message: ChatMessage
|
||||||
|
|||||||
@ -46,16 +46,16 @@ export default function HistoryList() {
|
|||||||
console.debug('modelId is undefined')
|
console.debug('modelId is undefined')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const model = downloadedModels.find((e) => e._id === convo.modelId)
|
const model = downloadedModels.find((e) => e.id === convo.modelId)
|
||||||
if (convo == null) {
|
if (convo == null) {
|
||||||
console.debug('modelId is undefined')
|
console.debug('modelId is undefined')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
startModel(model._id)
|
startModel(model.id)
|
||||||
}
|
}
|
||||||
if (activeConvoId !== convo._id) {
|
if (activeConvoId !== convo.id) {
|
||||||
setActiveConvoId(convo._id)
|
setActiveConvoId(convo.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ export default function HistoryList() {
|
|||||||
key={i}
|
key={i}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'relative flex cursor-pointer flex-col border-b border-border px-4 py-2 hover:bg-secondary/20',
|
'relative flex cursor-pointer flex-col border-b border-border px-4 py-2 hover:bg-secondary/20',
|
||||||
activeConvoId === convo._id && 'bg-secondary-10'
|
activeConvoId === convo.id && 'bg-secondary-10'
|
||||||
)}
|
)}
|
||||||
onClick={() => handleActiveModel(convo as Conversation)}
|
onClick={() => handleActiveModel(convo as Conversation)}
|
||||||
>
|
>
|
||||||
@ -100,7 +100,7 @@ export default function HistoryList() {
|
|||||||
<p className="mt-1 line-clamp-2 text-xs">
|
<p className="mt-1 line-clamp-2 text-xs">
|
||||||
{convo?.lastMessage ?? 'No new message'}
|
{convo?.lastMessage ?? 'No new message'}
|
||||||
</p>
|
</p>
|
||||||
{activeModel && activeConvoId === convo._id && (
|
{activeModel && activeConvoId === convo.id && (
|
||||||
<m.div
|
<m.div
|
||||||
className="absolute right-0 top-0 h-full w-1 bg-primary/50"
|
className="absolute right-0 top-0 h-full w-1 bg-primary/50"
|
||||||
layoutId="active-convo"
|
layoutId="active-convo"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
import { MessageSenderType, MessageStatus } from '@janhq/core'
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
|
|
||||||
import { Marked } from 'marked'
|
import { Marked } from 'marked'
|
||||||
@ -15,8 +16,6 @@ import BubbleLoader from '@/containers/Loader/Bubble'
|
|||||||
|
|
||||||
import { displayDate } from '@/utils/datetime'
|
import { displayDate } from '@/utils/datetime'
|
||||||
|
|
||||||
import { MessageSenderType, MessageStatus } from '@/models/ChatMessage'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
avatarUrl: string
|
avatarUrl: string
|
||||||
senderName: string
|
senderName: string
|
||||||
|
|||||||
@ -56,7 +56,7 @@ const ChatScreen = () => {
|
|||||||
const conversations = useAtomValue(userConversationsAtom)
|
const conversations = useAtomValue(userConversationsAtom)
|
||||||
const isEnableChat = (currentConvo && activeModel) || conversations.length > 0
|
const isEnableChat = (currentConvo && activeModel) || conversations.length > 0
|
||||||
const [isModelAvailable, setIsModelAvailable] = useState(
|
const [isModelAvailable, setIsModelAvailable] = useState(
|
||||||
downloadedModels.some((x) => x._id === currentConvo?.modelId)
|
downloadedModels.some((x) => x.id === currentConvo?.modelId)
|
||||||
)
|
)
|
||||||
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
@ -72,7 +72,7 @@ const ChatScreen = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsModelAvailable(
|
setIsModelAvailable(
|
||||||
downloadedModels.some((x) => x._id === currentConvo?.modelId)
|
downloadedModels.some((x) => x.id === currentConvo?.modelId)
|
||||||
)
|
)
|
||||||
}, [currentConvo, downloadedModels])
|
}, [currentConvo, downloadedModels])
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ const ChatScreen = () => {
|
|||||||
disabled={
|
disabled={
|
||||||
!activeModel ||
|
!activeModel ||
|
||||||
stateModel.loading ||
|
stateModel.loading ||
|
||||||
activeModel._id !== currentConvo?.modelId
|
activeModel.id !== currentConvo?.modelId
|
||||||
}
|
}
|
||||||
value={currentPrompt}
|
value={currentPrompt}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|||||||
@ -105,7 +105,7 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
|||||||
<ModelVersionList
|
<ModelVersionList
|
||||||
model={model}
|
model={model}
|
||||||
versions={model.availableVersions}
|
versions={model.availableVersions}
|
||||||
recommendedVersion={suitableModel?._id ?? ''}
|
recommendedVersion={suitableModel?.id ?? ''}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -35,8 +35,8 @@ const ExploreModelItemHeader: React.FC<Props> = ({
|
|||||||
const { performanceTag, title, getPerformanceForModel } =
|
const { performanceTag, title, getPerformanceForModel } =
|
||||||
useGetPerformanceTag()
|
useGetPerformanceTag()
|
||||||
const downloadAtom = useMemo(
|
const downloadAtom = useMemo(
|
||||||
() => atom((get) => get(modelDownloadStateAtom)[suitableModel._id]),
|
() => atom((get) => get(modelDownloadStateAtom)[suitableModel.id]),
|
||||||
[suitableModel._id]
|
[suitableModel.id]
|
||||||
)
|
)
|
||||||
const downloadState = useAtomValue(downloadAtom)
|
const downloadState = useAtomValue(downloadAtom)
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
@ -52,7 +52,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({
|
|||||||
}, [exploreModel, suitableModel])
|
}, [exploreModel, suitableModel])
|
||||||
|
|
||||||
const isDownloaded =
|
const isDownloaded =
|
||||||
downloadedModels.find((model) => model._id === suitableModel._id) != null
|
downloadedModels.find((model) => model.id === suitableModel.id) != null
|
||||||
|
|
||||||
let downloadButton = (
|
let downloadButton = (
|
||||||
<Button onClick={() => onDownloadClick()}>
|
<Button onClick={() => onDownloadClick()}>
|
||||||
|
|||||||
@ -1,18 +1,14 @@
|
|||||||
import { ModelCatalog } from '@janhq/core/lib/types'
|
import { ModelCatalog } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
import ExploreModelItem from '@/screens/ExploreModels/ExploreModelItem'
|
import ExploreModelItem from '@/screens/ExploreModels/ExploreModelItem'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
models: ModelCatalog[]
|
models: ModelCatalog[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ExploreModelList(props: Props) {
|
const ExploreModelList: React.FC<Props> = ({ models }) => (
|
||||||
const { models } = props
|
|
||||||
return (
|
|
||||||
<div className="relative h-full w-full flex-shrink-0">
|
<div className="relative h-full w-full flex-shrink-0">
|
||||||
{models?.map((item, i) => (
|
{models?.map((item, i) => <ExploreModelItem key={item.id} model={item} />)}
|
||||||
<ExploreModelItem key={item._id} model={item} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
export default ExploreModelList
|
||||||
|
|||||||
@ -1,22 +1,15 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
|
|
||||||
import { ModelCatalog, ModelVersion } from '@janhq/core/lib/types'
|
import { ModelCatalog, ModelVersion } from '@janhq/core/lib/types'
|
||||||
import { Button } from '@janhq/uikit'
|
import { Button } from '@janhq/uikit'
|
||||||
import { Badge } from '@janhq/uikit'
|
import { Badge } from '@janhq/uikit'
|
||||||
import { atom, useAtomValue } from 'jotai'
|
import { atom, useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import ModalCancelDownload from '@/containers/ModalCancelDownload'
|
import ModalCancelDownload from '@/containers/ModalCancelDownload'
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
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 { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||||
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { toGigabytes } from '@/utils/converter'
|
import { toGigabytes } from '@/utils/converter'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -30,13 +23,13 @@ const ModelVersionItem: React.FC<Props> = ({ model, modelVersion }) => {
|
|||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
const isDownloaded =
|
const isDownloaded =
|
||||||
downloadedModels.find((model) => model._id === modelVersion._id) != null
|
downloadedModels.find((model) => model.id === modelVersion.id) != null
|
||||||
|
|
||||||
const { modelDownloadStateAtom, downloadStates } = useDownloadState()
|
const { modelDownloadStateAtom, downloadStates } = useDownloadState()
|
||||||
|
|
||||||
const downloadAtom = useMemo(
|
const downloadAtom = useMemo(
|
||||||
() => atom((get) => get(modelDownloadStateAtom)[modelVersion._id ?? '']),
|
() => atom((get) => get(modelDownloadStateAtom)[modelVersion.id ?? '']),
|
||||||
[modelVersion._id]
|
[modelVersion.id]
|
||||||
)
|
)
|
||||||
const downloadState = useAtomValue(downloadAtom)
|
const downloadState = useAtomValue(downloadAtom)
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,10 @@ export default function ModelVersionList({
|
|||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
{versions.map((item) => (
|
{versions.map((item) => (
|
||||||
<ModelVersionItem
|
<ModelVersionItem
|
||||||
key={item._id}
|
key={item.id}
|
||||||
model={model}
|
model={model}
|
||||||
modelVersion={item}
|
modelVersion={item}
|
||||||
isRecommended={item._id === recommendedVersion}
|
isRecommended={item.id === recommendedVersion}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -40,7 +40,7 @@ const MyModelsScreen = () => {
|
|||||||
if (downloadedModels.length === 0) return <BlankStateMyModel />
|
if (downloadedModels.length === 0) return <BlankStateMyModel />
|
||||||
|
|
||||||
const onModelActionClick = (modelId: string) => {
|
const onModelActionClick = (modelId: string) => {
|
||||||
if (activeModel && activeModel._id === modelId) {
|
if (activeModel && activeModel.id === modelId) {
|
||||||
stopModel(modelId)
|
stopModel(modelId)
|
||||||
} else {
|
} else {
|
||||||
startModel(modelId)
|
startModel(modelId)
|
||||||
@ -53,7 +53,7 @@ const MyModelsScreen = () => {
|
|||||||
<div className="p-4" data-test-id="testid-my-models">
|
<div className="p-4" data-test-id="testid-my-models">
|
||||||
<div className="grid grid-cols-2 gap-4 lg:grid-cols-3">
|
<div className="grid grid-cols-2 gap-4 lg:grid-cols-3">
|
||||||
{downloadedModels.map((model, i) => {
|
{downloadedModels.map((model, i) => {
|
||||||
const isActiveModel = stateModel.model === model._id
|
const isActiveModel = stateModel.model === model.id
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
@ -114,7 +114,7 @@ const MyModelsScreen = () => {
|
|||||||
themes="danger"
|
themes="danger"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await stopModel(model._id)
|
await stopModel(model.id)
|
||||||
deleteModel(model)
|
deleteModel(model)
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
@ -135,7 +135,7 @@ const MyModelsScreen = () => {
|
|||||||
}
|
}
|
||||||
className="capitalize"
|
className="capitalize"
|
||||||
loading={isActiveModel ? stateModel.loading : false}
|
loading={isActiveModel ? stateModel.loading : false}
|
||||||
onClick={() => onModelActionClick(model._id)}
|
onClick={() => onModelActionClick(model.id)}
|
||||||
>
|
>
|
||||||
{isActiveModel ? stateModel.state : 'Start'}
|
{isActiveModel ? stateModel.state : 'Start'}
|
||||||
Model
|
Model
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import * as cn from './cloudNativeService'
|
|||||||
import { EventEmitter } from './eventsService'
|
import { EventEmitter } from './eventsService'
|
||||||
export const setupCoreServices = () => {
|
export const setupCoreServices = () => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
console.log('undefine', window)
|
console.debug('undefine', window)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
console.log('Setting up core services')
|
console.debug('Setting up core services')
|
||||||
}
|
}
|
||||||
if (!window.corePlugin) {
|
if (!window.corePlugin) {
|
||||||
window.corePlugin = {
|
window.corePlugin = {
|
||||||
|
|||||||
63
web/types/chatMessage.d.ts
vendored
63
web/types/chatMessage.d.ts
vendored
@ -1,63 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
enum MessageType {
|
|
||||||
Text = 'Text',
|
|
||||||
Image = 'Image',
|
|
||||||
ImageWithText = 'ImageWithText',
|
|
||||||
Error = 'Error',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum MessageSenderType {
|
|
||||||
Ai = 'assistant',
|
|
||||||
User = 'user',
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MessageStatus {
|
|
||||||
Ready = 'ready',
|
|
||||||
Pending = 'pending',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChatMessage {
|
|
||||||
id: string
|
|
||||||
conversationId: string
|
|
||||||
messageType: MessageType
|
|
||||||
messageSenderType: MessageSenderType
|
|
||||||
senderUid: string
|
|
||||||
senderName: string
|
|
||||||
senderAvatarUrl: string
|
|
||||||
text: string | undefined
|
|
||||||
imageUrls?: string[] | undefined
|
|
||||||
createdAt: number
|
|
||||||
status: MessageStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RawMessage {
|
|
||||||
_id?: string
|
|
||||||
conversationId?: string
|
|
||||||
user?: string
|
|
||||||
avatar?: string
|
|
||||||
message?: string
|
|
||||||
createdAt?: string
|
|
||||||
updatedAt?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Conversation {
|
|
||||||
_id: string
|
|
||||||
modelId?: string
|
|
||||||
name?: string
|
|
||||||
image?: string
|
|
||||||
message?: string
|
|
||||||
lastMessage?: string
|
|
||||||
summary?: string
|
|
||||||
createdAt?: string
|
|
||||||
updatedAt?: string
|
|
||||||
botId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the state of conversation like fetching, waiting for response, etc.
|
|
||||||
*/
|
|
||||||
type ConversationState = {
|
|
||||||
hasMore: boolean
|
|
||||||
waitingForResponse: boolean
|
|
||||||
error?: Error
|
|
||||||
}
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { ModelCatalog, ModelVersion } from '@janhq/core/lib/types'
|
import { ModelCatalog, ModelVersion } from '@janhq/core'
|
||||||
|
|
||||||
export const dummyModel: ModelCatalog = {
|
export const dummyModel: ModelCatalog = {
|
||||||
_id: 'aladar/TinyLLama-v0-GGUF',
|
id: 'aladar/TinyLLama-v0-GGUF',
|
||||||
name: 'TinyLLama-v0-GGUF',
|
name: 'TinyLLama-v0-GGUF',
|
||||||
shortDescription: 'TinyLlama-1.1B-Chat-v0.3-GGUF',
|
shortDescription: 'TinyLlama-1.1B-Chat-v0.3-GGUF',
|
||||||
longDescription: 'https://huggingface.co/aladar/TinyLLama-v0-GGUF/tree/main',
|
longDescription: 'https://huggingface.co/aladar/TinyLLama-v0-GGUF/tree/main',
|
||||||
@ -16,7 +16,7 @@ export const dummyModel: ModelCatalog = {
|
|||||||
createdAt: 0,
|
createdAt: 0,
|
||||||
availableVersions: [
|
availableVersions: [
|
||||||
{
|
{
|
||||||
_id: 'tinyllama-1.1b-chat-v0.3.Q2_K.gguf',
|
id: 'tinyllama-1.1b-chat-v0.3.Q2_K.gguf',
|
||||||
name: 'tinyllama-1.1b-chat-v0.3.Q2_K.gguf',
|
name: 'tinyllama-1.1b-chat-v0.3.Q2_K.gguf',
|
||||||
quantMethod: '',
|
quantMethod: '',
|
||||||
bits: 2,
|
bits: 2,
|
||||||
|
|||||||
@ -1,27 +1,48 @@
|
|||||||
import { ChatMessage } from '@/models/ChatMessage'
|
import {
|
||||||
|
ChatMessage,
|
||||||
|
Message,
|
||||||
|
MessageSenderType,
|
||||||
|
MessageStatus,
|
||||||
|
MessageType,
|
||||||
|
NewMessageResponse,
|
||||||
|
RawMessage,
|
||||||
|
} from '@janhq/core'
|
||||||
|
|
||||||
/**
|
export const toChatMessage = (
|
||||||
* Util function to merge two array of messages and remove duplicates.
|
m: RawMessage | Message | NewMessageResponse,
|
||||||
* Also preserve the order
|
conversationId?: string
|
||||||
*
|
): ChatMessage => {
|
||||||
* @param arr1 Message array 1
|
const createdAt = new Date(m.createdAt ?? '').getTime()
|
||||||
* @param arr2 Message array 2
|
const imageUrls: string[] = []
|
||||||
* @returns Merged array of messages
|
const imageUrl = undefined
|
||||||
*/
|
if (imageUrl) {
|
||||||
export function mergeAndRemoveDuplicates(
|
imageUrls.push(imageUrl)
|
||||||
arr1: ChatMessage[],
|
|
||||||
arr2: ChatMessage[]
|
|
||||||
): ChatMessage[] {
|
|
||||||
const mergedArray = arr1.concat(arr2)
|
|
||||||
const uniqueIdMap = new Map<string, boolean>()
|
|
||||||
const result: ChatMessage[] = []
|
|
||||||
|
|
||||||
for (const message of mergedArray) {
|
|
||||||
if (!uniqueIdMap.has(message.id)) {
|
|
||||||
uniqueIdMap.set(message.id, true)
|
|
||||||
result.push(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.reverse()
|
const messageType = MessageType.Text
|
||||||
|
const messageSenderType =
|
||||||
|
m.user === 'user' ? MessageSenderType.User : MessageSenderType.Ai
|
||||||
|
|
||||||
|
const content = m.message ?? ''
|
||||||
|
|
||||||
|
const senderName = m.user === 'user' ? 'You' : 'Assistant'
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: (m.id ?? 0).toString(),
|
||||||
|
conversationId: (
|
||||||
|
(m as RawMessage | NewMessageResponse)?.conversationId ??
|
||||||
|
conversationId ??
|
||||||
|
0
|
||||||
|
).toString(),
|
||||||
|
messageType: messageType,
|
||||||
|
messageSenderType: messageSenderType,
|
||||||
|
senderUid: m.user?.toString() || '0',
|
||||||
|
senderName: senderName,
|
||||||
|
senderAvatarUrl:
|
||||||
|
m.user === 'user' ? 'icons/avatar.svg' : 'icons/app_icon.svg',
|
||||||
|
text: content,
|
||||||
|
imageUrls: imageUrls,
|
||||||
|
createdAt: createdAt,
|
||||||
|
status: MessageStatus.Ready,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user