Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12,976 changes: 12,781 additions & 195 deletions tools/server/public/bundle.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions tools/server/public/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
This is a single file build of the frontend.
This is a static build of the frontend.
It is automatically generated by the build process.
Do not edit this file directly.
To make changes, refer to the "Web UI" section in the README.
Expand All @@ -18,7 +18,7 @@
<div style="display: contents">
<script>
{
__sveltekit_1ao0o9h = {
__sveltekit__ = {
base: new URL('.', location).pathname.slice(0, -1)
};

Expand Down
4 changes: 2 additions & 2 deletions tools/server/webui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tools/server/webui/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "webui",
"name": "llama-server-webui",
"private": true,
"version": "1.0.0",
"type": "module",
Expand Down
84 changes: 84 additions & 0 deletions tools/server/webui/scripts/vite-plugin-llama-cpp-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { readFileSync, writeFileSync, existsSync, readdirSync, copyFileSync } from 'fs';
import { resolve } from 'path';
import type { Plugin } from 'vite';

const GUIDE_FOR_FRONTEND = `
<!--
This is a static build of the frontend.
It is automatically generated by the build process.
Do not edit this file directly.
To make changes, refer to the "Web UI" section in the README.
-->
`.trim();

export function llamaCppBuildPlugin(): Plugin {
return {
name: 'llamacpp:build',
apply: 'build',
closeBundle() {
// Ensure the SvelteKit adapter has finished writing to ../public
setTimeout(() => {
try {
const indexPath = resolve('../public/index.html');
if (!existsSync(indexPath)) return;

let content = readFileSync(indexPath, 'utf-8');

const faviconPath = resolve('static/favicon.svg');

if (existsSync(faviconPath)) {
const faviconContent = readFileSync(faviconPath, 'utf-8');
const faviconBase64 = Buffer.from(faviconContent).toString('base64');
const faviconDataUrl = `data:image/svg+xml;base64,${faviconBase64}`;

content = content.replace(/href="[^"]*favicon\.svg"/g, `href="${faviconDataUrl}"`);

console.log('✓ Inlined favicon.svg as base64 data URL');
}

content = content.replace(/\r/g, '');
content = GUIDE_FOR_FRONTEND + '\n' + content;
content = content.replace(/\/_app\/immutable\/bundle\.[^"]+\.js/g, './bundle.js');
content = content.replace(
/\/_app\/immutable\/assets\/bundle\.[^"]+\.css/g,
'./bundle.css'
);
content = content.replace(/__sveltekit_[a-z0-9]+/g, '__sveltekit__');

writeFileSync(indexPath, content, 'utf-8');
console.log('✓ Updated index.html');

// Copy bundle.*.js -> ../public/bundle.js
const immutableDir = resolve('../public/_app/immutable');
const bundleDir = resolve('../public/_app/immutable/assets');

if (existsSync(immutableDir)) {
const jsFiles = readdirSync(immutableDir).filter((f) => f.match(/^bundle\..+\.js$/));

if (jsFiles.length > 0) {
copyFileSync(resolve(immutableDir, jsFiles[0]), resolve('../public/bundle.js'));
// Normalize __sveltekit_<hash> to __sveltekit__ in bundle.js
const bundleJsPath = resolve('../public/bundle.js');
let bundleJs = readFileSync(bundleJsPath, 'utf-8');
bundleJs = bundleJs.replace(/__sveltekit_[a-z0-9]+/g, '__sveltekit__');
writeFileSync(bundleJsPath, bundleJs, 'utf-8');
console.log(`✓ Copied ${jsFiles[0]} -> bundle.js`);
}
}

// Copy bundle.*.css -> ../public/bundle.css
if (existsSync(bundleDir)) {
const cssFiles = readdirSync(bundleDir).filter((f) => f.match(/^bundle\..+\.css$/));

if (cssFiles.length > 0) {
copyFileSync(resolve(bundleDir, cssFiles[0]), resolve('../public/bundle.css'));
console.log(`✓ Copied ${cssFiles[0]} -> bundle.css`);
}
}
} catch (error) {
console.error('Failed to update index.html:', error);
}
}, 100);
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
</p>
{:else}
<p class="text-xs text-muted-foreground">
Press <kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">{modKey} + Enter</kbd> to send,
Press <kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">{modKey} + Enter</kbd> to
send,
<kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">Enter</kbd> for new line
</p>
{/if}
Expand Down
3 changes: 3 additions & 0 deletions tools/server/webui/svelte.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const config = {
},
alias: {
$styles: 'src/styles'
},
version: {
name: 'llama-server-webui'
}
},

Expand Down
99 changes: 14 additions & 85 deletions tools/server/webui/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,108 +1,33 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { readFileSync, writeFileSync, existsSync, readdirSync, copyFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

import { defineConfig, searchForWorkspaceRoot } from 'vite';
import devtoolsJson from 'vite-plugin-devtools-json';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
import { llamaCppBuildPlugin } from './scripts/vite-plugin-llama-cpp-build';

const __dirname = dirname(fileURLToPath(import.meta.url));

const GUIDE_FOR_FRONTEND = `
<!--
This is a single file build of the frontend.
It is automatically generated by the build process.
Do not edit this file directly.
To make changes, refer to the "Web UI" section in the README.
-->
`.trim();

/**
* the maximum size of an embedded asset in bytes,
* e.g. maximum size of embedded font (see node_modules/katex/dist/fonts/*.woff2)
*/
const MAX_ASSET_SIZE = 32000;

/** public/index.html minified flag */
const ENABLE_JS_MINIFICATION = true;

function llamaCppBuildPlugin() {
return {
name: 'llamacpp:build',
apply: 'build' as const,
closeBundle() {
// Ensure the SvelteKit adapter has finished writing to ../public
setTimeout(() => {
try {
const indexPath = resolve('../public/index.html');

if (!existsSync(indexPath)) {
return;
}

let content = readFileSync(indexPath, 'utf-8');

const faviconPath = resolve('static/favicon.svg');
if (existsSync(faviconPath)) {
const faviconContent = readFileSync(faviconPath, 'utf-8');
const faviconBase64 = Buffer.from(faviconContent).toString('base64');
const faviconDataUrl = `data:image/svg+xml;base64,${faviconBase64}`;

content = content.replace(/href="[^"]*favicon\.svg"/g, `href="${faviconDataUrl}"`);

console.log('✓ Inlined favicon.svg as base64 data URL');
}

content = content.replace(/\r/g, '');
content = GUIDE_FOR_FRONTEND + '\n' + content;
content = content.replace(/\/_app\/immutable\/bundle\.[^"]+\.js/g, './bundle.js');
content = content.replace(
/\/_app\/immutable\/assets\/bundle\.[^"]+\.css/g,
'./bundle.css'
);

writeFileSync(indexPath, content, 'utf-8');
console.log('✓ Updated index.html');

// Copy bundle.*.js -> ../public/bundle.js
const immutableDir = resolve('../public/_app/immutable');
const bundleDir = resolve('../public/_app/immutable/assets');
if (existsSync(immutableDir)) {
const jsFiles = readdirSync(immutableDir).filter((f) => f.match(/^bundle\..+\.js$/));
if (jsFiles.length > 0) {
copyFileSync(resolve(immutableDir, jsFiles[0]), resolve('../public/bundle.js'));
console.log(`✓ Copied ${jsFiles[0]} -> bundle.js`);
}
}
// Copy bundle.*.css -> ../public/bundle.css
if (existsSync(bundleDir)) {
const cssFiles = readdirSync(bundleDir).filter((f) => f.match(/^bundle\..+\.css$/));
if (cssFiles.length > 0) {
copyFileSync(resolve(bundleDir, cssFiles[0]), resolve('../public/bundle.css'));
console.log(`✓ Copied ${cssFiles[0]} -> bundle.css`);
}
}
} catch (error) {
console.error('Failed to update index.html:', error);
}
}, 100);
}
};
}

export default defineConfig({
resolve: {
alias: {
'katex-fonts': resolve('node_modules/katex/dist/fonts')
}
},

build: {
assetsInlineLimit: MAX_ASSET_SIZE,
assetsInlineLimit: 32000,
chunkSizeWarningLimit: 3072,
minify: ENABLE_JS_MINIFICATION
minify: true
},

esbuild: {
lineLimit: 500,
minifyIdentifiers: false
},

css: {
preprocessorOptions: {
scss: {
Expand All @@ -114,7 +39,9 @@ export default defineConfig({
}
}
},

plugins: [tailwindcss(), sveltekit(), devtoolsJson(), llamaCppBuildPlugin()],

test: {
projects: [
{
Expand All @@ -131,6 +58,7 @@ export default defineConfig({
setupFiles: ['./vitest-setup-client.ts']
}
},

{
extends: './vite.config.ts',
test: {
Expand All @@ -139,6 +67,7 @@ export default defineConfig({
include: ['tests/unit/**/*.{test,spec}.{js,ts}']
}
},

{
extends: './vite.config.ts',
test: {
Expand Down