chore: simplify themes and assistants
This commit is contained in:
parent
27beb46801
commit
24b8a1b66a
@ -19,7 +19,7 @@ const writeBlob: (path: string, data: string) => Promise<any> = (path, data) =>
|
||||
* Reads the contents of a file at the specified path.
|
||||
* @returns {Promise<any>} A Promise that resolves with the contents of the file.
|
||||
*/
|
||||
const readFileSync = (...args: any[]) => globalThis.core.api?.readFileSync(...args)
|
||||
const readFileSync = (...args: any[]) => globalThis.core.api?.readFileSync({ args })
|
||||
/**
|
||||
* Check whether the file exists
|
||||
* @param {string} path
|
||||
@ -30,12 +30,12 @@ const existsSync = (...args: any[]) => globalThis.core.api?.existsSync({ args })
|
||||
* List the directory files
|
||||
* @returns {Promise<any>} A Promise that resolves with the contents of the directory.
|
||||
*/
|
||||
const readdirSync = (...args: any[]) => globalThis.core.api?.readdirSync(...args)
|
||||
const readdirSync = (...args: any[]) => globalThis.core.api?.readdirSync({ args })
|
||||
/**
|
||||
* Creates a directory at the specified path.
|
||||
* @returns {Promise<any>} A Promise that resolves when the directory is created successfully.
|
||||
*/
|
||||
const mkdir = (...args: any[]) => globalThis.core.api?.mkdir(...args)
|
||||
const mkdir = (...args: any[]) => globalThis.core.api?.mkdir({ args })
|
||||
|
||||
/**
|
||||
* Removes a directory at the specified path.
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
normalizeFilePath,
|
||||
getJanDataFolderPath,
|
||||
} from '../../helper'
|
||||
import { readdirSync } from 'fs'
|
||||
|
||||
export class App implements Processor {
|
||||
observer?: Function
|
||||
@ -69,10 +70,22 @@ export class App implements Processor {
|
||||
writeLog(args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app configurations.
|
||||
*/
|
||||
getAppConfigurations() {
|
||||
return appConfiguration()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get themes from the app data folder.
|
||||
* @returns
|
||||
*/
|
||||
getThemes() {
|
||||
const themesPath = join(getJanDataFolderPath(), 'themes')
|
||||
return readdirSync(themesPath)
|
||||
}
|
||||
|
||||
async updateAppConfiguration(args: any) {
|
||||
await updateAppConfiguration(args)
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ export enum NativeRoute {
|
||||
/**
|
||||
* App Route APIs
|
||||
* @description Enum of all the routes exposed by the app
|
||||
*/
|
||||
*/
|
||||
export enum AppRoute {
|
||||
getAppConfigurations = 'getAppConfigurations',
|
||||
updateAppConfiguration = 'updateAppConfiguration',
|
||||
@ -51,6 +51,7 @@ export enum AppRoute {
|
||||
log = 'log',
|
||||
systemInformation = 'systemInformation',
|
||||
showToast = 'showToast',
|
||||
getThemes = 'getThemes',
|
||||
}
|
||||
|
||||
export enum AppEvent {
|
||||
|
||||
@ -1,38 +1,11 @@
|
||||
import {
|
||||
fs,
|
||||
Assistant,
|
||||
events,
|
||||
joinPath,
|
||||
AssistantExtension,
|
||||
AssistantEvent,
|
||||
ToolManager,
|
||||
} from '@janhq/core'
|
||||
import { Assistant, AssistantExtension, ToolManager } from '@janhq/core'
|
||||
import { RetrievalTool } from './tools/retrieval'
|
||||
|
||||
export default class JanAssistantExtension extends AssistantExtension {
|
||||
private static readonly _homeDir = 'file://assistants'
|
||||
|
||||
async onLoad() {
|
||||
// Register the retrieval tool
|
||||
ToolManager.instance().register(new RetrievalTool())
|
||||
|
||||
// making the assistant directory
|
||||
const assistantDirExist = await fs.existsSync(
|
||||
JanAssistantExtension._homeDir
|
||||
)
|
||||
if (
|
||||
localStorage.getItem(`${this.name}-version`) !== VERSION ||
|
||||
!assistantDirExist
|
||||
) {
|
||||
if (!assistantDirExist) await fs.mkdir(JanAssistantExtension._homeDir)
|
||||
|
||||
// Write assistant metadata
|
||||
await this.createJanAssistant()
|
||||
// Finished migration
|
||||
localStorage.setItem(`${this.name}-version`, VERSION)
|
||||
// Update the assistant list
|
||||
events.emit(AssistantEvent.OnAssistantsUpdate, {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,87 +13,13 @@ export default class JanAssistantExtension extends AssistantExtension {
|
||||
*/
|
||||
onUnload(): void {}
|
||||
|
||||
async createAssistant(assistant: Assistant): Promise<void> {
|
||||
const assistantDir = await joinPath([
|
||||
JanAssistantExtension._homeDir,
|
||||
assistant.id,
|
||||
])
|
||||
if (!(await fs.existsSync(assistantDir))) await fs.mkdir(assistantDir)
|
||||
|
||||
// store the assistant metadata json
|
||||
const assistantMetadataPath = await joinPath([
|
||||
assistantDir,
|
||||
'assistant.json',
|
||||
])
|
||||
try {
|
||||
await fs.writeFileSync(
|
||||
assistantMetadataPath,
|
||||
JSON.stringify(assistant, null, 2)
|
||||
)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async getAssistants(): Promise<Assistant[]> {
|
||||
try {
|
||||
// get all the assistant directories
|
||||
// get all the assistant metadata json
|
||||
const results: Assistant[] = []
|
||||
|
||||
const allFileName: string[] = await fs.readdirSync(
|
||||
JanAssistantExtension._homeDir
|
||||
)
|
||||
|
||||
for (const fileName of allFileName) {
|
||||
const filePath = await joinPath([
|
||||
JanAssistantExtension._homeDir,
|
||||
fileName,
|
||||
])
|
||||
|
||||
if (!(await fs.fileStat(filePath))?.isDirectory) continue
|
||||
const jsonFiles: string[] = (await fs.readdirSync(filePath)).filter(
|
||||
(file: string) => file === 'assistant.json'
|
||||
)
|
||||
|
||||
if (jsonFiles.length !== 1) {
|
||||
// has more than one assistant file -> ignore
|
||||
continue
|
||||
}
|
||||
|
||||
const content = await fs.readFileSync(
|
||||
await joinPath([filePath, jsonFiles[0]]),
|
||||
'utf-8'
|
||||
)
|
||||
const assistant: Assistant =
|
||||
typeof content === 'object' ? content : JSON.parse(content)
|
||||
|
||||
results.push(assistant)
|
||||
}
|
||||
|
||||
return results
|
||||
} catch (err) {
|
||||
console.debug(err)
|
||||
return [this.defaultAssistant]
|
||||
}
|
||||
return [this.defaultAssistant]
|
||||
}
|
||||
|
||||
async deleteAssistant(assistant: Assistant): Promise<void> {
|
||||
if (assistant.id === 'jan') {
|
||||
return Promise.reject('Cannot delete Jan Assistant')
|
||||
}
|
||||
|
||||
// remove the directory
|
||||
const assistantDir = await joinPath([
|
||||
JanAssistantExtension._homeDir,
|
||||
assistant.id,
|
||||
])
|
||||
return fs.rm(assistantDir)
|
||||
}
|
||||
|
||||
private async createJanAssistant(): Promise<void> {
|
||||
await this.createAssistant(this.defaultAssistant)
|
||||
}
|
||||
/** DEPRECATED */
|
||||
async createAssistant(assistant: Assistant): Promise<void> {}
|
||||
async deleteAssistant(assistant: Assistant): Promise<void> {}
|
||||
|
||||
private defaultAssistant: Assistant = {
|
||||
avatar: '',
|
||||
|
||||
@ -106,6 +106,23 @@ pub fn get_jan_extensions_path(app_handle: tauri::AppHandle) -> PathBuf {
|
||||
get_jan_data_folder_path(app_handle).join("extensions")
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_themes(app_handle: tauri::AppHandle) -> Vec<String> {
|
||||
let mut themes = vec![];
|
||||
let themes_path = get_jan_data_folder_path(app_handle).join("themes");
|
||||
if themes_path.exists() {
|
||||
for entry in fs::read_dir(themes_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
if entry.path().is_dir() {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
themes.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
themes
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_configuration_file_path(app_handle: tauri::AppHandle) -> PathBuf {
|
||||
let app_path = app_handle.path().app_data_dir().unwrap_or_else(|err| {
|
||||
@ -274,6 +291,10 @@ pub fn install_extensions(app: tauri::AppHandle) -> Result<(), String> {
|
||||
.as_ref()
|
||||
.and_then(|manifest| manifest["version"].as_str())
|
||||
.unwrap_or(""),
|
||||
"productName": extension_manifest
|
||||
.as_ref()
|
||||
.and_then(|manifest| manifest["productName"].as_str())
|
||||
.unwrap_or(""),
|
||||
});
|
||||
|
||||
extensions_list.push(new_extension);
|
||||
|
||||
@ -41,6 +41,38 @@ pub fn exists_sync(app_handle: tauri::AppHandle, args: Vec<String>) -> Result<bo
|
||||
Ok(path.exists())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_file_sync(
|
||||
app_handle: tauri::AppHandle,
|
||||
args: Vec<String>,
|
||||
) -> Result<String, String> {
|
||||
if args.is_empty() || args[0].is_empty() {
|
||||
return Err("read_file_sync error: Invalid argument".to_string());
|
||||
}
|
||||
|
||||
let path = resolve_path(app_handle, &args[0]);
|
||||
fs::read_to_string(&path).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn readdir_sync(
|
||||
app_handle: tauri::AppHandle,
|
||||
args: Vec<String>,
|
||||
) -> Result<Vec<String>, String> {
|
||||
if args.is_empty() || args[0].is_empty() {
|
||||
return Err("read_dir_sync error: Invalid argument".to_string());
|
||||
}
|
||||
|
||||
let path = resolve_path(app_handle, &args[0]);
|
||||
println!("Reading directory: {:?}", path);
|
||||
let entries = fs::read_dir(&path).map_err(|e| e.to_string())?;
|
||||
let paths: Vec<String> = entries
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.path().to_string_lossy().to_string())
|
||||
.collect();
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
fn normalize_file_path(path: &str) -> String {
|
||||
path.replace("file:/", "").replace("file:\\", "")
|
||||
}
|
||||
@ -48,7 +80,8 @@ fn normalize_file_path(path: &str) -> String {
|
||||
fn resolve_path(app_handle: tauri::AppHandle, path: &str) -> PathBuf {
|
||||
let path = if path.starts_with("file:/") || path.starts_with("file:\\") {
|
||||
let normalized = normalize_file_path(path);
|
||||
get_jan_data_folder_path(app_handle).join(normalized)
|
||||
let relative_normalized = normalized.strip_prefix("/").unwrap_or(&normalized);
|
||||
get_jan_data_folder_path(app_handle).join(relative_normalized)
|
||||
} else {
|
||||
PathBuf::from(path)
|
||||
};
|
||||
|
||||
@ -47,10 +47,14 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// handlers::fs::join_path,
|
||||
// handlers::fs::mkdir,
|
||||
// handlers::fs::exists_sync,
|
||||
// handlers::fs::rm,
|
||||
handlers::fs::join_path,
|
||||
handlers::fs::mkdir,
|
||||
handlers::fs::exists_sync,
|
||||
handlers::fs::readdir_sync,
|
||||
handlers::fs::read_file_sync,
|
||||
handlers::fs::rm,
|
||||
// App commands
|
||||
handlers::cmd::get_themes,
|
||||
handlers::cmd::get_app_configurations,
|
||||
handlers::cmd::get_active_extensions,
|
||||
handlers::cmd::get_user_home_path,
|
||||
@ -111,13 +115,17 @@ pub fn run() {
|
||||
eprintln!("Failed to install extensions: {}", e);
|
||||
}
|
||||
|
||||
// Copy binaries to app_data
|
||||
// Copy engine binaries to app_data
|
||||
let app_data_dir = app.app_handle().path().app_data_dir().unwrap();
|
||||
let binaries_dir = app.app_handle().path().resource_dir().unwrap().join("binaries");
|
||||
let themes_dir = app.app_handle().path().resource_dir().unwrap().join("resources");
|
||||
|
||||
if let Err(e) = copy_dir_all(binaries_dir, app_data_dir) {
|
||||
if let Err(e) = copy_dir_all(binaries_dir, app_data_dir.clone()) {
|
||||
eprintln!("Failed to copy binaries: {}", e);
|
||||
}
|
||||
if let Err(e) = copy_dir_all(themes_dir, app_data_dir.clone()) {
|
||||
eprintln!("Failed to copy themes: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@ -51,7 +51,8 @@
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [
|
||||
"binaries/engines/**/*"
|
||||
"binaries/engines/**/*",
|
||||
"resources/themes/**/*"
|
||||
],
|
||||
"externalBin": ["binaries/cortex-server"]
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ export const themesOptionsAtom = atomWithStorage<
|
||||
|
||||
export const selectedThemeIdAtom = atomWithStorage<string>(
|
||||
THEME,
|
||||
'',
|
||||
'joi-light',
|
||||
undefined,
|
||||
{ getOnInit: true }
|
||||
)
|
||||
|
||||
@ -4,13 +4,10 @@ import { useTheme } from 'next-themes'
|
||||
|
||||
import { fs, joinPath } from '@janhq/core'
|
||||
|
||||
import { useAtom, useAtomValue } from 'jotai'
|
||||
import { useAtom } from 'jotai'
|
||||
|
||||
import cssVars from '@/utils/jsonToCssVariables'
|
||||
|
||||
import themeData from '@/../../public/theme.json' with { type: 'json' }
|
||||
|
||||
import { janDataFolderPathAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||
import {
|
||||
selectedThemeIdAtom,
|
||||
themeDataAtom,
|
||||
@ -20,7 +17,6 @@ import {
|
||||
type NativeThemeProps = 'light' | 'dark'
|
||||
|
||||
export const useLoadTheme = () => {
|
||||
const janDataFolderPath = useAtomValue(janDataFolderPathAtom)
|
||||
const [themeOptions, setThemeOptions] = useAtom(themesOptionsAtom)
|
||||
const [themeData, setThemeData] = useAtom(themeDataAtom)
|
||||
const [selectedIdTheme, setSelectedIdTheme] = useAtom(selectedThemeIdAtom)
|
||||
@ -51,48 +47,28 @@ export const useLoadTheme = () => {
|
||||
}
|
||||
|
||||
const getThemes = useCallback(async () => {
|
||||
if (!janDataFolderPath.length) return
|
||||
const folderPath = await joinPath([janDataFolderPath, 'themes'])
|
||||
const installedThemes = await fs.readdirSync(folderPath)
|
||||
const installedThemes = await window.core.api.getThemes()
|
||||
|
||||
const themesOptions: { name: string; value: string }[] = installedThemes
|
||||
.filter((x: string) => x !== '.DS_Store')
|
||||
.map(async (x: string) => {
|
||||
const y = await joinPath([`${folderPath}/${x}`, `theme.json`])
|
||||
const c: Theme = JSON.parse(await fs.readFileSync(y, 'utf-8'))
|
||||
return { name: c?.displayName, value: c.id }
|
||||
})
|
||||
Promise.all(themesOptions).then((results) => {
|
||||
setThemeOptions(results)
|
||||
})
|
||||
const themesOptions: { name: string; value: string }[] =
|
||||
installedThemes.map((x: string) => ({
|
||||
name: x.replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()),
|
||||
value: x,
|
||||
}))
|
||||
setThemeOptions(themesOptions)
|
||||
|
||||
if (janDataFolderPath.length > 0) {
|
||||
if (!selectedIdTheme.length) return setSelectedIdTheme('joi-light')
|
||||
const filePath = await joinPath([
|
||||
`${folderPath}/${selectedIdTheme}`,
|
||||
`theme.json`,
|
||||
])
|
||||
const theme: Theme = JSON.parse(await fs.readFileSync(filePath, 'utf-8'))
|
||||
if (!selectedIdTheme.length) return setSelectedIdTheme('joi-light')
|
||||
const filePath = await joinPath([
|
||||
'file://themes',
|
||||
selectedIdTheme,
|
||||
'theme.json',
|
||||
])
|
||||
|
||||
setThemeData(theme)
|
||||
setNativeTheme(theme.nativeTheme)
|
||||
applyTheme(theme)
|
||||
} else {
|
||||
// Apply default bundled theme
|
||||
const theme: Theme | undefined = themeData
|
||||
if (theme) {
|
||||
setThemeData(theme)
|
||||
applyTheme(theme)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
janDataFolderPath,
|
||||
selectedIdTheme,
|
||||
setNativeTheme,
|
||||
setSelectedIdTheme,
|
||||
setThemeData,
|
||||
setThemeOptions,
|
||||
])
|
||||
const theme: Theme = JSON.parse(await fs.readFileSync(filePath, 'utf-8'))
|
||||
|
||||
setThemeData(theme)
|
||||
setNativeTheme(theme.nativeTheme)
|
||||
applyTheme(theme)
|
||||
}, [])
|
||||
|
||||
const configureTheme = useCallback(async () => {
|
||||
if (!themeData || !themeOptions) {
|
||||
@ -105,11 +81,9 @@ export const useLoadTheme = () => {
|
||||
|
||||
useEffect(() => {
|
||||
configureTheme()
|
||||
}, [
|
||||
configureTheme,
|
||||
selectedIdTheme,
|
||||
setNativeTheme,
|
||||
setSelectedIdTheme,
|
||||
themeData?.nativeTheme,
|
||||
])
|
||||
}, [themeData])
|
||||
|
||||
useEffect(() => {
|
||||
getThemes()
|
||||
}, [])
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ const SettingLeftPanel = () => {
|
||||
|
||||
{extensionHasSettings
|
||||
.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
||||
.filter((e) => !e.name?.includes('Cortex'))
|
||||
.filter((e) => !e.name?.toLowerCase().includes('cortex'))
|
||||
.map((item) => (
|
||||
<SettingItem
|
||||
key={item.name}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user