From e31f01255c3876132e9e1e56c06c3b83c276bcd2 Mon Sep 17 00:00:00 2001 From: agnaidev Date: Sun, 3 May 2026 20:44:36 +0800 Subject: [PATCH 01/14] edit memory book in chat --- web/pages/Chat/components/MemoryModal.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/web/pages/Chat/components/MemoryModal.tsx b/web/pages/Chat/components/MemoryModal.tsx index 80768f8c7..53b68d032 100644 --- a/web/pages/Chat/components/MemoryModal.tsx +++ b/web/pages/Chat/components/MemoryModal.tsx @@ -1,4 +1,4 @@ -import { Edit, PlusIcon, Save, X } from 'lucide-solid' +import { Edit, Pencil, PlusIcon, Save, X } from 'lucide-solid' import { Component, createEffect, @@ -203,9 +203,14 @@ const ChatMemoryModal: Component<{
  • {book.name}
    - +
    + + +
  • )} From 886c714dbae0d1f72d2595e12fd60e741bd4f5a9 Mon Sep 17 00:00:00 2001 From: agnaidev Date: Sun, 3 May 2026 22:01:51 +0800 Subject: [PATCH 02/14] fix image preset --- web/store/data/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/store/data/common.ts b/web/store/data/common.ts index 501f8664b..2d9fe2a87 100644 --- a/web/store/data/common.ts +++ b/web/store/data/common.ts @@ -44,7 +44,7 @@ export async function getImagePromptEntities(messageId?: string) { switch (source) { case 'settings': { summary = entities.user.images?.summaryPrompt || '' - presetId = entities.user.summaryPreset || entities.user.images?.summaryPresetId || '' + presetId = entities.user.images?.summaryPresetId || entities.user.summaryPreset || '' break } From a18d08ee8adb97f5b68dac0bb387e1ef672c26c2 Mon Sep 17 00:00:00 2001 From: agnaidev Date: Tue, 5 May 2026 14:02:04 +0800 Subject: [PATCH 03/14] fix: llamacpp thinking budget fix: cancel should retain message --- .zed/debug.json | 20 ++++++++++++++++++++ common/requests/payloads.ts | 14 ++++++++++++-- web/store/data/bot-generate.ts | 4 ++-- web/store/data/inference.ts | 3 ++- 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 .zed/debug.json diff --git a/.zed/debug.json b/.zed/debug.json new file mode 100644 index 000000000..79aa58179 --- /dev/null +++ b/.zed/debug.json @@ -0,0 +1,20 @@ +// Project-local debug tasks +// +// For more documentation on how to configure debug tasks, +// see: https://zed.dev/docs/debugger +[ + { + "label": "Launch API (Zed)", + "adapter": "JavaScript", + "program": "${ZED_WORKTREE_ROOT}/srv/start.js", + "sourceMaps": true, + "console": "integratedTerminal", + "request": "launch", + "trace": true, + "type": "node", + "outFiles": [ + "${ZED_WORKTREE_ROOT}/srv/**/*.js", + "!**/node_modules/**" + ] + } +] diff --git a/common/requests/payloads.ts b/common/requests/payloads.ts index 7f7334256..c4f145ac8 100644 --- a/common/requests/payloads.ts +++ b/common/requests/payloads.ts @@ -378,6 +378,15 @@ function getBasePayload(opts: MinOpts, stops: string[] = []) { } if (format === 'llamacpp') { + const budget = + reasoningEffort === 'none' + ? 0 + : reasoningEffort === 'low' + ? gen.maxTokens! * 0.2 + : reasoningEffort === 'medium' + ? gen.maxTokens! * 0.5 + : gen.maxTokens! * 0.75 + const body = { prompt: messages ? undefined : prompt, messages, @@ -401,6 +410,7 @@ function getBasePayload(opts: MinOpts, stops: string[] = []) { tfs_z: gen.tailFreeSampling, json_schema, reasoning_effort: reasoningEffort, + thinking_budget_tokens: budget, chat_template_kwargs: { enable_thinking: reasoningEffort !== 'none' }, } return body @@ -648,7 +658,7 @@ export function toImageJinjaTemplate(opts: { jinja?: string; format?: ModelForma {%- if message['content'] is string %} {{- message['content'] }} {%- else %} - + {%- for block in message['content'] %} {%- if block['type'] == 'text' %} {{- block['text'] }} @@ -658,7 +668,7 @@ export function toImageJinjaTemplate(opts: { jinja?: string; format?: ModelForma {{- raise_exception('Only text and image blocks are supported in message content!') }} {%- endif %} {%- endfor %} - + {%- endif %} {%- elif message['role'] == 'system' %} {{- message['content'] }} diff --git a/web/store/data/bot-generate.ts b/web/store/data/bot-generate.ts index 0c5a7da66..ba961bfa5 100644 --- a/web/store/data/bot-generate.ts +++ b/web/store/data/bot-generate.ts @@ -435,8 +435,8 @@ async function handlePostStreamResponse(input: { retrying: undefined, partial: undefined, }) - console.log('aborted -- ignoring post stream handler') - return + // console.log('aborted -- ignoring post stream handler') + // return } const { replacing, parent, replyAs, continuing } = req.request diff --git a/web/store/data/inference.ts b/web/store/data/inference.ts index e6e9eeb27..05f69ae92 100644 --- a/web/store/data/inference.ts +++ b/web/store/data/inference.ts @@ -352,7 +352,8 @@ export async function inferenceStream(opts: InferenceOpts, onTick?: TickHandler) if (opts.signal) { opts.signal.signal.onabort = () => { inferenceCallbacks.delete(requestId) - lazy.resolve({ response: lastResponse }) + tickWrapper(lastResponse, 'done') + // lazy.resolve({ response: lastResponse }) } } if (conn?.local) { From df81e578c71da1f1fe171d4a302fbd9c8e9b6eed Mon Sep 17 00:00:00 2001 From: agnaidev Date: Sat, 16 May 2026 13:06:56 +0800 Subject: [PATCH 04/14] - refactor memory books selection - deepseek thinking payload - preset parsers operate on prompt - ui tweaks --- common/chat.ts | 20 +- common/prompt.ts | 6 +- common/types/image-schema.ts | 1 + common/types/presets.ts | 6 +- srv/adapter/openai.ts | 3 +- web/Navigation.tsx | 4 +- web/app.css | 4 +- .../Chat/components/ManageMemoryBooks.tsx | 189 ++++++++++++++++ web/pages/Chat/components/MemoryModal.tsx | 206 +----------------- web/pages/Image/ImageModal.tsx | 4 + web/pages/Profile/SubscriptionPage.tsx | 2 +- web/pages/Settings/Image/ImageSettings.tsx | 7 + web/shared/Copy.tsx | 7 +- web/shared/PhraseBias.tsx | 2 + web/store/data/image.ts | 82 +------ web/store/message.ts | 3 + web/tailwind.css | 16 +- 17 files changed, 278 insertions(+), 284 deletions(-) create mode 100644 web/pages/Chat/components/ManageMemoryBooks.tsx diff --git a/common/chat.ts b/common/chat.ts index 97503248d..56ccad93c 100644 --- a/common/chat.ts +++ b/common/chat.ts @@ -14,17 +14,35 @@ export type ChatNode = { export type ChatDepths = Record -export function runPresetParsers(parsers: PresetParser[], message: string) { +export function runPresetParsers( + parsers: PresetParser[], + message: string, + opts?: { preset?: boolean } +) { let current = message || '' for (const parser of parsers) { if (!parser.text?.trim()) continue switch (parser.type) { + case 'remove-prompt': { + if (!opts?.preset) break + current = current.split(parser.text.replace(/\\n/g, '\n')).join('') + break + } + case 'remove': { current = current.split(parser.text.replace(/\\n/g, '\n')).join('') break } + case 'replace-prompt': { + if (!opts?.preset) break + const from = parser.text.replace(/\\n/g, '\n') + const to = (parser.to || '').replace(/\\n/g, '\n') + current = current.split(from).join(to) + break + } + case 'replace': { const from = parser.text.replace(/\\n/g, '\n') const to = (parser.to || '').replace(/\\n/g, '\n') diff --git a/common/prompt.ts b/common/prompt.ts index daf08ac2a..a8c180eb1 100644 --- a/common/prompt.ts +++ b/common/prompt.ts @@ -13,6 +13,7 @@ import { ModelFormat, replaceArrayTags, replaceTags } from './presets/templates' import { OPENAI_CONTEXTS } from './presets/openai' import { NOVEL_MODELS } from './presets/novel' import { extractReasoning } from './reasoning' +import { runPresetParsers } from './chat' export type JsonOutput = { values: any; response: string; history: string; imageCaption: string } export type TickHandler = ( @@ -323,11 +324,14 @@ export async function assemblePrompt( const post = createPostPrompt(opts) const template = getTemplate(opts) + const parsers = opts.settings?.parsers || [] + const parsedLines = opts.lines.map((line) => runPresetParsers(parsers, line, { preset: true })) + let { parsed, inserts, length, sections, linesAddedCount, history, addedLines, blocks } = await injectPlaceholders(template, { opts: { ...opts, schema: opts.jsonSchema }, parts: opts.parts, - lines: opts.lines, + lines: parsedLines, history: opts.history, characters: opts.characters, lastMessage: opts.lastMessage, diff --git a/common/types/image-schema.ts b/common/types/image-schema.ts index 9a71bb439..13d85e3e1 100644 --- a/common/types/image-schema.ts +++ b/common/types/image-schema.ts @@ -12,6 +12,7 @@ export type BaseImageSettings = { suffix?: string negative?: string + autofix?: boolean template?: string clipSkip?: number width: number diff --git a/common/types/presets.ts b/common/types/presets.ts index 0b6ea06e9..6bb78f320 100644 --- a/common/types/presets.ts +++ b/common/types/presets.ts @@ -103,7 +103,11 @@ export interface UserGenPreset extends GenSettings { userId: string } -export type PresetParser = { type: 'replace' | 'remove'; text: string; to?: string } +export type PresetParser = { + type: 'replace' | 'remove' | 'remove-prompt' | 'replace-prompt' + text: string + to?: string +} export type Sampler = | 'topK' diff --git a/srv/adapter/openai.ts b/srv/adapter/openai.ts index ac9d3811f..b9033f3c3 100644 --- a/srv/adapter/openai.ts +++ b/srv/adapter/openai.ts @@ -401,7 +401,8 @@ function patchPayload(opts: AdapterProps, body: any, messages: CompletionItem { aria-label="Agnaistic main page" > diff --git a/web/app.css b/web/app.css index dfaed5b61..d2f984a91 100644 --- a/web/app.css +++ b/web/app.css @@ -59,6 +59,7 @@ html { transition: 400ms ease; @apply bg-[var(--bg-800)] sm:top-0 sm:clear-none; @apply z-20 sm:z-0; + @apply border-r border-[var(--bg-600)]; } #main-content { @@ -94,7 +95,8 @@ html { } .drawer > * > a.active { - @apply bg-[var(--hl-900)]; + @apply border-l-2 border-[var(--hl-400)] bg-[var(--hl-800)]; + padding-left: calc(0.5rem - 2px); } @keyframes hideDrawer { diff --git a/web/pages/Chat/components/ManageMemoryBooks.tsx b/web/pages/Chat/components/ManageMemoryBooks.tsx new file mode 100644 index 000000000..da338e4c1 --- /dev/null +++ b/web/pages/Chat/components/ManageMemoryBooks.tsx @@ -0,0 +1,189 @@ +import { Component, createMemo, createSignal, For } from 'solid-js' +import { memoryStore } from '/web/store/memory' +import { sortAlpha } from '/common/util' +import { AppSchema } from '/common/types' +import { createStore } from 'solid-js/store' +import { emptyBook } from '/common/memory' +import Modal from '/web/shared/Modal' +import Button from '/web/shared/Button' +import EditMemoryForm, { EntrySort } from '../../Memory/EditMemory' +import Select, { Option } from '/web/shared/Select' +import { Pencil, PlusIcon, X } from 'lucide-solid' +import { Pill } from '/web/shared/Card' + +export const ManageMemoryBooks: Component<{ + bookIds: string + updateIds: (bookIds: string) => void +}> = (props) => { + const books = memoryStore((s) => ({ + books: s.books, + items: s.books.list.map((book) => ({ label: book.name, value: book._id })), + embeds: s.embeds, + })) + + const [bookId, setBookId] = createSignal('') + const [state, setState] = createStore(emptyBook()) + const [openId, setOpenId] = createSignal() + const [entrySort, setEntrySort] = createSignal('creationDate') + + const updateEntrySort = (item: Option) => { + if (item.value === 'creationDate' || item.value === 'alpha') { + setEntrySort(item.value) + } + } + + const usedBooks = createMemo(() => { + const memoryId = props.bookIds || '' + const ids = memoryId.split(',').filter((id) => !!id.trim()) + const list = books.books.list.filter((item) => ids.includes(item._id)).sort(bookSorter) + const validIds = list.map((item) => item._id) + return { ids: validIds, list } + }) + + const availableBooks = createMemo(() => { + const used = usedBooks() + const set = new Set(used.ids) + + const available = books.books.list + .filter((item) => !set.has(item._id)) + .map((item) => ({ label: item.name, value: item._id })) + .sort(selectSorter) + + return [{ label: 'Select Book...', value: '' }].concat(available) + }) + + const removeBook = async (removeId: string) => { + const nextId = usedBooks() + .ids.filter((id) => id !== removeId) + .join(',') + + props.updateIds(nextId) + } + + const addBook = async (addId: string) => { + const nextId = usedBooks().ids.concat(addId).join(',') + + useMemoryBook(nextId) + } + + const useMemoryBook = (addBookId?: string) => { + if (!addBookId) return + + const alreadyAssigned = usedBooks().ids.includes(addBookId) + if (alreadyAssigned) return + + const nextId = usedBooks().ids.concat(addBookId).join(',') + props.updateIds(nextId) + } + + const changeBook = async (id: string) => { + const match: AppSchema.MemoryBook | undefined = + id === 'new' || id === '' + ? { + _id: '', + userId: '', + entries: [], + kind: 'memory', + name: '', + description: '', + } + : books.books.list.find((book) => book._id === id) + + if (match) setState(match) + setOpenId(id) + } + + const onSaveBook = async () => { + if (!state._id) { + memoryStore.create(state, (next) => { + setState('_id', next._id) + setOpenId('') + }) + } else { + await memoryStore.update(state._id, state) + setOpenId('') + } + } + + return ( + <> +
    +
    + + - Chat Memory Books{' '} - -
    - } - items={availableBooks()} - value={bookId()} - onChange={(item) => setBookId(item.value)} - /> -
    - -
      - - {(book) => ( -
    • - -
      {book.name}
      -
      - - -
      -
      -
    • - )} -
      -
    + { + if (!props.chat?._id) return + chatStore.editChat(props.chat?._id, { memoryId: next }) + }} + /> 0}> @@ -265,39 +111,9 @@ const ChatMemoryModal: Component<{ - - setOpenId('')} - title="Memory Book Editor" - maxWidth="full" - footer={ - <> - - - - } - > -
    - -
    -
    ) } export default ChatMemoryModal - -const bookSorter = sortAlpha({ prop: 'name', ignoreCase: true }) -const selectSorter = sortAlpha<{ label: string }>({ prop: 'label', ignoreCase: true }) diff --git a/web/pages/Image/ImageModal.tsx b/web/pages/Image/ImageModal.tsx index c9a61d3f3..b765a6bd1 100644 --- a/web/pages/Image/ImageModal.tsx +++ b/web/pages/Image/ImageModal.tsx @@ -271,6 +271,10 @@ const ImageCollectionModal: Component<{}> = (props) => { append: true, onImage: (image) => reel.reload(), onPrompt: (prompt) => update('prompt', prompt), + onTick: (text, state) => { + if (state !== 'partial' && state !== 'done') return + update('prompt', text) + }, }) } else { const result = await imageApi.generateImageAsync(imagePrompt, { diff --git a/web/pages/Profile/SubscriptionPage.tsx b/web/pages/Profile/SubscriptionPage.tsx index ce94061aa..bc764e939 100644 --- a/web/pages/Profile/SubscriptionPage.tsx +++ b/web/pages/Profile/SubscriptionPage.tsx @@ -213,7 +213,7 @@ export const SubscriptionPage: Component<{}> = (props) => { {(each) => ( <> - + This tier is currently gifted to you
    diff --git a/web/pages/Settings/Image/ImageSettings.tsx b/web/pages/Settings/Image/ImageSettings.tsx index caeb0a8e3..f62bfcf62 100644 --- a/web/pages/Settings/Image/ImageSettings.tsx +++ b/web/pages/Settings/Image/ImageSettings.tsx @@ -287,6 +287,13 @@ export const ImageSettingsModal = () => { onChange={(ev) => ctx.update('cfg', ev)} /> + ctx.update('autofix', ev)} + /> + setClicked(false), 1000) diff --git a/web/shared/PhraseBias.tsx b/web/shared/PhraseBias.tsx index 091cca04b..f8d0b1f4f 100644 --- a/web/shared/PhraseBias.tsx +++ b/web/shared/PhraseBias.tsx @@ -178,7 +178,9 @@ export const MessageParsers: Field = (props) => {