Back to line length of 100 + some svelte/ts settings

- Goes back to a max line length of 100
- Makes whitespace insignificant in Svelte files (see below)
- Avoids parens around single argument closures, in TS, when unncessary

*Whitespace Significance*

In HTML, `<p><b> 1 </b></p>` and `<p><b>1</b></p>` are not the same
thing. In the former, the "1" has spaces around it. If you were to try
to split this into multiple lines...

```html
<p>
    <b>
        1
    </b>
</p>
```

...you would lose that whitespace. The newlines reset any significance
around individual tokens. This meant prettier would format that code
as...

```html
<p>
    <b
    > 1 </b>
</p>
```

...which is insane and hideous.

We're saying all whitespace is insignificant from now on. Meaning
prettier no longer needs to retain it and can format that code as a sane
person.

This means you need to explicitly use `&nbsp;` characters if you
explicitly need whitespace around things. OR put it in a `span` and use
css.

TL;DR: do not rely on whitespace significance in HTML.
This commit is contained in:
Matte Noble
2025-05-23 12:23:32 -07:00
parent d08bc43218
commit 366f9f5b97
34 changed files with 122 additions and 133 deletions

View File

@@ -4,7 +4,8 @@
"tabWidth": 4, "tabWidth": 4,
"semi": true, "semi": true,
"trailingComma": "es5", "trailingComma": "es5",
"printWidth": 80, "printWidth": 100,
"arrowParens": "avoid",
"plugins": [ "plugins": [
"prettier-plugin-svelte", "prettier-plugin-svelte",
"prettier-plugin-tailwindcss" "prettier-plugin-tailwindcss"
@@ -14,7 +15,7 @@
"files": "*.svelte", "files": "*.svelte",
"options": { "options": {
"parser": "svelte", "parser": "svelte",
"printWidth": 100 "htmlWhitespaceSensitivity": "ignore"
} }
} }
] ]

View File

@@ -13,11 +13,13 @@
></path> ></path>
</g> </g>
<g> <g>
<path d="m24 12h-16c-.553 0-1-.448-1-1s.447-1 1-1h16c.553 0 1 .448 1 1s-.447 1-1 1z" <path
d="m24 12h-16c-.553 0-1-.448-1-1s.447-1 1-1h16c.553 0 1 .448 1 1s-.447 1-1 1z"
></path> ></path>
</g> </g>
<g> <g>
<path d="m16 16h-8c-.553 0-1-.448-1-1s.447-1 1-1h8c.553 0 1 .448 1 1s-.447 1-1 1z" <path
d="m16 16h-8c-.553 0-1-.448-1-1s.447-1 1-1h8c.553 0 1 .448 1 1s-.447 1-1 1z"
></path> ></path>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 946 B

After

Width:  |  Height:  |  Size: 978 B

View File

@@ -4,8 +4,8 @@
width="100%" width="100%"
height="100%" height="100%"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
><path >
<path
d="m2 12c0-1.8129.44444-3.48538 1.33333-5.01754.90059-1.53217 2.11696-2.74269 3.64913-3.63158 1.54386-.90059 3.21634-1.35088 5.01754-1.35088 1.8129 0 3.4854.45029 5.0175 1.35088 1.5322.88889 2.7427 2.10526 3.6316 3.64912.9006 1.53216 1.3509 3.1988 1.3509 5s-.4503 3.4737-1.3509 5.0175c-.8889 1.5322-2.1052 2.7486-3.6491 3.6492-1.5322.8889-3.1988 1.3333-5 1.3333s-3.47368-.4444-5.01754-1.3333c-1.53217-.9006-2.74854-2.117-3.64913-3.6492-.88889-1.5438-1.33333-3.2163-1.33333-5.0175zm4.50877 0c0 .2807.09942.5146.29825.7018l2.89473 2.8947c.22223.2105.49125.3158.80705.3158.3275 0 .5965-.1053.807-.3158l5.8947-5.89475c.1989-.17543.2983-.40935.2983-.70175 0-.2807-.0994-.51462-.2983-.70175-.1871-.19884-.421-.29825-.7017-.29825s-.5146.09941-.7018.29825l-5.2982 5.28065-2.29827-2.2807c-.17544-.1988-.40936-.2982-.70176-.2982-.269 0-.50292.0994-.70175.2982-.19883.1872-.29825.4211-.29825.7018z" d="m2 12c0-1.8129.44444-3.48538 1.33333-5.01754.90059-1.53217 2.11696-2.74269 3.64913-3.63158 1.54386-.90059 3.21634-1.35088 5.01754-1.35088 1.8129 0 3.4854.45029 5.0175 1.35088 1.5322.88889 2.7427 2.10526 3.6316 3.64912.9006 1.53216 1.3509 3.1988 1.3509 5s-.4503 3.4737-1.3509 5.0175c-.8889 1.5322-2.1052 2.7486-3.6491 3.6492-1.5322.8889-3.1988 1.3333-5 1.3333s-3.47368-.4444-5.01754-1.3333c-1.53217-.9006-2.74854-2.117-3.64913-3.6492-.88889-1.5438-1.33333-3.2163-1.33333-5.0175zm4.50877 0c0 .2807.09942.5146.29825.7018l2.89473 2.8947c.22223.2105.49125.3158.80705.3158.3275 0 .5965-.1053.807-.3158l5.8947-5.89475c.1989-.17543.2983-.40935.2983-.70175 0-.2807-.0994-.51462-.2983-.70175-.1871-.19884-.421-.29825-.7017-.29825s-.5146.09941-.7018.29825l-5.2982 5.28065-2.29827-2.2807c-.17544-.1988-.40936-.2982-.70176-.2982-.269 0-.50292.0994-.70175.2982-.19883.1872-.29825.4211-.29825.7018z"
> ></path>
</path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -41,7 +41,7 @@
} }
async function remove(key: string) { async function remove(key: string) {
env = env.filter((e) => e[0] !== key); env = env.filter(e => e[0] !== key);
await save(); await save();
} }
</script> </script>

