mirror of
https://github.com/doolijb/serene-pub.git
synced 2026-04-28 03:20:07 +00:00
Improve generating message abort behavior
This commit is contained in:
parent
aa7dc8ad92
commit
07eb651a18
17 changed files with 571 additions and 722 deletions
|
|
@ -8,6 +8,7 @@ set NODE_ARCHIVE=node-archive.win.zip
|
|||
set NODE_DIR=node-v20.13.1-win-x64
|
||||
set NODE_BIN_PATH=%NODE_DIR%\node.exe
|
||||
|
||||
REM Download Node.js if needed (do this first while we might still have admin privileges)
|
||||
if not exist "%NODE_BIN%" (
|
||||
echo Downloading Node.js...
|
||||
powershell -Command "Invoke-WebRequest -Uri %NODE_URL% -OutFile %NODE_ARCHIVE%"
|
||||
|
|
@ -16,4 +17,13 @@ if not exist "%NODE_BIN%" (
|
|||
rmdir /s /q %NODE_DIR%
|
||||
del %NODE_ARCHIVE%
|
||||
)
|
||||
|
||||
REM Check if running with administrator privileges
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
echo Administrator privileges detected. Re-launching with reduced privileges for PostgreSQL compatibility...
|
||||
powershell -Command "Start-Process -FilePath '%NODE_BIN%' -ArgumentList '%DIR%build\index.js %*' -Verb runAsUser"
|
||||
exit /b
|
||||
)
|
||||
|
||||
"%NODE_BIN%" "%DIR%build\index.js" %*
|
||||
|
|
|
|||
0
docker-compose.yml
Normal file
0
docker-compose.yml
Normal file
0
installer-package.json
Normal file
0
installer-package.json
Normal file
|
|
@ -43,11 +43,11 @@
|
|||
}
|
||||
|
||||
.rendered-chat-message-content em {
|
||||
@apply text-surface-700-300;
|
||||
@apply text-surface-800-200;
|
||||
}
|
||||
|
||||
.rendered-chat-message-content span.quoted-text {
|
||||
@apply text-tertiary-900-100;
|
||||
@apply text-tertiary-950-50;
|
||||
}
|
||||
|
||||
.sidebar-list-item {
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@
|
|||
class="bg-surface-100-900 relative h-full max-h-[100dvh] w-full justify-between"
|
||||
>
|
||||
<div
|
||||
class="relative flex min-h-svh max-h-svh min-w-full max-w-full flex-1 flex-col overflow-y-auto lg:flex-row lg:gap-2"
|
||||
class="relative flex h-svh min-w-full max-w-full flex-1 flex-col lg:flex-row lg:gap-2 overflow-hidden"
|
||||
>
|
||||
<!-- Left Sidebar -->
|
||||
<aside class="desktop-sidebar">
|
||||
|
|
@ -248,9 +248,9 @@
|
|||
{/if}
|
||||
</aside>
|
||||
<!-- Main Content -->
|
||||
<main class="flex flex-col min-h-svh max-h-svh overflow-hidden">
|
||||
<main class="flex flex-col h-full overflow-hidden">
|
||||
<Header />
|
||||
<div class="h-full overflow-hidden">
|
||||
<div class="flex-1 overflow-auto">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -80,10 +80,11 @@ export abstract class BaseConnectionAdapter {
|
|||
}
|
||||
|
||||
abstract generate(): Promise<
|
||||
[
|
||||
string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
CompiledPrompt
|
||||
]
|
||||
{
|
||||
completionResult: string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
compiledPrompt: CompiledPrompt,
|
||||
isAborted: boolean
|
||||
}
|
||||
>
|
||||
|
||||
abort() {
|
||||
|
|
|
|||
|
|
@ -112,13 +112,14 @@ class LMStudioAdapter extends BaseConnectionAdapter {
|
|||
}
|
||||
|
||||
async generate(): Promise<
|
||||
[
|
||||
string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
CompiledPrompt
|
||||
]
|
||||
{
|
||||
completionResult: string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
compiledPrompt: CompiledPrompt,
|
||||
isAborted: boolean
|
||||
}
|
||||
> {
|
||||
const modelName =
|
||||
this.connection.model ?? LMStudioAdapter.connectionDefaults.baseUrl
|
||||
this.connection.model ?? connectionDefaults.baseUrl
|
||||
const stream = this.connection!.extraJson?.stream || false
|
||||
if (typeof modelName !== "string")
|
||||
throw new Error("LMStudioAdapter: model must be a string")
|
||||
|
|
@ -147,15 +148,10 @@ class LMStudioAdapter extends BaseConnectionAdapter {
|
|||
const compiledPrompt: CompiledPrompt =
|
||||
await this.compilePrompt({})
|
||||
|
||||
// Select prompt or messages for LM Studio
|
||||
let prompt: string = ""
|
||||
// if ("prompt" in compiledPrompt && typeof compiledPrompt.prompt === "string") {
|
||||
prompt = compiledPrompt.prompt
|
||||
// } else if ("messages" in compiledPrompt && Array.isArray(compiledPrompt.messages)) {
|
||||
// prompt = compiledPrompt.messages.map(m => m.content).join("\n")
|
||||
// } else {
|
||||
// throw new Error("Compiled prompt missing both prompt and messages")
|
||||
// }
|
||||
|
||||
prompt = compiledPrompt.prompt!
|
||||
|
||||
let options: LLMPredictionOpts<unknown> = {
|
||||
stopStrings: stop,
|
||||
maxTokens: this.sampling.responseTokensEnabled
|
||||
|
|
@ -170,8 +166,8 @@ class LMStudioAdapter extends BaseConnectionAdapter {
|
|||
const modelClient = await this.getModelClient(modelName)
|
||||
|
||||
if (stream) {
|
||||
return [
|
||||
async (cb: (chunk: string) => void) => {
|
||||
return {
|
||||
completionResult: async (cb: (chunk: string) => void) => {
|
||||
let fullContent = ""
|
||||
let lastChunk = ""
|
||||
let abortedEarly = false
|
||||
|
|
@ -198,8 +194,9 @@ class LMStudioAdapter extends BaseConnectionAdapter {
|
|||
cb("FAILURE: " + (e.message || String(e)))
|
||||
}
|
||||
},
|
||||
compiledPrompt
|
||||
]
|
||||
compiledPrompt,
|
||||
isAborted: this.isAborting
|
||||
}
|
||||
} else {
|
||||
const content = await (async () => {
|
||||
try {
|
||||
|
|
@ -222,7 +219,7 @@ class LMStudioAdapter extends BaseConnectionAdapter {
|
|||
return "FAILURE: " + (e.message || String(e))
|
||||
}
|
||||
})()
|
||||
return [content ?? "", compiledPrompt]
|
||||
return {completionResult:content ?? "", compiledPrompt, isAborted: this.isAborting}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -280,10 +280,11 @@ class LlamaCppAdapter extends BaseConnectionAdapter {
|
|||
}
|
||||
|
||||
async generate(): Promise<
|
||||
[
|
||||
string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
CompiledPrompt
|
||||
]
|
||||
{
|
||||
completionResult: string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
compiledPrompt: CompiledPrompt,
|
||||
isAborted: boolean
|
||||
}
|
||||
> {
|
||||
const stream = this.connection.extraJson?.stream || false
|
||||
// Prepare stop strings
|
||||
|
|
@ -337,8 +338,8 @@ class LlamaCppAdapter extends BaseConnectionAdapter {
|
|||
"http://localhost:8080"
|
||||
|
||||
if (stream) {
|
||||
return [
|
||||
async (cb: (chunk: string) => void) => {
|
||||
return {
|
||||
completionResult: async (cb: (chunk: string) => void) => {
|
||||
let content = ""
|
||||
let cancelTokenSource = axios.CancelToken.source();
|
||||
try {
|
||||
|
|
@ -380,13 +381,14 @@ class LlamaCppAdapter extends BaseConnectionAdapter {
|
|||
cb("FAILURE: " + (e.message || String(e)))
|
||||
}
|
||||
},
|
||||
compiledPrompt
|
||||
]
|
||||
compiledPrompt,
|
||||
isAborted: this.isAborting
|
||||
}
|
||||
} else {
|
||||
const abortController = new AbortController();
|
||||
if (this.isAborting) {
|
||||
abortController.abort();
|
||||
return ["FAILURE: Request aborted by user.", compiledPrompt];
|
||||
return {completionResult: "FAILURE: Request aborted by user.", compiledPrompt, isAborted: true};
|
||||
}
|
||||
try {
|
||||
const response = await axios.post<CompletionResponse>(
|
||||
|
|
@ -396,12 +398,12 @@ class LlamaCppAdapter extends BaseConnectionAdapter {
|
|||
)
|
||||
const result = response.data
|
||||
const content = result?.content || result?.response || ""
|
||||
return [content, compiledPrompt]
|
||||
return {completionResult: content, compiledPrompt, isAborted: this.isAborting}
|
||||
} catch (e: any) {
|
||||
if (axios.isCancel?.(e) || e?.code === 'ERR_CANCELED' || e?.message?.includes('aborted')) {
|
||||
return ["FAILURE: Request aborted by user.", compiledPrompt]
|
||||
return {completionResult: "FAILURE: Request aborted by user.", compiledPrompt, isAborted: true}
|
||||
}
|
||||
return ["FAILURE: " + (e.message || String(e)), compiledPrompt]
|
||||
return {completionResult: "FAILURE: " + (e.message || String(e)), compiledPrompt, isAborted: true}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,10 +93,11 @@ class OllamaAdapter extends BaseConnectionAdapter {
|
|||
}
|
||||
|
||||
async generate(): Promise<
|
||||
[
|
||||
string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
CompiledPrompt
|
||||
]
|
||||
{
|
||||
completionResult: string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
compiledPrompt: CompiledPrompt,
|
||||
isAborted: boolean
|
||||
}
|
||||
> {
|
||||
const model = this.connection.model ?? connectionDefaults.baseUrl
|
||||
const stream = this.connection!.extraJson?.stream || false
|
||||
|
|
@ -163,8 +164,8 @@ class OllamaAdapter extends BaseConnectionAdapter {
|
|||
}
|
||||
|
||||
if (stream) {
|
||||
return [
|
||||
async (cb: (chunk: string) => void) => {
|
||||
return {
|
||||
completionResult: async (cb: (chunk: string) => void) => {
|
||||
let content = ""
|
||||
let abortedEarly = false
|
||||
try {
|
||||
|
|
@ -219,8 +220,9 @@ class OllamaAdapter extends BaseConnectionAdapter {
|
|||
cb("FAILURE: " + (e.message || String(e)))
|
||||
}
|
||||
},
|
||||
compiledPrompt
|
||||
]
|
||||
compiledPrompt,
|
||||
isAborted: this.isAborting
|
||||
}
|
||||
} else {
|
||||
const content = await (async () => {
|
||||
let content = ""
|
||||
|
|
@ -271,7 +273,7 @@ class OllamaAdapter extends BaseConnectionAdapter {
|
|||
return "FAILURE: " + (e.message || String(e))
|
||||
}
|
||||
})()
|
||||
return [content ?? "", compiledPrompt]
|
||||
return {completionResult: content ?? "", compiledPrompt, isAborted: this.isAborting}
|
||||
}
|
||||
}
|
||||
// --- Abort in-flight Ollama request ---
|
||||
|
|
|
|||
|
|
@ -58,10 +58,11 @@ export class OpenAIChatAdapter extends BaseConnectionAdapter {
|
|||
}
|
||||
|
||||
async generate(): Promise<
|
||||
[
|
||||
string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
CompiledPrompt
|
||||
]
|
||||
{
|
||||
completionResult: string | ((cb: (chunk: string) => void) => Promise<void>),
|
||||
compiledPrompt: CompiledPrompt,
|
||||
isAborted: boolean
|
||||
}
|
||||
> {
|
||||
const apiKey = this.connection.extraJson?.apiKey
|
||||
const baseURL =
|
||||
|
|
@ -108,8 +109,8 @@ export class OpenAIChatAdapter extends BaseConnectionAdapter {
|
|||
|
||||
try {
|
||||
if (stream) {
|
||||
return [
|
||||
async (cb: (chunk: string) => void) => {
|
||||
return {
|
||||
completionResult:async (cb: (chunk: string) => void) => {
|
||||
const streamResp =
|
||||
await openaiClient.chat.completions.create({
|
||||
...params,
|
||||
|
|
@ -127,8 +128,9 @@ export class OpenAIChatAdapter extends BaseConnectionAdapter {
|
|||
}
|
||||
}
|
||||
},
|
||||
compiledPrompt
|
||||
]
|
||||
compiledPrompt,
|
||||
isAborted: this.isAborting
|
||||
}
|
||||
} else {
|
||||
const response =
|
||||
await openaiClient.chat.completions.create(params)
|
||||
|
|
@ -140,7 +142,7 @@ export class OpenAIChatAdapter extends BaseConnectionAdapter {
|
|||
) {
|
||||
content = response.choices[0].message.content || ""
|
||||
}
|
||||
return [content, compiledPrompt]
|
||||
return {completionResult: content, compiledPrompt, isAborted: this.isAborting}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@ import * as dbConfig from "./drizzle.config"
|
|||
import type { MigrationConfig } from "drizzle-orm/migrator"
|
||||
import fs from "fs"
|
||||
import { dev } from "$app/environment"
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import { drizzle } from "drizzle-orm/node-postgres"
|
||||
import { sync } from "./defaults"
|
||||
import { startPg } from "./postgres.service"
|
||||
|
||||
const firstInit = await startPg()
|
||||
|
||||
export let db = drizzle(dbConfig.postgresUrl, {schema})
|
||||
export let db = drizzle(dbConfig.postgresUrl, { schema })
|
||||
|
||||
// Compare two version strings in '0.0.0' format
|
||||
export function compareVersions(a: string, b: string): -1 | 0 | 1 {
|
||||
const pa = a.split('.').map(Number)
|
||||
const pb = b.split('.').map(Number)
|
||||
const pa = a.split(".").map(Number)
|
||||
const pb = b.split(".").map(Number)
|
||||
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
||||
const na = pa[i] || 0
|
||||
const nb = pb[i] || 0
|
||||
|
|
@ -51,17 +51,30 @@ async function runMigrations(oldVersion?: string) {
|
|||
} as MigrationConfig)
|
||||
console.log("Migrations applied.")
|
||||
await sync()
|
||||
|
||||
////
|
||||
// DEPRECATE: Temporary migration from SQLite to PostgreSQL - remove in 0.4.0
|
||||
////
|
||||
const { dbPath: sqliteDbPath } = await import("../db_old/drizzle.config")
|
||||
const sqliteDbExists = fs.existsSync(sqliteDbPath)
|
||||
if (sqliteDbExists) {
|
||||
console.log("SQLite database found, migrating to PostgreSQL...")
|
||||
const { migrateToPg } = await import("../db_old/migrateToPg")
|
||||
await migrateToPg()
|
||||
}
|
||||
}
|
||||
|
||||
// Run migrations if in production environment
|
||||
if (!dev || firstInit) {
|
||||
|
||||
// If it doesn't exist, create a meta.json file in the data directory
|
||||
const metaPath = dbConfig.dataDir + "/meta.json"
|
||||
// Check if the file exists
|
||||
if (!fs.existsSync(metaPath)) {
|
||||
// Create the file with default content
|
||||
fs.writeFileSync(metaPath, JSON.stringify({ version: "0.0.0" }, null, 2))
|
||||
fs.writeFileSync(
|
||||
metaPath,
|
||||
JSON.stringify({ version: "0.0.0" }, null, 2)
|
||||
)
|
||||
}
|
||||
|
||||
// Check meta.json for version
|
||||
|
|
@ -69,7 +82,9 @@ if (!dev || firstInit) {
|
|||
// @ts-ignore
|
||||
const appVersion = __APP_VERSION__
|
||||
if (!appVersion) {
|
||||
throw new Error("App version is not defined. Please set __APP_VERSION__.")
|
||||
throw new Error(
|
||||
"App version is not defined. Please set __APP_VERSION__."
|
||||
)
|
||||
}
|
||||
const versionCompare = compareVersions(meta.version, appVersion)
|
||||
|
||||
|
|
@ -85,18 +100,21 @@ if (!dev || firstInit) {
|
|||
console.log(`Updated meta.json to version ${appVersion}.`)
|
||||
break
|
||||
case 1:
|
||||
console.warn(`Warning: Database version (${meta.version}) is newer than app version (${appVersion}).`)
|
||||
console.warn(
|
||||
`Warning: Database version (${meta.version}) is newer than app version (${appVersion}).`
|
||||
)
|
||||
// This could happen if the app version is rolled back or if the database was manually updated
|
||||
// Handle this case as needed, e.g., notify the user or log an error
|
||||
throw new Error(
|
||||
`Database version (${meta.version}) is newer than app version (${appVersion}). Please check your database integrity.`
|
||||
)
|
||||
default:
|
||||
console.error("Unexpected version comparison result:", versionCompare)
|
||||
console.error(
|
||||
"Unexpected version comparison result:",
|
||||
versionCompare
|
||||
)
|
||||
throw new Error("Unexpected version comparison result")
|
||||
}
|
||||
} else {
|
||||
await sync()
|
||||
// const { migrateToPg } = await import("../db_old/migrateToPg")
|
||||
// await migrateToPg()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -549,7 +549,3 @@ export const chatLorebooksRelations = relations(chatLorebooks, ({ one }) => ({
|
|||
references: [lorebooks.id]
|
||||
})
|
||||
}))
|
||||
|
||||
// For JSON/text columns, use text(...).notNull() if required, but do not use { mode: "json" } or .default({})
|
||||
// Example fix for a JSON/text column:
|
||||
// myJsonColumn: text("my_json_column").notNull(),
|
||||
|
|
|
|||
|
|
@ -63,109 +63,90 @@ export async function migrateToPg() {
|
|||
// Get characters from SQLite
|
||||
const characters = await sqlite.query.characters.findMany()
|
||||
characters.forEach(async (character) => {
|
||||
// Map null fields to undefined for compatibility with schema
|
||||
const mappedCharacter = {
|
||||
// Convert any String[] fields to string[] (primitive)
|
||||
const safeCharacter = {
|
||||
...character,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
metadata: character.metadata || {},
|
||||
lorebookId: null,
|
||||
// Add other nullable fields as needed
|
||||
alternateGreetings: character.alternateGreetings
|
||||
? character.alternateGreetings.map((s: any) => s.toString())
|
||||
: null,
|
||||
exampleDialogues: character.exampleDialogues
|
||||
? character.exampleDialogues.map((s: any) => s.toString())
|
||||
: null,
|
||||
}
|
||||
return await tx.insert(schema.characters).values(mappedCharacter)
|
||||
return await tx.insert(schema.characters).values({
|
||||
name: safeCharacter.name,
|
||||
description: safeCharacter.description ?? "",
|
||||
userId: safeCharacter.userId,
|
||||
nickname: safeCharacter.nickname ?? null,
|
||||
characterVersion: safeCharacter.characterVersion ?? null,
|
||||
personality: safeCharacter.personality ?? null,
|
||||
scenario: safeCharacter.scenario ?? null,
|
||||
firstMessage: safeCharacter.firstMessage ?? null,
|
||||
alternateGreetings: safeCharacter.alternateGreetings ?? null,
|
||||
exampleDialogues: safeCharacter.exampleDialogues ?? null,
|
||||
avatar: safeCharacter.avatar ?? null,
|
||||
creatorNotes: safeCharacter.creatorNotes ?? null,
|
||||
creatorNotesMultilingual: safeCharacter.creatorNotesMultilingual ?? null,
|
||||
groupOnlyGreetings: safeCharacter.groupOnlyGreetings ?? null,
|
||||
postHistoryInstructions: safeCharacter.postHistoryInstructions ?? null,
|
||||
isFavorite: !!safeCharacter.isFavorite,
|
||||
})
|
||||
})
|
||||
// Get personas from SQLite
|
||||
const personas = await sqlite.query.personas.findMany()
|
||||
personas.forEach(async (persona) => {
|
||||
return await tx.insert(schema.personas).values({
|
||||
...persona,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
})
|
||||
})
|
||||
// Get lorebooks from SQLite
|
||||
const lorebooks = await sqlite.query.lorebooks.findMany()
|
||||
lorebooks.forEach(async (lorebook) => {
|
||||
return await tx.insert(schema.lorebooks).values({
|
||||
...lorebook,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
})
|
||||
})
|
||||
// Get lorebook bindings from SQLite
|
||||
const lorebookBindings = await sqlite.query.lorebookBindings.findMany()
|
||||
lorebookBindings.forEach(async (binding) => {
|
||||
return await tx.insert(schema.lorebookBindings).values({
|
||||
...binding,
|
||||
})
|
||||
})
|
||||
// Get worldLoreEntries from SQLite
|
||||
const worldLoreEntries = await sqlite.query.worldLoreEntries.findMany()
|
||||
worldLoreEntries.forEach(async (entry) => {
|
||||
return await tx.insert(schema.worldLoreEntries).values({
|
||||
...entry,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
})
|
||||
})
|
||||
// Get characterLoreEntries from SQLite
|
||||
const characterLoreEntries = await sqlite.query.characterLoreEntries.findMany()
|
||||
characterLoreEntries.forEach(async (entry) => {
|
||||
return await tx.insert(schema.characterLoreEntries).values({
|
||||
...entry,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
})
|
||||
})
|
||||
// Get historyEntries from SQLite
|
||||
const historyEntries = await sqlite.query.historyEntries.findMany()
|
||||
historyEntries.forEach(async (entry) => {
|
||||
return await tx.insert(schema.historyEntries).values({
|
||||
...entry,
|
||||
date: undefined,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
day: entry.date?.day || undefined,
|
||||
month: entry.date?.month || undefined,
|
||||
year: entry.date?.year || undefined,
|
||||
})
|
||||
return await tx.insert(schema.personas).values({
|
||||
name: persona.name || "",
|
||||
description: persona.description || "",
|
||||
userId: persona.userId,
|
||||
avatar: persona.avatar || null,
|
||||
isDefault: !!persona.isDefault,
|
||||
position: persona.position || null
|
||||
})
|
||||
})
|
||||
// Get chats from SQLite
|
||||
const chats = await sqlite.query.chats.findMany()
|
||||
chats.forEach(async (chat) => {
|
||||
return await tx.insert(schema.chats).values({
|
||||
...chat,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
})
|
||||
return await tx.insert(schema.chats).values({
|
||||
id: chat.id,
|
||||
name: chat.name || "", // Add the required name property
|
||||
userId: chat.userId,
|
||||
scenario: chat.scenario,
|
||||
isGroup: !!chat.isGroup,
|
||||
groupReplyStrategy: chat.group_reply_strategy,
|
||||
})
|
||||
})
|
||||
// Get chatCharacters from SQLite
|
||||
const chatCharacters = await sqlite.query.chatCharacters.findMany()
|
||||
chatCharacters.forEach(async (chatCharacter) => {
|
||||
return await tx.insert(schema.chatCharacters).values({
|
||||
...chatCharacter,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
})
|
||||
// Map null isActive to undefined for compatibility with schema
|
||||
const mappedChatCharacter = {
|
||||
...chatCharacter,
|
||||
isActive: chatCharacter.isActive === null ? undefined : chatCharacter.isActive,
|
||||
}
|
||||
return await tx.insert(schema.chatCharacters).values(mappedChatCharacter)
|
||||
})
|
||||
// Get chatPersonas from SQLite
|
||||
const chatPersonas = await sqlite.query.chatPersonas.findMany()
|
||||
chatPersonas.forEach(async (chatPersona) => {
|
||||
return await tx.insert(schema.chatPersonas).values({
|
||||
...chatPersona,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
})
|
||||
return await tx.insert(schema.chatPersonas).values(chatPersona)
|
||||
})
|
||||
// Get chatMessages from SQLite
|
||||
const chatMessages = await sqlite.query.chatMessages.findMany()
|
||||
chatMessages.forEach(async (chatMessage) => {
|
||||
return await tx.insert(schema.chatMessages).values({
|
||||
...chatMessage,
|
||||
updatedAt: undefined,
|
||||
createdAt: undefined,
|
||||
metadata: chatMessage.metadata || {},
|
||||
})
|
||||
return await tx.insert(schema.chatMessages).values({
|
||||
id: chatMessage.id,
|
||||
userId: chatMessage.userId,
|
||||
content: chatMessage.content, // Add the required content property
|
||||
chatId: chatMessage.chatId,
|
||||
characterId: chatMessage.characterId ?? null,
|
||||
personaId: chatMessage.personaId ?? null,
|
||||
adapterId: chatMessage.adapterId ?? null,
|
||||
role: chatMessage.role ?? "user",
|
||||
isEdited: !!chatMessage.isEdited,
|
||||
isGenerating: !!chatMessage.isGenerating,
|
||||
isHidden: !!chatMessage.isHidden,
|
||||
})
|
||||
})
|
||||
// Get users from SQLite
|
||||
const users = await sqlite.query.users.findMany()
|
||||
|
|
|
|||
|
|
@ -1,609 +1,411 @@
|
|||
import { updated } from "$app/state"
|
||||
import { relations, sql } from "drizzle-orm"
|
||||
import {
|
||||
sqliteTable,
|
||||
integer,
|
||||
text,
|
||||
numeric,
|
||||
real,
|
||||
blob,
|
||||
SQLiteBoolean,
|
||||
uniqueIndex
|
||||
sqliteTable,
|
||||
integer,
|
||||
text,
|
||||
numeric,
|
||||
real,
|
||||
blob,
|
||||
SQLiteBoolean
|
||||
} from "drizzle-orm/sqlite-core"
|
||||
import { TokenCounterManager } from "../utils/TokenCounterManager"
|
||||
import { group } from "console"
|
||||
import { GroupReplyStrategies } from "../../shared/constants/GroupReplyStrategies"
|
||||
import { lorebook } from "../sockets/lorebooks"
|
||||
|
||||
export const users = sqliteTable("users", {
|
||||
id: integer("id").primaryKey(),
|
||||
username: text("username").notNull(),
|
||||
activeConnectionId: integer("active_connection_id").references(
|
||||
() => connections.id,
|
||||
{
|
||||
onDelete: "set null"
|
||||
}
|
||||
),
|
||||
activeSamplingConfigId: integer("active_sampling_id").references(
|
||||
() => samplingConfigs.id,
|
||||
{
|
||||
onDelete: "set null"
|
||||
}
|
||||
),
|
||||
activeContextConfigId: integer("active_context_config_id").references(
|
||||
() => contextConfigs.id,
|
||||
{
|
||||
onDelete: "set null"
|
||||
}
|
||||
),
|
||||
activePromptConfigId: integer("active_prompt_config_id").references(
|
||||
() => promptConfigs.id,
|
||||
{
|
||||
onDelete: "set null"
|
||||
}
|
||||
)
|
||||
id: integer("id").primaryKey(),
|
||||
username: text("username").notNull(),
|
||||
activeConnectionId: integer("active_connection_id").references(() => connections.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
activeSamplingConfigId: integer("active_sampling_id").references(() => samplingConfigs.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
activeContextConfigId: integer("active_context_config_id").references(() => contextConfigs.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
activePromptConfigId: integer("active_prompt_config_id").references(() => promptConfigs.id, {
|
||||
onDelete: "set null"
|
||||
})
|
||||
})
|
||||
|
||||
export const userRelations = relations(users, ({ many, one }) => ({
|
||||
lorebooks: many(lorebooks),
|
||||
characters: many(characters),
|
||||
activeSamplingConfig: one(samplingConfigs, {
|
||||
fields: [users.activeSamplingConfigId],
|
||||
references: [samplingConfigs.id]
|
||||
}),
|
||||
activeConnection: one(connections, {
|
||||
fields: [users.activeConnectionId],
|
||||
references: [connections.id]
|
||||
}),
|
||||
activeContextConfig: one(contextConfigs, {
|
||||
fields: [users.activeContextConfigId],
|
||||
references: [contextConfigs.id]
|
||||
}),
|
||||
activePromptConfig: one(promptConfigs, {
|
||||
fields: [users.activePromptConfigId],
|
||||
references: [promptConfigs.id]
|
||||
}),
|
||||
personas: many(personas)
|
||||
lorebooks: many(lorebooks),
|
||||
characters: many(characters),
|
||||
activeSamplingConfig: one(samplingConfigs, {
|
||||
fields: [users.activeSamplingConfigId],
|
||||
references: [samplingConfigs.id]
|
||||
}),
|
||||
activeConnection: one(connections, {
|
||||
fields: [users.activeConnectionId],
|
||||
references: [connections.id]
|
||||
}),
|
||||
activeContextConfig: one(contextConfigs, {
|
||||
fields: [users.activeContextConfigId],
|
||||
references: [contextConfigs.id]
|
||||
}),
|
||||
activePromptConfig: one(promptConfigs, {
|
||||
fields: [users.activePromptConfigId],
|
||||
references: [promptConfigs.id]
|
||||
}),
|
||||
personas: many(personas)
|
||||
}))
|
||||
|
||||
export const samplingConfigs = sqliteTable("sampling_configs", {
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name").notNull(), // Name for this sampling config (for selection)
|
||||
isImmutable: integer("is_immutable", { mode: "boolean" }).default(false), // Is this the built-in config? Then we don't want to allow mutation/deletion
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name").notNull(), // Name for this sampling config (for selection)
|
||||
isImmutable: integer("is_immutable", { mode: "boolean" }).default(0), // Is this the built-in config? Then we don't want to allow mutation/deletion
|
||||
|
||||
// Tuned defaults for roleplay:
|
||||
// More creative and less repetitive
|
||||
temperature: real("temperature").default(0.7), // Higher = more creative
|
||||
temperatureEnabled: integer("temperature_enabled", {
|
||||
mode: "boolean"
|
||||
}).default(true),
|
||||
// Tuned defaults for roleplay:
|
||||
// More creative and less repetitive
|
||||
temperature: real("temperature").default(0.7), // Higher = more creative
|
||||
temperatureEnabled: integer("temperature_enabled", { mode: "boolean" }).default(true),
|
||||
|
||||
topP: real("top_p").default(0.92), // Lower than 1, encourages diversity but not too random
|
||||
topPEnabled: integer("top_p_enabled", { mode: "boolean" }).default(true),
|
||||
topP: real("top_p").default(0.92), // Lower than 1, encourages diversity but not too random
|
||||
topPEnabled: integer("top_p_enabled", { mode: "boolean" }).default(true),
|
||||
|
||||
topK: integer("top_k").default(80), // Allows more token options for creative replies
|
||||
topKEnabled: integer("top_k_enabled", { mode: "boolean" }).default(true),
|
||||
topK: integer("top_k").default(80), // Allows more token options for creative replies
|
||||
topKEnabled: integer("top_k_enabled", { mode: "boolean" }).default(true),
|
||||
|
||||
repetitionPenalty: real("repetition_penalty").default(1.15), // Slightly encourages less repetition but not too harsh
|
||||
repetitionPenaltyEnabled: integer("repetition_penalty_enabled", {
|
||||
mode: "boolean"
|
||||
}).default(true),
|
||||
repetitionPenalty: real("repetition_penalty").default(1.15), // Slightly encourages less repetition but not too harsh
|
||||
repetitionPenaltyEnabled: integer("repetition_penalty_enabled", { mode: "boolean" }).default(
|
||||
true
|
||||
),
|
||||
|
||||
frequencyPenalty: real("frequency_penalty").default(0.2), // Mild penalty for repetitive phrases
|
||||
frequencyPenaltyEnabled: integer("frequency_penalty_enabled", {
|
||||
mode: "boolean"
|
||||
}).default(true),
|
||||
frequencyPenalty: real("frequency_penalty").default(0.2), // Mild penalty for repetitive phrases
|
||||
frequencyPenaltyEnabled: integer("frequency_penalty_enabled", { mode: "boolean" }).default(
|
||||
true
|
||||
),
|
||||
|
||||
presencePenalty: real("presence_penalty").default(0.6), // Encourage new topics and freshness
|
||||
presencePenaltyEnabled: integer("presence_penalty_enabled", {
|
||||
mode: "boolean"
|
||||
}).default(true),
|
||||
presencePenalty: real("presence_penalty").default(0.6), // Encourage new topics and freshness
|
||||
presencePenaltyEnabled: integer("presence_penalty_enabled", { mode: "boolean" }).default(true),
|
||||
|
||||
responseTokens: integer("response_tokens").default(512), // Allow longer, richer replies
|
||||
responseTokensEnabled: integer("response_tokens_enabled", {
|
||||
mode: "boolean"
|
||||
}).default(true),
|
||||
responseTokensUnlocked: integer("response_tokens_unlocked", {
|
||||
mode: "boolean"
|
||||
}).default(false), // Dynamic length allowed
|
||||
responseTokens: integer("response_tokens").default(512), // Allow longer, richer replies
|
||||
responseTokensEnabled: integer("response_tokens_enabled", { mode: "boolean" }).default(true),
|
||||
responseTokensUnlocked: integer("response_tokens_unlocked", { mode: "boolean" }).default(false), // Dynamic length allowed
|
||||
|
||||
contextTokens: integer("context_tokens").default(4096), // Keep more conversation in memory/context
|
||||
contextTokensEnabled: integer("context_tokens_enabled", {
|
||||
mode: "boolean"
|
||||
}).default(true),
|
||||
contextTokensUnlocked: integer("context_tokens_unlocked", {
|
||||
mode: "boolean"
|
||||
}).default(false), // Allow for context window expansion
|
||||
contextTokens: integer("context_tokens").default(4096), // Keep more conversation in memory/context
|
||||
contextTokensEnabled: integer("context_tokens_enabled", { mode: "boolean" }).default(true),
|
||||
contextTokensUnlocked: integer("context_tokens_unlocked", { mode: "boolean" }).default(false), // Allow for context window expansion
|
||||
|
||||
seed: integer("seed").default(-1), // -1 for random, can be used for deterministic sampling
|
||||
seedEnabled: integer("seed_enabled", { mode: "boolean" }).default(false)
|
||||
seed: integer("seed").default(-1), // -1 for random, can be used for deterministic sampling
|
||||
seedEnabled: integer("seed_enabled", { mode: "boolean" }).default(false)
|
||||
})
|
||||
|
||||
export const samplingRelations = relations(samplingConfigs, () => ({}))
|
||||
|
||||
export const connections = sqliteTable("connections", {
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name").notNull(), // Connection name (e.g., ollama, llama, chatgpt)
|
||||
type: text("type").notNull(), // Connection type/category (e.g., ollama, chatgpt, etc)
|
||||
baseUrl: text("base_url"), // Base URL or endpoint for API
|
||||
model: text("model"), // Model name or identifier
|
||||
// Ollama-specific options
|
||||
extraJson: text("extra_json", { mode: "json" }).$type<
|
||||
Record<string, any>
|
||||
>(), // Additional JSON options for the connections, api keys, etc.
|
||||
tokenCounter: text("token_counter").notNull().default("estimate"),
|
||||
promptFormat: text("prompt_format").default("vicuna")
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name").notNull(), // Connection name (e.g., ollama, llama, chatgpt)
|
||||
type: text("type").notNull(), // Connection type/category (e.g., ollama, chatgpt, etc)
|
||||
baseUrl: text("base_url"), // Base URL or endpoint for API
|
||||
model: text("model"), // Model name or identifier
|
||||
// Ollama-specific options
|
||||
extraJson: text("extra_json", { mode: "json" }).$type<Record<string, any>>(), // Additional JSON options for the connections, api keys, etc.
|
||||
tokenCounter: text("token_counter").notNull().default("estimate"),
|
||||
promptFormat: text("prompt_format").default("vicuna")
|
||||
})
|
||||
|
||||
export const connectionsRelations = relations(connections, () => ({}))
|
||||
|
||||
export const contextConfigs = sqliteTable("context_configs", {
|
||||
id: integer("id").primaryKey(),
|
||||
isImmutable: integer("is_immutable", { mode: "boolean" }).default(true),
|
||||
name: text("name").notNull(),
|
||||
template: text("template"), // Sillytavern storyString
|
||||
alwaysForceName: integer("always_force_name", { mode: "boolean" }).default(
|
||||
true
|
||||
) // Always force name2
|
||||
id: integer("id").primaryKey(),
|
||||
isImmutable: integer("is_immutable", { mode: "boolean" }).default(true),
|
||||
name: text("name").notNull(),
|
||||
template: text("template"), // Sillytavern storyString
|
||||
alwaysForceName: integer("always_force_name", { mode: "boolean" }).default(true) // Always force name2
|
||||
})
|
||||
|
||||
export const contextConfigsRelations = relations(contextConfigs, () => ({}))
|
||||
|
||||
export const promptConfigs = sqliteTable("prompt_configs", {
|
||||
id: integer("id").primaryKey(),
|
||||
isImmutable: integer("is_immutable", { mode: "boolean" }).default(true),
|
||||
name: text("name").notNull(),
|
||||
systemPrompt: text("system_prompt").notNull() // Maps to sillytavern sysPrompt.content
|
||||
id: integer("id").primaryKey(),
|
||||
isImmutable: integer("is_immutable", { mode: "boolean" }).default(true),
|
||||
name: text("name").notNull(),
|
||||
systemPrompt: text("system_prompt").notNull() // Maps to sillytavern sysPrompt.content
|
||||
})
|
||||
|
||||
export const promptConfigsRelations = relations(promptConfigs, () => ({}))
|
||||
|
||||
export const lorebooks = sqliteTable("lorebooks", {
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description").notNull().default(""),
|
||||
extraJson: text("extra_json", { mode: "json" })
|
||||
.notNull()
|
||||
.default({})
|
||||
.$type<Record<string, any>>(),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }), // FK to users.id
|
||||
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: text("updated_at").$onUpdate(() => sql`(CURRENT_TIMESTAMP)`)
|
||||
id: integer("id").primaryKey(),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }), // FK to users.id
|
||||
name: text("name").notNull(), // Lorebook name
|
||||
description: text("description"), // Lorebook description
|
||||
tags: text("tags"), // JSON array of tags
|
||||
entries: text("entries"), // JSON array of lorebook entries (for compatibility with SillyTavern)
|
||||
metadata: text("metadata"), // JSON object for any extra SillyTavern/world/lorebook fields
|
||||
createdAt: text("created_at"), // ISO date string
|
||||
updatedAt: text("updated_at") // ISO date string
|
||||
})
|
||||
|
||||
export const lorebooksRelations = relations(lorebooks, ({ many, one }) => ({
|
||||
worldLoreEntries: many(worldLoreEntries),
|
||||
characterLoreEntries: many(characterLoreEntries),
|
||||
historyEntries: many(historyEntries),
|
||||
user: one(users, {
|
||||
fields: [lorebooks.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
lorebookBindings: many(lorebookBindings)
|
||||
entries: many(lorebookEntries),
|
||||
user: one(users, {
|
||||
fields: [lorebooks.userId],
|
||||
references: [users.id]
|
||||
})
|
||||
}))
|
||||
|
||||
export const lorebookBindings = sqliteTable(
|
||||
"lorebook_bindings",
|
||||
{
|
||||
id: integer("id").primaryKey(),
|
||||
lorebookId: integer("lorebook_id")
|
||||
.notNull()
|
||||
.references(() => lorebooks.id, { onDelete: "cascade" }),
|
||||
characterId: integer("character_id").references(() => characters.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
personaId: integer("persona_id").references(() => personas.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
binding: text("binding").notNull() // e.g. "{char:1}"
|
||||
},
|
||||
(table) => ({
|
||||
uniqueBinding: uniqueIndex("lorebook_bindings_unique").on(
|
||||
table.lorebookId,
|
||||
table.characterId,
|
||||
table.personaId
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
export const lorebookBindingsRelations = relations(
|
||||
lorebookBindings,
|
||||
({ one, many }) => ({
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [lorebookBindings.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
}),
|
||||
character: one(characters, {
|
||||
fields: [lorebookBindings.characterId],
|
||||
references: [characters.id]
|
||||
}),
|
||||
persona: one(personas, {
|
||||
fields: [lorebookBindings.personaId],
|
||||
references: [personas.id]
|
||||
}),
|
||||
characterLoreEntries: many(characterLoreEntries)
|
||||
})
|
||||
)
|
||||
|
||||
export const worldLoreEntries = sqliteTable("world_lore_entries", {
|
||||
id: integer("id").primaryKey(),
|
||||
lorebookId: integer("lorebook_id")
|
||||
.notNull()
|
||||
.references(() => lorebooks.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
category: text("category"),
|
||||
keys: text("keys").notNull().default(""),
|
||||
useRegex: integer("use_regex", { mode: "boolean" }).default(false),
|
||||
caseSensitive: integer("case_sensitive", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
content: text("content").notNull().default(""),
|
||||
priority: integer("priority").notNull().default(1),
|
||||
constant: integer("constant", { mode: "boolean" }).notNull().default(false),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
extraJson: text("extra_json", { mode: "json" })
|
||||
.notNull()
|
||||
.default({})
|
||||
.$type<Record<string, any>>(),
|
||||
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: text("updated_at").$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
|
||||
position: integer("position").notNull().default(0)
|
||||
export const lorebookEntries = sqliteTable("lorebook_entries", {
|
||||
id: integer("id").primaryKey(),
|
||||
lorebookId: integer("lorebook_id")
|
||||
.notNull()
|
||||
.references(() => lorebooks.id, { onDelete: "cascade" }), // FK to lorebooks.id
|
||||
key: text("key"), // JSON array of keys
|
||||
keySecondary: text("key_secondary"), // JSON array of secondary keys
|
||||
comment: text("comment"),
|
||||
content: text("content"),
|
||||
constant: integer("constant", { mode: "boolean" }).default(false), // Is this entry a constant value?
|
||||
vectorized: integer("vectorized"),
|
||||
selective: integer("selective"),
|
||||
selectiveLogic: integer("selective_logic"),
|
||||
addMemo: integer("add_memo"),
|
||||
order: integer("order"),
|
||||
position: integer("position"),
|
||||
disable: integer("disable", { mode: "boolean" }).default(false), // Is this entry disabled?
|
||||
excludeRecursion: integer("exclude_recursion"),
|
||||
preventRecursion: integer("prevent_recursion"),
|
||||
delayUntilRecursion: integer("delay_until_recursion"),
|
||||
probability: integer("probability"),
|
||||
useProbability: integer("use_probability"),
|
||||
depth: integer("depth"),
|
||||
group: text("group"),
|
||||
groupOverride: integer("group_override"),
|
||||
groupWeight: integer("group_weight"),
|
||||
scanDepth: integer("scan_depth"),
|
||||
caseSensitive: integer("case_sensitive"),
|
||||
matchWholeWords: integer("match_whole_words"),
|
||||
useGroupScoring: integer("use_group_scoring"),
|
||||
automationId: text("automation_id"),
|
||||
role: text("role"),
|
||||
sticky: integer("sticky"),
|
||||
cooldown: integer("cooldown"),
|
||||
delay: integer("delay"),
|
||||
displayIndex: integer("display_index")
|
||||
})
|
||||
|
||||
export const worldLoreEntriesRelations = relations(
|
||||
worldLoreEntries,
|
||||
({ one }) => ({
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [worldLoreEntries.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
export const characterLoreEntries = sqliteTable("character_lore_entries", {
|
||||
id: integer("id").primaryKey(),
|
||||
lorebookId: integer("lorebook_id")
|
||||
.notNull()
|
||||
.references(() => lorebooks.id, { onDelete: "cascade" }),
|
||||
lorebookBindingId: integer("character_binding_id").references(
|
||||
() => lorebookBindings.id,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
name: text("name"),
|
||||
keys: text("keys").notNull().default(""),
|
||||
useRegex: integer("use_regex", { mode: "boolean" }).default(false),
|
||||
caseSensitive: integer("case_sensitive", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
content: text("content").notNull().default(""),
|
||||
priority: integer("priority").notNull().default(1),
|
||||
constant: integer("constant", { mode: "boolean" }).notNull().default(false),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
extraJson: text("extra_json", { mode: "json" })
|
||||
.notNull()
|
||||
.default({})
|
||||
.$type<Record<string, any>>(),
|
||||
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: text("updated_at").$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
|
||||
position: integer("position").notNull().default(0)
|
||||
})
|
||||
|
||||
export const characterLoreEntriesRelations = relations(
|
||||
characterLoreEntries,
|
||||
({ one }) => ({
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [characterLoreEntries.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
}),
|
||||
lorebookBinding: one(lorebookBindings, {
|
||||
fields: [characterLoreEntries.lorebookBindingId],
|
||||
references: [lorebookBindings.id]
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
export const historyEntries = sqliteTable("history_entries", {
|
||||
id: integer("id").primaryKey(),
|
||||
lorebookId: integer("lorebook_id")
|
||||
.notNull()
|
||||
.references(() => lorebooks.id, { onDelete: "cascade" }),
|
||||
date: text("date", { mode: "json" })
|
||||
.notNull()
|
||||
.default({ day: 1, month: 1, year: 1 })
|
||||
.$type<{ day: number | null; month: number | null; year: number }>(),
|
||||
keys: text("keys").notNull().default(""),
|
||||
useRegex: integer("use_regex", { mode: "boolean" }).default(false),
|
||||
caseSensitive: integer("case_sensitive", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
content: text("content").notNull().default(""),
|
||||
constant: integer("constant", { mode: "boolean" }).notNull().default(false),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
extraJson: text("extra_json", { mode: "json" })
|
||||
.notNull()
|
||||
.default({})
|
||||
.$type<Record<string, any>>(),
|
||||
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: text("updated_at").$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
|
||||
position: integer("position").notNull().default(0)
|
||||
})
|
||||
|
||||
export const historyEntriesRelations = relations(historyEntries, ({ one }) => ({
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [historyEntries.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
})
|
||||
export const lorebookEntriesRelations = relations(lorebookEntries, ({ one }) => ({
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [lorebookEntries.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
})
|
||||
}))
|
||||
|
||||
export const tags = sqliteTable("tags", {
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name").notNull(), // Tag name (unique)
|
||||
description: text("description")
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name").notNull(), // Tag name (unique)
|
||||
description: text("description")
|
||||
})
|
||||
|
||||
export const tagsRelations = relations(tags, ({ many }) => ({
|
||||
characterTags: many(characterTags)
|
||||
characterTags: many(characterTags)
|
||||
}))
|
||||
|
||||
export const characterTags = sqliteTable("character_tags", {
|
||||
characterId: integer("character_id")
|
||||
.notNull()
|
||||
.references(() => characters.id, { onDelete: "cascade" }), // FK to characters.id
|
||||
tagId: integer("tag_id")
|
||||
.notNull()
|
||||
.references(() => tags.id, { onDelete: "cascade" }) // FK to tags.id
|
||||
characterId: integer("character_id")
|
||||
.notNull()
|
||||
.references(() => characters.id, { onDelete: "cascade" }), // FK to characters.id
|
||||
tagId: integer("tag_id")
|
||||
.notNull()
|
||||
.references(() => tags.id, { onDelete: "cascade" }) // FK to tags.id
|
||||
})
|
||||
|
||||
export const characterTagsRelations = relations(characterTags, ({ one }) => ({
|
||||
character: one(characters, {
|
||||
fields: [characterTags.characterId],
|
||||
references: [characters.id]
|
||||
}),
|
||||
tag: one(tags, {
|
||||
fields: [characterTags.tagId],
|
||||
references: [tags.id]
|
||||
})
|
||||
character: one(characters, {
|
||||
fields: [characterTags.characterId],
|
||||
references: [characters.id]
|
||||
}),
|
||||
tag: one(tags, {
|
||||
fields: [characterTags.tagId],
|
||||
references: [tags.id]
|
||||
})
|
||||
}))
|
||||
|
||||
export const characters = sqliteTable("characters", {
|
||||
id: integer("id").primaryKey(),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }), // FK to users.id
|
||||
name: text("name").notNull(),
|
||||
nickname: text("nickname"), // Optional nickname
|
||||
characterVersion: text("character_version").default("1.0"), // Version of the character schema
|
||||
description: text("description").notNull(),
|
||||
personality: text("personality"), // Persona field
|
||||
scenario: text("scenario"),
|
||||
firstMessage: text("first_message"),
|
||||
alternateGreetings: text("alternate_greetings", { mode: "json" })
|
||||
.default("[]")
|
||||
.$type<string[]>(), // JSON array of alternate greetings
|
||||
exampleDialogues: text("example_dialogues"), // JSON/text
|
||||
metadata: text("metadata", { mode: "json" }), // JSON/text for extra fields
|
||||
avatar: text("avatar"), // Path or URL to avatar image
|
||||
creatorNotes: text("creator_notes"), // Notes from the character creator
|
||||
creatorNotesMultilingual: text("creator_notes_multilingual", {
|
||||
mode: "json"
|
||||
})
|
||||
.default("{}")
|
||||
.$type<Record<string, string>>(), // Multilingual creator notes as JSON object
|
||||
groupOnlyGreetings: text("group_only_greetings", { mode: "json" })
|
||||
.default("[]")
|
||||
.$type<String[]>(), // JSON array of greetings for group chats
|
||||
postHistoryInstructions: text("post_history_instructions"), // Instructions for post-history processing
|
||||
source: text("source", { mode: "json" }).default("[]").$type<string[]>(), // JSON array of sources (e.g., URLs, books)
|
||||
assets: text("assets", { mode: "json" }).default("[]").$type<
|
||||
Array<{
|
||||
type: string
|
||||
uri: string
|
||||
name: string
|
||||
ext: string
|
||||
}>
|
||||
>(), // JSON array of asset paths or URLs
|
||||
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: text("updated_at").$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
|
||||
lorebookId: integer("lorebook_id").references(() => lorebooks.id, {
|
||||
onDelete: "set null"
|
||||
}), // Optional FK to lorebooks.id
|
||||
extensions: text("extensions", { mode: "json" })
|
||||
.default("[]")
|
||||
.$type<Record<string, any>>(),
|
||||
isFavorite: integer("is_favorite", { mode: "boolean" }).default(false) // 1 if favorite, 0 otherwise
|
||||
id: integer("id").primaryKey(),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }), // FK to users.id
|
||||
name: text("name").notNull(),
|
||||
nickname: text("nickname"), // Optional nickname
|
||||
characterVersion: text("character_version").default("1.0"), // Version of the character schema
|
||||
description: text("description").notNull(),
|
||||
personality: text("personality"), // Persona field
|
||||
scenario: text("scenario"),
|
||||
firstMessage: text("first_message"),
|
||||
alternateGreetings: text("alternate_greetings", { mode: "json" })
|
||||
.default("[]")
|
||||
.$type<string[]>(), // JSON array of alternate greetings
|
||||
exampleDialogues: text("example_dialogues"), // JSON/text
|
||||
metadata: text("metadata"), // JSON/text for extra fields
|
||||
avatar: text("avatar"), // Path or URL to avatar image
|
||||
creatorNotes: text("creator_notes"), // Notes from the character creator
|
||||
creatorNotesMultilingual: text("creator_notes_multilingual", { mode: "json" })
|
||||
.default("{}")
|
||||
.$type<Record<string, string>>(), // Multilingual creator notes as JSON object
|
||||
groupOnlyGreetings: text("group_only_greetings", { mode: "json" })
|
||||
.default("[]")
|
||||
.$type<String[]>(), // JSON array of greetings for group chats
|
||||
postHistoryInstructions: text("post_history_instructions"), // Instructions for post-history processing
|
||||
source: text("source", { mode: "json" }).default("[]").$type<string[]>(), // JSON array of sources (e.g., URLs, books)
|
||||
assets: text("assets", { mode: "json" }).default("[]").$type<
|
||||
Array<{
|
||||
type: string
|
||||
uri: string
|
||||
name: string
|
||||
ext: string
|
||||
}>
|
||||
>(), // JSON array of asset paths or URLs
|
||||
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: text("updated_at").$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
|
||||
lorebookId: integer("lorebook_id").references(() => lorebooks.id, { onDelete: "set null" }), // Optional FK to lorebooks.id
|
||||
extensions: text("extensions", { mode: "json" }).default("[]").$type<Record<string, any>>(),
|
||||
isFavorite: integer("is_favorite", { mode: "boolean" }).default(false) // 1 if favorite, 0 otherwise
|
||||
})
|
||||
|
||||
export const charactersRelations = relations(characters, ({ many, one }) => ({
|
||||
characterTags: many(characterTags),
|
||||
user: one(users, {
|
||||
fields: [characters.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [characters.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
})
|
||||
characterTags: many(characterTags),
|
||||
user: one(users, {
|
||||
fields: [characters.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [characters.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
})
|
||||
}))
|
||||
|
||||
export const personas = sqliteTable("personas", {
|
||||
id: integer("id").primaryKey(),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }), // FK to users.id
|
||||
isDefault: integer("is_default", { mode: "boolean" }).default(false), // Is this the default persona for the user?
|
||||
avatar: text("avatar"), // e.g. 'user-default.png', '1747379438925-Ryvn.png'
|
||||
name: text("name").notNull(), // e.g. 'Warren', 'Master Desir'
|
||||
description: text("description").notNull(), // Persona description (long text)
|
||||
position: integer("position").default(0),
|
||||
connections: text("connections"), // JSON array of connection IDs or objects
|
||||
createdAt: text("created_at"),
|
||||
updatedAt: text("updated_at"),
|
||||
lorebookId: integer("lorebook_id").references(() => lorebooks.id, {
|
||||
onDelete: "set null"
|
||||
}) // Optional lorebook for this persona
|
||||
id: integer("id").primaryKey(),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }), // FK to users.id
|
||||
isDefault: integer("is_default", { mode: "boolean" }).default(false), // Is this the default persona for the user?
|
||||
avatar: text("avatar"), // e.g. 'user-default.png', '1747379438925-Ryvn.png'
|
||||
name: text("name").notNull(), // e.g. 'Warren', 'Master Desir'
|
||||
description: text("description").notNull(), // Persona description (long text)
|
||||
position: integer("position").default(0),
|
||||
connections: text("connections"), // JSON array of connection IDs or objects
|
||||
createdAt: text("created_at"),
|
||||
updatedAt: text("updated_at")
|
||||
})
|
||||
|
||||
export const personasRelations = relations(personas, ({ one, many }) => ({
|
||||
user: one(users, {
|
||||
fields: [personas.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [personas.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
})
|
||||
user: one(users, {
|
||||
fields: [personas.userId],
|
||||
references: [users.id]
|
||||
})
|
||||
}))
|
||||
|
||||
// Chats (group or 1:1)
|
||||
export const chats = sqliteTable("chats", {
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name"), // Optional chat/group name
|
||||
isGroup: integer("is_group", { mode: "boolean" }).default(false), // 1 for group chat, 0 for 1:1
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
createdAt: text("created_at"),
|
||||
updatedAt: text("updated_at"),
|
||||
scenario: text("scenario"),
|
||||
metadata: text("metadata"), // JSON for extra settings
|
||||
groupReplyStrategy: text("group_reply_strategy").default(
|
||||
GroupReplyStrategies.ORDERED
|
||||
),
|
||||
lorebookId: integer("lorebook_id").references(() => lorebooks.id, {
|
||||
onDelete: "set null"
|
||||
}) // Primary lorebook for this chat
|
||||
id: integer("id").primaryKey(),
|
||||
name: text("name"), // Optional chat/group name
|
||||
isGroup: integer("is_group", {mode: "boolean"}).default(false), // 1 for group chat, 0 for 1:1
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
createdAt: text("created_at"),
|
||||
updatedAt: text("updated_at"),
|
||||
scenario: text("scenario"),
|
||||
metadata: text("metadata"), // JSON for extra settings
|
||||
group_reply_strategy: text("group_reply_strategy").default(GroupReplyStrategies.ORDERED)
|
||||
})
|
||||
|
||||
export const chatsRelations = relations(chats, ({ one, many }) => ({
|
||||
user: one(users, {
|
||||
fields: [chats.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
chatMessages: many(chatMessages),
|
||||
chatPersonas: many(chatPersonas),
|
||||
chatCharacters: many(chatCharacters),
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [chats.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
})
|
||||
user: one(users, {
|
||||
fields: [chats.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
chatMessages: many(chatMessages),
|
||||
chatPersonas: many(chatPersonas),
|
||||
chatCharacters: many(chatCharacters)
|
||||
}))
|
||||
|
||||
// Chat messages
|
||||
export const chatMessages = sqliteTable("chat_messages", {
|
||||
id: integer("id").primaryKey(),
|
||||
chatId: integer("chat_id")
|
||||
.notNull()
|
||||
.references(() => chats.id, { onDelete: "cascade" }),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }), // nullable for system/character messages
|
||||
characterId: integer("character_id").references(() => characters.id, {
|
||||
onDelete: "set null"
|
||||
}), // nullable
|
||||
personaId: integer("persona_id").references(() => personas.id, {
|
||||
onDelete: "set null"
|
||||
}), // nullable
|
||||
role: text("role"), // 'user', 'character', 'system', etc
|
||||
content: text("content").notNull(),
|
||||
createdAt: text("created_at"),
|
||||
updatedAt: text("updated_at"),
|
||||
isEdited: integer("is_edited").default(0), // 1 if edited, 0 otherwise
|
||||
metadata: text("metadata", { mode: "json" }).$type<{isGreeting?: boolean, swipes?:{currentIdx: number | null, history: []}}>(), // JSON for extra info
|
||||
isGenerating: integer("is_generating", { mode: "boolean" }).default(false), // 1 if processing, 0 otherwise
|
||||
adapterId: text("adapter_id"), // UUID for in-flight adapter instance, nullable
|
||||
isHidden: integer("is_hidden", { mode: "boolean" }).default(false) // Whether this message is processed or not
|
||||
id: integer("id").primaryKey(),
|
||||
chatId: integer("chat_id")
|
||||
.notNull()
|
||||
.references(() => chats.id, { onDelete: "cascade" }),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }), // nullable for system/character messages
|
||||
characterId: integer("character_id").references(() => characters.id, { onDelete: "set null" }), // nullable
|
||||
personaId: integer("persona_id").references(() => personas.id, { onDelete: "set null" }), // nullable
|
||||
role: text("role"), // 'user', 'character', 'system', etc
|
||||
content: text("content").notNull(),
|
||||
createdAt: text("created_at"),
|
||||
updatedAt: text("updated_at"),
|
||||
isEdited: integer("is_edited").default(0), // 1 if edited, 0 otherwise
|
||||
metadata: text("metadata"), // JSON for extra info
|
||||
isGenerating: integer("is_generating", { mode: "boolean" }).default(false), // 1 if processing, 0 otherwise
|
||||
adapterId: text("adapter_id"), // UUID for in-flight adapter instance, nullable
|
||||
isHidden: integer("is_hidden", { mode: "boolean" }).default(false) // Whether this message is processed or not
|
||||
})
|
||||
|
||||
export const chatMessagesRelations = relations(chatMessages, ({ one }) => ({
|
||||
chat: one(chats, {
|
||||
fields: [chatMessages.chatId],
|
||||
references: [chats.id]
|
||||
}),
|
||||
user: one(users, {
|
||||
fields: [chatMessages.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
character: one(characters, {
|
||||
fields: [chatMessages.characterId],
|
||||
references: [characters.id]
|
||||
}),
|
||||
persona: one(personas, {
|
||||
fields: [chatMessages.personaId],
|
||||
references: [personas.id]
|
||||
})
|
||||
chat: one(chats, {
|
||||
fields: [chatMessages.chatId],
|
||||
references: [chats.id]
|
||||
}),
|
||||
user: one(users, {
|
||||
fields: [chatMessages.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
character: one(characters, {
|
||||
fields: [chatMessages.characterId],
|
||||
references: [characters.id]
|
||||
}),
|
||||
persona: one(personas, {
|
||||
fields: [chatMessages.personaId],
|
||||
references: [personas.id]
|
||||
})
|
||||
}))
|
||||
|
||||
// Many-to-many: chats <-> personas
|
||||
export const chatPersonas = sqliteTable("chat_personas", {
|
||||
chatId: integer("chat_id")
|
||||
.notNull()
|
||||
.references(() => chats.id, { onDelete: "cascade" }),
|
||||
personaId: integer("persona_id").references(() => personas.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
position: integer("position").default(0) // Position in the chat
|
||||
chatId: integer("chat_id")
|
||||
.notNull()
|
||||
.references(() => chats.id, { onDelete: "cascade" }),
|
||||
personaId: integer("persona_id")
|
||||
.references(() => personas.id, { onDelete: "set null" }),
|
||||
position: integer("position").default(0), // Position in the chat
|
||||
})
|
||||
|
||||
export const chatPersonasRelations = relations(chatPersonas, ({ one }) => ({
|
||||
chat: one(chats, {
|
||||
fields: [chatPersonas.chatId],
|
||||
references: [chats.id]
|
||||
}),
|
||||
persona: one(personas, {
|
||||
fields: [chatPersonas.personaId],
|
||||
references: [personas.id]
|
||||
})
|
||||
chat: one(chats, {
|
||||
fields: [chatPersonas.chatId],
|
||||
references: [chats.id]
|
||||
}),
|
||||
persona: one(personas, {
|
||||
fields: [chatPersonas.personaId],
|
||||
references: [personas.id]
|
||||
})
|
||||
}))
|
||||
|
||||
// Many-to-many: chats <-> characters
|
||||
export const chatCharacters = sqliteTable("chat_characters", {
|
||||
chatId: integer("chat_id")
|
||||
.notNull()
|
||||
.references(() => chats.id, { onDelete: "cascade" }),
|
||||
characterId: integer("character_id").references(() => characters.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
position: integer("position").default(0), // Position in the chat
|
||||
isActive: integer("is_active", { mode: "boolean" }).default(true) // 1 if active in chat, 0 if not
|
||||
chatId: integer("chat_id")
|
||||
.notNull()
|
||||
.references(() => chats.id, { onDelete: "cascade" }),
|
||||
characterId: integer("character_id")
|
||||
.references(() => characters.id, { onDelete: "set null" }),
|
||||
position: integer("position").default(0), // Position in the chat
|
||||
isActive: integer("is_active", { mode: "boolean" }).default(true) // 1 if active in chat, 0 if not
|
||||
})
|
||||
|
||||
export const chatCharactersRelations = relations(chatCharacters, ({ one }) => ({
|
||||
chat: one(chats, {
|
||||
fields: [chatCharacters.chatId],
|
||||
references: [chats.id]
|
||||
}),
|
||||
character: one(characters, {
|
||||
fields: [chatCharacters.characterId],
|
||||
references: [characters.id]
|
||||
})
|
||||
}))
|
||||
|
||||
// Many-to-many: chats <-> lorebooks
|
||||
export const chatLorebooks = sqliteTable(
|
||||
"chat_lorebooks",
|
||||
{
|
||||
chatId: integer("chat_id")
|
||||
.notNull()
|
||||
.references(() => chats.id, { onDelete: "cascade" }),
|
||||
lorebookId: integer("lorebook_id")
|
||||
.notNull()
|
||||
.references(() => lorebooks.id, { onDelete: "cascade" }),
|
||||
position: integer("position").default(0) // Optional: position/order in the chat
|
||||
},
|
||||
(table) => ({
|
||||
uniqueChatPosition: uniqueIndex(
|
||||
"chatLorebooks_chatId_position_unique"
|
||||
).on(table.chatId, table.position)
|
||||
})
|
||||
)
|
||||
|
||||
export const chatLorebooksRelations = relations(chatLorebooks, ({ one }) => ({
|
||||
chat: one(chats, {
|
||||
fields: [chatLorebooks.chatId],
|
||||
references: [chats.id]
|
||||
}),
|
||||
lorebook: one(lorebooks, {
|
||||
fields: [chatLorebooks.lorebookId],
|
||||
references: [lorebooks.id]
|
||||
})
|
||||
}))
|
||||
chat: one(chats, {
|
||||
fields: [chatCharacters.chatId],
|
||||
references: [chats.id]
|
||||
}),
|
||||
character: one(characters, {
|
||||
fields: [chatCharacters.characterId],
|
||||
references: [characters.id]
|
||||
})
|
||||
}))
|
||||
|
|
@ -657,8 +657,9 @@ export async function triggerGenerateMessage(
|
|||
const msgLimit = 10
|
||||
let currentMsg = 1
|
||||
let triggered = true
|
||||
let ok = true
|
||||
|
||||
while (currentMsg <= msgLimit) {
|
||||
while (currentMsg <= msgLimit && ok) {
|
||||
let chat = await getPromptChatFromDb(message.chatId, userId)
|
||||
if (!chat) {
|
||||
const res: Sockets.TriggerGenerateMessage.Response = {
|
||||
|
|
@ -710,7 +711,7 @@ export async function triggerGenerateMessage(
|
|||
{ chatMessage: generatingMessage },
|
||||
emitToUser
|
||||
)
|
||||
await generateResponse({
|
||||
ok =await generateResponse({
|
||||
socket,
|
||||
emitToUser,
|
||||
chatId: message.chatId,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export async function generateResponse({
|
|||
chatId: number
|
||||
userId: number
|
||||
generatingMessage: SelectChatMessage
|
||||
}) {
|
||||
}): Promise<boolean> {
|
||||
// Generate a UUID for this adapter instance
|
||||
const adapterId = uuidv4()
|
||||
// Save the adapterId to the chatMessage (set isGenerating true, content empty, and adapterId)
|
||||
|
|
@ -28,8 +28,6 @@ export async function generateResponse({
|
|||
.where(eq(schema.chatMessages.id, generatingMessage.id))
|
||||
// Instead of getChat, emit the chatMessage
|
||||
|
||||
// console.log("[generateResponse] Generating response for message:", generatingMessage.id, "in chat:", chatId, "for user:", userId, "with adapterId:", adapterId)
|
||||
|
||||
const req: Sockets.ChatMessage.Call = {
|
||||
chatMessage: {
|
||||
...generatingMessage,
|
||||
|
|
@ -39,11 +37,7 @@ export async function generateResponse({
|
|||
}
|
||||
}
|
||||
|
||||
await chatMessage(
|
||||
socket,
|
||||
req,
|
||||
emitToUser
|
||||
)
|
||||
await chatMessage(socket, req, emitToUser)
|
||||
|
||||
const chat = await db.query.chats.findFirst({
|
||||
where: (c, { eq }) => eq(c.id, chatId),
|
||||
|
|
@ -54,7 +48,7 @@ export async function generateResponse({
|
|||
where: (cm, { ne }) => ne(cm.id, generatingMessage.id),
|
||||
orderBy: (cm, { asc }) => asc(cm.id)
|
||||
},
|
||||
lorebook: true,
|
||||
lorebook: true
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -89,11 +83,16 @@ export async function generateResponse({
|
|||
activeAdapters.set(adapterId, adapter)
|
||||
|
||||
// Generate completion
|
||||
let [completionResult, compiledPrompt] = await adapter.generate() // TODO: save compiledPrompt to chatMessages
|
||||
let { completionResult, compiledPrompt, isAborted } =
|
||||
await adapter.generate() // TODO: save compiledPrompt to chatMessages
|
||||
let content = ""
|
||||
try {
|
||||
if (typeof completionResult === "function") {
|
||||
let ok = true
|
||||
await completionResult(async (chunk: string) => {
|
||||
if (!ok) {
|
||||
return
|
||||
}
|
||||
content += chunk
|
||||
|
||||
// --- SWIPE HISTORY LOGIC ---
|
||||
|
|
@ -101,12 +100,15 @@ export async function generateResponse({
|
|||
if (
|
||||
generatingMessage.metadata &&
|
||||
generatingMessage.metadata.swipes &&
|
||||
typeof generatingMessage.metadata.swipes.currentIdx === "number" &&
|
||||
typeof generatingMessage.metadata.swipes.currentIdx ===
|
||||
"number" &&
|
||||
generatingMessage.metadata.swipes.currentIdx > 0 &&
|
||||
Array.isArray(generatingMessage.metadata.swipes.history)
|
||||
) {
|
||||
const idx = generatingMessage.metadata.swipes.currentIdx
|
||||
const history: string[] = [...generatingMessage.metadata.swipes.history]
|
||||
const history: string[] = [
|
||||
...generatingMessage.metadata.swipes.history
|
||||
]
|
||||
history[idx] = content
|
||||
updateData = {
|
||||
...updateData,
|
||||
|
|
@ -120,34 +122,52 @@ export async function generateResponse({
|
|||
}
|
||||
}
|
||||
|
||||
await db
|
||||
const [updatedChatMsg] = await db
|
||||
.update(schema.chatMessages)
|
||||
.set(updateData)
|
||||
.where(eq(schema.chatMessages.id, generatingMessage.id))
|
||||
// Instead of getChat, emit the chatMessage
|
||||
await chatMessage(
|
||||
socket,
|
||||
{
|
||||
chatMessage: {
|
||||
...generatingMessage,
|
||||
content,
|
||||
isGenerating: true,
|
||||
...(updateData.metadata ? { metadata: updateData.metadata } : {})
|
||||
}
|
||||
},
|
||||
emitToUser
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.chatMessages.id, generatingMessage.id),
|
||||
eq(schema.chatMessages.isGenerating, true)
|
||||
)
|
||||
)
|
||||
.returning()
|
||||
if (!!updatedChatMsg) {
|
||||
const chatMsgReq: Sockets.ChatMessage.Call = {
|
||||
chatMessage: updatedChatMsg
|
||||
}
|
||||
await chatMessage(socket, chatMsgReq, emitToUser)
|
||||
} else {
|
||||
const chatMsgReq: Sockets.ChatMessage.Call = {
|
||||
id: generatingMessage.id
|
||||
}
|
||||
await chatMessage(socket, chatMsgReq, emitToUser)
|
||||
console.warn(
|
||||
"[generateResponse] Generating terminated early",
|
||||
generatingMessage.id
|
||||
)
|
||||
ok = false
|
||||
}
|
||||
})
|
||||
// Final update: mark as not generating, clear adapterId
|
||||
content = content.trim()
|
||||
const ret = await db
|
||||
.update(schema.chatMessages)
|
||||
.set({ content, isGenerating: false, adapterId: null })
|
||||
.where(and(eq(schema.chatMessages.id, generatingMessage.id), eq(schema.chatMessages.isGenerating, true))).returning()
|
||||
.where(
|
||||
and(
|
||||
eq(schema.chatMessages.id, generatingMessage.id),
|
||||
eq(schema.chatMessages.isGenerating, true)
|
||||
)
|
||||
)
|
||||
.returning()
|
||||
if (!ret || ret.length === 0) {
|
||||
console.error("[generateResponse] Failed to update generating message:", generatingMessage.id)
|
||||
console.error(
|
||||
"[generateResponse] Failed to update generating message:",
|
||||
generatingMessage.id
|
||||
)
|
||||
activeAdapters.delete(adapterId)
|
||||
return
|
||||
return false
|
||||
}
|
||||
// Instead of getChat, emit the chatMessage
|
||||
await chatMessage(
|
||||
|
|
@ -167,16 +187,23 @@ export async function generateResponse({
|
|||
content = content.trim()
|
||||
|
||||
// --- SWIPE HISTORY LOGIC (non-streamed) ---
|
||||
let updateData: any = { content, isGenerating: false, adapterId: null }
|
||||
let updateData: any = {
|
||||
content,
|
||||
isGenerating: false,
|
||||
adapterId: null
|
||||
}
|
||||
if (
|
||||
generatingMessage.metadata &&
|
||||
generatingMessage.metadata.swipes &&
|
||||
typeof generatingMessage.metadata.swipes.currentIdx === "number" &&
|
||||
typeof generatingMessage.metadata.swipes.currentIdx ===
|
||||
"number" &&
|
||||
generatingMessage.metadata.swipes.currentIdx > 0 &&
|
||||
Array.isArray(generatingMessage.metadata.swipes.history)
|
||||
) {
|
||||
const idx = generatingMessage.metadata.swipes.currentIdx
|
||||
const history: string[] = [...generatingMessage.metadata.swipes.history]
|
||||
const history: string[] = [
|
||||
...generatingMessage.metadata.swipes.history
|
||||
]
|
||||
history[idx] = content
|
||||
updateData = {
|
||||
...updateData,
|
||||
|
|
@ -193,12 +220,21 @@ export async function generateResponse({
|
|||
const ret = await db
|
||||
.update(schema.chatMessages)
|
||||
.set(updateData)
|
||||
.where(and(eq(schema.chatMessages.id, generatingMessage.id), eq(schema.chatMessages.isGenerating, true))).returning()
|
||||
.where(
|
||||
and(
|
||||
eq(schema.chatMessages.id, generatingMessage.id),
|
||||
eq(schema.chatMessages.isGenerating, true)
|
||||
)
|
||||
)
|
||||
.returning()
|
||||
// Instead of getChat, emit the chatMessage
|
||||
if (!ret || ret.length === 0) {
|
||||
console.error("[generateResponse] Failed to update generating message:", generatingMessage.id)
|
||||
console.error(
|
||||
"[generateResponse] Failed to update generating message:",
|
||||
generatingMessage.id
|
||||
)
|
||||
activeAdapters.delete(adapterId)
|
||||
return
|
||||
return false
|
||||
}
|
||||
await chatMessage(
|
||||
socket,
|
||||
|
|
@ -208,7 +244,9 @@ export async function generateResponse({
|
|||
content,
|
||||
isGenerating: false,
|
||||
adapterId: null,
|
||||
...(updateData.metadata ? { metadata: updateData.metadata } : {})
|
||||
...(updateData.metadata
|
||||
? { metadata: updateData.metadata }
|
||||
: {})
|
||||
}
|
||||
},
|
||||
emitToUser
|
||||
|
|
@ -228,4 +266,5 @@ export async function generateResponse({
|
|||
socket.io.to("user_" + userId).emit("personaMessageReceived", response)
|
||||
// Instead of getChat, emit the chatMessage
|
||||
await chatMessage(socket, { chatMessage: updatedMsg! }, emitToUser)
|
||||
return !!isAborted // Whether there were no interruptions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,14 +167,21 @@
|
|||
|
||||
let chatMessagesContainer: HTMLDivElement | null = $state(null)
|
||||
|
||||
// Auto-scroll to bottom when messages change or container is mounted
|
||||
$effect(() => {
|
||||
console.log(
|
||||
"chatMessagesContainer effect",
|
||||
chatMessagesContainer?.scrollTo({
|
||||
top: chatMessagesContainer.scrollHeight,
|
||||
behavior: "auto"
|
||||
})
|
||||
)
|
||||
// React to changes in messages and container
|
||||
const messagesLength = chat?.chatMessages?.length ?? 0
|
||||
if (chatMessagesContainer && messagesLength > 0) {
|
||||
// Use setTimeout to ensure DOM has updated
|
||||
setTimeout(() => {
|
||||
if (chatMessagesContainer) {
|
||||
chatMessagesContainer.scrollTo({
|
||||
top: chatMessagesContainer.scrollHeight,
|
||||
behavior: "smooth"
|
||||
})
|
||||
}
|
||||
}, 50) // Slightly longer delay to ensure content is rendered
|
||||
}
|
||||
})
|
||||
|
||||
function handleEditMessage(e: Event, msg: SelectChatMessage) {
|
||||
|
|
@ -315,12 +322,7 @@
|
|||
socket.on("chat", (msg: Sockets.Chat.Response) => {
|
||||
if (msg.chat.id === Number.parseInt(page.params.id)) {
|
||||
chat = msg.chat
|
||||
// Instantly jump to bottom on chat update
|
||||
console.log("Scrolling to bottom on chat update")
|
||||
chatMessagesContainer?.scrollTo({
|
||||
top: chatMessagesContainer.scrollHeight,
|
||||
behavior: "instant"
|
||||
})
|
||||
// Auto-scroll is handled by the $effect
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -339,12 +341,7 @@
|
|||
chatMessages: [...chat.chatMessages, msg.chatMessage]
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
chatMessagesContainer?.scrollTo({
|
||||
top: chatMessagesContainer.scrollHeight,
|
||||
behavior: "smooth"
|
||||
})
|
||||
}, 0)
|
||||
// Auto-scroll is handled by the $effect
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -415,7 +412,7 @@
|
|||
</svelte:head>
|
||||
|
||||
<div
|
||||
class="relative flex max-h-full min-h-full max-w-full min-w-full flex-col overflow-y-auto"
|
||||
class="relative flex h-full flex-col"
|
||||
>
|
||||
<div
|
||||
id="chat-history"
|
||||
|
|
@ -426,12 +423,12 @@
|
|||
{#if !chat || chat.chatMessages.length === 0}
|
||||
<div class="text-muted mt-8 text-center">No messages yet.</div>
|
||||
{:else}
|
||||
<ul class="flex flex-1 flex-col gap-3 overflow-y-auto">
|
||||
<ul class="flex flex-1 flex-col gap-3">
|
||||
{#each chat.chatMessages as msg (msg.id)}
|
||||
{@const character = getMessageCharacter(msg)}
|
||||
{@const isGreeting = !!msg.metadata?.isGreeting}
|
||||
<li
|
||||
class="bg-primary-50-950 flex flex-col overflow-y-auto rounded-lg p-2"
|
||||
class="preset-filled-primary-50-950 flex flex-col rounded-lg p-2"
|
||||
class:opacity-50={msg.isHidden &&
|
||||
editChatMessage?.id !== msg.id}
|
||||
>
|
||||
|
|
@ -633,6 +630,7 @@
|
|||
onSend={handleSend}
|
||||
compiledPrompt={draftCompiledPrompt}
|
||||
classes=""
|
||||
|
||||
extraTabs={[
|
||||
{
|
||||
value: "extraControls",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue