Implement Notebook interface
This commit is contained in:
parent
41ea26144e
commit
6d96745375
Binary file not shown.
|
|
@ -118,6 +118,17 @@
|
||||||
<ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
|
<ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
|
||||||
</Sidebar.Header>
|
</Sidebar.Header>
|
||||||
|
|
||||||
|
<div class="px-4 py-2">
|
||||||
|
<a
|
||||||
|
href="#/notebook"
|
||||||
|
class="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium hover:bg-accent hover:text-accent-foreground"
|
||||||
|
onclick={handleMobileSidebarItemClick}
|
||||||
|
>
|
||||||
|
<span class="i-lucide-book-open h-4 w-4"></span>
|
||||||
|
Notebook
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Sidebar.Group class="mt-4 space-y-2 p-0 px-4">
|
<Sidebar.Group class="mt-4 space-y-2 p-0 px-4">
|
||||||
{#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
|
{#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
|
||||||
<Sidebar.GroupLabel>
|
<Sidebar.GroupLabel>
|
||||||
|
|
|
||||||
|
|
@ -73,3 +73,6 @@ export { default as ModelsSelector } from './models/ModelsSelector.svelte';
|
||||||
export { default as ServerStatus } from './server/ServerStatus.svelte';
|
export { default as ServerStatus } from './server/ServerStatus.svelte';
|
||||||
export { default as ServerErrorSplash } from './server/ServerErrorSplash.svelte';
|
export { default as ServerErrorSplash } from './server/ServerErrorSplash.svelte';
|
||||||
export { default as ServerLoadingSplash } from './server/ServerLoadingSplash.svelte';
|
export { default as ServerLoadingSplash } from './server/ServerLoadingSplash.svelte';
|
||||||
|
|
||||||
|
// Notebook
|
||||||
|
export { default as NotebookScreen } from './notebook/NotebookScreen.svelte';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { notebookStore } from '$lib/stores/notebook.svelte';
|
||||||
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
|
import Textarea from '$lib/components/ui/textarea/textarea.svelte';
|
||||||
|
import { Play, Square } from '@lucide/svelte';
|
||||||
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
|
|
||||||
|
let { content } = $state(notebookStore);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex h-full flex-col p-4 md:p-6">
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<h1 class="text-2xl font-semibold">Notebook</h1>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
{#if notebookStore.isGenerating}
|
||||||
|
<Button variant="destructive" onclick={() => notebookStore.stop()}>
|
||||||
|
<Square class="mr-2 h-4 w-4" />
|
||||||
|
Stop
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<Button onclick={() => notebookStore.generate()}>
|
||||||
|
<Play class="mr-2 h-4 w-4" />
|
||||||
|
Generate
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 overflow-hidden rounded-lg border bg-background shadow-sm">
|
||||||
|
<textarea
|
||||||
|
class="h-full w-full resize-none border-0 bg-transparent p-4 font-mono text-sm focus:ring-0 focus-visible:ring-0"
|
||||||
|
placeholder="Enter your text here..."
|
||||||
|
bind:value={notebookStore.content}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 text-xs text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
Model: {config().model || 'Default'} | Temperature: {config().temperature ?? 0.8} | Max Tokens: {config()
|
||||||
|
.max_tokens ?? -1}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -247,6 +247,177 @@ export class ChatService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a completion request to the llama.cpp server.
|
||||||
|
* Supports both streaming and non-streaming responses.
|
||||||
|
*
|
||||||
|
* @param prompt - The text prompt to complete
|
||||||
|
* @param options - Configuration options for the completion request
|
||||||
|
* @returns {Promise<string | void>} that resolves to the complete response string (non-streaming) or void (streaming)
|
||||||
|
* @throws {Error} if the request fails or is aborted
|
||||||
|
*/
|
||||||
|
static async sendCompletion(
|
||||||
|
prompt: string,
|
||||||
|
options: SettingsChatServiceOptions = {},
|
||||||
|
signal?: AbortSignal
|
||||||
|
): Promise<string | void> {
|
||||||
|
const {
|
||||||
|
stream,
|
||||||
|
onChunk,
|
||||||
|
onComplete,
|
||||||
|
onError,
|
||||||
|
onModel,
|
||||||
|
onTimings,
|
||||||
|
// Generation parameters
|
||||||
|
temperature,
|
||||||
|
max_tokens,
|
||||||
|
// Sampling parameters
|
||||||
|
dynatemp_range,
|
||||||
|
dynatemp_exponent,
|
||||||
|
top_k,
|
||||||
|
top_p,
|
||||||
|
min_p,
|
||||||
|
xtc_probability,
|
||||||
|
xtc_threshold,
|
||||||
|
typ_p,
|
||||||
|
// Penalty parameters
|
||||||
|
repeat_last_n,
|
||||||
|
repeat_penalty,
|
||||||
|
presence_penalty,
|
||||||
|
frequency_penalty,
|
||||||
|
dry_multiplier,
|
||||||
|
dry_base,
|
||||||
|
dry_allowed_length,
|
||||||
|
dry_penalty_last_n,
|
||||||
|
// Other parameters
|
||||||
|
samplers,
|
||||||
|
backend_sampling,
|
||||||
|
custom,
|
||||||
|
timings_per_token
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const requestBody: ApiCompletionRequest = {
|
||||||
|
prompt,
|
||||||
|
stream
|
||||||
|
};
|
||||||
|
|
||||||
|
// Include model in request if provided
|
||||||
|
if (options.model) {
|
||||||
|
requestBody.model = options.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temperature !== undefined) requestBody.temperature = temperature;
|
||||||
|
if (max_tokens !== undefined) {
|
||||||
|
requestBody.max_tokens = max_tokens !== null && max_tokens !== 0 ? max_tokens : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dynatemp_range !== undefined) requestBody.dynatemp_range = dynatemp_range;
|
||||||
|
if (dynatemp_exponent !== undefined) requestBody.dynatemp_exponent = dynatemp_exponent;
|
||||||
|
if (top_k !== undefined) requestBody.top_k = top_k;
|
||||||
|
if (top_p !== undefined) requestBody.top_p = top_p;
|
||||||
|
if (min_p !== undefined) requestBody.min_p = min_p;
|
||||||
|
if (xtc_probability !== undefined) requestBody.xtc_probability = xtc_probability;
|
||||||
|
if (xtc_threshold !== undefined) requestBody.xtc_threshold = xtc_threshold;
|
||||||
|
if (typ_p !== undefined) requestBody.typ_p = typ_p;
|
||||||
|
|
||||||
|
if (repeat_last_n !== undefined) requestBody.repeat_last_n = repeat_last_n;
|
||||||
|
if (repeat_penalty !== undefined) requestBody.repeat_penalty = repeat_penalty;
|
||||||
|
if (presence_penalty !== undefined) requestBody.presence_penalty = presence_penalty;
|
||||||
|
if (frequency_penalty !== undefined) requestBody.frequency_penalty = frequency_penalty;
|
||||||
|
if (dry_multiplier !== undefined) requestBody.dry_multiplier = dry_multiplier;
|
||||||
|
if (dry_base !== undefined) requestBody.dry_base = dry_base;
|
||||||
|
if (dry_allowed_length !== undefined) requestBody.dry_allowed_length = dry_allowed_length;
|
||||||
|
if (dry_penalty_last_n !== undefined) requestBody.dry_penalty_last_n = dry_penalty_last_n;
|
||||||
|
|
||||||
|
if (samplers !== undefined) {
|
||||||
|
requestBody.samplers =
|
||||||
|
typeof samplers === 'string'
|
||||||
|
? samplers.split(';').filter((s: string) => s.trim())
|
||||||
|
: samplers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backend_sampling !== undefined) requestBody.backend_sampling = backend_sampling;
|
||||||
|
if (timings_per_token !== undefined) requestBody.timings_per_token = timings_per_token;
|
||||||
|
|
||||||
|
if (custom) {
|
||||||
|
try {
|
||||||
|
const customParams = typeof custom === 'string' ? JSON.parse(custom) : custom;
|
||||||
|
Object.assign(requestBody, customParams);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse custom parameters:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`./completion`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getJsonHeaders(),
|
||||||
|
body: JSON.stringify(requestBody),
|
||||||
|
signal
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await ChatService.parseErrorResponse(response);
|
||||||
|
if (onError) {
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
await ChatService.handleCompletionStreamResponse(
|
||||||
|
response,
|
||||||
|
onChunk,
|
||||||
|
onComplete,
|
||||||
|
onError,
|
||||||
|
onModel,
|
||||||
|
onTimings,
|
||||||
|
signal
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return ChatService.handleCompletionNonStreamResponse(
|
||||||
|
response,
|
||||||
|
onComplete,
|
||||||
|
onError,
|
||||||
|
onModel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
console.log('Completion request was aborted');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userFriendlyError: Error;
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||||
|
userFriendlyError = new Error(
|
||||||
|
'Unable to connect to server - please check if the server is running'
|
||||||
|
);
|
||||||
|
userFriendlyError.name = 'NetworkError';
|
||||||
|
} else if (error.message.includes('ECONNREFUSED')) {
|
||||||
|
userFriendlyError = new Error('Connection refused - server may be offline');
|
||||||
|
userFriendlyError.name = 'NetworkError';
|
||||||
|
} else if (error.message.includes('ETIMEDOUT')) {
|
||||||
|
userFriendlyError = new Error('Request timed out - the server took too long to respond');
|
||||||
|
userFriendlyError.name = 'TimeoutError';
|
||||||
|
} else {
|
||||||
|
userFriendlyError = error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userFriendlyError = new Error('Unknown error occurred while sending completion');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Error in sendCompletion:', error);
|
||||||
|
if (onError) {
|
||||||
|
onError(userFriendlyError);
|
||||||
|
}
|
||||||
|
throw userFriendlyError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// Streaming
|
// Streaming
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -781,4 +952,147 @@ export class ChatService {
|
||||||
|
|
||||||
onTimingsCallback(timings, promptProgress);
|
onTimingsCallback(timings, promptProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles streaming response from the completion API
|
||||||
|
*/
|
||||||
|
private static async handleCompletionStreamResponse(
|
||||||
|
response: Response,
|
||||||
|
onChunk?: (chunk: string) => void,
|
||||||
|
onComplete?: (
|
||||||
|
response: string,
|
||||||
|
reasoningContent?: string,
|
||||||
|
timings?: ChatMessageTimings,
|
||||||
|
toolCalls?: string
|
||||||
|
) => void,
|
||||||
|
onError?: (error: Error) => void,
|
||||||
|
onModel?: (model: string) => void,
|
||||||
|
onTimings?: (timings?: ChatMessageTimings, promptProgress?: ChatMessagePromptProgress) => void,
|
||||||
|
abortSignal?: AbortSignal
|
||||||
|
): Promise<void> {
|
||||||
|
const reader = response.body?.getReader();
|
||||||
|
|
||||||
|
if (!reader) {
|
||||||
|
throw new Error('No response body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let aggregatedContent = '';
|
||||||
|
let lastTimings: ChatMessageTimings | undefined;
|
||||||
|
let streamFinished = false;
|
||||||
|
let modelEmitted = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let chunk = '';
|
||||||
|
while (true) {
|
||||||
|
if (abortSignal?.aborted) break;
|
||||||
|
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
|
||||||
|
if (abortSignal?.aborted) break;
|
||||||
|
|
||||||
|
chunk += decoder.decode(value, { stream: true });
|
||||||
|
const lines = chunk.split('\n');
|
||||||
|
chunk = lines.pop() || '';
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (abortSignal?.aborted) break;
|
||||||
|
|
||||||
|
if (line.startsWith('data: ')) {
|
||||||
|
const data = line.slice(6);
|
||||||
|
if (data === '[DONE]') {
|
||||||
|
streamFinished = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed: ApiCompletionStreamChunk = JSON.parse(data);
|
||||||
|
const content = parsed.content;
|
||||||
|
const timings = parsed.timings;
|
||||||
|
const model = parsed.model;
|
||||||
|
|
||||||
|
if (model && !modelEmitted) {
|
||||||
|
modelEmitted = true;
|
||||||
|
onModel?.(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timings) {
|
||||||
|
ChatService.notifyTimings(timings, undefined, onTimings);
|
||||||
|
lastTimings = timings;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
aggregatedContent += content;
|
||||||
|
if (!abortSignal?.aborted) {
|
||||||
|
onChunk?.(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing JSON chunk:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abortSignal?.aborted) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abortSignal?.aborted) return;
|
||||||
|
|
||||||
|
if (streamFinished) {
|
||||||
|
onComplete?.(aggregatedContent, undefined, lastTimings, undefined);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const err = error instanceof Error ? error : new Error('Stream error');
|
||||||
|
onError?.(err);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles non-streaming response from the completion API
|
||||||
|
*/
|
||||||
|
private static async handleCompletionNonStreamResponse(
|
||||||
|
response: Response,
|
||||||
|
onComplete?: (
|
||||||
|
response: string,
|
||||||
|
reasoningContent?: string,
|
||||||
|
timings?: ChatMessageTimings,
|
||||||
|
toolCalls?: string
|
||||||
|
) => void,
|
||||||
|
onError?: (error: Error) => void,
|
||||||
|
onModel?: (model: string) => void
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
const responseText = await response.text();
|
||||||
|
|
||||||
|
if (!responseText.trim()) {
|
||||||
|
const noResponseError = new Error('No response received from server. Please try again.');
|
||||||
|
throw noResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: ApiCompletionResponse = JSON.parse(responseText);
|
||||||
|
|
||||||
|
if (data.model) {
|
||||||
|
onModel?.(data.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = data.content || '';
|
||||||
|
|
||||||
|
if (!content.trim()) {
|
||||||
|
const noResponseError = new Error('No response received from server. Please try again.');
|
||||||
|
throw noResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
onComplete?.(content, undefined, data.timings, undefined);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
} catch (error) {
|
||||||
|
const err = error instanceof Error ? error : new Error('Parse error');
|
||||||
|
onError?.(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { ChatService } from '$lib/services/chat';
|
||||||
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
|
|
||||||
|
export class NotebookStore {
|
||||||
|
content = $state('');
|
||||||
|
isGenerating = $state(false);
|
||||||
|
abortController: AbortController | null = null;
|
||||||
|
|
||||||
|
async generate(model?: string) {
|
||||||
|
if (this.isGenerating) return;
|
||||||
|
|
||||||
|
this.isGenerating = true;
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentConfig = config();
|
||||||
|
await ChatService.sendCompletion(
|
||||||
|
this.content,
|
||||||
|
{
|
||||||
|
...currentConfig,
|
||||||
|
model,
|
||||||
|
stream: true,
|
||||||
|
onChunk: (chunk) => {
|
||||||
|
this.content += chunk;
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
this.isGenerating = false;
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Notebook generation error:', error);
|
||||||
|
this.isGenerating = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this.abortController.signal
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Notebook generation failed:', error);
|
||||||
|
this.isGenerating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.abortController) {
|
||||||
|
this.abortController.abort();
|
||||||
|
this.abortController = null;
|
||||||
|
}
|
||||||
|
this.isGenerating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notebookStore = new NotebookStore();
|
||||||
|
|
@ -23,12 +23,12 @@ export interface ApiContextSizeError {
|
||||||
|
|
||||||
export interface ApiErrorResponse {
|
export interface ApiErrorResponse {
|
||||||
error:
|
error:
|
||||||
| ApiContextSizeError
|
| ApiContextSizeError
|
||||||
| {
|
| {
|
||||||
code: number;
|
code: number;
|
||||||
message: string;
|
message: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiChatMessageData {
|
export interface ApiChatMessageData {
|
||||||
|
|
@ -219,6 +219,39 @@ export interface ApiChatCompletionRequest {
|
||||||
timings_per_token?: boolean;
|
timings_per_token?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiCompletionRequest {
|
||||||
|
prompt: string;
|
||||||
|
stream?: boolean;
|
||||||
|
model?: string;
|
||||||
|
// Generation parameters
|
||||||
|
temperature?: number;
|
||||||
|
max_tokens?: number;
|
||||||
|
// Sampling parameters
|
||||||
|
dynatemp_range?: number;
|
||||||
|
dynatemp_exponent?: number;
|
||||||
|
top_k?: number;
|
||||||
|
top_p?: number;
|
||||||
|
min_p?: number;
|
||||||
|
xtc_probability?: number;
|
||||||
|
xtc_threshold?: number;
|
||||||
|
typ_p?: number;
|
||||||
|
// Penalty parameters
|
||||||
|
repeat_last_n?: number;
|
||||||
|
repeat_penalty?: number;
|
||||||
|
presence_penalty?: number;
|
||||||
|
frequency_penalty?: number;
|
||||||
|
dry_multiplier?: number;
|
||||||
|
dry_base?: number;
|
||||||
|
dry_allowed_length?: number;
|
||||||
|
dry_penalty_last_n?: number;
|
||||||
|
// Sampler configuration
|
||||||
|
samplers?: string[];
|
||||||
|
backend_sampling?: boolean;
|
||||||
|
// Custom parameters (JSON string)
|
||||||
|
custom?: Record<string, unknown>;
|
||||||
|
timings_per_token?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiChatCompletionToolCallFunctionDelta {
|
export interface ApiChatCompletionToolCallFunctionDelta {
|
||||||
name?: string;
|
name?: string;
|
||||||
arguments?: string;
|
arguments?: string;
|
||||||
|
|
@ -258,6 +291,32 @@ export interface ApiChatCompletionStreamChunk {
|
||||||
prompt_progress?: ChatMessagePromptProgress;
|
prompt_progress?: ChatMessagePromptProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiCompletionStreamChunk {
|
||||||
|
content: string;
|
||||||
|
stop: boolean;
|
||||||
|
model: string;
|
||||||
|
timings?: {
|
||||||
|
prompt_n?: number;
|
||||||
|
prompt_ms?: number;
|
||||||
|
predicted_n?: number;
|
||||||
|
predicted_ms?: number;
|
||||||
|
cache_n?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiCompletionResponse {
|
||||||
|
content: string;
|
||||||
|
stop: boolean;
|
||||||
|
model: string;
|
||||||
|
timings?: {
|
||||||
|
prompt_n?: number;
|
||||||
|
prompt_ms?: number;
|
||||||
|
predicted_n?: number;
|
||||||
|
predicted_ms?: number;
|
||||||
|
cache_n?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiChatCompletionResponse {
|
export interface ApiChatCompletionResponse {
|
||||||
model?: string;
|
model?: string;
|
||||||
choices: Array<{
|
choices: Array<{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { NotebookScreen } from '$lib/components/app';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Notebook - llama.cpp</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<NotebookScreen />
|
||||||
Loading…
Reference in New Issue