View File

@@ -40,7 +40,7 @@
<Flex bind:this={ref} class="bg-medium relative h-16 w-full hover:cursor-pointer"> <Flex bind:this={ref} class="bg-medium relative h-16 w-full hover:cursor-pointer">
<Flex <Flex
onclick={(e) => toggle(e)} onclick={e => toggle(e)}
class="border-light absolute top-0 left-0 w-full justify-between class="border-light absolute top-0 left-0 w-full justify-between
rounded-md border p-2 px-4" rounded-md border p-2 px-4"
> >

View File

@@ -60,13 +60,13 @@
} }
function isValid() { function isValid() {
return config.every((prop) => prop.valid); return config.every(prop => prop.valid);
} }
function validateAll() { function validateAll() {
config config
.filter((c) => c.required) .filter(c => c.required)
.forEach((prop) => { .forEach(prop => {
prop.valid = validate(prop.value); prop.valid = validate(prop.value);
}); });
} }
@@ -115,8 +115,13 @@
<svelte:window onmessage={onMessage} /> <svelte:window onmessage={onMessage} />
{#if srcdoc} {#if srcdoc}
<iframe title="stdioFunction" class="hidden h-0 w-0" sandbox="allow-scripts" allow="" {srcdoc}> <iframe
</iframe> title="stdioFunction"
class="hidden h-0 w-0"
sandbox="allow-scripts"
allow=""
{srcdoc}
></iframe>
{/if} {/if}
{#if server && config} {#if server && config}
@@ -126,7 +131,7 @@
{#if config.length > 0} {#if config.length > 0}
<div class="w-full px-8"> <div class="w-full px-8">
{#each config.filter((p) => p.required) as prop (prop.name)} {#each config.filter(p => p.required) as prop (prop.name)}
<Input <Input
label={prop.name} label={prop.name}
name={prop.name} name={prop.name}
@@ -137,7 +142,7 @@
{/each} {/each}
</div> </div>
{#if config.some((p) => !p.required)} {#if config.some(p => !p.required)}
<Flex class="mt-8 w-full flex-col items-start"> <Flex class="mt-8 w-full flex-col items-start">
<button <button
onclick={() => toggleOptional()} onclick={() => toggleOptional()}
@@ -148,7 +153,7 @@
{#if optionalIsOpen} {#if optionalIsOpen}
<div class="w-full px-8"> <div class="w-full px-8">
{#each config.filter((p) => !p.required) as prop (prop.name)} {#each config.filter(p => !p.required) as prop (prop.name)}
<Input <Input
label={prop.name} label={prop.name}
name={prop.name} name={prop.name}

View File

@@ -19,7 +19,7 @@
async function install() { async function install() {
update = await availableUpdate(); update = await availableUpdate();
await update?.downloadAndInstall((event) => { await update?.downloadAndInstall(event => {
switch (event.event) { switch (event.event) {
case 'Started': case 'Started':
totalDownload = (event.data.contentLength as number) / 1000.0; totalDownload = (event.data.contentLength as number) / 1000.0;

View File

@@ -21,8 +21,8 @@
<h1 class="text-purple text-3xl">Welcome to Tome</h1> <h1 class="text-purple text-3xl">Welcome to Tome</h1>
<p> <p>
Thanks for being an early adopter! We appreciate you kicking the tires of our Thanks for being an early adopter! We appreciate you kicking the tires of our
<strong>Technical Preview</strong> as we explore making local LLMs, MCP, and AI app composition <strong>Technical Preview</strong>
a better experience. as we explore making local LLMs, MCP, and AI app composition a better experience.
</p> </p>
<p> <p>
This is an extremely early build. There will be problems edges are rough features This is an extremely early build. There will be problems edges are rough features

View File

@@ -39,19 +39,13 @@ export const init: ClientInit = async () => {
await Config.migrate(); await Config.migrate();
info('[green]✔ config migrated'); info('[green]✔ config migrated');
await startup.addCheck( await startup.addCheck(StartupCheck.Agreement, async () => Config.agreedToWelcome);
StartupCheck.Agreement,
async () => Config.agreedToWelcome,
);
await startup.addCheck( await startup.addCheck(StartupCheck.UpdateAvailable, async () => await isUpToDate());
StartupCheck.UpdateAvailable,
async () => await isUpToDate(),
);
await startup.addCheck( await startup.addCheck(
StartupCheck.NoModels, StartupCheck.NoModels,
async () => Engine.all().flatMap(e => e.models).length > 0, async () => Engine.all().flatMap(e => e.models).length > 0
); );
}; };

View File

@@ -13,7 +13,7 @@ export enum DeepLinks {
} }
export function setupDeeplinks() { export function setupDeeplinks() {
listen<string>(DeepLinks.InstallMcpServer, async (event) => { listen<string>(DeepLinks.InstallMcpServer, async event => {
goto(`/mcp-servers/install?config=${event.payload}`); goto(`/mcp-servers/install?config=${event.payload}`);
}); });
} }

View File

@@ -29,7 +29,7 @@ export default class Gemini implements Client {
tools?: Tool[], tools?: Tool[],
options?: Options options?: Options
): Promise<IMessage> { ): Promise<IMessage> {
const messages = history.map((m) => GeminiMessage.from(m)).compact(); const messages = history.map(m => GeminiMessage.from(m)).compact();
let config: GenerateContentConfig = { let config: GenerateContentConfig = {
temperature: options?.temperature, temperature: options?.temperature,
@@ -50,7 +50,7 @@ export default class Gemini implements Client {
let toolCalls: ToolCall[] = []; let toolCalls: ToolCall[] = [];
if (functionCalls) { if (functionCalls) {
toolCalls = functionCalls.map((tc) => ({ toolCalls = functionCalls.map(tc => ({
function: { function: {
name: tc.name as string, name: tc.name as string,
arguments: tc.args || {}, arguments: tc.args || {},
@@ -69,8 +69,8 @@ export default class Gemini implements Client {
async models(): Promise<IModel[]> { async models(): Promise<IModel[]> {
return (await this.client.models.list()).page return (await this.client.models.list()).page
.filter((model) => this.supportedModels.includes(model.name as string)) .filter(model => this.supportedModels.includes(model.name as string))
.map((model) => { .map(model => {
const metadata = model; const metadata = model;
const name = metadata.name?.replace('models/', '') as string; const name = metadata.name?.replace('models/', '') as string;

View File

@@ -67,7 +67,7 @@ function fromToolResponse(message: IMessage): Content {
// Find the `toolCall` message for this response // Find the `toolCall` message for this response
const session = Session.find(message.sessionId as number); const session = Session.find(message.sessionId as number);
const messages = Session.messages(session); const messages = Session.messages(session);
const call = messages.flatMap((m) => m.toolCalls).find((tc) => tc.id == message.toolCallId); const call = messages.flatMap(m => m.toolCalls).find(tc => tc.id == message.toolCallId);
return { return {
role: 'user', role: 'user',

View File

@@ -32,7 +32,7 @@ function from(tools: Tool | Tool[]): ToolListUnion {
} }
function fromMany(tools: Tool[]): FunctionDeclaration[] { function fromMany(tools: Tool[]): FunctionDeclaration[] {
return tools.map((tool) => fromOne(tool)); return tools.map(tool => fromOne(tool));
} }
function fromOne(tool: Tool): FunctionDeclaration { function fromOne(tool: Tool): FunctionDeclaration {

View File

@@ -24,7 +24,7 @@ export default class Ollama implements Client {
tools: Tool[] = [], tools: Tool[] = [],
options: Options = {} options: Options = {}
): Promise<IMessage> { ): Promise<IMessage> {
const messages = history.map((m) => this.message.from(m)); const messages = history.map(m => this.message.from(m));
const response = await this.client.chat({ const response = await this.client.chat({
model: model.name, model: model.name,
messages, messages,
@@ -57,9 +57,7 @@ export default class Ollama implements Client {
async models(): Promise<IModel[]> { async models(): Promise<IModel[]> {
const models = (await this.client.list()).models; const models = (await this.client.list()).models;
return Promise.all( return Promise.all(models.map(async model => await this.info(model.name)));
models.map(async (model) => await this.info(model.name))
);
} }
async info(name: string): Promise<IModel> { async info(name: string): Promise<IModel> {

View File

@@ -10,7 +10,7 @@ export function from(message: IMessage): Message {
return { return {
role: message.role, role: message.role,
content: message.content, content: message.content,
tool_calls: message.toolCalls?.map((c) => ({ tool_calls: message.toolCalls?.map(c => ({
function: { function: {
name: c.function.name, name: c.function.name,
arguments: c.function.arguments, arguments: c.function.arguments,

View File

@@ -33,7 +33,7 @@ export default class OpenAI implements Client {
tools: Tool[] = [], tools: Tool[] = [],
options: Options = {} options: Options = {}
): Promise<IMessage> { ): Promise<IMessage> {
const messages = history.map((m) => OpenAiMessage.from(m)); const messages = history.map(m => OpenAiMessage.from(m));
const response = await this.client.chat.completions.create({ const response = await this.client.chat.completions.create({
model: model.name, model: model.name,
messages, messages,
@@ -45,7 +45,7 @@ export default class OpenAI implements Client {
let toolCalls: ToolCall[] = []; let toolCalls: ToolCall[] = [];
if (tool_calls) { if (tool_calls) {
toolCalls = tool_calls.map((tc) => ({ toolCalls = tool_calls.map(tc => ({
function: { function: {
name: tc.function.name, name: tc.function.name,
arguments: JSON.parse(tc.function.arguments), arguments: JSON.parse(tc.function.arguments),
@@ -66,10 +66,10 @@ export default class OpenAI implements Client {
let allModels = (await this.client.models.list()).data; let allModels = (await this.client.models.list()).data;
if (this.supportedModels !== 'all') { if (this.supportedModels !== 'all') {
allModels = allModels.filter((model) => this.supportedModels.includes(model.id)); allModels = allModels.filter(model => this.supportedModels.includes(model.id));
} }
return allModels.map((model) => { return allModels.map(model => {
const { id, ...metadata } = model; const { id, ...metadata } = model;
const name = id.replace('models/', ''); // Gemini model ids are prefixed with "model/" const name = id.replace('models/', ''); // Gemini model ids are prefixed with "model/"

View File

@@ -53,7 +53,7 @@ function toolCalls(message: IMessage): OpenAI.ChatCompletionMessageToolCall[] |
return; return;
} }
return message.toolCalls.map((call) => ({ return message.toolCalls.map(call => ({
id: call.id as string, id: call.id as string,
type: 'function', type: 'function',
function: { function: {

View File

@@ -93,7 +93,7 @@ Array.prototype.findBy = function <T extends Obj>(
key: string, key: string,
value: any value: any
): T | undefined { ): T | undefined {
return this.find((item) => item[key] == value); return this.find(item => item[key] == value);
}; };
/** /**
@@ -106,5 +106,5 @@ Array.prototype.findBy = function <T extends Obj>(
* ``` * ```
*/ */
Array.prototype.compact = function <T>(this: T[]): Exclude<T, undefined>[] { Array.prototype.compact = function <T>(this: T[]): Exclude<T, undefined>[] {
return this.filter((i) => i !== undefined) as Exclude<T, undefined>[]; return this.filter(i => i !== undefined) as Exclude<T, undefined>[];
}; };

View File

@@ -13,7 +13,10 @@ export interface HttpOptions extends RequestInit {
} }
export async function fetch(url: string | URL | Request, options?: RequestInit) { export async function fetch(url: string | URL | Request, options?: RequestInit) {
const response: globalThis.Response = await invoke('fetch', { url, options }); const response: globalThis.Response = await invoke('fetch', {
url,
options,
});
const { body, ...init } = response; const { body, ...init } = response;
return new globalThis.Response(body, init); return new globalThis.Response(body, init);
} }
@@ -37,7 +40,10 @@ export abstract class HttpClient {
async post<T>(uri: string, options: HttpOptions = {}): Promise<Response<T>> { async post<T>(uri: string, options: HttpOptions = {}): Promise<Response<T>> {
const raw = Object.remove(options, 'raw'); const raw = Object.remove(options, 'raw');
const raise: boolean | undefined = Object.remove(options, 'raise'); const raise: boolean | undefined = Object.remove(options, 'raise');
const response = await this.request(uri, { ...options, method: 'POST' }); const response = await this.request(uri, {
...options,
method: 'POST',
});
return raw ? response : await this.parse(response, raise); return raw ? response : await this.parse(response, raise);
} }
@@ -51,14 +57,20 @@ export abstract class HttpClient {
async delete<T>(uri: string, options: HttpOptions = {}): Promise<Response<T>> { async delete<T>(uri: string, options: HttpOptions = {}): Promise<Response<T>> {
const raw = Object.remove(options, 'raw'); const raw = Object.remove(options, 'raw');
const raise: boolean | undefined = Object.remove(options, 'raise'); const raise: boolean | undefined = Object.remove(options, 'raise');
const response = await this.request(uri, { ...options, method: 'DELETE' }); const response = await this.request(uri, {
...options,
method: 'DELETE',
});
return raw ? response : await this.parse(response, raise); return raw ? response : await this.parse(response, raise);
} }
async head<T>(uri: string, options: HttpOptions = {}): Promise<Response<T>> { async head<T>(uri: string, options: HttpOptions = {}): Promise<Response<T>> {
const raw = Object.remove(options, 'raw'); const raw = Object.remove(options, 'raw');
const raise: boolean | undefined = Object.remove(options, 'raise'); const raise: boolean | undefined = Object.remove(options, 'raise');
const response = await this.request(uri, { ...options, method: 'HEAD' }); const response = await this.request(uri, {
...options,
method: 'HEAD',
});
return raw ? response : await this.parse(response, raise); return raw ? response : await this.parse(response, raise);
} }

View File

@@ -39,7 +39,9 @@ export class OllamaClient extends HttpClient {
stream: false, stream: false,
}); });
const response = (await this.post('/api/chat', { body })) as OllamaResponse; const response = (await this.post('/api/chat', {
body,
})) as OllamaResponse;
let thought: string | undefined; let thought: string | undefined;
let content: string = response.message.content let content: string = response.message.content
@@ -75,7 +77,12 @@ export class OllamaClient extends HttpClient {
async connected(): Promise<boolean> { async connected(): Promise<boolean> {
return ( return (
((await this.get('', { raw: true, timeout: 500 })) as globalThis.Response).status == 200 (
(await this.get('', {
raw: true,
timeout: 500,
})) as globalThis.Response
).status == 200
); );
} }

View File

@@ -53,7 +53,7 @@ function _log(args: any[], level: Level) {
if (typeof args[0] !== 'string') { if (typeof args[0] !== 'string') {
console[level](...fmt(level, ''), ...args); console[level](...fmt(level, ''), ...args);
} else { } else {
console[level](...args.flatMap((arg) => (typeof arg === 'string' ? fmt(level, arg) : arg))); console[level](...args.flatMap(arg => (typeof arg === 'string' ? fmt(level, arg) : arg)));
} }
} }
@@ -70,7 +70,7 @@ function fmt(level: Level, text: string): string[] {
function colorize(text: string): string[] { function colorize(text: string): string[] {
let args: string[] = []; let args: string[] = [];
text = text.replace(/\[\w+\]/g, (block) => { text = text.replace(/\[\w+\]/g, block => {
const code = block.replace('[', '').replace(']', ''); const code = block.replace('[', '').replace(']', '');
const color = (colors as Obj)[code]; const color = (colors as Obj)[code];
args.push(`color: ${color};`); args.push(`color: ${color};`);

View File

@@ -10,7 +10,7 @@ export * from '$lib/mcp.d';
// can send to the LLM. // can send to the LLM.
// //
export async function getMCPTools(session: ISession): Promise<Tool[]> { export async function getMCPTools(session: ISession): Promise<Tool[]> {
return (await invoke<McpTool[]>('get_mcp_tools', { sessionId: session.id })).map((tool) => { return (await invoke<McpTool[]>('get_mcp_tools', { sessionId: session.id })).map(tool => {
return { return {
type: 'function', type: 'function',
function: { function: {

View File

@@ -57,13 +57,13 @@ export default class App extends Model<IApp, Row>('apps') {
}; };
static hasContext(app: IApp): boolean { static hasContext(app: IApp): boolean {
return app.nodes?.find((n) => n.type == NodeType.Context) !== undefined; return app.nodes?.find(n => n.type == NodeType.Context) !== undefined;
} }
static context(app: IApp): string { static context(app: IApp): string {
return app.nodes return app.nodes
.filter((n) => n.type == NodeType.Context) .filter(n => n.type == NodeType.Context)
.map((n) => n.config.value) .map(n => n.config.value)
.join('\n\n'); .join('\n\n');
} }
@@ -73,7 +73,7 @@ export default class App extends Model<IApp, Row>('apps') {
} }
static removeNode(app: IApp, node: Node): IApp { static removeNode(app: IApp, node: Node): IApp {
app.nodes = app.nodes.filter((n) => n.uuid !== node.uuid); app.nodes = app.nodes.filter(n => n.uuid !== node.uuid);
return app; return app;
} }
@@ -102,7 +102,7 @@ export default class App extends Model<IApp, Row>('apps') {
]); ]);
if (result.rowsAffected == 1) { if (result.rowsAffected == 1) {
app.mcpServers = app.mcpServers.filter((m) => m.id == mcpServer.id); app.mcpServers = app.mcpServers.filter(m => m.id == mcpServer.id);
return app.mcpServers; return app.mcpServers;
} }

View File

@@ -136,7 +136,7 @@ export default function Model<Interface extends Obj, Row extends Obj>(table: str
static async sync(): Promise<void> { static async sync(): Promise<void> {
repo = []; repo = [];
(await this.query(`SELECT * FROM ${table}`)).forEach((record) => this.syncOne(record)); (await this.query(`SELECT * FROM ${table}`)).forEach(record => this.syncOne(record));
info(`[green]✔ synced ${table}`); info(`[green]✔ synced ${table}`);
} }
@@ -175,7 +175,7 @@ export default function Model<Interface extends Obj, Row extends Obj>(table: str
* Find an individual record by`id`. * Find an individual record by`id`.
*/ */
static find(id: number | string): Interface { static find(id: number | string): Interface {
return this.all().find((m) => m.id == Number(id)) as Interface; return this.all().find(m => m.id == Number(id)) as Interface;
} }
/** /**
@@ -189,7 +189,7 @@ export default function Model<Interface extends Obj, Row extends Obj>(table: str
* Find a collection of records by a set of the model's properties. * Find a collection of records by a set of the model's properties.
*/ */
static where(params: Partial<Interface>): Interface[] { static where(params: Partial<Interface>): Interface[] {
return repo.filter((m) => { return repo.filter(m => {
return Object.entries(params).every(([key, value]) => m[key] == value); return Object.entries(params).every(([key, value]) => m[key] == value);
}); });
} }
@@ -329,7 +329,7 @@ export default function Model<Interface extends Obj, Row extends Obj>(table: str
).rowsAffected >= 1; ).rowsAffected >= 1;
if (success) { if (success) {
instances.forEach((instance) => { instances.forEach(instance => {
this.syncRemove(instance); this.syncRemove(instance);
}); });
} }
@@ -343,7 +343,7 @@ export default function Model<Interface extends Obj, Row extends Obj>(table: str
protected static async query(sql: string, values: unknown[] = []): Promise<Interface[]> { protected static async query(sql: string, values: unknown[] = []): Promise<Interface[]> {
const result: Row[] = await (await this.db()).select<Row[]>(sql, values); const result: Row[] = await (await this.db()).select<Row[]>(sql, values);
return await Promise.all(result.map(async (row) => await this.fromSql(row))); return await Promise.all(result.map(async row => await this.fromSql(row)));
} }
/** /**
@@ -374,7 +374,7 @@ export default function Model<Interface extends Obj, Row extends Obj>(table: str
* Remove an instance from the repo * Remove an instance from the repo
*/ */
private static syncRemove(instance: Interface) { private static syncRemove(instance: Interface) {
repo = repo.filter((i) => i.id !== instance.id); repo = repo.filter(i => i.id !== instance.id);
} }
/** /**
@@ -389,7 +389,7 @@ export default function Model<Interface extends Obj, Row extends Obj>(table: str
* This leaves only persisted records(ones with an`id`). * This leaves only persisted records(ones with an`id`).
*/ */
private static removeEphemeralInstances() { private static removeEphemeralInstances() {
repo = repo.filter((record) => record.id !== undefined); repo = repo.filter(record => record.id !== undefined);
} }
/** /**
@@ -490,7 +490,7 @@ export function BareModel<T extends Obj>() {
} }
static delete(instance: T) { static delete(instance: T) {
repo = repo.filter((i) => i !== instance); repo = repo.filter(i => i !== instance);
} }
static all(): T[] { static all(): T[] {

View File

@@ -14,11 +14,7 @@ interface Row {
value: string; value: string;
} }
type ConfigKey = type ConfigKey = 'latest-session-id' | 'welcome-agreed' | 'skipped-version' | 'default-model';
| 'latest-session-id'
| 'welcome-agreed'
| 'skipped-version'
| 'default-model';
export default class Config extends Model<IConfig, Row>('config') { export default class Config extends Model<IConfig, Row>('config') {
@getset('latest-session-id') @getset('latest-session-id')
@@ -81,7 +77,7 @@ export default class Config extends Model<IConfig, Row>('config') {
} }
function getset(key: ConfigKey) { function getset(key: ConfigKey) {
return function(target: object, property: string) { return function (target: object, property: string) {
function get() { function get() {
return Config.get(key); return Config.get(key);
} }

View File

@@ -75,6 +75,6 @@ export default class Engine extends BareModel<IEngine>() {
}); });
} }
Model.reset(this.all().flatMap((engine) => engine.models)); Model.reset(this.all().flatMap(engine => engine.models));
} }
} }

View File

@@ -10,7 +10,7 @@ export function from(message: IMessage): Message {
return { return {
role: message.role, role: message.role,
content: message.content, content: message.content,
tool_calls: message.toolCalls?.map((c) => ({ tool_calls: message.toolCalls?.map(c => ({
function: { function: {
name: c.function.name, name: c.function.name,
arguments: c.function.arguments, arguments: c.function.arguments,

View File

@@ -53,7 +53,7 @@ function toolCalls(message: IMessage): OpenAI.ChatCompletionMessageToolCall[] |
return; return;
} }
return message.toolCalls.map((call) => ({ return message.toolCalls.map(call => ({
id: call.id as string, id: call.id as string,
type: 'function', type: 'function',
function: { function: {

View File

@@ -86,7 +86,7 @@ export default class Session extends Base<ISession, Row>('sessions') {
static async removeMcpServer(session: ISession, server: IMcpServer): Promise<ISession> { static async removeMcpServer(session: ISession, server: IMcpServer): Promise<ISession> {
session.config.enabledMcpServers = session.config.enabledMcpServers.filter( session.config.enabledMcpServers = session.config.enabledMcpServers.filter(
(s) => s !== server.name s => s !== server.name
); );
return await this.update(session); return await this.update(session);
} }

View File

@@ -4,13 +4,13 @@ import Message from '$lib/models/message';
export async function migrate() { export async function migrate() {
await Promise.all( await Promise.all(
Message.all().map(async (message) => { Message.all().map(async message => {
if (message.toolCalls.length == 0) { if (message.toolCalls.length == 0) {
return; return;
} }
await Promise.all( await Promise.all(
message.toolCalls.map(async (tc) => { message.toolCalls.map(async tc => {
// Ollama tool calls didn't always have an `id`. Add one // Ollama tool calls didn't always have an `id`. Add one
// now if that's the case. // now if that's the case.
if (!tc.id) { if (!tc.id) {

View File

@@ -25,12 +25,12 @@
} }
} }
onNavigate((navigation) => { onNavigate(navigation => {
if (!document.startViewTransition) { if (!document.startViewTransition) {
return; return;
} }
return new Promise((resolve) => { return new Promise(resolve => {
document.startViewTransition(async () => { document.startViewTransition(async () => {
resolve(); resolve();
await navigation.complete; await navigation.complete;

View File

@@ -7,11 +7,7 @@
import Svg from '$components/Svg.svelte'; import Svg from '$components/Svg.svelte';
import Updater from '$components/Updater.svelte'; import Updater from '$components/Updater.svelte';
import Welcome from '$components/Welcome.svelte'; import Welcome from '$components/Welcome.svelte';
import startup, { import startup, { type Condition, type OnSuccess, StartupCheck } from '$lib/startup';
type Condition,
type OnSuccess,
StartupCheck,
} from '$lib/startup';
const messages = { const messages = {
[StartupCheck.NoModels]: 'No Engines Connected', [StartupCheck.NoModels]: 'No Engines Connected',
@@ -73,9 +69,6 @@
{/if} {/if}
</Modal> </Modal>
{:else} {:else}
<Svg <Svg name="Logo" class="text-dark fixed top-[50%] left-[50%] h-32 w-32 -translate-[50%]" />
name="Logo"
class="text-dark fixed top-[50%] left-[50%] h-32 w-32 -translate-[50%]"
/>
{/if} {/if}
</Layout> </Layout>

View File

@@ -20,14 +20,12 @@
import Session, { type ISession } from '$lib/models/session'; import Session, { type ISession } from '$lib/models/session';
const session: ISession = $derived(Session.find(page.params.session_id)); const session: ISession = $derived(Session.find(page.params.session_id));
const model: IModel | undefined = $derived( const model: IModel | undefined = $derived(Model.find(session.config.model));
Model.find(session.config.model)
);
const sessions: ISession[] = $derived(Session.all()); const sessions: ISession[] = $derived(Session.all());
const mcpServers: IMcpServer[] = $derived(McpServer.all()); const mcpServers: IMcpServer[] = $derived(McpServer.all());
const engines: IEngine[] = $derived(Engine.all()); const engines: IEngine[] = $derived(Engine.all());
const hasModels = $derived(engines.flatMap((e) => e.models).length > 0); const hasModels = $derived(engines.flatMap(e => e.models).length > 0);
let advancedIsOpen = $state(false); let advancedIsOpen = $state(false);
@@ -37,8 +35,8 @@
} }
async function startMcpServers(session: ISession) { async function startMcpServers(session: ISession) {
session.config.enabledMcpServers.forEach(async (name) => { session.config.enabledMcpServers.forEach(async name => {
const server = mcpServers.find((s) => s.name == name); const server = mcpServers.find(s => s.name == name);
if (server) { if (server) {
await startMcpServer(server); await startMcpServer(server);
@@ -102,9 +100,7 @@
</script> </script>
{#snippet titlebar()} {#snippet titlebar()}
<Flex <Flex class="border-r-light z-10 h-full w-[300px] items-center border-r px-8 pr-4">
class="border-r-light z-10 h-full w-[300px] items-center border-r px-8 pr-4"
>
<h1 class="grow font-[500]">Chat</h1> <h1 class="grow font-[500]">Chat</h1>
<button <button
onclick={() => addSession()} onclick={() => addSession()}
@@ -118,18 +114,14 @@
<Layout {titlebar}> <Layout {titlebar}>
<Flex class="h-full items-start"> <Flex class="h-full items-start">
<Flex <Flex class="border-light bg-medium h-content w-[300px] flex-col overflow-auto border-r">
class="border-light bg-medium h-content w-[300px] flex-col overflow-auto border-r"
>
{#each sessions as sess (sess.id)} {#each sessions as sess (sess.id)}
<Flex <Flex
class={`text-medium border-b-light w-full justify-between border-b class={`text-medium border-b-light w-full justify-between border-b
border-l-transparent text-sm ${sess.id == session?.id ? '!border-l-purple border-l' : ''}`} border-l-transparent text-sm ${sess.id == session?.id ? '!border-l-purple border-l' : ''}`}
> >
<Menu items={menuItems(sess)}> <Menu items={menuItems(sess)}>
<Deleteable <Deleteable ondelete={async () => await deleteSession(sess)}>
ondelete={async () => await deleteSession(sess)}
>
<Link <Link
href={`/chat/${sess.id}`} href={`/chat/${sess.id}`}
class="w-full py-3 pl-8 text-left" class="w-full py-3 pl-8 text-left"
@@ -145,17 +137,13 @@
</Flex> </Flex>
{#if session} {#if session}
<Flex <Flex class="bg-medium h-full w-[calc(100%-600px)] grow items-start">
class="bg-medium h-full w-[calc(100%-600px)] grow items-start"
>
{#key session.id} {#key session.id}
<Chat {session} bind:model={session.config.model} /> <Chat {session} bind:model={session.config.model} />
{/key} {/key}
</Flex> </Flex>
<Flex <Flex class="bg-medium border-light h-full w-[300px] flex-col items-start border-l p-4">
class="bg-medium border-light h-full w-[300px] flex-col items-start border-l p-4"
>
{#key session.config.model} {#key session.config.model}
<ModelMenu <ModelMenu
{engines} {engines}
@@ -190,10 +178,8 @@
<Flex class="text-light z-0 mb-4 ml-2"> <Flex class="text-light z-0 mb-4 ml-2">
<Toggle <Toggle
label={server.name} label={server.name}
value={Session.hasMcpServer( value={Session.hasMcpServer(session, server.name) &&
session, model?.supportsTools
server.name
) && model?.supportsTools
? 'on' ? 'on'
: 'off'} : 'off'}
disabled={!model?.supportsTools} disabled={!model?.supportsTools}
@@ -209,19 +195,14 @@
class="text-dark mb-4 ml-2 self-start text-sm font-medium hover:cursor-pointer" class="text-dark mb-4 ml-2 self-start text-sm font-medium hover:cursor-pointer"
onclick={() => toggleAdvanced()} onclick={() => toggleAdvanced()}
> >
Advanced <span class="ml-4" Advanced <span class="ml-4">
>{advancedIsOpen ? '⏷' : '⏵'}</span {advancedIsOpen ? '⏷' : '⏵'}
> </span>
</button> </button>
{#if advancedIsOpen} {#if advancedIsOpen}
<Flex <Flex class="m-auto w-full flex-col items-start px-4 pt-4 pl-0">
class="m-auto w-full flex-col items-start px-4 pt-4 pl-0" <label for="ctx_num" class="text-medium mb-1 ml-2 text-sm">
>
<label
for="ctx_num"
class="text-medium mb-1 ml-2 text-sm"
>
Context Window Size Context Window Size
</label> </label>
<input <input
@@ -234,10 +215,7 @@
bind:value={session.config.contextWindow} bind:value={session.config.contextWindow}
/> />
<label <label for="temperature" class="text-medium mt-4 mb-1 ml-2 text-sm">
for="temperature"
class="text-medium mt-4 mb-1 ml-2 text-sm"
>
Temperature Temperature
</label> </label>

View File

@@ -28,7 +28,10 @@
{#if config.type !== 'stdio'} {#if config.type !== 'stdio'}
<Flex class="text-red w-full"> <Flex class="text-red w-full">
<Svg name="Warning" class="mr-8 h-6 w-6" /> <Svg name="Warning" class="mr-8 h-6 w-6" />
<p>Tome only supports <code>stdio</code> MCP servers</p> <p>
Tome only supports <code>stdio</code>
MCP servers
</p>
</Flex> </Flex>
{:else} {:else}
<Flex class="w-full flex-col items-start"> <Flex class="w-full flex-col items-start">