Remove reasoning
Browse files- .env +0 -2
- scripts/populate.ts +0 -26
- src/lib/components/NavMenu.svelte +1 -1
- src/lib/components/chat/ChatMessage.svelte +2 -38
- src/lib/constants/routerExamples.ts +1 -2
- src/lib/server/api/routes/groups/models.ts +0 -2
- src/lib/server/models.ts +0 -16
- src/lib/server/router/endpoint.ts +1 -3
- src/lib/server/textGeneration/generate.ts +3 -172
- src/lib/server/textGeneration/reasoning.ts +0 -40
- src/lib/types/Message.ts +0 -1
- src/routes/api/conversation/[id]/+server.ts +0 -1
- src/routes/conversation/[id]/+page.svelte +1 -25
- src/routes/conversation/[id]/+server.ts +0 -11
- src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts +0 -1
- src/routes/models/+page.svelte +15 -21
.env
CHANGED
|
@@ -36,8 +36,6 @@ AUTOMATIC_LOGIN=
|
|
| 36 |
### Local Storage ###
|
| 37 |
MONGO_STORAGE_PATH= # where is the db folder stored
|
| 38 |
|
| 39 |
-
REASONING_SUMMARY=false # Change this to false to disable reasoning summary
|
| 40 |
-
|
| 41 |
## Models overrides
|
| 42 |
MODELS=
|
| 43 |
|
|
|
|
| 36 |
### Local Storage ###
|
| 37 |
MONGO_STORAGE_PATH= # where is the db folder stored
|
| 38 |
|
|
|
|
|
|
|
| 39 |
## Models overrides
|
| 40 |
MODELS=
|
| 41 |
|
scripts/populate.ts
CHANGED
|
@@ -22,8 +22,6 @@ import { generateSearchTokens } from "../src/lib/utils/searchTokens.ts";
|
|
| 22 |
import { ReviewStatus } from "../src/lib/types/Review.ts";
|
| 23 |
import fs from "fs";
|
| 24 |
import path from "path";
|
| 25 |
-
import { MessageUpdateType } from "../src/lib/types/MessageUpdate.ts";
|
| 26 |
-
import { MessageReasoningUpdateType } from "../src/lib/types/MessageUpdate.ts";
|
| 27 |
|
| 28 |
const rl = readline.createInterface({
|
| 29 |
input: process.stdin,
|
|
@@ -62,7 +60,6 @@ async function generateMessages(preprompt?: string): Promise<Message[]> {
|
|
| 62 |
const convLength = faker.number.int({ min: 1, max: 25 }) * 2; // must always be even
|
| 63 |
|
| 64 |
for (let i = 0; i < convLength; i++) {
|
| 65 |
-
const hasReasoning = Math.random() < 0.2;
|
| 66 |
lastId = addChildren(
|
| 67 |
{
|
| 68 |
messages,
|
|
@@ -80,17 +77,6 @@ async function generateMessages(preprompt?: string): Promise<Message[]> {
|
|
| 80 |
: ""),
|
| 81 |
createdAt: faker.date.recent({ days: 30 }),
|
| 82 |
updatedAt: faker.date.recent({ days: 30 }),
|
| 83 |
-
reasoning: hasReasoning ? faker.lorem.paragraphs(2) : undefined,
|
| 84 |
-
updates: hasReasoning
|
| 85 |
-
? [
|
| 86 |
-
{
|
| 87 |
-
type: MessageUpdateType.Reasoning,
|
| 88 |
-
subtype: MessageReasoningUpdateType.Status,
|
| 89 |
-
uuid: crypto.randomUUID(),
|
| 90 |
-
status: "thinking",
|
| 91 |
-
},
|
| 92 |
-
]
|
| 93 |
-
: [],
|
| 94 |
interrupted: !isUser && i === convLength - 1 && isInterrupted,
|
| 95 |
},
|
| 96 |
lastId
|
|
@@ -101,7 +87,6 @@ async function generateMessages(preprompt?: string): Promise<Message[]> {
|
|
| 101 |
const convLength = faker.number.int({ min: 2, max: 200 });
|
| 102 |
|
| 103 |
for (let i = 0; i < convLength; i++) {
|
| 104 |
-
const hasReasoning = Math.random() < 0.2;
|
| 105 |
addChildren(
|
| 106 |
{
|
| 107 |
messages,
|
|
@@ -117,17 +102,6 @@ async function generateMessages(preprompt?: string): Promise<Message[]> {
|
|
| 117 |
(!isUser && Math.random() < 0.1
|
| 118 |
? "\n```\n" + faker.helpers.arrayElement(samples) + "\n```\n"
|
| 119 |
: ""),
|
| 120 |
-
reasoning: hasReasoning ? faker.lorem.paragraphs(2) : undefined,
|
| 121 |
-
updates: hasReasoning
|
| 122 |
-
? [
|
| 123 |
-
{
|
| 124 |
-
type: MessageUpdateType.Reasoning,
|
| 125 |
-
subtype: MessageReasoningUpdateType.Status,
|
| 126 |
-
uuid: crypto.randomUUID(),
|
| 127 |
-
status: "thinking",
|
| 128 |
-
},
|
| 129 |
-
]
|
| 130 |
-
: [],
|
| 131 |
createdAt: faker.date.recent({ days: 30 }),
|
| 132 |
updatedAt: faker.date.recent({ days: 30 }),
|
| 133 |
interrupted: !isUser && i === convLength - 1 && isInterrupted,
|
|
|
|
| 22 |
import { ReviewStatus } from "../src/lib/types/Review.ts";
|
| 23 |
import fs from "fs";
|
| 24 |
import path from "path";
|
|
|
|
|
|
|
| 25 |
|
| 26 |
const rl = readline.createInterface({
|
| 27 |
input: process.stdin,
|
|
|
|
| 60 |
const convLength = faker.number.int({ min: 1, max: 25 }) * 2; // must always be even
|
| 61 |
|
| 62 |
for (let i = 0; i < convLength; i++) {
|
|
|
|
| 63 |
lastId = addChildren(
|
| 64 |
{
|
| 65 |
messages,
|
|
|
|
| 77 |
: ""),
|
| 78 |
createdAt: faker.date.recent({ days: 30 }),
|
| 79 |
updatedAt: faker.date.recent({ days: 30 }),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
interrupted: !isUser && i === convLength - 1 && isInterrupted,
|
| 81 |
},
|
| 82 |
lastId
|
|
|
|
| 87 |
const convLength = faker.number.int({ min: 2, max: 200 });
|
| 88 |
|
| 89 |
for (let i = 0; i < convLength; i++) {
|
|
|
|
| 90 |
addChildren(
|
| 91 |
{
|
| 92 |
messages,
|
|
|
|
| 102 |
(!isUser && Math.random() < 0.1
|
| 103 |
? "\n```\n" + faker.helpers.arrayElement(samples) + "\n```\n"
|
| 104 |
: ""),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
createdAt: faker.date.recent({ days: 30 }),
|
| 106 |
updatedAt: faker.date.recent({ days: 30 }),
|
| 107 |
interrupted: !isUser && i === convLength - 1 && isInterrupted,
|
src/lib/components/NavMenu.svelte
CHANGED
|
@@ -192,7 +192,7 @@
|
|
| 192 |
|
| 193 |
<span class="flex gap-1">
|
| 194 |
<a
|
| 195 |
-
href="{base}/settings"
|
| 196 |
class="flex h-9 flex-none flex-grow items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
| 197 |
>
|
| 198 |
Settings
|
|
|
|
| 192 |
|
| 193 |
<span class="flex gap-1">
|
| 194 |
<a
|
| 195 |
+
href="{base}/settings/application"
|
| 196 |
class="flex h-9 flex-none flex-grow items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
| 197 |
>
|
| 198 |
Settings
|
src/lib/components/chat/ChatMessage.svelte
CHANGED
|
@@ -12,11 +12,7 @@
|
|
| 12 |
import CarbonPen from "~icons/carbon/pen";
|
| 13 |
import UploadedFile from "./UploadedFile.svelte";
|
| 14 |
|
| 15 |
-
import {
|
| 16 |
-
MessageUpdateType,
|
| 17 |
-
type MessageReasoningUpdate,
|
| 18 |
-
MessageReasoningUpdateType,
|
| 19 |
-
} from "$lib/types/MessageUpdate";
|
| 20 |
import MarkdownRenderer from "./MarkdownRenderer.svelte";
|
| 21 |
import OpenReasoningResults from "./OpenReasoningResults.svelte";
|
| 22 |
import Alternatives from "./Alternatives.svelte";
|
|
@@ -71,29 +67,9 @@
|
|
| 71 |
let editContentEl: HTMLTextAreaElement | undefined = $state();
|
| 72 |
let editFormEl: HTMLFormElement | undefined = $state();
|
| 73 |
|
| 74 |
-
let reasoningUpdates = $derived(
|
| 75 |
-
(message.updates?.filter(({ type }) => type === MessageUpdateType.Reasoning) ??
|
| 76 |
-
[]) as MessageReasoningUpdate[]
|
| 77 |
-
);
|
| 78 |
-
|
| 79 |
-
// const messageFinalAnswer = $derived(
|
| 80 |
-
// message.updates?.find(
|
| 81 |
-
// ({ type }) => type === MessageUpdateType.FinalAnswer
|
| 82 |
-
// ) as MessageFinalAnswerUpdate
|
| 83 |
-
// );
|
| 84 |
-
// const urlNotTrailing = $derived(page.url.pathname.replace(/\/$/, ""));
|
| 85 |
-
// let downloadLink = $derived(urlNotTrailing + `/message/${message.id}/prompt`);
|
| 86 |
-
|
| 87 |
// Zero-config reasoning autodetection: detect <think> blocks in content
|
| 88 |
const THINK_BLOCK_REGEX = /(<think>[\s\S]*?(?:<\/think>|$))/g;
|
| 89 |
-
let
|
| 90 |
-
let hasServerReasoning = $derived(
|
| 91 |
-
reasoningUpdates &&
|
| 92 |
-
reasoningUpdates.length > 0 &&
|
| 93 |
-
!!message.reasoning &&
|
| 94 |
-
message.reasoning.trim().length > 0
|
| 95 |
-
);
|
| 96 |
-
let hasClientThink = $derived(!hasServerReasoning && thinkSegments.length > 1);
|
| 97 |
|
| 98 |
$effect(() => {
|
| 99 |
if (isCopied) {
|
|
@@ -143,18 +119,6 @@
|
|
| 143 |
</div>
|
| 144 |
{/if}
|
| 145 |
|
| 146 |
-
{#if hasServerReasoning}
|
| 147 |
-
{@const summaries = reasoningUpdates
|
| 148 |
-
.filter((u) => u.subtype === MessageReasoningUpdateType.Status)
|
| 149 |
-
.map((u) => u.status)}
|
| 150 |
-
|
| 151 |
-
<OpenReasoningResults
|
| 152 |
-
summary={summaries[summaries.length - 1] || ""}
|
| 153 |
-
content={message.reasoning || ""}
|
| 154 |
-
loading={loading && message.content.length === 0}
|
| 155 |
-
/>
|
| 156 |
-
{/if}
|
| 157 |
-
|
| 158 |
<div bind:this={contentEl}>
|
| 159 |
{#if isLast && loading && message.content.length === 0}
|
| 160 |
<IconLoading classNames="loading inline ml-2 first:ml-0" />
|
|
|
|
| 12 |
import CarbonPen from "~icons/carbon/pen";
|
| 13 |
import UploadedFile from "./UploadedFile.svelte";
|
| 14 |
|
| 15 |
+
import { MessageUpdateType } from "$lib/types/MessageUpdate";
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
import MarkdownRenderer from "./MarkdownRenderer.svelte";
|
| 17 |
import OpenReasoningResults from "./OpenReasoningResults.svelte";
|
| 18 |
import Alternatives from "./Alternatives.svelte";
|
|
|
|
| 67 |
let editContentEl: HTMLTextAreaElement | undefined = $state();
|
| 68 |
let editFormEl: HTMLFormElement | undefined = $state();
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
// Zero-config reasoning autodetection: detect <think> blocks in content
|
| 71 |
const THINK_BLOCK_REGEX = /(<think>[\s\S]*?(?:<\/think>|$))/g;
|
| 72 |
+
let hasClientThink = $derived(message.content.split(THINK_BLOCK_REGEX).length > 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
$effect(() => {
|
| 75 |
if (isCopied) {
|
|
|
|
| 119 |
</div>
|
| 120 |
{/if}
|
| 121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
<div bind:this={contentEl}>
|
| 123 |
{#if isLast && loading && message.content.length === 0}
|
| 124 |
<IconLoading classNames="loading inline ml-2 first:ml-0" />
|
src/lib/constants/routerExamples.ts
CHANGED
|
@@ -55,8 +55,7 @@ export const routerExamples: RouterExample[] = [
|
|
| 55 |
followUps: [
|
| 56 |
{
|
| 57 |
title: "Dark mode",
|
| 58 |
-
prompt:
|
| 59 |
-
"Extend the Tailwind CSS landing page with a toggleable dark mode that remembers the user's choice.",
|
| 60 |
},
|
| 61 |
{
|
| 62 |
title: "Add blog teasers",
|
|
|
|
| 55 |
followUps: [
|
| 56 |
{
|
| 57 |
title: "Dark mode",
|
| 58 |
+
prompt: "Add dark mode and make it the default",
|
|
|
|
| 59 |
},
|
| 60 |
{
|
| 61 |
title: "Add blog teasers",
|
src/lib/server/api/routes/groups/models.ts
CHANGED
|
@@ -13,7 +13,6 @@ export type GETModelsResponse = Array<{
|
|
| 13 |
datasetUrl?: string;
|
| 14 |
displayName: string;
|
| 15 |
description?: string;
|
| 16 |
-
reasoning: boolean;
|
| 17 |
logoUrl?: string;
|
| 18 |
providers?: Array<{ provider: string } & Record<string, unknown>>;
|
| 19 |
promptExamples?: { title: string; prompt: string }[];
|
|
@@ -50,7 +49,6 @@ export const modelGroup = new Elysia().group("/models", (app) =>
|
|
| 50 |
datasetUrl: model.datasetUrl,
|
| 51 |
displayName: model.displayName,
|
| 52 |
description: model.description,
|
| 53 |
-
reasoning: !!model.reasoning,
|
| 54 |
logoUrl: model.logoUrl,
|
| 55 |
providers: model.providers as unknown as Array<
|
| 56 |
{ provider: string } & Record<string, unknown>
|
|
|
|
| 13 |
datasetUrl?: string;
|
| 14 |
displayName: string;
|
| 15 |
description?: string;
|
|
|
|
| 16 |
logoUrl?: string;
|
| 17 |
providers?: Array<{ provider: string } & Record<string, unknown>>;
|
| 18 |
promptExamples?: { title: string; prompt: string }[];
|
|
|
|
| 49 |
datasetUrl: model.datasetUrl,
|
| 50 |
displayName: model.displayName,
|
| 51 |
description: model.description,
|
|
|
|
| 52 |
logoUrl: model.logoUrl,
|
| 53 |
providers: model.providers as unknown as Array<
|
| 54 |
{ provider: string } & Record<string, unknown>
|
src/lib/server/models.ts
CHANGED
|
@@ -15,21 +15,6 @@ const sanitizeJSONEnv = (val: string, fallback: string) => {
|
|
| 15 |
return unquoted || fallback;
|
| 16 |
};
|
| 17 |
|
| 18 |
-
const reasoningSchema = z.union([
|
| 19 |
-
z.object({
|
| 20 |
-
type: z.literal("regex"), // everything is reasoning, extract the answer from the regex
|
| 21 |
-
regex: z.string(),
|
| 22 |
-
}),
|
| 23 |
-
z.object({
|
| 24 |
-
type: z.literal("tokens"), // use beginning and end tokens that define the reasoning portion of the answer
|
| 25 |
-
beginToken: z.string(), // empty string means the model starts in reasoning mode
|
| 26 |
-
endToken: z.string(),
|
| 27 |
-
}),
|
| 28 |
-
z.object({
|
| 29 |
-
type: z.literal("summarize"), // everything is reasoning, summarize the answer
|
| 30 |
-
}),
|
| 31 |
-
]);
|
| 32 |
-
|
| 33 |
const modelConfig = z.object({
|
| 34 |
/** Used as an identifier in DB */
|
| 35 |
id: z.string().optional(),
|
|
@@ -75,7 +60,6 @@ const modelConfig = z.object({
|
|
| 75 |
embeddingModel: z.never().optional(),
|
| 76 |
/** Used to enable/disable system prompt usage */
|
| 77 |
systemRoleSupported: z.boolean().default(true),
|
| 78 |
-
reasoning: reasoningSchema.optional(),
|
| 79 |
});
|
| 80 |
|
| 81 |
type ModelConfig = z.infer<typeof modelConfig>;
|
|
|
|
| 15 |
return unquoted || fallback;
|
| 16 |
};
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
const modelConfig = z.object({
|
| 19 |
/** Used as an identifier in DB */
|
| 20 |
id: z.string().optional(),
|
|
|
|
| 60 |
embeddingModel: z.never().optional(),
|
| 61 |
/** Used to enable/disable system prompt usage */
|
| 62 |
systemRoleSupported: z.boolean().default(true),
|
|
|
|
| 63 |
});
|
| 64 |
|
| 65 |
type ModelConfig = z.infer<typeof modelConfig>;
|
src/lib/server/router/endpoint.ts
CHANGED
|
@@ -86,12 +86,10 @@ function stripReasoningBlocks(text: string): string {
|
|
| 86 |
}
|
| 87 |
|
| 88 |
function stripReasoningFromMessage(message: EndpointMessage): EndpointMessage {
|
| 89 |
-
const { reasoning: _reasoning, ...rest } = message;
|
| 90 |
-
void _reasoning;
|
| 91 |
const content =
|
| 92 |
typeof message.content === "string" ? stripReasoningBlocks(message.content) : message.content;
|
| 93 |
return {
|
| 94 |
-
...
|
| 95 |
content,
|
| 96 |
};
|
| 97 |
}
|
|
|
|
| 86 |
}
|
| 87 |
|
| 88 |
function stripReasoningFromMessage(message: EndpointMessage): EndpointMessage {
|
|
|
|
|
|
|
| 89 |
const content =
|
| 90 |
typeof message.content === "string" ? stripReasoningBlocks(message.content) : message.content;
|
| 91 |
return {
|
| 92 |
+
...message,
|
| 93 |
content,
|
| 94 |
};
|
| 95 |
}
|
src/lib/server/textGeneration/generate.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
| 1 |
-
import { config } from "$lib/server/config";
|
| 2 |
import {
|
| 3 |
-
MessageReasoningUpdateType,
|
| 4 |
MessageUpdateType,
|
| 5 |
type MessageUpdate,
|
| 6 |
} from "$lib/types/MessageUpdate";
|
| 7 |
import { AbortedGenerations } from "../abortedGenerations";
|
| 8 |
import type { TextGenerationContext } from "./types";
|
| 9 |
import type { EndpointMessage } from "../endpoints/endpoints";
|
| 10 |
-
import { generateFromDefaultEndpoint } from "../generateFromDefaultEndpoint";
|
| 11 |
-
import { generateSummaryOfReasoning } from "./reasoning";
|
| 12 |
import { logger } from "../logger";
|
| 13 |
|
| 14 |
type GenerateContext = Omit<TextGenerationContext, "messages"> & { messages: EndpointMessage[] };
|
|
@@ -27,29 +23,6 @@ export async function* generate(
|
|
| 27 |
}: GenerateContext,
|
| 28 |
preprompt?: string
|
| 29 |
): AsyncIterable<MessageUpdate> {
|
| 30 |
-
// reasoning mode is false by default
|
| 31 |
-
let reasoning = false;
|
| 32 |
-
let reasoningBuffer = "";
|
| 33 |
-
let lastReasoningUpdate = new Date();
|
| 34 |
-
let status = "";
|
| 35 |
-
const startTime = new Date();
|
| 36 |
-
if (
|
| 37 |
-
model.reasoning &&
|
| 38 |
-
// if the beginToken is an empty string, the model starts in reasoning mode
|
| 39 |
-
(model.reasoning.type === "regex" ||
|
| 40 |
-
model.reasoning.type === "summarize" ||
|
| 41 |
-
(model.reasoning.type === "tokens" && model.reasoning.beginToken === ""))
|
| 42 |
-
) {
|
| 43 |
-
// if the model has reasoning in regex or summarize mode, it starts in reasoning mode
|
| 44 |
-
// and we extract the answer from the reasoning
|
| 45 |
-
reasoning = true;
|
| 46 |
-
yield {
|
| 47 |
-
type: MessageUpdateType.Reasoning,
|
| 48 |
-
subtype: MessageReasoningUpdateType.Status,
|
| 49 |
-
status: "Started reasoning...",
|
| 50 |
-
};
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
const stream = await endpoint({
|
| 54 |
messages,
|
| 55 |
preprompt,
|
|
@@ -89,161 +62,19 @@ export async function* generate(
|
|
| 89 |
text = text.slice(0, text.length - stopToken.length);
|
| 90 |
}
|
| 91 |
|
| 92 |
-
let finalAnswer = text;
|
| 93 |
-
if (model.reasoning && model.reasoning.type === "regex") {
|
| 94 |
-
const regex = new RegExp(model.reasoning.regex);
|
| 95 |
-
finalAnswer = regex.exec(reasoningBuffer)?.[1] ?? text;
|
| 96 |
-
} else if (model.reasoning && model.reasoning.type === "summarize") {
|
| 97 |
-
yield {
|
| 98 |
-
type: MessageUpdateType.Reasoning,
|
| 99 |
-
subtype: MessageReasoningUpdateType.Status,
|
| 100 |
-
status: "Summarizing reasoning...",
|
| 101 |
-
};
|
| 102 |
-
try {
|
| 103 |
-
const summary = yield* generateFromDefaultEndpoint({
|
| 104 |
-
messages: [
|
| 105 |
-
{
|
| 106 |
-
from: "user",
|
| 107 |
-
content: `Question: ${
|
| 108 |
-
messages[messages.length - 1].content
|
| 109 |
-
}\n\nReasoning: ${reasoningBuffer}`,
|
| 110 |
-
},
|
| 111 |
-
],
|
| 112 |
-
preprompt: `Your task is to summarize concisely all your reasoning steps and then give the final answer. Keep it short, one short paragraph at most. If the reasoning steps explicitly include a code solution, make sure to include it in your answer.
|
| 113 |
-
|
| 114 |
-
If the user is just having a casual conversation that doesn't require explanations, answer directly without explaining your steps, otherwise make sure to summarize step by step, make sure to skip dead-ends in your reasoning and removing excess detail.
|
| 115 |
-
|
| 116 |
-
Do not use prefixes such as Response: or Answer: when answering to the user.`,
|
| 117 |
-
generateSettings: {
|
| 118 |
-
max_tokens: 1024,
|
| 119 |
-
},
|
| 120 |
-
modelId: model.id,
|
| 121 |
-
locals,
|
| 122 |
-
});
|
| 123 |
-
finalAnswer = summary;
|
| 124 |
-
yield {
|
| 125 |
-
type: MessageUpdateType.Reasoning,
|
| 126 |
-
subtype: MessageReasoningUpdateType.Status,
|
| 127 |
-
status: `Done in ${Math.round((new Date().getTime() - startTime.getTime()) / 1000)}s.`,
|
| 128 |
-
};
|
| 129 |
-
} catch (e) {
|
| 130 |
-
finalAnswer = text;
|
| 131 |
-
logger.error(e);
|
| 132 |
-
}
|
| 133 |
-
} else if (model.reasoning && model.reasoning.type === "tokens") {
|
| 134 |
-
// make sure to remove the content of the reasoning buffer from
|
| 135 |
-
// the final answer to avoid duplication
|
| 136 |
-
|
| 137 |
-
// if the beginToken is an empty string, we don't need to remove anything
|
| 138 |
-
const beginIndex = model.reasoning.beginToken
|
| 139 |
-
? reasoningBuffer.indexOf(model.reasoning.beginToken)
|
| 140 |
-
: 0;
|
| 141 |
-
const endIndex = reasoningBuffer.lastIndexOf(model.reasoning.endToken);
|
| 142 |
-
|
| 143 |
-
if (beginIndex !== -1 && endIndex !== -1) {
|
| 144 |
-
// Remove the reasoning section (including tokens) from final answer
|
| 145 |
-
finalAnswer =
|
| 146 |
-
text.slice(0, beginIndex) + text.slice(endIndex + model.reasoning.endToken.length);
|
| 147 |
-
}
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
yield {
|
| 151 |
type: MessageUpdateType.FinalAnswer,
|
| 152 |
-
text:
|
| 153 |
interrupted,
|
| 154 |
};
|
| 155 |
continue;
|
| 156 |
}
|
| 157 |
|
| 158 |
-
if (model.reasoning && model.reasoning.type === "tokens") {
|
| 159 |
-
if (output.token.text === model.reasoning.beginToken) {
|
| 160 |
-
reasoning = true;
|
| 161 |
-
reasoningBuffer += output.token.text;
|
| 162 |
-
continue;
|
| 163 |
-
} else if (output.token.text === model.reasoning.endToken) {
|
| 164 |
-
reasoning = false;
|
| 165 |
-
reasoningBuffer += output.token.text;
|
| 166 |
-
yield {
|
| 167 |
-
type: MessageUpdateType.Reasoning,
|
| 168 |
-
subtype: MessageReasoningUpdateType.Status,
|
| 169 |
-
status: `Done in ${Math.round((new Date().getTime() - startTime.getTime()) / 1000)}s.`,
|
| 170 |
-
};
|
| 171 |
-
continue;
|
| 172 |
-
}
|
| 173 |
-
}
|
| 174 |
// ignore special tokens
|
| 175 |
if (output.token.special) continue;
|
| 176 |
|
| 177 |
-
//
|
| 178 |
-
|
| 179 |
-
reasoningBuffer += output.token.text;
|
| 180 |
-
|
| 181 |
-
if (model.reasoning && model.reasoning.type === "tokens") {
|
| 182 |
-
// split reasoning buffer so that anything that comes after the end token is separated
|
| 183 |
-
// add it to the normal buffer, and yield two updates, one for the reasoning and one for the normal content
|
| 184 |
-
// also set reasoning to false
|
| 185 |
-
|
| 186 |
-
if (reasoningBuffer.lastIndexOf(model.reasoning.endToken) !== -1) {
|
| 187 |
-
const endTokenIndex = reasoningBuffer.lastIndexOf(model.reasoning.endToken);
|
| 188 |
-
const textBuffer = reasoningBuffer.slice(endTokenIndex + model.reasoning.endToken.length);
|
| 189 |
-
reasoningBuffer = reasoningBuffer.slice(
|
| 190 |
-
0,
|
| 191 |
-
endTokenIndex + model.reasoning.endToken.length + 1
|
| 192 |
-
);
|
| 193 |
-
|
| 194 |
-
yield {
|
| 195 |
-
type: MessageUpdateType.Reasoning,
|
| 196 |
-
subtype: MessageReasoningUpdateType.Stream,
|
| 197 |
-
token: output.token.text,
|
| 198 |
-
};
|
| 199 |
-
|
| 200 |
-
yield {
|
| 201 |
-
type: MessageUpdateType.Stream,
|
| 202 |
-
token: textBuffer,
|
| 203 |
-
};
|
| 204 |
-
|
| 205 |
-
yield {
|
| 206 |
-
type: MessageUpdateType.Reasoning,
|
| 207 |
-
subtype: MessageReasoningUpdateType.Status,
|
| 208 |
-
status: `Done in ${Math.round((new Date().getTime() - startTime.getTime()) / 1000)}s.`,
|
| 209 |
-
};
|
| 210 |
-
|
| 211 |
-
reasoning = false;
|
| 212 |
-
continue;
|
| 213 |
-
}
|
| 214 |
-
}
|
| 215 |
-
// yield status update if it has changed
|
| 216 |
-
if (status !== "") {
|
| 217 |
-
yield {
|
| 218 |
-
type: MessageUpdateType.Reasoning,
|
| 219 |
-
subtype: MessageReasoningUpdateType.Status,
|
| 220 |
-
status,
|
| 221 |
-
};
|
| 222 |
-
status = "";
|
| 223 |
-
}
|
| 224 |
-
|
| 225 |
-
// create a new status every 5 seconds
|
| 226 |
-
if (
|
| 227 |
-
config.REASONING_SUMMARY === "true" &&
|
| 228 |
-
new Date().getTime() - lastReasoningUpdate.getTime() > 4000
|
| 229 |
-
) {
|
| 230 |
-
lastReasoningUpdate = new Date();
|
| 231 |
-
try {
|
| 232 |
-
generateSummaryOfReasoning(reasoningBuffer, model.id, locals).then((summary) => {
|
| 233 |
-
status = summary;
|
| 234 |
-
});
|
| 235 |
-
} catch (e) {
|
| 236 |
-
logger.error(e);
|
| 237 |
-
}
|
| 238 |
-
}
|
| 239 |
-
yield {
|
| 240 |
-
type: MessageUpdateType.Reasoning,
|
| 241 |
-
subtype: MessageReasoningUpdateType.Stream,
|
| 242 |
-
token: output.token.text,
|
| 243 |
-
};
|
| 244 |
-
} else {
|
| 245 |
-
yield { type: MessageUpdateType.Stream, token: output.token.text };
|
| 246 |
-
}
|
| 247 |
|
| 248 |
// abort check
|
| 249 |
const date = AbortedGenerations.getInstance().getAbortTime(conv._id.toString());
|
|
|
|
|
|
|
| 1 |
import {
|
|
|
|
| 2 |
MessageUpdateType,
|
| 3 |
type MessageUpdate,
|
| 4 |
} from "$lib/types/MessageUpdate";
|
| 5 |
import { AbortedGenerations } from "../abortedGenerations";
|
| 6 |
import type { TextGenerationContext } from "./types";
|
| 7 |
import type { EndpointMessage } from "../endpoints/endpoints";
|
|
|
|
|
|
|
| 8 |
import { logger } from "../logger";
|
| 9 |
|
| 10 |
type GenerateContext = Omit<TextGenerationContext, "messages"> & { messages: EndpointMessage[] };
|
|
|
|
| 23 |
}: GenerateContext,
|
| 24 |
preprompt?: string
|
| 25 |
): AsyncIterable<MessageUpdate> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
const stream = await endpoint({
|
| 27 |
messages,
|
| 28 |
preprompt,
|
|
|
|
| 62 |
text = text.slice(0, text.length - stopToken.length);
|
| 63 |
}
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
yield {
|
| 66 |
type: MessageUpdateType.FinalAnswer,
|
| 67 |
+
text: text,
|
| 68 |
interrupted,
|
| 69 |
};
|
| 70 |
continue;
|
| 71 |
}
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
// ignore special tokens
|
| 74 |
if (output.token.special) continue;
|
| 75 |
|
| 76 |
+
// yield normal token
|
| 77 |
+
yield { type: MessageUpdateType.Stream, token: output.token.text };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
// abort check
|
| 80 |
const date = AbortedGenerations.getInstance().getAbortTime(conv._id.toString());
|
src/lib/server/textGeneration/reasoning.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
| 1 |
-
import { generateFromDefaultEndpoint } from "../generateFromDefaultEndpoint";
|
| 2 |
-
import { getReturnFromGenerator } from "$lib/utils/getReturnFromGenerator";
|
| 3 |
-
|
| 4 |
-
export async function generateSummaryOfReasoning(
|
| 5 |
-
buffer: string,
|
| 6 |
-
modelId: string | undefined,
|
| 7 |
-
locals: App.Locals | undefined
|
| 8 |
-
): Promise<string> {
|
| 9 |
-
let summary: string | undefined;
|
| 10 |
-
|
| 11 |
-
// Tools removed: no tool-based summarization path
|
| 12 |
-
|
| 13 |
-
if (!summary) {
|
| 14 |
-
summary = await getReturnFromGenerator(
|
| 15 |
-
generateFromDefaultEndpoint({
|
| 16 |
-
messages: [
|
| 17 |
-
{
|
| 18 |
-
from: "user",
|
| 19 |
-
content: buffer.slice(-300),
|
| 20 |
-
},
|
| 21 |
-
],
|
| 22 |
-
preprompt: `You are tasked with summarizing the latest reasoning steps. Never describe results of the reasoning, only the process. Remain vague in your summary.
|
| 23 |
-
The text might be incomplete, try your best to summarize it in one very short sentence, starting with a gerund and ending with three points.
|
| 24 |
-
Example: "Thinking about life...", "Summarizing the results...", "Processing the input..."`,
|
| 25 |
-
generateSettings: {
|
| 26 |
-
max_tokens: 50,
|
| 27 |
-
},
|
| 28 |
-
modelId,
|
| 29 |
-
locals,
|
| 30 |
-
})
|
| 31 |
-
);
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
if (!summary) {
|
| 35 |
-
return "Reasoning...";
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
const parts = summary.split("...");
|
| 39 |
-
return parts[0].slice(0, 100) + "...";
|
| 40 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/types/Message.ts
CHANGED
|
@@ -8,7 +8,6 @@ export type Message = Partial<Timestamps> & {
|
|
| 8 |
content: string;
|
| 9 |
updates?: MessageUpdate[];
|
| 10 |
|
| 11 |
-
reasoning?: string;
|
| 12 |
score?: -1 | 0 | 1;
|
| 13 |
/**
|
| 14 |
* Either contains the base64 encoded image data
|
|
|
|
| 8 |
content: string;
|
| 9 |
updates?: MessageUpdate[];
|
| 10 |
|
|
|
|
| 11 |
score?: -1 | 0 | 1;
|
| 12 |
/**
|
| 13 |
* Either contains the base64 encoded image data
|
src/routes/api/conversation/[id]/+server.ts
CHANGED
|
@@ -28,7 +28,6 @@ export async function GET({ locals, params }) {
|
|
| 28 |
// websearch removed
|
| 29 |
files: message.files,
|
| 30 |
updates: message.updates,
|
| 31 |
-
reasoning: message.reasoning,
|
| 32 |
})),
|
| 33 |
};
|
| 34 |
return Response.json(res);
|
|
|
|
| 28 |
// websearch removed
|
| 29 |
files: message.files,
|
| 30 |
updates: message.updates,
|
|
|
|
| 31 |
})),
|
| 32 |
};
|
| 33 |
return Response.json(res);
|
src/routes/conversation/[id]/+page.svelte
CHANGED
|
@@ -9,11 +9,7 @@
|
|
| 9 |
import { ERROR_MESSAGES, error } from "$lib/stores/errors";
|
| 10 |
import { findCurrentModel } from "$lib/utils/models";
|
| 11 |
import type { Message } from "$lib/types/Message";
|
| 12 |
-
import {
|
| 13 |
-
MessageReasoningUpdateType,
|
| 14 |
-
MessageUpdateStatus,
|
| 15 |
-
MessageUpdateType,
|
| 16 |
-
} from "$lib/types/MessageUpdate";
|
| 17 |
import titleUpdate from "$lib/stores/titleUpdate";
|
| 18 |
import file2base64 from "$lib/utils/file2base64";
|
| 19 |
import { addChildren } from "$lib/utils/tree/addChildren";
|
|
@@ -263,9 +259,6 @@
|
|
| 263 |
// Initialize lastUpdateTime outside the loop to persist between updates
|
| 264 |
let lastUpdateTime = new Date();
|
| 265 |
|
| 266 |
-
let reasoningBuffer = "";
|
| 267 |
-
let reasoningLastUpdate = new Date();
|
| 268 |
-
|
| 269 |
for await (const update of messageUpdatesIterator) {
|
| 270 |
if ($isAborted) {
|
| 271 |
messageUpdatesAbortController.abort();
|
|
@@ -279,8 +272,6 @@
|
|
| 279 |
}
|
| 280 |
|
| 281 |
const isHighFrequencyUpdate =
|
| 282 |
-
(update.type === MessageUpdateType.Reasoning &&
|
| 283 |
-
update.subtype === MessageReasoningUpdateType.Stream) ||
|
| 284 |
update.type === MessageUpdateType.Stream ||
|
| 285 |
(update.type === MessageUpdateType.Status &&
|
| 286 |
update.status === MessageUpdateStatus.KeepAlive);
|
|
@@ -324,21 +315,6 @@
|
|
| 324 |
...(messageToWriteTo.files ?? []),
|
| 325 |
{ type: "hash", value: update.sha, mime: update.mime, name: update.name },
|
| 326 |
];
|
| 327 |
-
} else if (update.type === MessageUpdateType.Reasoning) {
|
| 328 |
-
if (!messageToWriteTo.reasoning) {
|
| 329 |
-
messageToWriteTo.reasoning = "";
|
| 330 |
-
}
|
| 331 |
-
if (update.subtype === MessageReasoningUpdateType.Stream) {
|
| 332 |
-
reasoningBuffer += update.token;
|
| 333 |
-
if (
|
| 334 |
-
currentTime.getTime() - reasoningLastUpdate.getTime() >
|
| 335 |
-
updateDebouncer.maxUpdateTime
|
| 336 |
-
) {
|
| 337 |
-
messageToWriteTo.reasoning += reasoningBuffer;
|
| 338 |
-
reasoningBuffer = "";
|
| 339 |
-
reasoningLastUpdate = currentTime;
|
| 340 |
-
}
|
| 341 |
-
}
|
| 342 |
} else if (update.type === MessageUpdateType.RouterMetadata) {
|
| 343 |
// Update router metadata immediately when received
|
| 344 |
messageToWriteTo.routerMetadata = {
|
|
|
|
| 9 |
import { ERROR_MESSAGES, error } from "$lib/stores/errors";
|
| 10 |
import { findCurrentModel } from "$lib/utils/models";
|
| 11 |
import type { Message } from "$lib/types/Message";
|
| 12 |
+
import { MessageUpdateStatus, MessageUpdateType } from "$lib/types/MessageUpdate";
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
import titleUpdate from "$lib/stores/titleUpdate";
|
| 14 |
import file2base64 from "$lib/utils/file2base64";
|
| 15 |
import { addChildren } from "$lib/utils/tree/addChildren";
|
|
|
|
| 259 |
// Initialize lastUpdateTime outside the loop to persist between updates
|
| 260 |
let lastUpdateTime = new Date();
|
| 261 |
|
|
|
|
|
|
|
|
|
|
| 262 |
for await (const update of messageUpdatesIterator) {
|
| 263 |
if ($isAborted) {
|
| 264 |
messageUpdatesAbortController.abort();
|
|
|
|
| 272 |
}
|
| 273 |
|
| 274 |
const isHighFrequencyUpdate =
|
|
|
|
|
|
|
| 275 |
update.type === MessageUpdateType.Stream ||
|
| 276 |
(update.type === MessageUpdateType.Status &&
|
| 277 |
update.status === MessageUpdateStatus.KeepAlive);
|
|
|
|
| 315 |
...(messageToWriteTo.files ?? []),
|
| 316 |
{ type: "hash", value: update.sha, mime: update.mime, name: update.name },
|
| 317 |
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
} else if (update.type === MessageUpdateType.RouterMetadata) {
|
| 319 |
// Update router metadata immediately when received
|
| 320 |
messageToWriteTo.routerMetadata = {
|
src/routes/conversation/[id]/+server.ts
CHANGED
|
@@ -8,7 +8,6 @@ import { error } from "@sveltejs/kit";
|
|
| 8 |
import { ObjectId } from "mongodb";
|
| 9 |
import { z } from "zod";
|
| 10 |
import {
|
| 11 |
-
MessageReasoningUpdateType,
|
| 12 |
MessageUpdateStatus,
|
| 13 |
MessageUpdateType,
|
| 14 |
type MessageUpdate,
|
|
@@ -349,12 +348,6 @@ export async function POST({ request, locals, params, getClientAddress }) {
|
|
| 349 |
lastTokenTimestamp = new Date();
|
| 350 |
}
|
| 351 |
lastTokenTimestamp = new Date();
|
| 352 |
-
} else if (
|
| 353 |
-
event.type === MessageUpdateType.Reasoning &&
|
| 354 |
-
event.subtype === MessageReasoningUpdateType.Stream
|
| 355 |
-
) {
|
| 356 |
-
messageToWriteTo.reasoning ??= "";
|
| 357 |
-
messageToWriteTo.reasoning += event.token;
|
| 358 |
}
|
| 359 |
|
| 360 |
// Set the title
|
|
@@ -399,10 +392,6 @@ export async function POST({ request, locals, params, getClientAddress }) {
|
|
| 399 |
!(
|
| 400 |
event.type === MessageUpdateType.Status &&
|
| 401 |
event.status === MessageUpdateStatus.KeepAlive
|
| 402 |
-
) &&
|
| 403 |
-
!(
|
| 404 |
-
event.type === MessageUpdateType.Reasoning &&
|
| 405 |
-
event.subtype === MessageReasoningUpdateType.Stream
|
| 406 |
)
|
| 407 |
) {
|
| 408 |
messageToWriteTo?.updates?.push(event);
|
|
|
|
| 8 |
import { ObjectId } from "mongodb";
|
| 9 |
import { z } from "zod";
|
| 10 |
import {
|
|
|
|
| 11 |
MessageUpdateStatus,
|
| 12 |
MessageUpdateType,
|
| 13 |
type MessageUpdate,
|
|
|
|
| 348 |
lastTokenTimestamp = new Date();
|
| 349 |
}
|
| 350 |
lastTokenTimestamp = new Date();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
}
|
| 352 |
|
| 353 |
// Set the title
|
|
|
|
| 392 |
!(
|
| 393 |
event.type === MessageUpdateType.Status &&
|
| 394 |
event.status === MessageUpdateStatus.KeepAlive
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
)
|
| 396 |
) {
|
| 397 |
messageToWriteTo?.updates?.push(event);
|
src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts
CHANGED
|
@@ -59,7 +59,6 @@ export async function GET({ params, locals }) {
|
|
| 59 |
content: msg.content,
|
| 60 |
createdAt: msg.createdAt,
|
| 61 |
updatedAt: msg.updatedAt,
|
| 62 |
-
reasoning: msg.reasoning,
|
| 63 |
updates: msg.updates?.filter((u) => u.type === "title"),
|
| 64 |
files: msg.files,
|
| 65 |
})),
|
|
|
|
| 59 |
content: msg.content,
|
| 60 |
createdAt: msg.createdAt,
|
| 61 |
updatedAt: msg.updatedAt,
|
|
|
|
| 62 |
updates: msg.updates?.filter((u) => u.type === "title"),
|
| 63 |
files: msg.files,
|
| 64 |
})),
|
src/routes/models/+page.svelte
CHANGED
|
@@ -7,7 +7,9 @@
|
|
| 7 |
|
| 8 |
import CarbonHelpFilled from "~icons/carbon/help-filled";
|
| 9 |
import CarbonView from "~icons/carbon/view";
|
|
|
|
| 10 |
import { useSettingsStore } from "$lib/stores/settings";
|
|
|
|
| 11 |
interface Props {
|
| 12 |
data: PageData;
|
| 13 |
}
|
|
@@ -98,27 +100,19 @@
|
|
| 98 |
<CarbonView class="text-xxs text-blue-700 dark:text-blue-500" />
|
| 99 |
</span>
|
| 100 |
{/if}
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
<path
|
| 115 |
-
class="stroke-purple-700"
|
| 116 |
-
style="stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 50;"
|
| 117 |
-
d="M16 6v3.33M16 6c0-2.65 3.25-4.3 5.4-2.62 1.2.95 1.6 2.65.95 4.04a3.63 3.63 0 0 1 4.61.16 3.45 3.45 0 0 1 .46 4.37 5.32 5.32 0 0 1 1.87 4.75c-.22 1.66-1.39 3.6-3.07 4.14M16 6c0-2.65-3.25-4.3-5.4-2.62a3.37 3.37 0 0 0-.95 4.04 3.65 3.65 0 0 0-4.6.16 3.37 3.37 0 0 0-.49 4.27 5.57 5.57 0 0 0-1.85 4.85 5.3 5.3 0 0 0 3.07 4.15M16 9.33v17.34m0-17.34c0 2.18 1.82 4 4 4m6.22 7.5c.67 1.3.56 2.91-.27 4.11a4.05 4.05 0 0 1-4.62 1.5c0 1.53-1.05 2.9-2.66 2.9A2.7 2.7 0 0 1 16 26.66m10.22-5.83a4.05 4.05 0 0 0-3.55-2.17m-16.9 2.18a4.05 4.05 0 0 0 .28 4.1c1 1.44 2.92 2.09 4.59 1.5 0 1.52 1.12 2.88 2.7 2.88A2.7 2.7 0 0 0 16 26.67M5.78 20.85a4.04 4.04 0 0 1 3.55-2.18"
|
| 118 |
-
/>
|
| 119 |
-
</svg>
|
| 120 |
-
</span>
|
| 121 |
-
{/if}
|
| 122 |
{#if model.id === $settings.activeModel}
|
| 123 |
<span
|
| 124 |
class="rounded-full bg-black px-2 py-0.5 text-xs text-white dark:bg-white dark:text-black"
|
|
|
|
| 7 |
|
| 8 |
import CarbonHelpFilled from "~icons/carbon/help-filled";
|
| 9 |
import CarbonView from "~icons/carbon/view";
|
| 10 |
+
import CarbonSettings from "~icons/carbon/settings";
|
| 11 |
import { useSettingsStore } from "$lib/stores/settings";
|
| 12 |
+
import { goto } from "$app/navigation";
|
| 13 |
interface Props {
|
| 14 |
data: PageData;
|
| 15 |
}
|
|
|
|
| 100 |
<CarbonView class="text-xxs text-blue-700 dark:text-blue-500" />
|
| 101 |
</span>
|
| 102 |
{/if}
|
| 103 |
+
<button
|
| 104 |
+
type="button"
|
| 105 |
+
title="Model settings"
|
| 106 |
+
aria-label="Model settings for {model.displayName}"
|
| 107 |
+
class="flex size-[21px] items-center justify-center rounded-md border border-gray-300 text-xs text-gray-600 hover:bg-gray-100 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700"
|
| 108 |
+
onclick={(e) => {
|
| 109 |
+
e.preventDefault();
|
| 110 |
+
e.stopPropagation();
|
| 111 |
+
goto(`${base}/settings/${model.id}`);
|
| 112 |
+
}}
|
| 113 |
+
>
|
| 114 |
+
<CarbonSettings class="text-xs" />
|
| 115 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
{#if model.id === $settings.activeModel}
|
| 117 |
<span
|
| 118 |
class="rounded-full bg-black px-2 py-0.5 text-xs text-white dark:bg-white dark:text-black"
|