Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ec2baf7
Dynamically select port for inference service
mschuettlerTNG Aug 7, 2024
15a6dc2
Replace black image from safety checker with NSFW notice
mschuettlerTNG Aug 9, 2024
fbbd733
Allow LLM drop-down to be pre-filled
mschuettlerTNG Aug 9, 2024
c4ac460
Add first draft of LNL branding
mschuettlerTNG Aug 10, 2024
5208b82
Make NSFW notice display more robust
mschuettlerTNG Aug 12, 2024
b780aad
Move styles and increase square size
mschuettlerTNG Aug 13, 2024
e0adad2
Prevent LLM picker circles from becoming elliptical
mschuettlerTNG Aug 13, 2024
ef4d540
Adds exception handling when app crashes during loading
DanielHirschTNG Aug 16, 2024
591b7dd
Show error when backend crashes
DanielHirschTNG Aug 16, 2024
cc9dd57
Make platform part of window title configurable (#1)
mschuettlerTNG Aug 19, 2024
7022832
Add Arc badge (#6)
mschuettlerTNG Aug 20, 2024
6d374ea
Improve display of images (#19)
mschuettlerTNG Aug 20, 2024
263f437
Add shadcn-vue for resolution picker and error display (#2, #8)
mschuettlerTNG Aug 20, 2024
c2e633a
Adds exception handling when app crashes during loading
DanielHirschTNG Aug 16, 2024
3d6dffd
Show error when backend crashes
DanielHirschTNG Aug 16, 2024
447dd4d
Log the error from the backend back to the user
DanielHirschTNG Aug 20, 2024
b54792e
Return after exception is shown
DanielHirschTNG Aug 20, 2024
2bc691f
Add Collapsible for displaying error message to user (#8)
DanielHirschTNG Aug 20, 2024
823f375
Merge branch 'fix/exception_handling' of github.com:TNG/tng-intel-ai-…
DanielHirschTNG Aug 20, 2024
e2a59a9
Remove duplicated code
DanielHirschTNG Aug 20, 2024
2b2caef
fix npm run dev scripts not recognized
qiacheng Aug 21, 2024
a217001
Add buttons to control font size in chat ui (#10)
DanielHirschTNG Aug 22, 2024
d77962b
Add user information via dialog when HD mode is selected (#13)
DanielHirschTNG Aug 22, 2024
ce1f1a0
Set platform title env variable for build jobs (#1)
mschuettlerTNG Aug 22, 2024
652df21
Add resolution picker for non-manual resolutions (#2)
mschuettlerTNG Aug 22, 2024
111cf9a
Adjust background style according to feedback (#3)
mschuettlerTNG Aug 22, 2024
2647982
Add more models to LLM picker (#7)
mschuettlerTNG Aug 22, 2024
a4a3eb1
Inject hugging face api tokens via env variable to allow download of …
mschuettlerTNG Aug 23, 2024
40bd2db
Improve styling, forward logs to front-end (#8)
mschuettlerTNG Aug 23, 2024
701cfa5
Make chatter name size smaller than chat message (#10)
mschuettlerTNG Aug 23, 2024
8001bc0
Improve backend status handling (#8)
mschuettlerTNG Aug 23, 2024
411fbf8
Merge pull request #22 from TNG/fix/exception_handling
mschuettlerTNG Aug 23, 2024
9be35cb
Merge pull request #20 from TNG/feat/resolution-picker
mschuettlerTNG Aug 23, 2024
aaf8fbf
Update dialog styling (#13)
DanielHirschTNG Aug 23, 2024
2e29f37
Scale chat icons as well (#10)
mschuettlerTNG Aug 23, 2024
e231a92
Merge pull request #23 from TNG/feat/chat-font-control
mschuettlerTNG Aug 23, 2024
e846afe
Merge pull request #24 from TNG/feat/hd-warning
mschuettlerTNG Aug 23, 2024
b201bbb
Remove debug code
mschuettlerTNG Aug 23, 2024
c09cd54
Add missing handler
mschuettlerTNG Aug 23, 2024
7866885
Update package-lock
mschuettlerTNG Aug 23, 2024
da71822
Add history pane to llm chat (#9)
DanielHirschTNG Sep 1, 2024
a5a389c
Fix: Added overflow when code content overflows
DanielHirschTNG Sep 1, 2024
147f5ff
Fix: Breaks lines in code elements if an overflow occurs
DanielHirschTNG Sep 1, 2024
d576e92
Show / hide history view (#9)
DanielHirschTNG Sep 3, 2024
fc8c475
Update buttons (#9)
DanielHirschTNG Sep 3, 2024
fdee9ae
Adjust conversation history after feedback (#9)
mschuettlerTNG Sep 6, 2024
cc33b39
Make Hugging Face token configurable via settings menu (#7)
mschuettlerTNG Sep 6, 2024
1dc5118
Merge pull request #36 from TNG/feat/llm_history
mschuettlerTNG Sep 6, 2024
d700d66
Merge remote-tracking branch 'tng-private-repo/LNL' into feat/configu…
mschuettlerTNG Sep 6, 2024
402abe3
Merge pull request #37 from TNG/feat/configure_hf_token_via_settings
mschuettlerTNG Sep 6, 2024
8110380
Add configurable Hugging Face token support to backend (#7)
mschuettlerTNG Sep 6, 2024
2ba0ee2
Improve UX for gated model notifications (#7)
mschuettlerTNG Sep 10, 2024
b3fa158
Show hover tooltip in collapsed conversation history (#9)
mschuettlerTNG Sep 10, 2024
4a17e92
Remove duplicate line after merge
mschuettlerTNG Sep 10, 2024
80a2ab5
Allow theme configuration via settings.json (#25)
mschuettlerTNG Sep 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions WebUI/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "new-york",
"typescript": true,
"tsConfigPath": "./tsconfig.json",
"tailwind": {
"config": "tailwind.config.cjs",
"css": "src/assets/css/index.css",
"baseColor": "slate",
"cssVariables": false
},
"framework": "vite",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
10 changes: 10 additions & 0 deletions WebUI/electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type KVObject = {
[key: string]: any;
};

type Theme = 'dark' | 'lnl';

type LocalSettings = {
apiHost: string;
settingPath: string;
Expand All @@ -38,6 +40,10 @@ type LocalSettings = {
port:number;
} & KVObject;

type ThemeSettings = {
availableThemes: Theme[];
};

type ModelPaths = {
llm: string,
embedding: string,
Expand All @@ -64,4 +70,8 @@ type SetupData = {
envType: string,
isAdminExec:boolean,
version:string,
}

type BackendStatus = {
status: 'starting' | 'running' | 'stopped',
}
158 changes: 128 additions & 30 deletions WebUI/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { randomUUID } from "node:crypto";
import koffi from 'koffi';
import sudo from "sudo-prompt";
import { PathsManager } from "./pathsManager";
import getPort, { portNumbers } from "get-port";

// }
// The built directory structure
Expand Down Expand Up @@ -68,11 +69,31 @@ const settings: LocalSettings = {
isAdminExec: false,
debug: 0,
envType: "ultra",
port: 59999
port: 59999,
availableThemes: ["dark", "lnl"],
};

const logger = {
info: (message: string, source: 'electron-backend' | 'ai-backend' = 'electron-backend') => {
console.info(`[${source}]: ${message}`);
try {
win?.webContents.send('debugLog', { level: 'info', source, message })
} catch (error) {
console.error('Could not send debug log to renderer process');
}
},
error: (message: string, source: 'electron-backend' | 'ai-backend' = 'electron-backend') => {
console.error(`[${source}]: ${message}`);
try {
win?.webContents.send('debugLog', { level: 'error', source, message })
} catch (error) {
console.error('Could not send debug log to renderer process');
}
}
}

function loadSettings() {

async function loadSettings() {
const settingPath = app.isPackaged
? path.join(process.resourcesPath, "settings.json")
: path.join(__dirname, "../../external/settings-dev.json");
Expand All @@ -87,6 +108,7 @@ function loadSettings() {
}
});
}
settings.port = await getPort({ port: portNumbers(59000, 59999) });
settings.apiHost = `http://127.0.0.1:${settings.port}`;
}

Expand Down Expand Up @@ -134,7 +156,7 @@ async function createWindow() {
...details.responseHeaders,
"Access-Control-Allow-Origin": ["*"],
"Access-Control-Allow-Methods": ["GET,POST"],
"Access-Control-Allow-Headers": ["x-requested-with,Content-Type"],
"Access-Control-Allow-Headers": ["x-requested-with,Content-Type,Authorization"],
}
callback(details);
} else {
Expand All @@ -158,7 +180,7 @@ async function createWindow() {

if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL);
console.log("load url:" + VITE_DEV_SERVER_URL);
logger.info("load url:" + VITE_DEV_SERVER_URL);
} else {
win.loadFile(path.join(process.env.DIST, "index.html"));
}
Expand All @@ -174,7 +196,7 @@ function logMessage(message: string) {
if (app.isPackaged) {
fs.appendFileSync(path.join(externalRes, "debug.log"), message + "\r\n");
} else {
console.log(message);
logger.info(message);
}
}

Expand Down Expand Up @@ -233,6 +255,12 @@ function initEventHandle() {
}
});

ipcMain.handle("getThemeSettings", async () => {
return {
availableThemes: settings.availableThemes,
};
});

ipcMain.handle("getLocalSettings", async () => {
return {
apiHost: settings.apiHost,
Expand Down Expand Up @@ -324,13 +352,13 @@ function initEventHandle() {
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
fs.writeFileSync(result.filePath, buffer);
console.log("File downloaded and saved:", result.filePath);
logger.info(`File downloaded and saved: ${result.filePath}`);
} catch (error) {
console.error("Download and save error:", error);
logger.error(`Download and save error: ${JSON.stringify(error, Object.getOwnPropertyNames, 2)}`);
}
}
} catch (err) {
console.error(err);
} catch (error) {
logger.error(`${JSON.stringify(error, Object.getOwnPropertyNames, 2)}`);
};
});

Expand Down Expand Up @@ -358,13 +386,15 @@ function initEventHandle() {
return fs.existsSync(path);
});

ipcMain.handle("getPythonBackendStatus", () => apiService.status)

let pathsManager = new PathsManager(path.join(externalRes, app.isPackaged ? "model_config.json" : "model_config.dev.json"));

ipcMain.handle("getInitSetting", (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) { return; }
return {
apiHost:settings.apiHost,
apiHost: settings.apiHost,
modelLists: pathsManager.sacanAll(),
modelPaths: pathsManager.modelPaths,
envType: settings.envType,
Expand Down Expand Up @@ -395,6 +425,34 @@ function initEventHandle() {
return pathsManager.scanLLMModles();
});

ipcMain.handle("refreshEmbeddingModels", (event) => {
return pathsManager.scanEmbedding();
});

ipcMain.handle("getDownloadedDiffusionModels", (event) => {
return pathsManager.scanSDModleLists(false);
});

ipcMain.handle("getDownloadedInpaintModels", (event) => {
return pathsManager.scanInpaint(false);
});

ipcMain.handle("getDownloadedLoras", (event) => {
return pathsManager.scanLora(false);
});

ipcMain.handle("getDownloadedLLMs", (event) => {
return pathsManager.scanLLMModles(false);
});

ipcMain.handle("getDownloadedEmbeddingModels", (event) => {
return pathsManager.scanEmbedding(false);
});

ipcMain.on("openDevTools", () => {
win?.webContents.openDevTools({ mode: "detach", activate: true });
});

ipcMain.on("openImageWithSystem", (event, url: string) => {
// Assuming 'settings' and 'externalRes' are properly defined
let imagePath = url.replace(settings.apiHost + "/", ""); // Remove the API host part
Expand Down Expand Up @@ -437,10 +495,14 @@ function initEventHandle() {
}
const apiService: {
webProcess: ChildProcess | null,
normalExit: boolean
normalExit: boolean,
status: BackendStatus,
desiredState: 'running' | 'stopped'
} = {
webProcess: null,
normalExit: true
normalExit: true,
status: { status: "starting" },
desiredState: 'running'
}

function isProcessRunning(pid: number) {
Expand All @@ -455,30 +517,66 @@ function wakeupApiService() {
const wordkDir = path.resolve(app.isPackaged ? path.join(process.resourcesPath, "service") : path.join(__dirname, "../../../service"));
const baseDir = app.isPackaged ? process.resourcesPath : path.join(__dirname, "../../../");
const pythonExe = path.resolve(path.join(baseDir, "env/python.exe"));
const newEnv = {
const additionalEnvVariables = {
"SYCL_ENABLE_DEFAULT_CONTEXTS": "1",
"SYCL_CACHE_PERSISTENT": "1",
"PYTHONIOENCODING": "utf-8"
};

if (settings.debug) {
apiService.webProcess = spawn("cmd.exe", ["/c", pythonExe, "web_api.py", "--port", settings.port.toString()], {
cwd: wordkDir,
detached: true,
windowsHide: false,
env: Object.assign(process.env, newEnv)
});
} else {
apiService.webProcess = spawn(pythonExe, ["web_api.py", "--port", settings.port.toString()], {
cwd: wordkDir,
windowsHide: true,
env: Object.assign(process.env, newEnv)
});
}
spawnAPI(pythonExe, wordkDir, additionalEnvVariables);
}

function spawnAPI(pythonExe: string, wordkDir: string, additionalEnvVariables: Record<string, string>, tries = 0) {
if (apiService.desiredState === 'stopped') return;
tries++;
let stderrData = '';
let maxTries = 2;
logger.info(`#${tries} try to start python API`)

const webProcess = spawn(pythonExe, ["web_api.py", "--port", settings.port.toString()], {
cwd: wordkDir,
windowsHide: true,
env: Object.assign(process.env, additionalEnvVariables)
});

apiService.webProcess = webProcess;

const handleFailure = (err: Error | null, code: number | null) => {
logger.error(`Error: ${err || `Process exited with code ${code}`}`);
if (tries < maxTries) {
spawnAPI(pythonExe, wordkDir, additionalEnvVariables, tries);
} else {
apiService.status = { status: "stopped" };
logger.error(`Maximum attempts reached. Giving up.`);
if (webProcess.stderr != null) {
// TODO: catch + retry
logger.info(`stderrData: ${stderrData}`);
win?.webContents.send('reportError', stderrData);
//throw new Error(`Backend could not start:\n ${stderrData}`)
}
}
};

apiService.status = { status: "running" };

webProcess.on('error', (err) => handleFailure(err, null));
webProcess.on('exit', (code, signal) => handleFailure(null, code));
webProcess.stderr?.on('data', (data) => {
stderrData = data.toString();
});

webProcess.stdout.on('data', (message) => {
logger.info(`${message}`, 'ai-backend')
})
webProcess.stderr.on('data', (message) => {
logger.error(`${message}`, 'ai-backend')
})
}


function closeApiService() {
apiService.normalExit = true;
apiService.desiredState = 'stopped';
if (apiService.webProcess != null && apiService.webProcess.pid && isProcessRunning(apiService.webProcess.pid)) {
apiService.webProcess.kill();
apiService.webProcess = null;
Expand Down Expand Up @@ -521,8 +619,8 @@ ipcMain.on("openImageWin", (_: IpcMainEvent, url: string, title: string, width:
ipcMain.handle('showSaveDialog', async (event, options: Electron.SaveDialogOptions) => {
dialog.showSaveDialog(options).then(result => {
return result;
}).catch(err => {
console.error(err);
}).catch(error => {
logger.error(`${JSON.stringify(error, Object.getOwnPropertyNames, 2)}`);
});
});

Expand Down Expand Up @@ -584,7 +682,7 @@ app.whenReady().then(async () => {
});
app.exit();
} else {
loadSettings();
await loadSettings();
initEventHandle();
createWindow();
wakeupApiService();
Expand Down
30 changes: 15 additions & 15 deletions WebUI/electron/pathsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ export class PathsManager {
throw ex;
}
}
scanSDModleLists() {
const models = [
scanSDModleLists(returnDefaults = true) {
const models = returnDefaults ? [
"Lykon/dreamshaper-8",
"RunDiffusion/Juggernaut-XL-v9",
]
] : [];
const dir = this.modelPaths.stableDiffusion;
if (fs.existsSync(dir)) {

Expand Down Expand Up @@ -94,10 +94,10 @@ export class PathsManager {
}
return models
}
scanLLMModles() {
const models = [
scanLLMModles(returnDefaults = true) {
const models = returnDefaults ? [
"microsoft/Phi-3-mini-4k-instruct",
]
] : [];
const dir = this.modelPaths.llm;
if (fs.existsSync(dir)) {
const modelsSet = new Set(models);
Expand All @@ -119,12 +119,12 @@ export class PathsManager {
}
return models
}
scanLora() {
const models = [
scanLora(returnDefaults = true) {
const models = returnDefaults ? [
"None",
"latent-consistency/lcm-lora-sdxl",
"latent-consistency/lcm-lora-sdv1-5",
]
] : [];
const loraDir = this.modelPaths.lora;
if (fs.existsSync(loraDir)) {
const modelsSet = new Set(models);
Expand Down Expand Up @@ -153,16 +153,16 @@ export class PathsManager {
}
return models
}
scanEmbedding() {
return [
scanEmbedding(returnDefaults = true) {
return returnDefaults ? [
"BAAI/bge-large-en-v1.5",
"BAAI/bge-large-zh-v1.5"
]
] : [];
}
scanInpaint() {
const models = [
scanInpaint(returnDefaults = true) {
const models = returnDefaults ? [
"Lykon/dreamshaper-8-inpainting"
]
] : [];
const dir = this.modelPaths.inpaint;
if (fs.existsSync(dir)) {
const modelsSet = new Set(models);
Expand Down
Loading