Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub (part 9)
Browse files- backend/llama.cpp/tools/ui/tests/unit/pwa.spec.ts +193 -0
- backend/llama.cpp/tools/ui/tests/unit/reasoning-context.test.ts +89 -0
- backend/llama.cpp/tools/ui/tests/unit/redact.test.ts +20 -0
- backend/llama.cpp/tools/ui/tests/unit/request-helpers.test.ts +124 -0
- backend/llama.cpp/tools/ui/tests/unit/sanitize-headers.test.ts +73 -0
- backend/llama.cpp/tools/ui/tests/unit/stream-discovery.test.ts +74 -0
- backend/llama.cpp/tools/ui/tests/unit/stream-resume.test.ts +128 -0
- backend/llama.cpp/tools/ui/tests/unit/uri-template.test.ts +176 -0
- backend/llama.cpp/tools/ui/tsconfig.json +34 -0
- backend/llama.cpp/tools/ui/vite.config.ts +106 -0
- backend/llama.cpp/tools/ui/vitest-setup-client.ts +80 -0
- backend/llama.cpp/tools/ui/vitest.shims.d.ts +1 -0
- backend/llama.cpp/ty.toml +32 -0
- backend/llama.cpp/vendor/cpp-httplib/CMakeLists.txt +195 -0
- backend/llama.cpp/vendor/cpp-httplib/LICENSE +22 -0
- backend/llama.cpp/vendor/cpp-httplib/httplib.cpp +0 -0
- backend/llama.cpp/vendor/cpp-httplib/httplib.h +0 -0
- backend/llama.cpp/vendor/miniaudio/miniaudio.h +0 -0
- backend/llama.cpp/vendor/nlohmann/json.hpp +0 -0
- backend/llama.cpp/vendor/nlohmann/json_fwd.hpp +187 -0
backend/llama.cpp/tools/ui/tests/unit/pwa.spec.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
| 2 |
+
import { resolve } from 'node:path';
|
| 3 |
+
import { describe, expect, it } from 'vitest';
|
| 4 |
+
|
| 5 |
+
const DIST_DIR = resolve(__dirname, '../../dist');
|
| 6 |
+
const distExists = existsSync(DIST_DIR);
|
| 7 |
+
|
| 8 |
+
// PWA Build Output tests are integration tests that require a built dist/.
|
| 9 |
+
// CI builds first then runs these tests; local devs should run `npm run build` or use `npm run test:pwa`.
|
| 10 |
+
describe('PWA Build Output', () => {
|
| 11 |
+
if (!distExists) {
|
| 12 |
+
console.warn(`⚠ Skipping PWA Build Output tests - dist/ not found (run 'npm run build' first)`);
|
| 13 |
+
it('skipped - dist/ not found', () => {});
|
| 14 |
+
return;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
const swContent = readFileSync(resolve(DIST_DIR, 'sw.js'), 'utf-8');
|
| 18 |
+
const indexContent = readFileSync(resolve(DIST_DIR, 'index.html'), 'utf-8');
|
| 19 |
+
|
| 20 |
+
describe('Core files exist', () => {
|
| 21 |
+
it('service worker (sw.js) exists', () => {
|
| 22 |
+
expect(existsSync(resolve(DIST_DIR, 'sw.js')), 'sw.js not found').toBeTruthy();
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
it('workbox library exists (hashed filename)', () => {
|
| 26 |
+
// SvelteKit generates workbox-{hash}.js files
|
| 27 |
+
const files = readdirSync(DIST_DIR).filter((f) => f.match(/^workbox-[^.]+\.js$/));
|
| 28 |
+
expect(files.length).toBeGreaterThan(0);
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
it('manifest.webmanifest exists', () => {
|
| 32 |
+
expect(
|
| 33 |
+
existsSync(resolve(DIST_DIR, 'manifest.webmanifest')),
|
| 34 |
+
'manifest.webmanifest not found'
|
| 35 |
+
).toBeTruthy();
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
it('SvelteKit bundle.js exists in _app/immutable/', () => {
|
| 39 |
+
// SvelteKit generates hashed bundle names in _app/immutable/
|
| 40 |
+
const appDir = resolve(DIST_DIR, '_app', 'immutable');
|
| 41 |
+
expect(existsSync(appDir), '_app/immutable/ not found').toBeTruthy();
|
| 42 |
+
const files = readdirSync(appDir).filter((f) => f.startsWith('bundle.') && f.endsWith('.js'));
|
| 43 |
+
expect(files.length).toBeGreaterThan(0);
|
| 44 |
+
});
|
| 45 |
+
|
| 46 |
+
it('SvelteKit bundle.css exists in _app/immutable/assets/', () => {
|
| 47 |
+
// SvelteKit generates hashed CSS bundles in _app/immutable/assets/
|
| 48 |
+
const cssDir = resolve(DIST_DIR, '_app', 'immutable', 'assets');
|
| 49 |
+
expect(existsSync(cssDir), '_app/immutable/assets/ not found').toBeTruthy();
|
| 50 |
+
const files = readdirSync(cssDir).filter(
|
| 51 |
+
(f) => f.startsWith('bundle.') && f.endsWith('.css')
|
| 52 |
+
);
|
| 53 |
+
expect(files.length).toBeGreaterThan(0);
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
it('version.json exists in _app/', () => {
|
| 57 |
+
// SvelteKit stores version.json in _app directory
|
| 58 |
+
expect(
|
| 59 |
+
existsSync(resolve(DIST_DIR, '_app', 'version.json')),
|
| 60 |
+
'_app/version.json not found'
|
| 61 |
+
).toBeTruthy();
|
| 62 |
+
});
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
describe('version.json content', () => {
|
| 66 |
+
it('has valid JSON with version field', () => {
|
| 67 |
+
const content = readFileSync(resolve(DIST_DIR, '_app', 'version.json'), 'utf-8');
|
| 68 |
+
const parsed = JSON.parse(content);
|
| 69 |
+
expect(parsed).toHaveProperty('version');
|
| 70 |
+
expect(typeof parsed.version).toBe('string');
|
| 71 |
+
expect(parsed.version.length).toBeGreaterThan(0);
|
| 72 |
+
});
|
| 73 |
+
});
|
| 74 |
+
|
| 75 |
+
describe('Service worker content', () => {
|
| 76 |
+
it('service worker has minified self.define format', () => {
|
| 77 |
+
expect(swContent).toBeTruthy();
|
| 78 |
+
// SvelteKit's workbox-plugin-sveltekit produces a minified SW with self.define
|
| 79 |
+
expect(swContent).toMatch(/if\(!self.define\)/);
|
| 80 |
+
});
|
| 81 |
+
|
| 82 |
+
it('references hashed workbox file (SvelteKit build output)', () => {
|
| 83 |
+
expect(swContent).toBeTruthy();
|
| 84 |
+
// SvelteKit's workbox-plugin-sveltekit references hashed workbox files
|
| 85 |
+
expect(swContent).toMatch(/define\(\["\.\/workbox-[a-zA-Z0-9]+"\]/);
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
it('precache contains SvelteKit bundle.js with content hash', () => {
|
| 89 |
+
expect(swContent).toBeTruthy();
|
| 90 |
+
// SvelteKit uses content-hashed bundle names in _app/immutable/
|
| 91 |
+
expect(swContent).toMatch(/"_app\/immutable\/bundle\.[a-zA-Z0-9_-]+\.js"/);
|
| 92 |
+
});
|
| 93 |
+
|
| 94 |
+
it('precache contains SvelteKit bundle.css with content hash', () => {
|
| 95 |
+
expect(swContent).toBeTruthy();
|
| 96 |
+
// SvelteKit uses content-hashed CSS bundle names in _app/immutable/assets/
|
| 97 |
+
expect(swContent).toMatch(/"_app\/immutable\/assets\/bundle\.[a-zA-Z0-9_-]+\.css"/);
|
| 98 |
+
});
|
| 99 |
+
|
| 100 |
+
it('precache contains _app/version.json', () => {
|
| 101 |
+
expect(swContent).toBeTruthy();
|
| 102 |
+
// SvelteKit stores version.json in _app directory
|
| 103 |
+
expect(swContent).toMatch(/"_app\/version\.json"/);
|
| 104 |
+
});
|
| 105 |
+
|
| 106 |
+
it('precache contains manifest.webmanifest', () => {
|
| 107 |
+
expect(swContent).toBeTruthy();
|
| 108 |
+
expect(swContent).toMatch(/"manifest\.webmanifest"/);
|
| 109 |
+
});
|
| 110 |
+
|
| 111 |
+
it('no navigation route — API endpoints bypass PWA', () => {
|
| 112 |
+
expect(swContent).toBeTruthy();
|
| 113 |
+
// NavigationRoute is intentionally absent so direct browser
|
| 114 |
+
// navigation to server API endpoints returns JSON, not HTML.
|
| 115 |
+
expect(swContent).not.toMatch(/NavigationRoute/);
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
it('has runtime caching for API routes', () => {
|
| 119 |
+
expect(swContent).toBeTruthy();
|
| 120 |
+
expect(swContent).toMatch(/api-cache/);
|
| 121 |
+
expect(swContent).toMatch(/NetworkFirst/);
|
| 122 |
+
});
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
describe('index.html content', () => {
|
| 126 |
+
it('has modulepreload link for SvelteKit bundle with content hash', () => {
|
| 127 |
+
expect(indexContent).toBeTruthy();
|
| 128 |
+
// SvelteKit generates hashed bundle names in _app/immutable/
|
| 129 |
+
expect(indexContent).toMatch(/href="(\.\/|\/)_app\/immutable\/bundle\.[a-zA-Z0-9_-]+\.js"/);
|
| 130 |
+
});
|
| 131 |
+
|
| 132 |
+
it('has stylesheet link for SvelteKit bundle.css with content hash', () => {
|
| 133 |
+
expect(indexContent).toBeTruthy();
|
| 134 |
+
expect(indexContent).toMatch(
|
| 135 |
+
/href="(\.\/|\/)_app\/immutable\/assets\/bundle\.[a-zA-Z0-9_-]+\.css"/
|
| 136 |
+
);
|
| 137 |
+
});
|
| 138 |
+
|
| 139 |
+
it('has dynamic import for SvelteKit bundle with content hash', () => {
|
| 140 |
+
expect(indexContent).toBeTruthy();
|
| 141 |
+
expect(indexContent).toMatch(
|
| 142 |
+
/import\("(\.\/|\/)_app\/immutable\/bundle\.[a-zA-Z0-9_-]+\.js"\)/
|
| 143 |
+
);
|
| 144 |
+
});
|
| 145 |
+
|
| 146 |
+
it('has __sveltekit__ variable (SvelteKit adds hash suffix)', () => {
|
| 147 |
+
expect(indexContent).toBeTruthy();
|
| 148 |
+
// SvelteKit 2.x uses __sveltekit__ as base with random suffix
|
| 149 |
+
expect(indexContent).toMatch(/__sveltekit_[a-zA-Z0-9-]+/);
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
it('has PWA manifest link', () => {
|
| 153 |
+
expect(indexContent).toBeTruthy();
|
| 154 |
+
expect(indexContent).toMatch(/rel="manifest" href="(\.?\/)?manifest\.webmanifest"/);
|
| 155 |
+
});
|
| 156 |
+
|
| 157 |
+
it('has apple-touch-icon link', () => {
|
| 158 |
+
expect(indexContent).toBeTruthy();
|
| 159 |
+
expect(indexContent).toMatch(/rel="apple-touch-icon"/);
|
| 160 |
+
});
|
| 161 |
+
|
| 162 |
+
it('has _app paths for SvelteKit bundles', () => {
|
| 163 |
+
expect(indexContent).toBeTruthy();
|
| 164 |
+
// SvelteKit uses _app paths for hashed assets
|
| 165 |
+
expect(indexContent).toMatch(/_app\//);
|
| 166 |
+
});
|
| 167 |
+
});
|
| 168 |
+
|
| 169 |
+
describe('SvelteKit _app directory', () => {
|
| 170 |
+
it('_app directory exists (SvelteKit uses it for hashed assets)', () => {
|
| 171 |
+
expect(existsSync(resolve(DIST_DIR, '_app'))).toBeTruthy();
|
| 172 |
+
});
|
| 173 |
+
});
|
| 174 |
+
|
| 175 |
+
describe('Hashed workbox files', () => {
|
| 176 |
+
it('workbox-*.js files exist in dist root (SvelteKit build output)', () => {
|
| 177 |
+
const files = readdirSync(DIST_DIR).filter((f) => f.match(/^workbox-[^.]+\.js$/));
|
| 178 |
+
expect(files.length).toBeGreaterThan(0);
|
| 179 |
+
});
|
| 180 |
+
});
|
| 181 |
+
|
| 182 |
+
describe('Static assets', () => {
|
| 183 |
+
it('has favicon.ico', () => {
|
| 184 |
+
expect(existsSync(resolve(DIST_DIR, 'favicon.ico'))).toBeTruthy();
|
| 185 |
+
});
|
| 186 |
+
|
| 187 |
+
it('has PWA icons', () => {
|
| 188 |
+
expect(existsSync(resolve(DIST_DIR, 'pwa-64x64.png'))).toBeTruthy();
|
| 189 |
+
expect(existsSync(resolve(DIST_DIR, 'pwa-192x192.png'))).toBeTruthy();
|
| 190 |
+
expect(existsSync(resolve(DIST_DIR, 'pwa-512x512.png'))).toBeTruthy();
|
| 191 |
+
});
|
| 192 |
+
});
|
| 193 |
+
});
|
backend/llama.cpp/tools/ui/tests/unit/reasoning-context.test.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from 'vitest';
|
| 2 |
+
import { MessageRole } from '$lib/enums';
|
| 3 |
+
|
| 4 |
+
/**
|
| 5 |
+
* Tests for the new reasoning content handling.
|
| 6 |
+
* In the new architecture, reasoning content is stored in a dedicated
|
| 7 |
+
* `reasoningContent` field on DatabaseMessage, not embedded in content with tags.
|
| 8 |
+
* The API sends it as `reasoning_content` on ApiChatMessageData.
|
| 9 |
+
*/
|
| 10 |
+
|
| 11 |
+
describe('reasoning content in new structured format', () => {
|
| 12 |
+
it('reasoning is stored as separate field, not in content', () => {
|
| 13 |
+
// Simulate what the new chat store does
|
| 14 |
+
const message = {
|
| 15 |
+
content: 'The answer is 4.',
|
| 16 |
+
reasoningContent: 'Let me think: 2+2=4, basic arithmetic.'
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
// Content should be clean
|
| 20 |
+
expect(message.content).not.toContain('<<<');
|
| 21 |
+
expect(message.content).toBe('The answer is 4.');
|
| 22 |
+
|
| 23 |
+
// Reasoning in dedicated field
|
| 24 |
+
expect(message.reasoningContent).toBe('Let me think: 2+2=4, basic arithmetic.');
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
it('convertDbMessageToApiChatMessageData includes reasoning_content', () => {
|
| 28 |
+
// Simulate the conversion logic
|
| 29 |
+
const dbMessage = {
|
| 30 |
+
role: MessageRole.ASSISTANT,
|
| 31 |
+
content: 'The answer is 4.',
|
| 32 |
+
reasoningContent: 'Let me think: 2+2=4, basic arithmetic.'
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
const apiMessage: Record<string, unknown> = {
|
| 36 |
+
role: dbMessage.role,
|
| 37 |
+
content: dbMessage.content
|
| 38 |
+
};
|
| 39 |
+
if (dbMessage.reasoningContent) {
|
| 40 |
+
apiMessage.reasoning_content = dbMessage.reasoningContent;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
expect(apiMessage.content).toBe('The answer is 4.');
|
| 44 |
+
expect(apiMessage.reasoning_content).toBe('Let me think: 2+2=4, basic arithmetic.');
|
| 45 |
+
// No internal tags leak into either field
|
| 46 |
+
expect(apiMessage.content).not.toContain('<<<');
|
| 47 |
+
expect(apiMessage.reasoning_content).not.toContain('<<<');
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
it('API message excludes reasoning when excludeReasoningFromContext is true', () => {
|
| 51 |
+
const dbMessage = {
|
| 52 |
+
role: MessageRole.ASSISTANT,
|
| 53 |
+
content: 'The answer is 4.',
|
| 54 |
+
reasoningContent: 'internal thinking'
|
| 55 |
+
};
|
| 56 |
+
|
| 57 |
+
const excludeReasoningFromContext = true;
|
| 58 |
+
|
| 59 |
+
const apiMessage: Record<string, unknown> = {
|
| 60 |
+
role: dbMessage.role,
|
| 61 |
+
content: dbMessage.content
|
| 62 |
+
};
|
| 63 |
+
if (!excludeReasoningFromContext && dbMessage.reasoningContent) {
|
| 64 |
+
apiMessage.reasoning_content = dbMessage.reasoningContent;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
expect(apiMessage.content).toBe('The answer is 4.');
|
| 68 |
+
expect(apiMessage.reasoning_content).toBeUndefined();
|
| 69 |
+
});
|
| 70 |
+
|
| 71 |
+
it('handles messages with no reasoning', () => {
|
| 72 |
+
const dbMessage = {
|
| 73 |
+
role: MessageRole.ASSISTANT,
|
| 74 |
+
content: 'No reasoning here.',
|
| 75 |
+
reasoningContent: undefined
|
| 76 |
+
};
|
| 77 |
+
|
| 78 |
+
const apiMessage: Record<string, unknown> = {
|
| 79 |
+
role: dbMessage.role,
|
| 80 |
+
content: dbMessage.content
|
| 81 |
+
};
|
| 82 |
+
if (dbMessage.reasoningContent) {
|
| 83 |
+
apiMessage.reasoning_content = dbMessage.reasoningContent;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
expect(apiMessage.content).toBe('No reasoning here.');
|
| 87 |
+
expect(apiMessage.reasoning_content).toBeUndefined();
|
| 88 |
+
});
|
| 89 |
+
});
|
backend/llama.cpp/tools/ui/tests/unit/redact.test.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, expect, it } from 'vitest';
|
| 2 |
+
import { redactValue } from '$lib/utils/redact';
|
| 3 |
+
|
| 4 |
+
describe('redactValue', () => {
|
| 5 |
+
it('returns [redacted] by default', () => {
|
| 6 |
+
expect(redactValue('secret-token')).toBe('[redacted]');
|
| 7 |
+
});
|
| 8 |
+
|
| 9 |
+
it('shows last N characters when showLastChars is provided', () => {
|
| 10 |
+
expect(redactValue('session-abc12', 5)).toBe('....abc12');
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
it('handles value shorter than showLastChars', () => {
|
| 14 |
+
expect(redactValue('ab', 5)).toBe('....ab');
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
it('returns [redacted] when showLastChars is 0', () => {
|
| 18 |
+
expect(redactValue('secret', 0)).toBe('[redacted]');
|
| 19 |
+
});
|
| 20 |
+
});
|
backend/llama.cpp/tools/ui/tests/unit/request-helpers.test.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, expect, it } from 'vitest';
|
| 2 |
+
import {
|
| 3 |
+
getRequestUrl,
|
| 4 |
+
getRequestMethod,
|
| 5 |
+
getRequestBody,
|
| 6 |
+
summarizeRequestBody,
|
| 7 |
+
formatDiagnosticErrorMessage,
|
| 8 |
+
extractJsonRpcMethods
|
| 9 |
+
} from '$lib/utils/request-helpers';
|
| 10 |
+
|
| 11 |
+
describe('getRequestUrl', () => {
|
| 12 |
+
it('returns a plain string input as-is', () => {
|
| 13 |
+
expect(getRequestUrl('https://example.com/mcp')).toBe('https://example.com/mcp');
|
| 14 |
+
});
|
| 15 |
+
|
| 16 |
+
it('returns href from a URL object', () => {
|
| 17 |
+
expect(getRequestUrl(new URL('https://example.com/mcp'))).toBe('https://example.com/mcp');
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
it('returns url from a Request object', () => {
|
| 21 |
+
const req = new Request('https://example.com/mcp');
|
| 22 |
+
expect(getRequestUrl(req)).toBe('https://example.com/mcp');
|
| 23 |
+
});
|
| 24 |
+
});
|
| 25 |
+
|
| 26 |
+
describe('getRequestMethod', () => {
|
| 27 |
+
it('prefers method from init', () => {
|
| 28 |
+
expect(getRequestMethod('https://example.com', { method: 'POST' })).toBe('POST');
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
it('falls back to Request.method', () => {
|
| 32 |
+
const req = new Request('https://example.com', { method: 'PUT' });
|
| 33 |
+
expect(getRequestMethod(req)).toBe('PUT');
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
it('falls back to baseInit.method', () => {
|
| 37 |
+
expect(getRequestMethod('https://example.com', undefined, { method: 'DELETE' })).toBe('DELETE');
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
it('defaults to GET', () => {
|
| 41 |
+
expect(getRequestMethod('https://example.com')).toBe('GET');
|
| 42 |
+
});
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
describe('getRequestBody', () => {
|
| 46 |
+
it('returns body from init', () => {
|
| 47 |
+
expect(getRequestBody('https://example.com', { body: 'payload' })).toBe('payload');
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
it('returns undefined when no body is present', () => {
|
| 51 |
+
expect(getRequestBody('https://example.com')).toBeUndefined();
|
| 52 |
+
});
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
describe('summarizeRequestBody', () => {
|
| 56 |
+
it('returns empty for null', () => {
|
| 57 |
+
expect(summarizeRequestBody(null)).toEqual({ kind: 'empty' });
|
| 58 |
+
});
|
| 59 |
+
|
| 60 |
+
it('returns empty for undefined', () => {
|
| 61 |
+
expect(summarizeRequestBody(undefined)).toEqual({ kind: 'empty' });
|
| 62 |
+
});
|
| 63 |
+
|
| 64 |
+
it('returns string kind with size', () => {
|
| 65 |
+
expect(summarizeRequestBody('hello')).toEqual({ kind: 'string', size: 5 });
|
| 66 |
+
});
|
| 67 |
+
|
| 68 |
+
it('returns blob kind with size', () => {
|
| 69 |
+
const blob = new Blob(['abc']);
|
| 70 |
+
expect(summarizeRequestBody(blob)).toEqual({ kind: 'blob', size: 3 });
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
it('returns formdata kind', () => {
|
| 74 |
+
expect(summarizeRequestBody(new FormData())).toEqual({ kind: 'formdata' });
|
| 75 |
+
});
|
| 76 |
+
|
| 77 |
+
it('returns arraybuffer kind with size', () => {
|
| 78 |
+
expect(summarizeRequestBody(new ArrayBuffer(8))).toEqual({ kind: 'arraybuffer', size: 8 });
|
| 79 |
+
});
|
| 80 |
+
});
|
| 81 |
+
|
| 82 |
+
describe('formatDiagnosticErrorMessage', () => {
|
| 83 |
+
it('appends CORS hint for Failed to fetch', () => {
|
| 84 |
+
expect(formatDiagnosticErrorMessage(new TypeError('Failed to fetch'))).toBe(
|
| 85 |
+
'Failed to fetch (check CORS?)'
|
| 86 |
+
);
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
it('passes through other error messages unchanged', () => {
|
| 90 |
+
expect(formatDiagnosticErrorMessage(new Error('timeout'))).toBe('timeout');
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
it('handles non-Error values', () => {
|
| 94 |
+
expect(formatDiagnosticErrorMessage('some string')).toBe('some string');
|
| 95 |
+
});
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
describe('extractJsonRpcMethods', () => {
|
| 99 |
+
it('extracts methods from a JSON-RPC array', () => {
|
| 100 |
+
const body = JSON.stringify([
|
| 101 |
+
{ jsonrpc: '2.0', id: 1, method: 'initialize' },
|
| 102 |
+
{ jsonrpc: '2.0', method: 'notifications/initialized' }
|
| 103 |
+
]);
|
| 104 |
+
expect(extractJsonRpcMethods(body)).toEqual(['initialize', 'notifications/initialized']);
|
| 105 |
+
});
|
| 106 |
+
|
| 107 |
+
it('extracts method from a single JSON-RPC message', () => {
|
| 108 |
+
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' });
|
| 109 |
+
expect(extractJsonRpcMethods(body)).toEqual(['tools/list']);
|
| 110 |
+
});
|
| 111 |
+
|
| 112 |
+
it('returns undefined for non-string body', () => {
|
| 113 |
+
expect(extractJsonRpcMethods(null)).toBeUndefined();
|
| 114 |
+
expect(extractJsonRpcMethods(undefined)).toBeUndefined();
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
it('returns undefined for invalid JSON', () => {
|
| 118 |
+
expect(extractJsonRpcMethods('not json')).toBeUndefined();
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
it('returns undefined when no methods found', () => {
|
| 122 |
+
expect(extractJsonRpcMethods(JSON.stringify({ foo: 'bar' }))).toBeUndefined();
|
| 123 |
+
});
|
| 124 |
+
});
|
backend/llama.cpp/tools/ui/tests/unit/sanitize-headers.test.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, expect, it } from 'vitest';
|
| 2 |
+
import { sanitizeHeaders } from '$lib/utils/api-headers';
|
| 3 |
+
import { CORS_PROXY_HEADER_PREFIX } from '$lib/constants';
|
| 4 |
+
|
| 5 |
+
describe('sanitizeHeaders', () => {
|
| 6 |
+
it('returns empty object for undefined input', () => {
|
| 7 |
+
expect(sanitizeHeaders()).toEqual({});
|
| 8 |
+
});
|
| 9 |
+
|
| 10 |
+
it('passes through non-sensitive headers', () => {
|
| 11 |
+
const headers = new Headers({ 'content-type': 'application/json', accept: 'text/html' });
|
| 12 |
+
expect(sanitizeHeaders(headers)).toEqual({
|
| 13 |
+
'content-type': 'application/json',
|
| 14 |
+
accept: 'text/html'
|
| 15 |
+
});
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
it('redacts known sensitive headers', () => {
|
| 19 |
+
const headers = new Headers({
|
| 20 |
+
authorization: 'Bearer secret',
|
| 21 |
+
'x-api-key': 'key-123',
|
| 22 |
+
'content-type': 'application/json'
|
| 23 |
+
});
|
| 24 |
+
const result = sanitizeHeaders(headers);
|
| 25 |
+
expect(result.authorization).toBe('[redacted]');
|
| 26 |
+
expect(result['x-api-key']).toBe('[redacted]');
|
| 27 |
+
expect(result['content-type']).toBe('application/json');
|
| 28 |
+
});
|
| 29 |
+
|
| 30 |
+
it('partially redacts headers specified in partialRedactHeaders', () => {
|
| 31 |
+
const headers = new Headers({ 'mcp-session-id': 'session-12345' });
|
| 32 |
+
const partial = new Map([['mcp-session-id', 5]]);
|
| 33 |
+
expect(sanitizeHeaders(headers, undefined, partial)['mcp-session-id']).toBe('....12345');
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
it('fully redacts mcp-session-id when no partialRedactHeaders is given', () => {
|
| 37 |
+
const headers = new Headers({ 'mcp-session-id': 'session-12345' });
|
| 38 |
+
expect(sanitizeHeaders(headers)['mcp-session-id']).toBe('[redacted]');
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
it('redacts extra headers provided by the caller', () => {
|
| 42 |
+
const headers = new Headers({
|
| 43 |
+
'x-vendor-key': 'vendor-secret',
|
| 44 |
+
'content-type': 'application/json'
|
| 45 |
+
});
|
| 46 |
+
const result = sanitizeHeaders(headers, ['x-vendor-key']);
|
| 47 |
+
expect(result['x-vendor-key']).toBe('[redacted]');
|
| 48 |
+
expect(result['content-type']).toBe('application/json');
|
| 49 |
+
});
|
| 50 |
+
|
| 51 |
+
it('handles case-insensitive extra header names', () => {
|
| 52 |
+
const headers = new Headers({ 'X-Custom-Token': 'token-value' });
|
| 53 |
+
const result = sanitizeHeaders(headers, ['X-CUSTOM-TOKEN']);
|
| 54 |
+
expect(result['x-custom-token']).toBe('[redacted]');
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
it('redacts proxied sensitive and custom target headers', () => {
|
| 58 |
+
const proxiedAuthorization = `${CORS_PROXY_HEADER_PREFIX}authorization`;
|
| 59 |
+
const proxiedSessionId = `${CORS_PROXY_HEADER_PREFIX}mcp-session-id`;
|
| 60 |
+
const proxiedVendorKey = `${CORS_PROXY_HEADER_PREFIX}x-vendor-key`;
|
| 61 |
+
const headers = new Headers({
|
| 62 |
+
[proxiedAuthorization]: 'Bearer secret',
|
| 63 |
+
[proxiedSessionId]: 'session-12345',
|
| 64 |
+
[proxiedVendorKey]: 'vendor-secret'
|
| 65 |
+
});
|
| 66 |
+
const partial = new Map([['mcp-session-id', 5]]);
|
| 67 |
+
const result = sanitizeHeaders(headers, ['x-vendor-key'], partial);
|
| 68 |
+
|
| 69 |
+
expect(result[proxiedAuthorization]).toBe('[redacted]');
|
| 70 |
+
expect(result[proxiedSessionId]).toBe('....12345');
|
| 71 |
+
expect(result[proxiedVendorKey]).toBe('[redacted]');
|
| 72 |
+
});
|
| 73 |
+
});
|
backend/llama.cpp/tools/ui/tests/unit/stream-discovery.test.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, expect, it } from 'vitest';
|
| 2 |
+
import { ChatService } from '$lib/services/chat.service';
|
| 3 |
+
import type { ApiStreamSession } from '$lib/types';
|
| 4 |
+
|
| 5 |
+
function makeSession(overrides: Partial<ApiStreamSession>): ApiStreamSession {
|
| 6 |
+
return {
|
| 7 |
+
conversation_id: 'conv',
|
| 8 |
+
is_done: true,
|
| 9 |
+
total_bytes: 0,
|
| 10 |
+
started_at: 0,
|
| 11 |
+
completed_at: 0,
|
| 12 |
+
...overrides
|
| 13 |
+
};
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
describe('selectActiveStream', () => {
|
| 17 |
+
it('returns null on empty input', () => {
|
| 18 |
+
expect(ChatService.selectActiveStream([])).toBeNull();
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
it('returns null on null or undefined input', () => {
|
| 22 |
+
expect(ChatService.selectActiveStream(null)).toBeNull();
|
| 23 |
+
expect(ChatService.selectActiveStream(undefined)).toBeNull();
|
| 24 |
+
});
|
| 25 |
+
|
| 26 |
+
it('returns the single session when it is running', () => {
|
| 27 |
+
const s = makeSession({ conversation_id: 'only', is_done: false, started_at: 42 });
|
| 28 |
+
expect(ChatService.selectActiveStream([s])).toBe(s);
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
it('returns null when the single session is finalized', () => {
|
| 32 |
+
const s = makeSession({ conversation_id: 'only', is_done: true, started_at: 42 });
|
| 33 |
+
expect(ChatService.selectActiveStream([s])).toBeNull();
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
it('prefers a still running session over a finalized one regardless of started_at', () => {
|
| 37 |
+
const finalized = makeSession({ conversation_id: 'old', is_done: true, started_at: 1000 });
|
| 38 |
+
const running = makeSession({ conversation_id: 'new', is_done: false, started_at: 10 });
|
| 39 |
+
expect(ChatService.selectActiveStream([finalized, running])?.conversation_id).toBe('new');
|
| 40 |
+
expect(ChatService.selectActiveStream([running, finalized])?.conversation_id).toBe('new');
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
+
it('among running sessions, picks the most recently started one', () => {
|
| 44 |
+
const a = makeSession({ conversation_id: 'a', is_done: false, started_at: 100 });
|
| 45 |
+
const b = makeSession({ conversation_id: 'b', is_done: false, started_at: 200 });
|
| 46 |
+
const c = makeSession({ conversation_id: 'c', is_done: false, started_at: 150 });
|
| 47 |
+
expect(ChatService.selectActiveStream([a, b, c])?.conversation_id).toBe('b');
|
| 48 |
+
expect(ChatService.selectActiveStream([c, a, b])?.conversation_id).toBe('b');
|
| 49 |
+
});
|
| 50 |
+
|
| 51 |
+
it('returns null when all sessions are finalized, the DB already holds the content', () => {
|
| 52 |
+
const a = makeSession({ conversation_id: 'a', is_done: true, started_at: 10 });
|
| 53 |
+
const b = makeSession({ conversation_id: 'b', is_done: true, started_at: 30 });
|
| 54 |
+
const c = makeSession({ conversation_id: 'c', is_done: true, started_at: 20 });
|
| 55 |
+
expect(ChatService.selectActiveStream([a, b, c])).toBeNull();
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
it('keeps the first match on ties when both are running with identical started_at', () => {
|
| 59 |
+
// reduce visits left to right, the initial accumulator stays unless a strictly greater value appears
|
| 60 |
+
const a = makeSession({ conversation_id: 'first', is_done: false, started_at: 50 });
|
| 61 |
+
const b = makeSession({ conversation_id: 'second', is_done: false, started_at: 50 });
|
| 62 |
+
expect(ChatService.selectActiveStream([a, b])?.conversation_id).toBe('first');
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
it('handles a typical realistic mix: two finalized old, one freshly running, one freshly finalized', () => {
|
| 66 |
+
const old1 = makeSession({ conversation_id: 'old1', is_done: true, started_at: 100 });
|
| 67 |
+
const old2 = makeSession({ conversation_id: 'old2', is_done: true, started_at: 200 });
|
| 68 |
+
const freshFin = makeSession({ conversation_id: 'freshFin', is_done: true, started_at: 500 });
|
| 69 |
+
const running = makeSession({ conversation_id: 'running', is_done: false, started_at: 400 });
|
| 70 |
+
expect(ChatService.selectActiveStream([old1, old2, freshFin, running])?.conversation_id).toBe(
|
| 71 |
+
'running'
|
| 72 |
+
);
|
| 73 |
+
});
|
| 74 |
+
});
|
backend/llama.cpp/tools/ui/tests/unit/stream-resume.test.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
| 2 |
+
|
| 3 |
+
// node env unit project has no DOM, install a minimal localStorage backed by a Map
|
| 4 |
+
beforeAll(() => {
|
| 5 |
+
const store = new Map<string, string>();
|
| 6 |
+
const polyfill: Storage = {
|
| 7 |
+
get length() {
|
| 8 |
+
return store.size;
|
| 9 |
+
},
|
| 10 |
+
clear: () => store.clear(),
|
| 11 |
+
getItem: (k) => (store.has(k) ? store.get(k)! : null),
|
| 12 |
+
key: (i) => Array.from(store.keys())[i] ?? null,
|
| 13 |
+
removeItem: (k) => {
|
| 14 |
+
store.delete(k);
|
| 15 |
+
},
|
| 16 |
+
setItem: (k, v) => {
|
| 17 |
+
store.set(k, String(v));
|
| 18 |
+
}
|
| 19 |
+
};
|
| 20 |
+
(globalThis as unknown as { localStorage: Storage }).localStorage = polyfill;
|
| 21 |
+
});
|
| 22 |
+
|
| 23 |
+
import { ChatService } from '$lib/services/chat.service';
|
| 24 |
+
import { STREAM_RESUME_LOCALSTORAGE_KEY_PREFIX } from '$lib/constants';
|
| 25 |
+
|
| 26 |
+
describe('ChatService stream resume', () => {
|
| 27 |
+
beforeEach(() => {
|
| 28 |
+
localStorage.clear();
|
| 29 |
+
});
|
| 30 |
+
afterEach(() => {
|
| 31 |
+
localStorage.clear();
|
| 32 |
+
});
|
| 33 |
+
|
| 34 |
+
it('returns null when no state exists for the conversation', () => {
|
| 35 |
+
expect(ChatService.getStreamState('conv-a')).toBeNull();
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
it('saves and reads back the byte count', () => {
|
| 39 |
+
ChatService.saveStreamState('conv-a', 4242);
|
| 40 |
+
const got = ChatService.getStreamState('conv-a');
|
| 41 |
+
expect(got).not.toBeNull();
|
| 42 |
+
expect(got!.bytesReceived).toBe(4242);
|
| 43 |
+
expect(typeof got!.updatedAt).toBe('number');
|
| 44 |
+
});
|
| 45 |
+
|
| 46 |
+
it('overwrites the previous byte count on a new save for the same conversation', () => {
|
| 47 |
+
ChatService.saveStreamState('conv-a', 100);
|
| 48 |
+
ChatService.saveStreamState('conv-a', 200);
|
| 49 |
+
const got = ChatService.getStreamState('conv-a');
|
| 50 |
+
expect(got!.bytesReceived).toBe(200);
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
it('keeps states for distinct conversations isolated', () => {
|
| 54 |
+
ChatService.saveStreamState('conv-a', 10);
|
| 55 |
+
ChatService.saveStreamState('conv-b', 20);
|
| 56 |
+
expect(ChatService.getStreamState('conv-a')!.bytesReceived).toBe(10);
|
| 57 |
+
expect(ChatService.getStreamState('conv-b')!.bytesReceived).toBe(20);
|
| 58 |
+
});
|
| 59 |
+
|
| 60 |
+
it('clears the state for a given conversation', () => {
|
| 61 |
+
ChatService.saveStreamState('conv-a', 10);
|
| 62 |
+
ChatService.clearStreamState('conv-a');
|
| 63 |
+
expect(ChatService.getStreamState('conv-a')).toBeNull();
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
it('ignores empty conversation id on save', () => {
|
| 67 |
+
ChatService.saveStreamState('', 1);
|
| 68 |
+
expect(ChatService.getStreamState('')).toBeNull();
|
| 69 |
+
});
|
| 70 |
+
|
| 71 |
+
it('returns null on corrupted storage payload', () => {
|
| 72 |
+
localStorage.setItem(`${STREAM_RESUME_LOCALSTORAGE_KEY_PREFIX}conv-a`, '{not-json');
|
| 73 |
+
expect(ChatService.getStreamState('conv-a')).toBeNull();
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
+
it('persists the model alongside the byte count', () => {
|
| 77 |
+
ChatService.saveStreamState('conv-a', 10, 'model-x');
|
| 78 |
+
expect(ChatService.getStreamState('conv-a')!.model).toBe('model-x');
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
it('stores a null model when none is provided', () => {
|
| 82 |
+
ChatService.saveStreamState('conv-a', 10);
|
| 83 |
+
expect(ChatService.getStreamState('conv-a')!.model).toBeNull();
|
| 84 |
+
});
|
| 85 |
+
|
| 86 |
+
it('overwrites the model on a new save for the same conversation', () => {
|
| 87 |
+
ChatService.saveStreamState('conv-a', 10, 'model-x');
|
| 88 |
+
ChatService.saveStreamState('conv-a', 20, 'model-y');
|
| 89 |
+
expect(ChatService.getStreamState('conv-a')!.model).toBe('model-y');
|
| 90 |
+
});
|
| 91 |
+
|
| 92 |
+
describe('resumeStreamIdentity', () => {
|
| 93 |
+
it('appends the persisted model so the resume key matches the frozen POST identity', () => {
|
| 94 |
+
ChatService.saveStreamState('conv-a', 10, 'model-x');
|
| 95 |
+
expect(
|
| 96 |
+
ChatService.resumeStreamIdentity('conv-a', ChatService.getStreamState('conv-a'), 'dropdown')
|
| 97 |
+
).toBe('conv-a::model-x');
|
| 98 |
+
});
|
| 99 |
+
|
| 100 |
+
it('keeps the bare conv id when the persisted model is null', () => {
|
| 101 |
+
ChatService.saveStreamState('conv-a', 10);
|
| 102 |
+
expect(
|
| 103 |
+
ChatService.resumeStreamIdentity('conv-a', ChatService.getStreamState('conv-a'), 'dropdown')
|
| 104 |
+
).toBe('conv-a');
|
| 105 |
+
});
|
| 106 |
+
|
| 107 |
+
it('falls back to the current model only when no state is persisted', () => {
|
| 108 |
+
expect(ChatService.resumeStreamIdentity('conv-a', null, 'dropdown')).toBe('conv-a::dropdown');
|
| 109 |
+
});
|
| 110 |
+
|
| 111 |
+
it('ignores the fallback when a state exists, the persisted value is authoritative', () => {
|
| 112 |
+
ChatService.saveStreamState('conv-a', 10, 'model-x');
|
| 113 |
+
expect(
|
| 114 |
+
ChatService.resumeStreamIdentity('conv-a', ChatService.getStreamState('conv-a'), 'dropdown')
|
| 115 |
+
).toBe('conv-a::model-x');
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
it('falls back when a legacy state has no model field', () => {
|
| 119 |
+
localStorage.setItem(
|
| 120 |
+
`${STREAM_RESUME_LOCALSTORAGE_KEY_PREFIX}conv-a`,
|
| 121 |
+
JSON.stringify({ bytesReceived: 10, updatedAt: 1 })
|
| 122 |
+
);
|
| 123 |
+
expect(
|
| 124 |
+
ChatService.resumeStreamIdentity('conv-a', ChatService.getStreamState('conv-a'), 'dropdown')
|
| 125 |
+
).toBe('conv-a::dropdown');
|
| 126 |
+
});
|
| 127 |
+
});
|
| 128 |
+
});
|
backend/llama.cpp/tools/ui/tests/unit/uri-template.test.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from 'vitest';
|
| 2 |
+
import {
|
| 3 |
+
extractTemplateVariables,
|
| 4 |
+
expandTemplate,
|
| 5 |
+
isTemplateComplete,
|
| 6 |
+
normalizeResourceUri
|
| 7 |
+
} from '../../src/lib/utils/uri-template';
|
| 8 |
+
import { URI_TEMPLATE_OPERATORS } from '../../src/lib/constants/uri-template';
|
| 9 |
+
|
| 10 |
+
describe('extractTemplateVariables', () => {
|
| 11 |
+
it('extracts simple variables', () => {
|
| 12 |
+
const vars = extractTemplateVariables('file:///{path}');
|
| 13 |
+
expect(vars).toEqual([{ name: 'path', operator: '' }]);
|
| 14 |
+
});
|
| 15 |
+
|
| 16 |
+
it('extracts multiple variables', () => {
|
| 17 |
+
const vars = extractTemplateVariables('db://{schema}/{table}');
|
| 18 |
+
expect(vars).toEqual([
|
| 19 |
+
{ name: 'schema', operator: '' },
|
| 20 |
+
{ name: 'table', operator: '' }
|
| 21 |
+
]);
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
it('extracts variables with operators', () => {
|
| 25 |
+
const vars = extractTemplateVariables('http://example.com{+path}');
|
| 26 |
+
expect(vars).toEqual([{ name: 'path', operator: URI_TEMPLATE_OPERATORS.RESERVED }]);
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
it('extracts comma-separated variable lists', () => {
|
| 30 |
+
const vars = extractTemplateVariables('{x,y,z}');
|
| 31 |
+
expect(vars).toEqual([
|
| 32 |
+
{ name: 'x', operator: '' },
|
| 33 |
+
{ name: 'y', operator: '' },
|
| 34 |
+
{ name: 'z', operator: '' }
|
| 35 |
+
]);
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
it('deduplicates variable names', () => {
|
| 39 |
+
const vars = extractTemplateVariables('{name}/{name}');
|
| 40 |
+
expect(vars).toEqual([{ name: 'name', operator: '' }]);
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
+
it('handles fragment expansion', () => {
|
| 44 |
+
const vars = extractTemplateVariables('http://example.com/page{#section}');
|
| 45 |
+
expect(vars).toEqual([{ name: 'section', operator: URI_TEMPLATE_OPERATORS.FRAGMENT }]);
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
+
it('handles path segment expansion', () => {
|
| 49 |
+
const vars = extractTemplateVariables('http://example.com{/path}');
|
| 50 |
+
expect(vars).toEqual([{ name: 'path', operator: URI_TEMPLATE_OPERATORS.PATH_SEGMENT }]);
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
it('returns empty array for template without variables', () => {
|
| 54 |
+
const vars = extractTemplateVariables('http://example.com/static');
|
| 55 |
+
expect(vars).toEqual([]);
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
it('strips explode modifier', () => {
|
| 59 |
+
const vars = extractTemplateVariables('{list*}');
|
| 60 |
+
expect(vars).toEqual([{ name: 'list', operator: '' }]);
|
| 61 |
+
});
|
| 62 |
+
|
| 63 |
+
it('strips prefix modifier', () => {
|
| 64 |
+
const vars = extractTemplateVariables('{value:5}');
|
| 65 |
+
expect(vars).toEqual([{ name: 'value', operator: '' }]);
|
| 66 |
+
});
|
| 67 |
+
});
|
| 68 |
+
|
| 69 |
+
describe('expandTemplate', () => {
|
| 70 |
+
it('expands simple variable', () => {
|
| 71 |
+
const result = expandTemplate('file:///{path}', { path: 'src/main.rs' });
|
| 72 |
+
expect(result).toBe('file:///src%2Fmain.rs');
|
| 73 |
+
});
|
| 74 |
+
|
| 75 |
+
it('expands reserved variable (no encoding)', () => {
|
| 76 |
+
const result = expandTemplate('file:///{+path}', { path: 'src/main.rs' });
|
| 77 |
+
expect(result).toBe('file:///src/main.rs');
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
it('expands multiple variables', () => {
|
| 81 |
+
const result = expandTemplate('db://{schema}/{table}', {
|
| 82 |
+
schema: 'public',
|
| 83 |
+
table: 'users'
|
| 84 |
+
});
|
| 85 |
+
expect(result).toBe('db://public/users');
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
it('leaves empty for missing variables', () => {
|
| 89 |
+
const result = expandTemplate('{missing}', {});
|
| 90 |
+
expect(result).toBe('');
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
it('expands fragment', () => {
|
| 94 |
+
const result = expandTemplate('http://example.com/page{#section}', {
|
| 95 |
+
section: 'intro'
|
| 96 |
+
});
|
| 97 |
+
expect(result).toBe('http://example.com/page#intro');
|
| 98 |
+
});
|
| 99 |
+
|
| 100 |
+
it('expands path segments', () => {
|
| 101 |
+
const result = expandTemplate('http://example.com{/path}', { path: 'docs' });
|
| 102 |
+
expect(result).toBe('http://example.com/docs');
|
| 103 |
+
});
|
| 104 |
+
|
| 105 |
+
it('expands query parameters', () => {
|
| 106 |
+
const result = expandTemplate('http://example.com{?q}', { q: 'search term' });
|
| 107 |
+
expect(result).toBe('http://example.com?q=search%20term');
|
| 108 |
+
});
|
| 109 |
+
|
| 110 |
+
it('expands multiple query parameters', () => {
|
| 111 |
+
const result = expandTemplate('http://example.com{?q,sort}', {
|
| 112 |
+
q: 'search term',
|
| 113 |
+
sort: 'descending'
|
| 114 |
+
});
|
| 115 |
+
expect(result).toBe('http://example.com?q=search%20term&sort=descending');
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
it('keeps static parts unchanged', () => {
|
| 119 |
+
const result = expandTemplate('http://example.com/static', {});
|
| 120 |
+
expect(result).toBe('http://example.com/static');
|
| 121 |
+
});
|
| 122 |
+
});
|
| 123 |
+
|
| 124 |
+
describe('isTemplateComplete', () => {
|
| 125 |
+
it('returns true when all variables are filled', () => {
|
| 126 |
+
expect(isTemplateComplete('file:///{path}', { path: 'test.txt' })).toBe(true);
|
| 127 |
+
});
|
| 128 |
+
|
| 129 |
+
it('returns false when a variable is missing', () => {
|
| 130 |
+
expect(isTemplateComplete('db://{schema}/{table}', { schema: 'public' })).toBe(false);
|
| 131 |
+
});
|
| 132 |
+
|
| 133 |
+
it('returns false when a variable is empty', () => {
|
| 134 |
+
expect(isTemplateComplete('file:///{path}', { path: '' })).toBe(false);
|
| 135 |
+
});
|
| 136 |
+
|
| 137 |
+
it('returns false when a variable is whitespace only', () => {
|
| 138 |
+
expect(isTemplateComplete('file:///{path}', { path: ' ' })).toBe(false);
|
| 139 |
+
});
|
| 140 |
+
|
| 141 |
+
it('returns true for template without variables', () => {
|
| 142 |
+
expect(isTemplateComplete('http://example.com/static', {})).toBe(true);
|
| 143 |
+
});
|
| 144 |
+
|
| 145 |
+
it('returns true when all multiple variables are filled', () => {
|
| 146 |
+
expect(isTemplateComplete('db://{schema}/{table}', { schema: 'public', table: 'users' })).toBe(
|
| 147 |
+
true
|
| 148 |
+
);
|
| 149 |
+
});
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
describe('normalizeResourceUri', () => {
|
| 153 |
+
it('passes through a normal URI unchanged', () => {
|
| 154 |
+
expect(normalizeResourceUri('svelte://svelte/$effect.md')).toBe('svelte://svelte/$effect.md');
|
| 155 |
+
});
|
| 156 |
+
|
| 157 |
+
it('normalizes triple-slash URIs from path-style template expansion', () => {
|
| 158 |
+
expect(normalizeResourceUri('svelte:///svelte/$effect.md')).toBe('svelte://svelte/$effect.md');
|
| 159 |
+
});
|
| 160 |
+
|
| 161 |
+
it('normalizes quadruple-slash URIs', () => {
|
| 162 |
+
expect(normalizeResourceUri('svelte:////svelte/$effect.md')).toBe('svelte://svelte/$effect.md');
|
| 163 |
+
});
|
| 164 |
+
|
| 165 |
+
it('handles file:// URIs', () => {
|
| 166 |
+
expect(normalizeResourceUri('file:///home/user/doc.txt')).toBe('file://home/user/doc.txt');
|
| 167 |
+
});
|
| 168 |
+
|
| 169 |
+
it('handles http URIs unchanged', () => {
|
| 170 |
+
expect(normalizeResourceUri('http://example.com/path')).toBe('http://example.com/path');
|
| 171 |
+
});
|
| 172 |
+
|
| 173 |
+
it('returns non-URI strings unchanged', () => {
|
| 174 |
+
expect(normalizeResourceUri('not-a-uri')).toBe('not-a-uri');
|
| 175 |
+
});
|
| 176 |
+
});
|
backend/llama.cpp/tools/ui/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"extends": "./.svelte-kit/tsconfig.json",
|
| 3 |
+
"compilerOptions": {
|
| 4 |
+
"allowJs": true,
|
| 5 |
+
"checkJs": true,
|
| 6 |
+
"esModuleInterop": true,
|
| 7 |
+
"forceConsistentCasingInFileNames": true,
|
| 8 |
+
"resolveJsonModule": true,
|
| 9 |
+
"skipLibCheck": true,
|
| 10 |
+
"sourceMap": true,
|
| 11 |
+
"strict": true,
|
| 12 |
+
"moduleResolution": "bundler"
|
| 13 |
+
},
|
| 14 |
+
"include": [
|
| 15 |
+
".svelte-kit/ambient.d.ts",
|
| 16 |
+
".svelte-kit/non-ambient.d.ts",
|
| 17 |
+
".svelte-kit/types/**/$types.d.ts",
|
| 18 |
+
"vite.config.js",
|
| 19 |
+
"vite.config.ts",
|
| 20 |
+
"src/**/*.js",
|
| 21 |
+
"src/**/*.ts",
|
| 22 |
+
"src/**/*.svelte",
|
| 23 |
+
"tests/**/*.ts",
|
| 24 |
+
"tests/**/*.svelte",
|
| 25 |
+
".storybook/**/*.ts",
|
| 26 |
+
".storybook/**/*.svelte"
|
| 27 |
+
],
|
| 28 |
+
"exclude": ["src/lib/services/sandbox-worker.js"]
|
| 29 |
+
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
| 30 |
+
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
| 31 |
+
//
|
| 32 |
+
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
| 33 |
+
// from the referenced tsconfig.json - TypeScript does not merge them in
|
| 34 |
+
}
|
backend/llama.cpp/tools/ui/vite.config.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import tailwindcss from '@tailwindcss/vite';
|
| 2 |
+
import { sveltekit } from '@sveltejs/kit/vite';
|
| 3 |
+
import { SvelteKitPWA } from '@vite-pwa/sveltekit';
|
| 4 |
+
import { dirname, resolve } from 'path';
|
| 5 |
+
import { fileURLToPath } from 'url';
|
| 6 |
+
|
| 7 |
+
import { defineConfig, searchForWorkspaceRoot } from 'vite';
|
| 8 |
+
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
|
| 9 |
+
import { splashScreenPlugin } from './scripts/vite-plugin-splash-screen';
|
| 10 |
+
import { buildInfoPlugin } from './scripts/vite-plugin-build-info';
|
| 11 |
+
import { relativizeBasePlugin } from './scripts/vite-plugin-relativize-base';
|
| 12 |
+
import { playwright } from '@vitest/browser-playwright';
|
| 13 |
+
import { SVELTEKIT_PWA_OPTIONS } from './src/lib/constants/pwa';
|
| 14 |
+
|
| 15 |
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
| 16 |
+
|
| 17 |
+
const SERVER_ORIGIN = import.meta.env?.VITE_PUBLIC_SERVER_ORIGIN || 'http://localhost:8080';
|
| 18 |
+
|
| 19 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 20 |
+
const browserBaseConfig: any = {
|
| 21 |
+
enabled: true,
|
| 22 |
+
provider: playwright({
|
| 23 |
+
launchOptions: {
|
| 24 |
+
args: ['--no-sandbox']
|
| 25 |
+
}
|
| 26 |
+
}),
|
| 27 |
+
instances: [{ browser: 'chromium' }]
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
export default defineConfig({
|
| 31 |
+
resolve: {
|
| 32 |
+
alias: {
|
| 33 |
+
'katex-fonts': resolve('node_modules/katex/dist/fonts')
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
|
| 37 |
+
build: {
|
| 38 |
+
assetsInlineLimit: 32000,
|
| 39 |
+
chunkSizeWarningLimit: 3072,
|
| 40 |
+
minify: true
|
| 41 |
+
},
|
| 42 |
+
|
| 43 |
+
plugins: [
|
| 44 |
+
tailwindcss(),
|
| 45 |
+
sveltekit(),
|
| 46 |
+
SvelteKitPWA(SVELTEKIT_PWA_OPTIONS),
|
| 47 |
+
splashScreenPlugin(),
|
| 48 |
+
buildInfoPlugin(),
|
| 49 |
+
relativizeBasePlugin()
|
| 50 |
+
],
|
| 51 |
+
|
| 52 |
+
test: {
|
| 53 |
+
projects: [
|
| 54 |
+
{
|
| 55 |
+
extends: './vite.config.ts',
|
| 56 |
+
test: {
|
| 57 |
+
name: 'client',
|
| 58 |
+
browser: browserBaseConfig,
|
| 59 |
+
include: ['tests/client/**/*.svelte.{test,spec}.{js,ts}'],
|
| 60 |
+
setupFiles: ['./vitest-setup-client.ts']
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
|
| 64 |
+
{
|
| 65 |
+
extends: './vite.config.ts',
|
| 66 |
+
test: {
|
| 67 |
+
name: 'unit',
|
| 68 |
+
environment: 'node',
|
| 69 |
+
include: ['tests/unit/**/*.{test,spec}.{js,ts}']
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
|
| 73 |
+
{
|
| 74 |
+
extends: './vite.config.ts',
|
| 75 |
+
test: {
|
| 76 |
+
name: 'ui',
|
| 77 |
+
browser: { ...browserBaseConfig, instances: [{ browser: 'chromium', headless: true }] },
|
| 78 |
+
setupFiles: ['./.storybook/vitest.setup.ts']
|
| 79 |
+
},
|
| 80 |
+
plugins: [
|
| 81 |
+
storybookTest({
|
| 82 |
+
storybookScript: 'pnpm run storybook --no-open'
|
| 83 |
+
})
|
| 84 |
+
]
|
| 85 |
+
}
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
|
| 89 |
+
server: {
|
| 90 |
+
proxy: {
|
| 91 |
+
'/v1': SERVER_ORIGIN,
|
| 92 |
+
'/props': SERVER_ORIGIN,
|
| 93 |
+
'/models': SERVER_ORIGIN,
|
| 94 |
+
'/tools': SERVER_ORIGIN,
|
| 95 |
+
'/slots': SERVER_ORIGIN,
|
| 96 |
+
'/cors-proxy': SERVER_ORIGIN
|
| 97 |
+
},
|
| 98 |
+
headers: {
|
| 99 |
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
| 100 |
+
'Cross-Origin-Opener-Policy': 'same-origin'
|
| 101 |
+
},
|
| 102 |
+
fs: {
|
| 103 |
+
allow: [searchForWorkspaceRoot(process.cwd()), resolve(__dirname, 'tests')]
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
});
|
backend/llama.cpp/tools/ui/vitest-setup-client.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="@vitest/browser/matchers" />
|
| 2 |
+
/// <reference types="@vitest/browser/providers/playwright" />
|
| 3 |
+
|
| 4 |
+
import { beforeEach, vi } from 'vitest';
|
| 5 |
+
|
| 6 |
+
// Mock fetch for API calls during client tests.
|
| 7 |
+
// In test environment there is no backend server, so we intercept
|
| 8 |
+
// the specific endpoints the app uses and return valid mock data.
|
| 9 |
+
beforeEach(() => {
|
| 10 |
+
const originalFetch = globalThis.fetch;
|
| 11 |
+
|
| 12 |
+
vi.spyOn(globalThis, 'fetch').mockImplementation(
|
| 13 |
+
async (input: RequestInfo | URL, init?: RequestInit) => {
|
| 14 |
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
| 15 |
+
|
| 16 |
+
// Mock server props endpoint
|
| 17 |
+
if (url.includes('/server')) {
|
| 18 |
+
return new Response(
|
| 19 |
+
JSON.stringify({
|
| 20 |
+
mode: 'router',
|
| 21 |
+
version: 'test',
|
| 22 |
+
git_commit: 'test',
|
| 23 |
+
git_branch: 'test'
|
| 24 |
+
}),
|
| 25 |
+
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
| 26 |
+
);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Mock models list endpoint
|
| 30 |
+
if (/\/v1\/models|\/models\b/.test(url)) {
|
| 31 |
+
return new Response(
|
| 32 |
+
JSON.stringify({
|
| 33 |
+
object: 'list',
|
| 34 |
+
data: [
|
| 35 |
+
{
|
| 36 |
+
id: 'test-model.gguf',
|
| 37 |
+
object: 'model',
|
| 38 |
+
owned_by: 'llamacpp',
|
| 39 |
+
created: 0,
|
| 40 |
+
in_cache: false,
|
| 41 |
+
path: 'models/test-model.gguf',
|
| 42 |
+
status: { value: 'unloaded' },
|
| 43 |
+
meta: {}
|
| 44 |
+
}
|
| 45 |
+
],
|
| 46 |
+
models: [
|
| 47 |
+
{
|
| 48 |
+
model: 'test-model.gguf',
|
| 49 |
+
name: 'Test Model',
|
| 50 |
+
details: {}
|
| 51 |
+
}
|
| 52 |
+
]
|
| 53 |
+
}),
|
| 54 |
+
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
| 55 |
+
);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Mock /props endpoint (used for modalities)
|
| 59 |
+
if (url.includes('/props')) {
|
| 60 |
+
return new Response(
|
| 61 |
+
JSON.stringify({
|
| 62 |
+
default_generation_settings: { n_ctx: 2048 }
|
| 63 |
+
}),
|
| 64 |
+
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
| 65 |
+
);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
// Mock /tools endpoint (used for built-in tools list)
|
| 69 |
+
if (url.includes('/tools')) {
|
| 70 |
+
return new Response(JSON.stringify([]), {
|
| 71 |
+
status: 200,
|
| 72 |
+
headers: { 'Content-Type': 'application/json' }
|
| 73 |
+
});
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Default: use real fetch
|
| 77 |
+
return originalFetch(input, init);
|
| 78 |
+
}
|
| 79 |
+
);
|
| 80 |
+
});
|
backend/llama.cpp/tools/ui/vitest.shims.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="@vitest/browser-playwright" />
|
backend/llama.cpp/ty.toml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[environment]
|
| 2 |
+
extra-paths = ["./gguf-py", "./examples/model-conversion/scripts", "./tools/server/tests", "./scripts/snapdragon/qdc/tests"]
|
| 3 |
+
python-version = "3.10"
|
| 4 |
+
|
| 5 |
+
[rules]
|
| 6 |
+
deprecated = "warn"
|
| 7 |
+
|
| 8 |
+
[src]
|
| 9 |
+
exclude = [
|
| 10 |
+
"./tools/mtmd/legacy-models/**",
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
[[overrides]]
|
| 14 |
+
include = [
|
| 15 |
+
"./tools/server/tests/**",
|
| 16 |
+
"./scripts/snapdragon/qdc/**",
|
| 17 |
+
"./tools/mtmd/tests/**",
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
[overrides.rules]
|
| 21 |
+
unresolved-reference = "ignore"
|
| 22 |
+
unresolved-import = "ignore"
|
| 23 |
+
unresolved-attribute = "ignore"
|
| 24 |
+
|
| 25 |
+
[[overrides]]
|
| 26 |
+
include = [
|
| 27 |
+
"./examples/pydantic_models_to_grammar.py",
|
| 28 |
+
]
|
| 29 |
+
|
| 30 |
+
[overrides.rules]
|
| 31 |
+
unsupported-operator = "ignore"
|
| 32 |
+
not-subscriptable = "ignore"
|
backend/llama.cpp/vendor/cpp-httplib/CMakeLists.txt
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
set(TARGET cpp-httplib)
|
| 2 |
+
license_add_file("cpp-httplib" "LICENSE")
|
| 3 |
+
|
| 4 |
+
find_package(Threads REQUIRED)
|
| 5 |
+
|
| 6 |
+
llama_add_compile_flags()
|
| 7 |
+
|
| 8 |
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
| 9 |
+
|
| 10 |
+
add_library(${TARGET} STATIC httplib.cpp httplib.h)
|
| 11 |
+
|
| 12 |
+
# disable warnings in 3rd party code
|
| 13 |
+
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
| 14 |
+
target_compile_options(${TARGET} PRIVATE /w)
|
| 15 |
+
else()
|
| 16 |
+
target_compile_options(${TARGET} PRIVATE -w)
|
| 17 |
+
endif()
|
| 18 |
+
|
| 19 |
+
target_link_libraries(${TARGET} PRIVATE Threads::Threads)
|
| 20 |
+
|
| 21 |
+
if (WIN32 AND NOT MSVC)
|
| 22 |
+
target_link_libraries(${TARGET} PUBLIC ws2_32)
|
| 23 |
+
endif()
|
| 24 |
+
|
| 25 |
+
target_compile_features(${TARGET} PRIVATE cxx_std_17)
|
| 26 |
+
|
| 27 |
+
target_compile_definitions(${TARGET} PRIVATE
|
| 28 |
+
# increase max payload length to allow use of larger context size
|
| 29 |
+
CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH=1048576
|
| 30 |
+
# increase backlog size to avoid connection resets for >> 1 slots
|
| 31 |
+
CPPHTTPLIB_LISTEN_BACKLOG=512
|
| 32 |
+
# increase max URI length to handle longer prompts in query string
|
| 33 |
+
CPPHTTPLIB_REQUEST_URI_MAX_LENGTH=32768
|
| 34 |
+
# disable Nagle's algorithm
|
| 35 |
+
CPPHTTPLIB_TCP_NODELAY=1
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
set(OPENSSL_NO_ASM ON CACHE BOOL "Disable OpenSSL ASM code when building BoringSSL or LibreSSL")
|
| 39 |
+
|
| 40 |
+
if (LLAMA_BUILD_BORINGSSL)
|
| 41 |
+
set(FIPS OFF CACHE BOOL "Enable FIPS (BoringSSL)")
|
| 42 |
+
|
| 43 |
+
set(BORINGSSL_GIT "https://boringssl.googlesource.com/boringssl" CACHE STRING "BoringSSL git repository")
|
| 44 |
+
set(BORINGSSL_VERSION "0.20260616.0" CACHE STRING "BoringSSL version")
|
| 45 |
+
|
| 46 |
+
message(STATUS "Fetching BoringSSL version ${BORINGSSL_VERSION}")
|
| 47 |
+
|
| 48 |
+
set(BORINGSSL_ARGS
|
| 49 |
+
GIT_REPOSITORY ${BORINGSSL_GIT}
|
| 50 |
+
GIT_TAG ${BORINGSSL_VERSION}
|
| 51 |
+
)
|
| 52 |
+
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
|
| 53 |
+
list(APPEND BORINGSSL_ARGS EXCLUDE_FROM_ALL)
|
| 54 |
+
endif()
|
| 55 |
+
|
| 56 |
+
include(FetchContent)
|
| 57 |
+
FetchContent_Declare(boringssl ${BORINGSSL_ARGS})
|
| 58 |
+
|
| 59 |
+
set(SAVED_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
|
| 60 |
+
set(SAVED_BUILD_TESTING ${BUILD_TESTING})
|
| 61 |
+
|
| 62 |
+
set(BUILD_SHARED_LIBS OFF)
|
| 63 |
+
set(BUILD_TESTING OFF)
|
| 64 |
+
|
| 65 |
+
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
|
| 66 |
+
FetchContent_MakeAvailable(boringssl)
|
| 67 |
+
else()
|
| 68 |
+
FetchContent_GetProperties(boringssl)
|
| 69 |
+
if(NOT boringssl_POPULATED)
|
| 70 |
+
FetchContent_Populate(boringssl)
|
| 71 |
+
add_subdirectory(${boringssl_SOURCE_DIR} ${boringssl_BINARY_DIR} EXCLUDE_FROM_ALL)
|
| 72 |
+
endif()
|
| 73 |
+
endif()
|
| 74 |
+
|
| 75 |
+
set(BUILD_SHARED_LIBS ${SAVED_BUILD_SHARED_LIBS})
|
| 76 |
+
set(BUILD_TESTING ${SAVED_BUILD_TESTING})
|
| 77 |
+
|
| 78 |
+
license_add_file("BoringSSL" "${boringssl_SOURCE_DIR}/LICENSE")
|
| 79 |
+
|
| 80 |
+
set(CPPHTTPLIB_OPENSSL_SUPPORT TRUE)
|
| 81 |
+
target_link_libraries(${TARGET} PUBLIC ssl crypto)
|
| 82 |
+
|
| 83 |
+
elseif (LLAMA_BUILD_LIBRESSL)
|
| 84 |
+
set(LIBRESSL_VERSION "4.3.2" CACHE STRING "LibreSSL version")
|
| 85 |
+
|
| 86 |
+
message(STATUS "Fetching LibreSSL version ${LIBRESSL_VERSION}")
|
| 87 |
+
|
| 88 |
+
set(LIBRESSL_ARGS
|
| 89 |
+
URL "https://cdn.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz"
|
| 90 |
+
)
|
| 91 |
+
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
|
| 92 |
+
list(APPEND LIBRESSL_ARGS DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
|
| 93 |
+
endif()
|
| 94 |
+
|
| 95 |
+
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
|
| 96 |
+
list(APPEND LIBRESSL_ARGS EXCLUDE_FROM_ALL)
|
| 97 |
+
endif()
|
| 98 |
+
|
| 99 |
+
include(FetchContent)
|
| 100 |
+
FetchContent_Declare(libressl ${LIBRESSL_ARGS})
|
| 101 |
+
|
| 102 |
+
set(SAVED_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
|
| 103 |
+
set(SAVED_BUILD_TESTING ${BUILD_TESTING})
|
| 104 |
+
|
| 105 |
+
set(BUILD_SHARED_LIBS OFF)
|
| 106 |
+
set(BUILD_TESTING OFF)
|
| 107 |
+
|
| 108 |
+
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
|
| 109 |
+
FetchContent_MakeAvailable(libressl)
|
| 110 |
+
else()
|
| 111 |
+
FetchContent_GetProperties(libressl)
|
| 112 |
+
if(NOT libressl_POPULATED)
|
| 113 |
+
FetchContent_Populate(libressl)
|
| 114 |
+
add_subdirectory(${libressl_SOURCE_DIR} ${libressl_BINARY_DIR} EXCLUDE_FROM_ALL)
|
| 115 |
+
endif()
|
| 116 |
+
endif()
|
| 117 |
+
|
| 118 |
+
set(BUILD_SHARED_LIBS ${SAVED_BUILD_SHARED_LIBS})
|
| 119 |
+
set(BUILD_TESTING ${SAVED_BUILD_TESTING})
|
| 120 |
+
|
| 121 |
+
license_add_file("LibreSSL" "${libressl_SOURCE_DIR}/COPYING")
|
| 122 |
+
|
| 123 |
+
set(CPPHTTPLIB_OPENSSL_SUPPORT TRUE)
|
| 124 |
+
target_link_libraries(${TARGET} PUBLIC ssl crypto)
|
| 125 |
+
|
| 126 |
+
elseif (LLAMA_OPENSSL)
|
| 127 |
+
find_package(OpenSSL)
|
| 128 |
+
if (OpenSSL_FOUND)
|
| 129 |
+
include(CheckCSourceCompiles)
|
| 130 |
+
set(SAVED_CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES})
|
| 131 |
+
set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR})
|
| 132 |
+
check_c_source_compiles("
|
| 133 |
+
#include <openssl/opensslv.h>
|
| 134 |
+
#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
|
| 135 |
+
# if OPENSSL_VERSION_NUMBER < 0x1010107f
|
| 136 |
+
# error bad version
|
| 137 |
+
# endif
|
| 138 |
+
#else
|
| 139 |
+
# if OPENSSL_VERSION_NUMBER < 0x30000000L
|
| 140 |
+
# error bad version
|
| 141 |
+
# endif
|
| 142 |
+
#endif
|
| 143 |
+
int main() { return 0; }
|
| 144 |
+
" OPENSSL_VERSION_SUPPORTED)
|
| 145 |
+
set(CMAKE_REQUIRED_INCLUDES ${SAVED_CMAKE_REQUIRED_INCLUDES})
|
| 146 |
+
if (OPENSSL_VERSION_SUPPORTED)
|
| 147 |
+
message(STATUS "OpenSSL found: ${OPENSSL_VERSION}")
|
| 148 |
+
set(CPPHTTPLIB_OPENSSL_SUPPORT TRUE)
|
| 149 |
+
target_link_libraries(${TARGET} PUBLIC OpenSSL::SSL OpenSSL::Crypto)
|
| 150 |
+
endif()
|
| 151 |
+
else()
|
| 152 |
+
message(WARNING "OpenSSL not found, HTTPS support disabled")
|
| 153 |
+
endif()
|
| 154 |
+
endif()
|
| 155 |
+
|
| 156 |
+
# disable warnings in 3rd party code
|
| 157 |
+
if(LLAMA_BUILD_BORINGSSL OR LLAMA_BUILD_LIBRESSL)
|
| 158 |
+
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
| 159 |
+
target_compile_options(ssl PRIVATE /w)
|
| 160 |
+
target_compile_options(crypto PRIVATE /w)
|
| 161 |
+
if(LLAMA_BUILD_BORINGSSL)
|
| 162 |
+
target_compile_options(fipsmodule PRIVATE /w)
|
| 163 |
+
endif()
|
| 164 |
+
if(LLAMA_BUILD_LIBRESSL)
|
| 165 |
+
target_compile_options(ssl_obj PRIVATE /w)
|
| 166 |
+
target_compile_options(bs_obj PRIVATE /w)
|
| 167 |
+
target_compile_options(compat_obj PRIVATE /w)
|
| 168 |
+
target_compile_options(crypto_obj PRIVATE /w)
|
| 169 |
+
endif()
|
| 170 |
+
else()
|
| 171 |
+
target_compile_options(ssl PRIVATE -w)
|
| 172 |
+
target_compile_options(crypto PRIVATE -w)
|
| 173 |
+
if(LLAMA_BUILD_BORINGSSL)
|
| 174 |
+
target_compile_options(fipsmodule PRIVATE -w)
|
| 175 |
+
endif()
|
| 176 |
+
if(LLAMA_BUILD_LIBRESSL)
|
| 177 |
+
target_compile_options(ssl_obj PRIVATE -w)
|
| 178 |
+
target_compile_options(bs_obj PRIVATE -w)
|
| 179 |
+
target_compile_options(compat_obj PRIVATE -w)
|
| 180 |
+
target_compile_options(crypto_obj PRIVATE -w)
|
| 181 |
+
endif()
|
| 182 |
+
endif()
|
| 183 |
+
endif()
|
| 184 |
+
|
| 185 |
+
if (CPPHTTPLIB_OPENSSL_SUPPORT)
|
| 186 |
+
target_compile_definitions(${TARGET} PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT) # used in server.cpp
|
| 187 |
+
if (APPLE AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
| 188 |
+
find_library(CORE_FOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
|
| 189 |
+
find_library(SECURITY_FRAMEWORK Security REQUIRED)
|
| 190 |
+
target_link_libraries(${TARGET} PUBLIC ${CORE_FOUNDATION_FRAMEWORK} ${SECURITY_FRAMEWORK})
|
| 191 |
+
endif()
|
| 192 |
+
if (WIN32 AND NOT MSVC)
|
| 193 |
+
target_link_libraries(${TARGET} PUBLIC crypt32)
|
| 194 |
+
endif()
|
| 195 |
+
endif()
|
backend/llama.cpp/vendor/cpp-httplib/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The MIT License (MIT)
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2017 yhirose
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
| 22 |
+
|
backend/llama.cpp/vendor/cpp-httplib/httplib.cpp
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
backend/llama.cpp/vendor/cpp-httplib/httplib.h
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
backend/llama.cpp/vendor/miniaudio/miniaudio.h
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
backend/llama.cpp/vendor/nlohmann/json.hpp
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
backend/llama.cpp/vendor/nlohmann/json_fwd.hpp
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// __ _____ _____ _____
|
| 2 |
+
// __| | __| | | | JSON for Modern C++
|
| 3 |
+
// | | |__ | | | | | | version 3.12.0
|
| 4 |
+
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
| 5 |
+
//
|
| 6 |
+
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
|
| 7 |
+
// SPDX-License-Identifier: MIT
|
| 8 |
+
|
| 9 |
+
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
| 10 |
+
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
| 11 |
+
|
| 12 |
+
#include <cstdint> // int64_t, uint64_t
|
| 13 |
+
#include <map> // map
|
| 14 |
+
#include <memory> // allocator
|
| 15 |
+
#include <string> // string
|
| 16 |
+
#include <vector> // vector
|
| 17 |
+
|
| 18 |
+
// #include <nlohmann/detail/abi_macros.hpp>
|
| 19 |
+
// __ _____ _____ _____
|
| 20 |
+
// __| | __| | | | JSON for Modern C++
|
| 21 |
+
// | | |__ | | | | | | version 3.12.0
|
| 22 |
+
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
| 23 |
+
//
|
| 24 |
+
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
|
| 25 |
+
// SPDX-License-Identifier: MIT
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
// This file contains all macro definitions affecting or depending on the ABI
|
| 30 |
+
|
| 31 |
+
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
|
| 32 |
+
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
|
| 33 |
+
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
|
| 34 |
+
#warning "Already included a different version of the library!"
|
| 35 |
+
#endif
|
| 36 |
+
#endif
|
| 37 |
+
#endif
|
| 38 |
+
|
| 39 |
+
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
|
| 40 |
+
#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
|
| 41 |
+
#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
|
| 42 |
+
|
| 43 |
+
#ifndef JSON_DIAGNOSTICS
|
| 44 |
+
#define JSON_DIAGNOSTICS 0
|
| 45 |
+
#endif
|
| 46 |
+
|
| 47 |
+
#ifndef JSON_DIAGNOSTIC_POSITIONS
|
| 48 |
+
#define JSON_DIAGNOSTIC_POSITIONS 0
|
| 49 |
+
#endif
|
| 50 |
+
|
| 51 |
+
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
| 52 |
+
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
| 53 |
+
#endif
|
| 54 |
+
|
| 55 |
+
#if JSON_DIAGNOSTICS
|
| 56 |
+
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
| 57 |
+
#else
|
| 58 |
+
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
|
| 59 |
+
#endif
|
| 60 |
+
|
| 61 |
+
#if JSON_DIAGNOSTIC_POSITIONS
|
| 62 |
+
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
|
| 63 |
+
#else
|
| 64 |
+
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
|
| 65 |
+
#endif
|
| 66 |
+
|
| 67 |
+
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
| 68 |
+
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
|
| 69 |
+
#else
|
| 70 |
+
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
| 71 |
+
#endif
|
| 72 |
+
|
| 73 |
+
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
| 74 |
+
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
| 75 |
+
#endif
|
| 76 |
+
|
| 77 |
+
// Construct the namespace ABI tags component
|
| 78 |
+
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
|
| 79 |
+
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
|
| 80 |
+
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
|
| 81 |
+
|
| 82 |
+
#define NLOHMANN_JSON_ABI_TAGS \
|
| 83 |
+
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
| 84 |
+
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
| 85 |
+
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
| 86 |
+
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
|
| 87 |
+
|
| 88 |
+
// Construct the namespace version component
|
| 89 |
+
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
| 90 |
+
_v ## major ## _ ## minor ## _ ## patch
|
| 91 |
+
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
|
| 92 |
+
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
|
| 93 |
+
|
| 94 |
+
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
| 95 |
+
#define NLOHMANN_JSON_NAMESPACE_VERSION
|
| 96 |
+
#else
|
| 97 |
+
#define NLOHMANN_JSON_NAMESPACE_VERSION \
|
| 98 |
+
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
|
| 99 |
+
NLOHMANN_JSON_VERSION_MINOR, \
|
| 100 |
+
NLOHMANN_JSON_VERSION_PATCH)
|
| 101 |
+
#endif
|
| 102 |
+
|
| 103 |
+
// Combine namespace components
|
| 104 |
+
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
|
| 105 |
+
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
|
| 106 |
+
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
|
| 107 |
+
|
| 108 |
+
#ifndef NLOHMANN_JSON_NAMESPACE
|
| 109 |
+
#define NLOHMANN_JSON_NAMESPACE \
|
| 110 |
+
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
| 111 |
+
NLOHMANN_JSON_ABI_TAGS, \
|
| 112 |
+
NLOHMANN_JSON_NAMESPACE_VERSION)
|
| 113 |
+
#endif
|
| 114 |
+
|
| 115 |
+
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
|
| 116 |
+
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
|
| 117 |
+
namespace nlohmann \
|
| 118 |
+
{ \
|
| 119 |
+
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
| 120 |
+
NLOHMANN_JSON_ABI_TAGS, \
|
| 121 |
+
NLOHMANN_JSON_NAMESPACE_VERSION) \
|
| 122 |
+
{
|
| 123 |
+
#endif
|
| 124 |
+
|
| 125 |
+
#ifndef NLOHMANN_JSON_NAMESPACE_END
|
| 126 |
+
#define NLOHMANN_JSON_NAMESPACE_END \
|
| 127 |
+
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
|
| 128 |
+
} // namespace nlohmann
|
| 129 |
+
#endif
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
/*!
|
| 133 |
+
@brief namespace for Niels Lohmann
|
| 134 |
+
@see https://github.com/nlohmann
|
| 135 |
+
@since version 1.0.0
|
| 136 |
+
*/
|
| 137 |
+
NLOHMANN_JSON_NAMESPACE_BEGIN
|
| 138 |
+
|
| 139 |
+
/*!
|
| 140 |
+
@brief default JSONSerializer template argument
|
| 141 |
+
|
| 142 |
+
This serializer ignores the template arguments and uses ADL
|
| 143 |
+
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
|
| 144 |
+
for serialization.
|
| 145 |
+
*/
|
| 146 |
+
template<typename T = void, typename SFINAE = void>
|
| 147 |
+
struct adl_serializer;
|
| 148 |
+
|
| 149 |
+
/// a class to store JSON values
|
| 150 |
+
/// @sa https://json.nlohmann.me/api/basic_json/
|
| 151 |
+
template<template<typename U, typename V, typename... Args> class ObjectType =
|
| 152 |
+
std::map,
|
| 153 |
+
template<typename U, typename... Args> class ArrayType = std::vector,
|
| 154 |
+
class StringType = std::string, class BooleanType = bool,
|
| 155 |
+
class NumberIntegerType = std::int64_t,
|
| 156 |
+
class NumberUnsignedType = std::uint64_t,
|
| 157 |
+
class NumberFloatType = double,
|
| 158 |
+
template<typename U> class AllocatorType = std::allocator,
|
| 159 |
+
template<typename T, typename SFINAE = void> class JSONSerializer =
|
| 160 |
+
adl_serializer,
|
| 161 |
+
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
|
| 162 |
+
class CustomBaseClass = void>
|
| 163 |
+
class basic_json;
|
| 164 |
+
|
| 165 |
+
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
| 166 |
+
/// @sa https://json.nlohmann.me/api/json_pointer/
|
| 167 |
+
template<typename RefStringType>
|
| 168 |
+
class json_pointer;
|
| 169 |
+
|
| 170 |
+
/*!
|
| 171 |
+
@brief default specialization
|
| 172 |
+
@sa https://json.nlohmann.me/api/json/
|
| 173 |
+
*/
|
| 174 |
+
using json = basic_json<>;
|
| 175 |
+
|
| 176 |
+
/// @brief a minimal map-like container that preserves insertion order
|
| 177 |
+
/// @sa https://json.nlohmann.me/api/ordered_map/
|
| 178 |
+
template<class Key, class T, class IgnoredLess, class Allocator>
|
| 179 |
+
struct ordered_map;
|
| 180 |
+
|
| 181 |
+
/// @brief specialization that maintains the insertion order of object keys
|
| 182 |
+
/// @sa https://json.nlohmann.me/api/ordered_json/
|
| 183 |
+
using ordered_json = basic_json<nlohmann::ordered_map>;
|
| 184 |
+
|
| 185 |
+
NLOHMANN_JSON_NAMESPACE_END
|
| 186 |
+
|
| 187 |
+
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_
|