Merge pull request #5 from presenton/shared_user_config_implementation
Adds: shared user config
This commit is contained in:
commit
d01165f06b
9 changed files with 119 additions and 71 deletions
32
app/main.ts
32
app/main.ts
|
|
@ -1,17 +1,21 @@
|
|||
require("dotenv").config();
|
||||
import { app, BrowserWindow } from "electron";
|
||||
import path from "path";
|
||||
import { findTwoUnusedPorts, killProcess } from "./utils";
|
||||
import { createUserConfig, findTwoUnusedPorts, killProcess } from "./utils";
|
||||
import { startFastApiServer, startNextJsServer } from "./servers";
|
||||
import { ChildProcessByStdio } from "child_process";
|
||||
import { localhost } from "./constants";
|
||||
|
||||
var isDev = process.env.DEBUG === "True";
|
||||
var isDev = !app.isPackaged;
|
||||
var baseDir = isDev ? process.cwd() : process.resourcesPath;
|
||||
var resourcesDir = path.join(baseDir, "resources");
|
||||
var fastapiDir = isDev ? path.join(baseDir, "servers/fastapi") : path.join(resourcesDir, "fastapi");
|
||||
var nextjsDir = isDev ? path.join(baseDir, "servers/nextjs") : path.join(resourcesDir, "nextjs");
|
||||
|
||||
var tempDir = app.getPath("temp");
|
||||
var dataDir = app.getPath("userData");
|
||||
var userConfigPath = path.join(dataDir, "userConfig.json");
|
||||
|
||||
var win: BrowserWindow | undefined;
|
||||
var fastApiProcess: ChildProcessByStdio<any, any, any> | undefined;
|
||||
var nextjsProcess: ChildProcessByStdio<any, any, any> | undefined;
|
||||
|
|
@ -33,13 +37,13 @@ async function startServers(fastApiPort: number, nextjsPort: number) {
|
|||
fastApiPort,
|
||||
{
|
||||
DEBUG: isDev ? "True" : "False",
|
||||
LLM: process.env.LLM || "",
|
||||
LIBREOFFICE: process.env.LIBREOFFICE || "",
|
||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY || "",
|
||||
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || "",
|
||||
APP_DATA_DIRECTORY: process.env.APP_DATA_DIRECTORY || "",
|
||||
TEMP_DIRECTORY: process.env.TEMP_DIRECTORY || "",
|
||||
|
||||
LLM: process.env.LLM,
|
||||
LIBREOFFICE: process.env.LIBREOFFICE,
|
||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
||||
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,
|
||||
APP_DATA_DIRECTORY: dataDir,
|
||||
TEMP_DIRECTORY: tempDir,
|
||||
USER_CONFIG_PATH: userConfigPath,
|
||||
},
|
||||
isDev
|
||||
);
|
||||
|
|
@ -48,8 +52,9 @@ async function startServers(fastApiPort: number, nextjsPort: number) {
|
|||
nextjsPort,
|
||||
{
|
||||
NEXT_PUBLIC_FAST_API: `${localhost}:${fastApiPort}`,
|
||||
TEMP_DIRECTORY: process.env.TEMP_DIRECTORY || "",
|
||||
TEMP_DIRECTORY: tempDir,
|
||||
NEXT_PUBLIC_URL: `${localhost}:${nextjsPort}`,
|
||||
USER_CONFIG_PATH: userConfigPath,
|
||||
},
|
||||
isDev
|
||||
);
|
||||
|
|
@ -70,8 +75,15 @@ async function stopServers() {
|
|||
app.whenReady().then(async () => {
|
||||
createWindow();
|
||||
win?.loadFile(path.join(resourcesDir, "ui/homepage/index.html"));
|
||||
|
||||
win?.webContents.openDevTools();
|
||||
|
||||
createUserConfig(app, {
|
||||
LLM: process.env.LLM,
|
||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
||||
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,
|
||||
})
|
||||
|
||||
const [fastApiPort, nextjsPort] = await findTwoUnusedPorts();
|
||||
console.log(`FastAPI port: ${fastApiPort}, NextJS port: ${nextjsPort}`);
|
||||
|
||||
|
|
|
|||
28
app/types/index.d.ts
vendored
28
app/types/index.d.ts
vendored
|
|
@ -1,15 +1,23 @@
|
|||
interface FastApiEnv {
|
||||
DEBUG: string,
|
||||
LLM: string,
|
||||
LIBREOFFICE: string,
|
||||
OPENAI_API_KEY: string,
|
||||
GOOGLE_API_KEY: string,
|
||||
APP_DATA_DIRECTORY: string,
|
||||
TEMP_DIRECTORY: string,
|
||||
DEBUG?: string,
|
||||
LLM?: string,
|
||||
LIBREOFFICE?: string,
|
||||
OPENAI_API_KEY?: string,
|
||||
GOOGLE_API_KEY?: string,
|
||||
APP_DATA_DIRECTORY?: string,
|
||||
TEMP_DIRECTORY?: string,
|
||||
USER_CONFIG_PATH?: string,
|
||||
}
|
||||
|
||||
interface NextJsEnv {
|
||||
NEXT_PUBLIC_FAST_API: string,
|
||||
TEMP_DIRECTORY: string,
|
||||
NEXT_PUBLIC_URL: string,
|
||||
NEXT_PUBLIC_FAST_API?: string,
|
||||
TEMP_DIRECTORY?: string,
|
||||
NEXT_PUBLIC_URL?: string,
|
||||
USER_CONFIG_PATH?: string,
|
||||
}
|
||||
|
||||
interface UserConfig {
|
||||
LLM?: string,
|
||||
OPENAI_API_KEY?: string,
|
||||
GOOGLE_API_KEY?: string,
|
||||
}
|
||||
32
app/utils.ts
32
app/utils.ts
|
|
@ -1,5 +1,34 @@
|
|||
import net from 'net'
|
||||
import treeKill from 'tree-kill'
|
||||
import { exec } from 'child_process'
|
||||
import { promisify } from 'util'
|
||||
import { platform } from 'os'
|
||||
import type { App } from "electron"
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
export function createUserConfig(app: App, userConfig: UserConfig) {
|
||||
const dataDir = app.getPath("userData")
|
||||
const configPath = path.join(dataDir, "userConfig.json")
|
||||
|
||||
let existingConfig: UserConfig = {}
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
const configData = fs.readFileSync(configPath, 'utf-8')
|
||||
existingConfig = JSON.parse(configData)
|
||||
}
|
||||
|
||||
const mergedConfig: UserConfig = {
|
||||
LLM: userConfig.LLM || existingConfig.LLM,
|
||||
OPENAI_API_KEY: userConfig.OPENAI_API_KEY || existingConfig.OPENAI_API_KEY,
|
||||
GOOGLE_API_KEY: userConfig.GOOGLE_API_KEY || existingConfig.GOOGLE_API_KEY,
|
||||
}
|
||||
|
||||
fs.writeFileSync(configPath, JSON.stringify(mergedConfig))
|
||||
}
|
||||
|
||||
|
||||
export function killProcess(pid: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -41,4 +70,5 @@ export async function findTwoUnusedPorts(startPort: number = 40000): Promise<[nu
|
|||
}
|
||||
|
||||
return [ports[0], ports[1]];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
|
||||
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
|
||||
|
||||
module.exports = {
|
||||
packagerConfig: {
|
||||
asar: true,
|
||||
extraResource: [
|
||||
'resources',
|
||||
]
|
||||
},
|
||||
rebuildConfig: {},
|
||||
makers: [
|
||||
// {
|
||||
// name: '@electron-forge/maker-squirrel',
|
||||
// config: {},
|
||||
// },
|
||||
// {
|
||||
// name: '@electron-forge/maker-zip',
|
||||
// platforms: ['darwin'],
|
||||
// },
|
||||
{
|
||||
name: '@electron-forge/maker-deb',
|
||||
config: {},
|
||||
},
|
||||
// {
|
||||
// name: '@electron-forge/maker-rpm',
|
||||
// config: {},
|
||||
// },
|
||||
],
|
||||
plugins: [
|
||||
{
|
||||
name: '@electron-forge/plugin-auto-unpack-natives',
|
||||
config: {},
|
||||
},
|
||||
// Fuses are used to enable/disable various Electron functionality
|
||||
// at package time, before code signing the application
|
||||
new FusesPlugin({
|
||||
version: FuseVersion.V1,
|
||||
[FuseV1Options.RunAsNode]: false,
|
||||
[FuseV1Options.EnableCookieEncryption]: true,
|
||||
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
|
||||
[FuseV1Options.EnableNodeCliInspectArguments]: false,
|
||||
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
|
||||
[FuseV1Options.OnlyLoadAppFromAsar]: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import os
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlmodel import SQLModel
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from api.routers.presentation.router import presentation_router
|
||||
from api.services.database import sql_engine
|
||||
from api.utils import update_env_with_user_config
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
|
|
@ -24,4 +25,12 @@ app.add_middleware(
|
|||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def update_env_middleware(request: Request, call_next):
|
||||
update_env_with_user_config()
|
||||
return await call_next(request)
|
||||
|
||||
|
||||
app.include_router(presentation_router)
|
||||
|
|
|
|||
|
|
@ -35,3 +35,9 @@ class SSEResponse(BaseModel):
|
|||
|
||||
def to_string(self):
|
||||
return f"event: {self.event}\ndata: {self.data}\n\n"
|
||||
|
||||
|
||||
class UserConfig(BaseModel):
|
||||
LLM: Optional[str] = None
|
||||
OPENAI_API_KEY: Optional[str] = None
|
||||
GOOGLE_API_KEY: Optional[str] = None
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
from typing import List, Optional
|
||||
|
|
@ -7,7 +8,7 @@ import aiohttp
|
|||
from fastapi import HTTPException, UploadFile
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from api.models import LogMetadata
|
||||
from api.models import LogMetadata, UserConfig
|
||||
from api.services.logging import LoggingService
|
||||
|
||||
|
||||
|
|
@ -17,6 +18,35 @@ def get_presentation_dir(presentation_id: str) -> str:
|
|||
return presentation_dir
|
||||
|
||||
|
||||
def get_user_config():
|
||||
user_config_path = os.getenv("USER_CONFIG_PATH")
|
||||
|
||||
existing_config = UserConfig()
|
||||
try:
|
||||
if os.path.exists(user_config_path):
|
||||
with open(user_config_path, "r") as f:
|
||||
existing_config = UserConfig(**json.load(f))
|
||||
except Exception as e:
|
||||
print("Error while loading user config")
|
||||
pass
|
||||
|
||||
return UserConfig(
|
||||
LLM=os.getenv("LLM") or existing_config.LLM,
|
||||
OPENAI_API_KEY=os.getenv("OPENAI_API_KEY") or existing_config.OPENAI_API_KEY,
|
||||
GOOGLE_API_KEY=os.getenv("GOOGLE_API_KEY") or existing_config.GOOGLE_API_KEY,
|
||||
)
|
||||
|
||||
|
||||
def update_env_with_user_config():
|
||||
user_config = get_user_config()
|
||||
if user_config.LLM:
|
||||
os.environ["LLM"] = user_config.LLM
|
||||
if user_config.OPENAI_API_KEY:
|
||||
os.environ["OPENAI_API_KEY"] = user_config.OPENAI_API_KEY
|
||||
if user_config.GOOGLE_API_KEY:
|
||||
os.environ["GOOGLE_API_KEY"] = user_config.GOOGLE_API_KEY
|
||||
|
||||
|
||||
def replace_file_name(old_name: str, new_name: str) -> str:
|
||||
splitted = old_name.split(".")
|
||||
if len(splitted) < 1:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from langchain_core.prompts import ChatPromptTemplate
|
|||
from langchain_core.messages import BaseMessage
|
||||
from langchain_text_splitters import CharacterTextSplitter
|
||||
|
||||
|
||||
sysmte_prompt = """
|
||||
Generate a blog-style summary of the provided document in **more than 2000 words**, focusing on **prominently featuring any numerical data and statistics**. Maintain as much information as possible.
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ def generate_presentation_stream(
|
|||
language: str,
|
||||
summary: str,
|
||||
) -> AsyncIterator[AIMessageChunk]:
|
||||
|
||||
schema = LLMPresentationModel.model_json_schema()
|
||||
|
||||
system_prompt = f"{CREATE_PRESENTATION_PROMPT} -|0|--|0|- Follow this schema while giving out response: {schema}. Make description short and obey the character limits. Output should be in JSON format. Give out only JSON, nothing else."
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue