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