From 5e8466c2ed9e0569deafe1fcf7f19710569d96a4 Mon Sep 17 00:00:00 2001 From: Sceik <60520115+sceuick@users.noreply.github.com> Date: Sun, 10 Nov 2024 12:31:30 +0800 Subject: [PATCH] Updates and Improvements (#1069) - Token probabilities - Fix LTM toggle - Image settings: config ui, user ui, add and auto-use recommended settings - Chat graphs: msg previews and labels - Fix json schema field updates - XTC and DRY samplers - Fix prompt template display --- .babelrc | 2 +- .github/inject.js | 5 +- common/adapters.ts | 2 + common/image-prompt.ts | 19 +- common/types/admin.ts | 21 +- common/types/presets.ts | 12 + common/types/schema.ts | 2 + common/util.ts | 5 + package.json | 3 + pnpm-lock.yaml | 100 ++-- srv/adapter/agnaistic.ts | 5 +- srv/adapter/payloads.ts | 29 ++ srv/api/admin.ts | 7 +- srv/api/character.ts | 137 ------ srv/api/chat/inference.ts | 8 + srv/api/chat/message.ts | 13 +- srv/api/settings.ts | 3 - srv/api/user/settings.ts | 5 + srv/db/admin.ts | 7 +- srv/image/index.ts | 29 +- srv/image/stable-diffusion.ts | 63 ++- srv/image/types.ts | 15 + web/pages/Admin/Config/Characters.tsx | 71 --- web/pages/Admin/Config/General.tsx | 6 + web/pages/Admin/Config/Images.tsx | 357 +++++++------- web/pages/Admin/Configuration.tsx | 22 +- web/pages/Admin/SubscriptionModel.tsx | 4 +- web/pages/Character/form/ReelControl.tsx | 7 +- web/pages/Character/port.ts | 28 +- web/pages/Chat/components/ChatGraph.tsx | 70 ++- web/pages/Chat/components/GraphModal.tsx | 22 +- web/pages/Chat/components/LogProbs.tsx | 105 ++++ web/pages/Chat/components/MemoryModal.tsx | 2 +- web/pages/Chat/components/Message.tsx | 4 +- web/pages/GenerationPresets/index.tsx | 4 +- web/pages/Profile/SubscriptionPage.tsx | 5 + web/pages/Settings/AISettings.tsx | 2 +- web/pages/Settings/Image/ImageSettings.tsx | 485 +++++-------------- web/pages/Settings/Image/ServiceSettings.tsx | 275 +++++++++++ web/pages/Settings/UISettings.tsx | 1 + web/shared/Card.tsx | 11 +- web/shared/JsonSchema.tsx | 6 +- web/shared/Mode/ModeGenSettings.tsx | 10 +- web/shared/PhraseBias.tsx | 6 +- web/shared/PresetSettings/Fields.tsx | 9 +- web/shared/PresetSettings/General.tsx | 65 ++- web/shared/PresetSettings/Prompt.tsx | 9 +- web/shared/PresetSettings/Sliders.tsx | 229 +++++++-- web/shared/PresetSettings/Toggles.tsx | 21 +- web/shared/PresetSettings/index.tsx | 47 +- web/shared/PresetSettings/settings.ts | 4 + web/shared/PresetSettings/types.ts | 52 +- web/shared/PromptEditor/index.tsx | 27 +- web/shared/RangeInput.tsx | 185 +++++-- web/shared/Select.tsx | 2 +- web/shared/TextInput.tsx | 24 +- web/shared/Toggle.tsx | 2 +- web/shared/util.ts | 106 ++-- web/store/api.ts | 4 +- web/store/embeddings/index.ts | 24 +- 60 files changed, 1688 insertions(+), 1117 deletions(-) delete mode 100644 web/pages/Admin/Config/Characters.tsx create mode 100644 web/pages/Chat/components/LogProbs.tsx create mode 100644 web/pages/Settings/Image/ServiceSettings.tsx diff --git a/.babelrc b/.babelrc index ddc86b754..546dba4ab 100644 --- a/.babelrc +++ b/.babelrc @@ -1,9 +1,9 @@ { + "plugins": [], "presets": ["solid"], "env": { "development": { "presets": ["babel-preset-solid"] - // "plugins": ["module:solid-refresh/babel"] } } } diff --git a/.github/inject.js b/.github/inject.js index c4fcfec71..329ae5472 100644 --- a/.github/inject.js +++ b/.github/inject.js @@ -8,7 +8,10 @@ const tags = ['', '', ''] const indexFile = path.resolve(__dirname, '../dist/index.html') const outFile = path.resolve(__dirname, '../dist/index.html') -let content = fs.readFileSync(indexFile).toString().replace('{{unknown}}', process.env.GITHUB_SHA) +let content = fs + .readFileSync(indexFile) + .toString() + .replace('{{unknown}}",', process.env.GITHUB_SHA + '";') if (inject) { for (const tag of tags) { diff --git a/common/adapters.ts b/common/adapters.ts index 855b885e5..5e55876da 100644 --- a/common/adapters.ts +++ b/common/adapters.ts @@ -438,6 +438,8 @@ export const samplerDisableValues: { [key in keyof PresetAISettings]?: number } frequencyPenalty: 0, presencePenalty: 0, tailFreeSampling: 1, + xtcThreshold: 0, + dryMultiplier: 0, } export function adaptersToOptions(adapters: AIAdapter[]) { diff --git a/common/image-prompt.ts b/common/image-prompt.ts index 9416339ef..ef91b26d4 100644 --- a/common/image-prompt.ts +++ b/common/image-prompt.ts @@ -1,5 +1,4 @@ import { AppSchema } from './types/schema' -import { tokenize } from './tokenize' import { BOT_REPLACE, SELF_REPLACE } from './prompt' export type ImagePromptOpts = { @@ -21,7 +20,7 @@ export async function createAppearancePrompt( const prefix = '' const max = getMaxImageContext(user) - let size = await tokenize(prefix) + let size = prefix.length const { persona } = avatar @@ -37,7 +36,7 @@ export async function createAppearancePrompt( if (!value) continue for (const visual of value) { - size += await tokenize(visual) + size += visual.length if (size > max) break visuals.push(visual) } @@ -71,7 +70,7 @@ export async function createImagePrompt(opts: ImagePromptOpts) { for (const index of indexes.reverse()) { const line = msg.slice(index) - const size = await tokenize(line) + const size = line.length tokens += size if (tokens > maxTokens) { @@ -85,7 +84,7 @@ export async function createImagePrompt(opts: ImagePromptOpts) { if (tokens > maxTokens) break const handle = userId ? opts.members.find((pr) => pr.userId === userId)?.handle : opts.char.name - tokens += await tokenize(handle + ':') + tokens += (handle || '').length if (tokens > maxTokens) { lines.push(last.trim()) @@ -135,3 +134,13 @@ function tokenizeMessage(line: string) { return matches } + +export function fixImagePrompt(prompt: string) { + return prompt + .replace(/ +/g, ' ') + .replace(/,+/g, ',') + .split(',') + .filter((t) => !!t.trim()) + .map((t) => t.trim()) + .join(', ') +} diff --git a/common/types/admin.ts b/common/types/admin.ts index d62ae1a94..935fa3900 100644 --- a/common/types/admin.ts +++ b/common/types/admin.ts @@ -5,7 +5,6 @@ import type { OpenRouterModel, RegisteredAdapter, } from '../adapters' -import { JsonField } from '../prompt' import { SubscriptionModelOption, SubscriptionTier } from './presets' import { ThemeColor } from './ui' @@ -48,7 +47,16 @@ export type ImageModel = { desc: string override: string level: number - init: { clipSkip?: number; steps: number; cfg: number; height: number; width: number } + init: { + clipSkip?: number + steps: number + cfg: number + height: number + width: number + suffix: string + prefix: string + negative: string + } limit: { clipSkip?: number; steps: number; cfg: number; height: number; width: number } } @@ -87,6 +95,7 @@ export interface Configuration { maintenance: boolean supportEmail: string + stripeCustomerPortal: string /** Markdown */ maintenanceMessage: string @@ -124,14 +133,6 @@ export interface Configuration { maxGuidanceTokens: number maxGuidanceVariables: number - modPresetId: string - modPrompt: string - modFieldPrompt: string - modSchema: JsonField[] - - charlibPublish: 'off' | 'users' | 'subscribers' | 'moderators' | 'admins' - charlibGuidelines: string - actionCalls: ActionCall[] } diff --git a/common/types/presets.ts b/common/types/presets.ts index ca1b16f7b..a18df3220 100644 --- a/common/types/presets.ts +++ b/common/types/presets.ts @@ -82,10 +82,22 @@ export interface GenSettings { temp: number tempLast?: boolean + dynatemp_range?: number dynatemp_exponent?: number + + xtcProbability?: number + xtcThreshold?: number + + dryMultiplier?: number + dryBase?: number + dryAllowedLength?: number + dryRange?: number + drySequenceBreakers?: string[] + smoothingFactor?: number smoothingCurve?: number + maxTokens: number maxContextLength?: number useMaxContext?: boolean diff --git a/common/types/schema.ts b/common/types/schema.ts index 43f75f722..ccdb45f00 100644 --- a/common/types/schema.ts +++ b/common/types/schema.ts @@ -146,6 +146,8 @@ export namespace AppSchema { texttospeech?: TTSSettings images?: ImageSettings + useRecommendedImages?: string // 'all' | 'except-(size|affix|negative)' | 'none' + adapterConfig?: { [key in AIAdapter]?: Record } ui?: UISettings diff --git a/common/util.ts b/common/util.ts index af5057dcd..ad1d1e28d 100644 --- a/common/util.ts +++ b/common/util.ts @@ -18,6 +18,11 @@ export function findOne(id: string, list: T[]): T | v } } +export function round(value: number, places = 2) { + const pow = Math.pow(10, places) + return Math.round(value * pow) / pow +} + export function toArray(values?: T | T[]): T[] { if (values === undefined) return [] if (Array.isArray(values)) return values diff --git a/package.json b/package.json index 76ca8c462..478e4b6bd 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "agnaistic": "./srv/bin.js", "agnai": "./srv/bin.js" }, + "browserslist": "> 0.1%, not ie 11", "homepage": "https://github.com/agnaistic/agnai/issues", "files": [ "poetry.lock", @@ -153,6 +154,7 @@ "assert": "^2.0.0", "babel-preset-solid": "^1.6.9", "browserify-zlib": "^0.2.0", + "browserslist": "^4.24.2", "buffer": "^5.5.0", "chai": "^4.3.7", "concurrently": "^7.6.0", @@ -162,6 +164,7 @@ "exifreader": "^4.13.0", "https-browserify": "^1.0.0", "js-cookie": "^3.0.1", + "jwt-decode": "^4.0.0", "libsodium-wrappers-sumo": "^0.7.11", "localforage": "^1.10.0", "lucide-solid": "0.356.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b20960a0..e233cfcee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -235,6 +235,9 @@ devDependencies: browserify-zlib: specifier: ^0.2.0 version: 0.2.0 + browserslist: + specifier: ^4.24.2 + version: 4.24.2 buffer: specifier: ^5.5.0 version: 5.5.0 @@ -262,6 +265,9 @@ devDependencies: js-cookie: specifier: ^3.0.1 version: 3.0.1 + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 libsodium-wrappers-sumo: specifier: ^0.7.11 version: 0.7.11 @@ -1438,7 +1444,7 @@ packages: '@babel/compat-data': 7.22.5 '@babel/core': 7.20.12 '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.9 + browserslist: 4.24.2 lru-cache: 5.1.1 semver: 6.3.0 dev: true @@ -1449,7 +1455,7 @@ packages: dependencies: '@babel/compat-data': 7.24.7 '@babel/helper-validator-option': 7.24.7 - browserslist: 4.22.2 + browserslist: 4.24.2 lru-cache: 5.1.1 semver: 6.3.1 dev: true @@ -2528,7 +2534,7 @@ packages: '@parcel/workers': 2.12.0(@parcel/core@2.12.0) abortcontroller-polyfill: 1.7.5 base-x: 3.0.9 - browserslist: 4.22.2 + browserslist: 4.24.2 clone: 2.1.2 dotenv: 7.0.0 dotenv-expand: 5.1.0 @@ -2626,7 +2632,7 @@ packages: '@parcel/plugin': 2.12.0(@parcel/core@2.12.0) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.12.0 - browserslist: 4.22.2 + browserslist: 4.24.2 lightningcss: 1.24.1 nullthrows: 1.1.1 transitivePeerDependencies: @@ -2949,7 +2955,7 @@ packages: '@parcel/plugin': 2.12.0(@parcel/core@2.12.0) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.12.0 - browserslist: 4.22.2 + browserslist: 4.24.2 json5: 2.2.3 nullthrows: 1.1.1 semver: 7.5.4 @@ -2966,7 +2972,7 @@ packages: '@parcel/plugin': 2.12.0(@parcel/core@2.12.0) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.12.0 - browserslist: 4.22.2 + browserslist: 4.24.2 lightningcss: 1.24.1 nullthrows: 1.1.1 transitivePeerDependencies: @@ -3031,7 +3037,7 @@ packages: '@parcel/utils': 2.12.0 '@parcel/workers': 2.12.0(@parcel/core@2.12.0) '@swc/helpers': 0.5.1 - browserslist: 4.22.2 + browserslist: 4.24.2 nullthrows: 1.1.1 regenerator-runtime: 0.13.11 semver: 7.5.4 @@ -4306,17 +4312,6 @@ packages: pako: 1.0.11 dev: true - /browserslist@4.21.9: - resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001570 - electron-to-chromium: 1.4.440 - node-releases: 2.0.12 - update-browserslist-db: 1.0.11(browserslist@4.21.9) - dev: true - /browserslist@4.22.2: resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -4328,6 +4323,17 @@ packages: update-browserslist-db: 1.0.13(browserslist@4.22.2) dev: true + /browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001677 + electron-to-chromium: 1.5.52 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.2) + dev: true + /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: @@ -4409,6 +4415,10 @@ packages: resolution: {integrity: sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz} dev: true + /caniuse-lite@1.0.30001677: + resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz} + dev: true + /chai@4.3.7: resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} engines: {node: '>=4'} @@ -4950,14 +4960,14 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false - /electron-to-chromium@1.4.440: - resolution: {integrity: sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw==} - dev: true - /electron-to-chromium@1.4.615: resolution: {integrity: sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.615.tgz} dev: true + /electron-to-chromium@1.5.52: + resolution: {integrity: sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz} + dev: true + /elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} dependencies: @@ -5055,6 +5065,11 @@ packages: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, tarball: https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz} + engines: {node: '>=6'} + dev: true + /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} dev: false @@ -6054,6 +6069,11 @@ packages: safe-buffer: 5.2.1 dev: false + /jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==, tarball: https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz} + engines: {node: '>=18'} + dev: true + /libsodium-sumo@0.7.11: resolution: {integrity: sha512-bY+7ph7xpk51Ez2GbE10lXAQ5sJma6NghcIDaSPbM/G9elfrjLa0COHl/7P6Wb/JizQzl5UQontOOP1z0VwbLA==} dev: true @@ -6656,14 +6676,14 @@ packages: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true - /node-releases@2.0.12: - resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} - dev: true - /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz} dev: true + /node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz} + dev: true + /nodemon@3.0.1: resolution: {integrity: sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==} engines: {node: '>=10'} @@ -6953,6 +6973,10 @@ packages: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==, tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz} dev: true + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz} + dev: true + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -8068,26 +8092,26 @@ packages: engines: {node: '>= 0.8'} dev: false - /update-browserslist-db@1.0.11(browserslist@4.21.9): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} + /update-browserslist-db@1.0.13(browserslist@4.22.2): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.9 + browserslist: 4.22.2 escalade: 3.1.1 - picocolors: registry.npmjs.org/picocolors@1.0.0 + picocolors: 1.0.1 dev: true - /update-browserslist-db@1.0.13(browserslist@4.22.2): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz} + /update-browserslist-db@1.1.1(browserslist@4.24.2): + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.22.2 - escalade: 3.1.1 - picocolors: 1.0.0 + browserslist: 4.24.2 + escalade: 3.2.0 + picocolors: 1.1.1 dev: true /uri-js@4.4.1: @@ -9066,12 +9090,6 @@ packages: client-oauth2: registry.npmjs.org/client-oauth2@4.3.3 dev: false - registry.npmjs.org/picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz} - name: picocolors - version: 1.0.0 - dev: true - registry.npmjs.org/picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz} name: picomatch diff --git a/srv/adapter/agnaistic.ts b/srv/adapter/agnaistic.ts index 4a714fa2f..2c75ccc4e 100644 --- a/srv/adapter/agnaistic.ts +++ b/srv/adapter/agnaistic.ts @@ -256,7 +256,9 @@ export const handleAgnaistic: ModelAdapter = async function* (opts) { `id=${opts.user._id}`, `model=${subPreset.subModel}`, `level=${level}`, - ].join('&') + ] + .filter((p) => !!p) + .join('&') const resp = gen.streamResponse ? await websocketStream({ @@ -292,6 +294,7 @@ export const handleAgnaistic: ModelAdapter = async function* (opts) { if (generated.value.error) { opts.log.error({ err: generated.value.error }, 'Agnaistic request failed') yield generated.value + await releaseLock(lockId) return } diff --git a/srv/adapter/payloads.ts b/srv/adapter/payloads.ts index 290de0944..987babbdf 100644 --- a/srv/adapter/payloads.ts +++ b/srv/adapter/payloads.ts @@ -4,6 +4,7 @@ import { clamp, neat } from '/common/util' import { JsonSchema, toJsonSchema } from '/common/prompt' import { defaultPresets } from '/common/default-preset' import { getEncoderByName } from '../tokenize' +import { decryptText } from '../db/util' const chat_template = neat` {%- if messages[0]['role'] == 'system' -%} @@ -102,6 +103,14 @@ function getBasePayload(opts: AdapterProps, stops: string[] = []) { json_schema, imageData: opts.imageData, context_size: opts.contextSize, + xtc_threshold: gen.xtcThreshold, + xtc_probability: gen.xtcProbability, + + dry_multiplier: gen.dryMultiplier, + dry_base: gen.dryBase, + dry_allowed_length: gen.dryAllowedLength, + dry_range: gen.dryRange, + dry_sequence_breakers: gen.drySequenceBreakers, } if (gen.dynatemp_range) { @@ -115,6 +124,10 @@ function getBasePayload(opts: AdapterProps, stops: string[] = []) { body.temp_exponent = gen.dynatemp_exponent } + if (subscription?.preset?.subApiKey) { + body.api_key = decryptText(subscription.preset.subApiKey) + } + return body } @@ -273,6 +286,14 @@ function getBasePayload(opts: AdapterProps, stops: string[] = []) { stream: gen.streamResponse, token_healing: gen.tokenHealing, temperature_last: gen.minP ? !!gen.tempLast : false, + xtc_threshold: gen.xtcThreshold, + xtc_probability: gen.xtcProbability, + + dry_multiplier: gen.dryMultiplier, + dry_base: gen.dryBase, + dry_allowed_length: gen.dryAllowedLength, + dry_range: gen.dryRange, + dry_sequence_breakers: gen.drySequenceBreakers, json_schema, } @@ -337,6 +358,14 @@ function getBasePayload(opts: AdapterProps, stops: string[] = []) { smoothing_factor: gen.smoothingFactor, smoothing_curve: gen.smoothingCurve, tfs: gen.tailFreeSampling, + xtc_threshold: gen.xtcThreshold, + xtc_probability: gen.xtcProbability, + + dry_multiplier: gen.dryMultiplier, + dry_base: gen.dryBase, + dry_allowed_length: gen.dryAllowedLength, + dry_range: gen.dryRange, + dry_sequence_breakers: gen.drySequenceBreakers, } if (gen.dynatemp_range) { diff --git a/srv/api/admin.ts b/srv/api/admin.ts index f56292070..9b4cb753c 100644 --- a/srv/api/admin.ts +++ b/srv/api/admin.ts @@ -99,12 +99,7 @@ const updateConfiguration = handle(async ({ body }) => { imagesModels: ['any'], supportEmail: 'string', googleClientId: 'string', - modPrompt: 'string', - modFieldPrompt: 'string', - modPresetId: 'string', - modSchema: 'any', - charlibPublish: ['off', 'users', 'subscribers', 'moderators', 'admins'], - charlibGuidelines: 'string', + stripeCustomerPortal: 'string', }, body ) diff --git a/srv/api/character.ts b/srv/api/character.ts index 3b983ca79..07666c673 100644 --- a/srv/api/character.ts +++ b/srv/api/character.ts @@ -13,9 +13,6 @@ import { v4 } from 'uuid' import { validBook } from './memory' import { isObject, tryParse } from '/common/util' import { assertStrict } from '/common/valid/validate' -import { buildModPrompt, fromJsonResponse } from '/common/prompt' -import { createInferenceStream } from '../adapter/generate' -import { sendOne } from './ws' const router = Router() @@ -158,139 +155,6 @@ const getCharacters = handle(async ({ userId }) => { return { characters: chars } }) -const publishCharacter = handle(async ({ userId, body, log }, res) => { - assertValid( - { requestId: 'string?', character: 'any?', characterId: 'any?', imageData: 'string?' }, - body - ) - const config = await store.admin.getServerConfiguration() - - if (!config.modPresetId) { - throw new StatusError(`Mod preset not configured`, 400) - } - - const user = await store.users.getUser(userId) - if (!user) { - throw new StatusError('Not authorized', 401) - } - - const settings = await store.presets.getUserPreset(config.modPresetId) - if (!settings) { - throw new StatusError('Mod preset not found', 400) - } - - let character = body.character - - if (body.characterId) { - character = await store.characters.getCharacter(userId, body.characterId) - } - - if (!character) { - throw new StatusError(`Character not provided`, 400) - } - - const prompt = buildModPrompt({ - char: character, - prompt: config.modPrompt, - fields: config.modFieldPrompt, - }) - - const requestId = body.requestId || v4() - - const { stream, service } = await createInferenceStream({ - requestId, - jsonSchema: config.modSchema, - user, - log, - prompt, - settings, - imageData: body.imageData, - }) - - res.json({ success: true, generating: true, requestId }) - - if (user.admin) { - sendOne(userId, { type: 'inference-prompt', prompt }) - } - - let response = '' - let partial = '' - let output: any = {} - - try { - for await (const gen of stream) { - if (typeof gen === 'string') { - response = gen - continue - } - - if ('meta' in gen && user.admin) { - sendOne(userId, { type: 'inference-meta', meta: gen.meta, requestId }) - } - - if ('partial' in gen) { - partial = gen.partial - fromJsonResponse(config.modSchema, gen.partial, output) - if (user.admin) - sendOne(userId, { type: 'inference-partial', partial, service, requestId, output }) - continue - } - - if ('error' in gen) { - sendOne(userId, { type: 'inference-error', partial, error: gen.error, requestId }) - continue - } - - if ('warning' in gen) { - sendOne(userId, { type: 'inference-warning', requestId, warning: gen.warning }) - continue - } - } - } catch (ex: any) { - if (ex instanceof StatusError) { - sendOne(userId, { - type: 'inference-error', - partial, - error: `[${ex.status}] ${ex.message}`, - requestId, - }) - } else { - sendOne(userId, { type: 'inference-error', partial, error: `${ex.message || ex}`, requestId }) - } - } - - if (!response) return - if (user.admin) sendOne(userId, { type: 'inference', requestId, response: response, output }) - - let acceptable = true - for (const [key, value] of Object.entries(output)) { - const def = config.modSchema.find((s) => s.name === key) - if (!def) continue - if (!def.type.valid) continue - - switch (def.type.type) { - case 'integer': - case 'string': - continue - - case 'bool': { - const expected = def.type.valid === 'true' - if (value !== expected) acceptable = false - continue - } - - case 'enum': { - const values = def.type.valid.split(',').map((v) => v.trim()) - const valid = values.includes((value || '') as string) - if (!valid) acceptable = false - continue - } - } - } - - sendOne(userId, { type: 'publish-response', acceptable, requestId }) -}) - const editPartCharacter = handle(async ({ body, params, userId }) => { const id = params.id assertStrict({ type: characterPost }, body) @@ -512,7 +376,6 @@ router.post('/image', createImage) router.use(loggedIn) router.post('/', createCharacter) router.get('/', getCharacters) -router.post('/publish', publishCharacter) router.post('/:id/update', editPartCharacter) router.post('/:id', editFullCharacter) router.get('/:id', getCharacter) diff --git a/srv/api/chat/inference.ts b/srv/api/chat/inference.ts index 61911b7e7..e691f1afe 100644 --- a/srv/api/chat/inference.ts +++ b/srv/api/chat/inference.ts @@ -26,6 +26,8 @@ const validImage = { cfg_scale: 'number?', clip_skip: 'number?', steps: 'number?', + sampler: 'string?', + use_recommended: 'string?', } as const const validInference = { @@ -82,6 +84,10 @@ const validInferenceApi = { export const generateImageApi = wrap(async ({ authed, userId, log, body }) => { assertValid(validImage, body) + if (body.use_recommended && authed) { + authed.useRecommendedImages = body.use_recommended + } + const result = await generateImageSync( { user: authed!, @@ -89,6 +95,8 @@ export const generateImageApi = wrap(async ({ authed, userId, log, body }) => { prompt: body.prompt, source: 'api', parentId: undefined, + params: body, + noAffix: true, }, log ) diff --git a/srv/api/chat/message.ts b/srv/api/chat/message.ts index e1661f15d..b37416b37 100644 --- a/srv/api/chat/message.ts +++ b/srv/api/chat/message.ts @@ -267,7 +267,8 @@ export const generateMessageV2 = handle(async (req, res) => { let retries: string[] = [] let error = false let adapter = 'local' - let meta = {} + let meta: Record = {} + let probs: any if (body.response === undefined) { const { stream, ...metadata } = await createChatStream( @@ -377,6 +378,11 @@ export const generateMessageV2 = handle(async (req, res) => { } } + if (meta.probs) { + probs = meta.probs + delete meta.probs + } + let responseText = body.kind === 'continue' ? `${body.continuing.msg} ${generated}` : generated const parent = getNewMessageParent(body, userMsg) const updatedAt = new Date().toISOString() @@ -425,6 +431,8 @@ export const generateMessageV2 = handle(async (req, res) => { name: replyAs.name, }) + msg.meta.probs = probs + sendMany(members, { type: 'message-created', requestId, @@ -454,6 +462,7 @@ export const generateMessageV2 = handle(async (req, res) => { json: hydration ? hydration : (null as any), }) treeLeafId = body.replacing._id + meta.probs = probs sendMany(members, { type: 'message-retry', requestId, @@ -482,6 +491,7 @@ export const generateMessageV2 = handle(async (req, res) => { json: hydration, name: replyAs.name, }) + msg.meta.probs = probs treeLeafId = requestId sendMany(members, { type: 'message-created', @@ -504,6 +514,7 @@ export const generateMessageV2 = handle(async (req, res) => { state: 'continued', }) treeLeafId = body.continuing._id + meta.probs = probs sendMany(members, { type: 'message-retry', requestId, diff --git a/srv/api/settings.ts b/srv/api/settings.ts index 92088aca0..acb9d892c 100644 --- a/srv/api/settings.ts +++ b/srv/api/settings.ts @@ -53,9 +53,6 @@ export async function getAppConfig(user?: AppSchema.User) { configuration.imagesHost = '' configuration.ttsHost = '' configuration.ttsApiKey = '' - configuration.modFieldPrompt = '' - configuration.modPrompt = '' - configuration.modSchema = [] configuration.imagesModels = filterImageModels( user!, diff --git a/srv/api/user/settings.ts b/srv/api/user/settings.ts index 50c327d07..7a0646400 100644 --- a/srv/api/user/settings.ts +++ b/srv/api/user/settings.ts @@ -201,6 +201,7 @@ export const updatePartialConfig = handle(async ({ userId, body }) => { chargenPreset: 'string?', images: 'any?', disableLTM: 'boolean?', + useRecommendedImages: 'string?', }, body ) @@ -215,6 +216,10 @@ export const updatePartialConfig = handle(async ({ userId, body }) => { update.defaultPreset = body.defaultPreset } + if (body.useRecommendedImages !== undefined) { + update.useRecommendedImages = body.useRecommendedImages + } + if (body.disableLTM !== undefined) { update.disableLTM = body.disableLTM } diff --git a/srv/db/admin.ts b/srv/db/admin.ts index 7e93a582e..9646407aa 100644 --- a/srv/db/admin.ts +++ b/srv/db/admin.ts @@ -39,14 +39,9 @@ export async function getServerConfiguration() { maxGuidanceVariables: 15, googleClientId: '', googleEnabled: false, - charlibPublish: 'off', - charlibGuidelines: '', - modFieldPrompt: '', - modPresetId: '', - modPrompt: '', - modSchema: [], actionCalls: [], lockSeconds: 0, + stripeCustomerPortal: '', } await db('configuration').insertOne(next) diff --git a/srv/image/index.ts b/srv/image/index.ts index cada68de7..0facae52c 100644 --- a/srv/image/index.ts +++ b/srv/image/index.ts @@ -161,7 +161,14 @@ async function runImageGenerate(options: { switch (imageSettings?.type || 'horde') { case 'novel': image = await handleNovelImage( - { user, prompt, negative, settings: imageSettings }, + { + user, + prompt, + negative, + settings: imageSettings, + params: opts.params, + raw_prompt: opts.prompt, + }, log, guestId ) @@ -170,7 +177,15 @@ async function runImageGenerate(options: { case 'sd': case 'agnai': image = await handleSDImage( - { user, prompt, negative, settings: imageSettings, override: opts.model }, + { + user, + prompt, + negative, + settings: imageSettings, + override: opts.model, + params: opts.params, + raw_prompt: opts.prompt, + }, log, guestId ) @@ -179,7 +194,14 @@ async function runImageGenerate(options: { case 'horde': default: image = await handleHordeImage( - { user, prompt, negative, settings: imageSettings }, + { + user, + prompt, + negative, + settings: imageSettings, + params: opts.params, + raw_prompt: opts.prompt, + }, log, guestId ) @@ -204,6 +226,7 @@ function getImagePrompt(opts: ImageGenerateRequest, imageSettings: ImageSettings } prompt = prompt.trim() + opts.raw_prompt = prompt if (!opts.noAffix) { const parts = [prompt] diff --git a/srv/image/stable-diffusion.ts b/srv/image/stable-diffusion.ts index 3f09b806e..608649a1d 100644 --- a/srv/image/stable-diffusion.ts +++ b/srv/image/stable-diffusion.ts @@ -8,6 +8,7 @@ import { store } from '../db' import { getUserSubscriptionTier } from '/common/util' import { getCachedTiers } from '../db/subscriptions' import { config } from '../config' +import { fixImagePrompt } from '/common/image-prompt' const defaultSettings: SDSettings = { type: 'sd', @@ -126,7 +127,13 @@ async function getConfig({ user, settings, override }: ImageRequestOpts): Promis `model=${temp?.name || model.name}`, ] - return { kind: 'agnai', host: srv.imagesHost, params: `?${params.join('&')}`, model, temp } + return { + kind: 'agnai', + host: srv.imagesHost, + params: `?${params.join('&')}`, + model: temp || model, + temp, + } } function getPayload( @@ -138,22 +145,23 @@ function getPayload( const sampler = (kind === 'agnai' ? opts.settings?.agnai?.sampler : opts.settings?.sd?.sampler) || defaultSettings.sampler + const payload: SDRequest = { prompt: opts.prompt, // enable_hr: true, // hr_scale: 1.5, // hr_second_pass_steps: 15, // hr_upscaler: "", - clip_skip: opts.settings?.clipSkip ?? model?.init.clipSkip ?? 0, - height: opts.settings?.height ?? model?.init.height ?? 1024, - width: opts.settings?.width ?? model?.init.width ?? 1024, + clip_skip: opts.params?.clip_skip ?? opts.settings?.clipSkip ?? model?.init.clipSkip ?? 0, + height: opts.params?.height ?? opts.settings?.height ?? model?.init.height ?? 1024, + width: opts.params?.width ?? opts.settings?.width ?? model?.init.width ?? 1024, n_iter: 1, batch_size: 1, - negative_prompt: opts.negative, - sampler_name: (SD_SAMPLER_REV as any)[sampler], - cfg_scale: opts.settings?.cfg ?? model?.init.cfg ?? 9, + negative_prompt: opts.params?.negative ?? opts.negative, + sampler_name: (SD_SAMPLER_REV as any)[opts.params?.sampler ?? sampler], + cfg_scale: opts.params?.cfg_scale ?? opts.settings?.cfg ?? model?.init.cfg ?? 9, seed: Math.trunc(Math.random() * 1_000_000_000), - steps: opts.settings?.steps ?? model?.init.steps ?? 28, + steps: opts.params?.steps ?? opts.settings?.steps ?? model?.init.steps ?? 28, restore_faces: false, save_images: false, send_images: true, @@ -161,15 +169,42 @@ function getPayload( } if (model) { - payload.steps = Math.min(model.limit.steps, payload.steps) - payload.cfg_scale = Math.min(model.limit.cfg, payload.cfg_scale) - payload.width = Math.min(model.limit.width, payload.width) - payload.height = Math.min(model.limit.height, payload.height) + payload.steps = Math.min(+model.limit.steps, payload.steps) + payload.cfg_scale = Math.min(+model.limit.cfg, payload.cfg_scale) + payload.width = Math.min(+model.limit.width, payload.width) + payload.height = Math.min(+model.limit.height, payload.height) + } + + const rec = opts.user.useRecommendedImages + if (rec && rec !== 'none' && model) { + const init = model.init + if (init.cfg) payload.cfg_scale = +init.cfg + if (init.clipSkip !== undefined) payload.clip_skip = +init.clipSkip + if (init.steps) payload.steps = +init.steps + + if (!rec.includes('size')) { + payload.width = +init.width + payload.height = +init.height + } + + if (!rec.includes('affix')) { + const prompt = [ + init.prefix || opts.settings?.prefix, + opts.raw_prompt, + init.suffix || opts.settings?.suffix, + ].join(',') + + payload.prompt = fixImagePrompt(prompt) + + if (init.negative) { + payload.negative_prompt = init.negative + } + } } // width and height must be divisible by 64 - payload.width = Math.floor(payload.width / 64) * 64 - payload.height = Math.floor(payload.height / 64) * 64 + payload.width = Math.ceil(payload.width / 32) * 32 + payload.height = Math.ceil(payload.height / 32) * 32 return payload } diff --git a/srv/image/types.ts b/srv/image/types.ts index 52916dc73..3096b1bc9 100644 --- a/srv/image/types.ts +++ b/srv/image/types.ts @@ -5,6 +5,10 @@ import { ImageSettings } from '/common/types/image-schema' export type ImageGenerateRequest = { user: AppSchema.User prompt: string + + /** Prompt without any prefix or suffix applied */ + raw_prompt?: string + source: string sync?: boolean @@ -17,6 +21,15 @@ export type ImageGenerateRequest = { characterId?: string requestId?: string parentId: string | undefined + params?: { + clip_skip?: number + cfg_scale?: number + width?: number + height?: number + negative?: string + steps?: number + sampler?: string + } } export type ImageRequestOpts = { @@ -25,6 +38,8 @@ export type ImageRequestOpts = { negative: string settings: ImageSettings | undefined override?: string + raw_prompt: string | undefined + params?: ImageGenerateRequest['params'] } export type ImageAdapter = ( diff --git a/web/pages/Admin/Config/Characters.tsx b/web/pages/Admin/Config/Characters.tsx deleted file mode 100644 index eee212411..000000000 --- a/web/pages/Admin/Config/Characters.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Component, Setter, createSignal } from 'solid-js' -import { usePresetOptions } from '/web/shared/hooks' -import { PresetSelect } from '/web/shared/PresetSelect' -import TextInput from '/web/shared/TextInput' -import { adminStore } from '/web/store' -import { JsonSchema } from '/web/shared/JsonSchema' -import { neat } from '/common/util' -import Select from '/web/shared/Select' -import { JsonField } from '/common/prompt' - -export const CharLibrary: Component<{ setSchema: Setter }> = (props) => { - const presets = usePresetOptions() - const state = adminStore() - - const [presetId, setPresetId] = createSignal(state.config?.modPresetId) - - return ( -
- userStore.updatePartialConfig({ useRecommendedImages: ev.value })} + /> + + +
+
-
- +
+
-
- +
+
-
- +
+
@@ -279,14 +222,13 @@ export const ImageSettingsModal = () => { - - - - ) -} - -const AgnaiSettings: Component<{ cfg: ImageSettings | undefined }> = (props) => { - const settings = settingStore((s) => { - const models = s.config.serverConfig?.imagesModels || [] - return { - models, - names: models.map((m) => ({ label: m.desc.trim(), value: m.id || m.name })), + case 'Chat': { + chatStore.editChat(entity.chat?._id!, { imageSettings: store }, undefined) + return } - }) - - const [curr, setCurr] = createSignal(props.cfg?.agnai?.model) - const [sampler, setSampler] = createSignal(props.cfg?.agnai?.sampler) - createEffect( - on( - () => props.cfg, - () => { - setCurr(props.cfg?.agnai?.model) - setSampler(props.cfg?.agnai?.sampler || SD_SAMPLER['Euler a']) - } - ) - ) - - const model = createMemo(() => { - const original = props.cfg?.agnai?.model - const id = - settings.models.length === 1 - ? settings.models[0].id || settings.models[0].name - : curr() || original - const match = settings.models.find((m) => m.id === id || m.name === id) - return match - }) - - const samplers = createMemo(() => { - return Object.entries(SD_SAMPLER_REV).map(([key, value]) => ({ - label: value, - value: key, - })) - }) + case 'Character': { + characterStore.editPartialCharacter(entity.char?._id!, { imageSettings: store }) + return + } - return ( - <> -
Agnaistic
- - No additional options available - - setSampler(ev.value)} - /> - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- StepsCFGWidthHeight
Recommended{model()?.init.steps}{model()?.init.cfg}{model()?.init.width}{model()?.init.height}
Maximums{model()?.limit.steps}{model()?.limit.cfg}{model()?.limit.width}{model()?.limit.height}
-
-
- - ) + default: + return + } } - -const Th: Component<{ children?: any }> = (props) => ( - - {props.children} - -) -const Td: Component<{ children?: any }> = (props) => ( - - {props.children} - -) diff --git a/web/pages/Settings/Image/ServiceSettings.tsx b/web/pages/Settings/Image/ServiceSettings.tsx new file mode 100644 index 000000000..ae7c18b7d --- /dev/null +++ b/web/pages/Settings/Image/ServiceSettings.tsx @@ -0,0 +1,275 @@ +import { Component, Show, createEffect, createMemo } from 'solid-js' +import { + NOVEL_IMAGE_MODEL, + NOVEL_SAMPLER_REV, + SD_SAMPLER, + SD_SAMPLER_REV, +} from '../../../../common/image' +import Select from '../../../shared/Select' +import TextInput from '../../../shared/TextInput' +import { settingStore, userStore } from '../../../store' +import { ImageSettings } from '/common/types/image-schema' +import { SetStoreFunction } from 'solid-js/store' +import { applyStoreProperty } from '/web/shared/util' + +export const NovelSettings: Component<{ + cfg: ImageSettings + setter: SetStoreFunction +}> = (props) => { + const state = userStore() + + const models = Object.entries(NOVEL_IMAGE_MODEL).map(([key, value]) => ({ label: key, value })) + const samplers = Object.entries(NOVEL_SAMPLER_REV).map(([key, value]) => ({ + label: value, + value: key, + })) + return ( + <> +
NovelAI
+ +
+ You do not have a valid NovelAI key set. You will not be able to generate images using + Novel. +
+
+ + Note: The Anlas Threshold means anything above this value is cost Anlas credits + + props.setter(applyStoreProperty(props.cfg, 'novel.sampler', ev.value))} + /> + + ) +} + +export const HordeSettings: Component<{ + cfg: ImageSettings + setter: SetStoreFunction +}> = (props) => { + const cfg = settingStore() + + const models = createMemo(() => { + const map = new Map() + + for (const worker of cfg.imageWorkers) { + for (const model of worker.models) { + if (!map.has(model)) { + map.set(model, 0) + } + + const current = map.get(model) ?? 0 + map.set(model, current + 1) + } + } + + const items = Array.from(map.entries()) + .sort(([, l], [, r]) => (l > r ? -1 : l === r ? 0 : 1)) + .map(([name, count]) => ({ + label: `${name} (${count})`, + value: name, + })) + return items + }) + + createEffect(() => { + settingStore.getHordeImageWorkers() + }) + + const samplers = Object.entries(SD_SAMPLER_REV).map(([key, value]) => ({ + label: value, + value: key, + })) + return ( + <> +
Horde
+ props.setter(applyStoreProperty(props.cfg, 'horde.sampler', ev.value))} + /> + + ) +} + +export const SDSettings: Component<{ + cfg: ImageSettings + setter: SetStoreFunction +}> = (props) => { + const samplers = Object.entries(SD_SAMPLER_REV).map(([key, value]) => ({ + label: value, + value: key, + })) + return ( + <> +
Stable Diffusion
+ + props.setter(applyStoreProperty(props.cfg, 'sd.url', ev.currentTarget.value)) + } + /> + props.setter(applyStoreProperty(props.cfg, 'agnai.model', ev.value))} + /> + + userStore.saveUI({ themeBg: item.value })} diff --git a/web/shared/Card.tsx b/web/shared/Card.tsx index 26b1ff427..e61e9e1c7 100644 --- a/web/shared/Card.tsx +++ b/web/shared/Card.tsx @@ -1,4 +1,4 @@ -import { Component, JSX, Show, createMemo } from 'solid-js' +import { Accessor, Component, JSX, Show, createMemo } from 'solid-js' import { userStore } from '../store' import { useBgStyle } from './hooks' import { hooks } from './util' @@ -13,7 +13,7 @@ export const Card: Component<{ bgOpacity?: number border?: boolean bg?: string - hide?: boolean + hide?: boolean | Accessor size?: Size ariaRole?: JSX.AriaAttributes['role'] ariaLabel?: string @@ -24,11 +24,16 @@ export const Card: Component<{ opacity: props.bgOpacity ?? 0.08, }) + const hide = createMemo(() => { + if (typeof props.hide === 'function') return props.hide() + return props.hide + }) + return (
props.update({ type: ev.value as any }, props.index)} + onChange={(ev) => props.updateType({ type: ev.value as any }, props.index)} /> props.updateType({ title: ev.currentTarget.value }, props.index)} /> + props.updateType({ description: ev.currentTarget.value }, props.index) + } />
diff --git a/web/shared/Mode/ModeGenSettings.tsx b/web/shared/Mode/ModeGenSettings.tsx index 65997c4d3..dea8fe577 100644 --- a/web/shared/Mode/ModeGenSettings.tsx +++ b/web/shared/Mode/ModeGenSettings.tsx @@ -30,7 +30,7 @@ export const ModeGenSettings: Component<{ options: presets.map((pre) => ({ label: pre.name, value: pre._id })), })) - const [store, setStore] = getPresetEditor() + const [store, setStore, hides] = getPresetEditor() const presetOptions = createMemo(() => getPresetOptions(state.presets, { builtin: true, base: true }) @@ -174,7 +174,13 @@ export const ModeGenSettings: Component<{ /> - +
) diff --git a/web/shared/PhraseBias.tsx b/web/shared/PhraseBias.tsx index 0fb058a64..6f94a401e 100644 --- a/web/shared/PhraseBias.tsx +++ b/web/shared/PhraseBias.tsx @@ -3,7 +3,7 @@ import { FormLabel } from './FormLabel' import TextInput from './TextInput' import { MinusCircle } from 'lucide-solid' import Button from './Button' -import { hidePresetSetting, isValidServiceSetting } from './util' +import { isValidServiceSetting } from './util' import { InlineRangeInput } from './RangeInput' import { Field } from './PresetSettings/Fields' @@ -93,10 +93,8 @@ export const StoppingStrings: Field = (props) => { props.setter('stopSequences', next) } - const hide = createMemo(() => hidePresetSetting(props.state, 'stopSequences')) - return ( -
+
diff --git a/web/shared/PresetSettings/Fields.tsx b/web/shared/PresetSettings/Fields.tsx index a3d8edd66..60e0b80e4 100644 --- a/web/shared/PresetSettings/Fields.tsx +++ b/web/shared/PresetSettings/Fields.tsx @@ -11,7 +11,6 @@ import { MODEL_FORMATS } from './General' import { defaultPresets } from '/common/default-preset' import { FormLabel } from '../FormLabel' import { SubscriptionModelLevel } from '/common/types/presets' -import { hidePresetSetting } from '../util' import { Card } from '../Card' import PromptEditor from '../PromptEditor' import { CustomSelect } from '../CustomSelect' @@ -114,7 +113,7 @@ export const ContextSize: Field<{ subMax: Partial }> = ( export const SystemPrompt: Field = (props) => { return ( - + The task the AI is performing. Leave blank if uncertain.} @@ -133,7 +132,7 @@ export const SystemPrompt: Field = (props) => { export const Jailbreak: Field = (props) => { return ( - + { value={props.state.thirdPartyUrl || ''} disabled={props.state.disabled} hide={ - hidePresetSetting(props.state, 'thirdPartyUrl') || + props.hides.thirdPartyUrl || props.state.thirdPartyFormat === 'featherless' || props.state.thirdPartyFormat === 'mistral' || props.state.thirdPartyFormat === 'gemini' @@ -204,7 +203,7 @@ export const ThirdPartyKey: Field = (props) => { value={props.state.thirdPartyKey} disabled={props.state.disabled} type="password" - hide={hidePresetSetting(props.state, 'thirdPartyKey')} + hide={props.hides.thirdPartyKey} onChange={(ev) => props.setter('thirdPartyKey', ev.currentTarget.value)} /> diff --git a/web/shared/PresetSettings/General.tsx b/web/shared/PresetSettings/General.tsx index 4bb561c98..60172f95e 100644 --- a/web/shared/PresetSettings/General.tsx +++ b/web/shared/PresetSettings/General.tsx @@ -13,7 +13,7 @@ import { import { Toggle } from '../Toggle' import { settingStore, userStore } from '../../store' import { Card } from '../Card' -import { hidePresetSetting, isValidServiceSetting, serviceHasSetting } from '../util' +import { isValidServiceSetting, serviceHasSetting } from '../util' import { HordeDetails } from '../../pages/Settings/components/HordeAISettings' import { PhraseBias, StoppingStrings } from '../PhraseBias' import { BUILTIN_FORMATS } from '/common/presets/templates' @@ -117,7 +117,7 @@ export const GeneralSettings: Component = (props) => { return (
- + = (props) => { helperMarkdown={`When enabled your browser will make requests instead of Agnaistic.\n**NOTE**: Your chat will not support multiplayer.`} service={props.state.service} format={props.state.thirdPartyFormat} - hide={hidePresetSetting(props.state, 'localRequests')} + hide={props.hides.localRequests} value={props.state.localRequests} onChange={(ev) => props.setter('localRequests', ev)} /> @@ -152,10 +152,7 @@ export const GeneralSettings: Component = (props) => { helperText="No paths will be added to your URL." value={props.state.thirdPartyUrlNoSuffix} service={props.state.service} - hide={ - hidePresetSetting(props.state, 'thirdPartyUrl') || - props.state.thirdPartyModel === 'featherless' - } + hide={props.hides.thirdPartyFormat || props.state.thirdPartyModel === 'featherless'} onChange={(ev) => props.setter('thirdPartyUrlNoSuffix', ev)} />
@@ -177,9 +174,19 @@ export const GeneralSettings: Component = (props) => { ) } > - + - + = (props) => { items={novelModels()} value={props.state.novelModel || ''} disabled={props.state.disabled} - hide={hidePresetSetting(props.state, 'novelModel')} + hide={props.hides.novelModel} onChange={(ev) => props.setter('novelModel', ev.value)} /> @@ -246,7 +253,7 @@ export const GeneralSettings: Component = (props) => { fieldName="novelModelOverride" helperText="Advanced: Use a custom NovelAI model" label="NovelAI Model Override" - hide={hidePresetSetting(props.state, 'novelModel')} + hide={props.hides.novelModel} />
@@ -258,11 +265,7 @@ export const GeneralSettings: Component = (props) => { helperText="Which Claude model to use, models marked as 'Latest' will automatically switch when a new minor version is released." value={props.state.claudeModel ?? defaultPresets.claude.claudeModel} disabled={props.state.disabled} - hide={ - props.state.service !== 'claude' && - props.state.service !== 'kobold' && - props.state.thirdPartyFormat !== 'claude' - } + hide={props.hides.claudeModel} onChange={(ev) => props.setter('claudeModel', ev.value)} /> 1}> @@ -276,7 +279,7 @@ export const GeneralSettings: Component = (props) => { Publicly available language models. } - hide={hidePresetSetting(props.state, 'replicateModelName')} + hide={props.hides.replicateModelName} onChange={(ev) => props.setter('replicateModelName', ev.value)} /> @@ -287,7 +290,7 @@ export const GeneralSettings: Component = (props) => { helperText="Which Replicate API input parameters to use." value={props.state.replicateModelType} disabled={!!props.state.replicateModelName || props.state.disabled} - hide={hidePresetSetting(props.state, 'replicateModelName')} + hide={props.hides.replicateModelName} onChange={(ev) => props.setter('replicateModelType', ev.value)} /> = (props) => { value={props.state.replicateModelVersion} placeholder={`E.g. ${defaultPresets.replicate_vicuna_13b.replicateModelVersion}`} disabled={!!props.state.replicateModelName || props.state.disabled} - hide={hidePresetSetting(props.state, 'replicateModelVersion')} + hide={props.hides.replicateModelName} onChange={(ev) => props.setter('replicateModelVersion', ev.currentTarget.value)} /> @@ -321,12 +324,19 @@ export const GeneralSettings: Component = (props) => { - + @@ -352,7 +362,12 @@ export const GeneralSettings: Component = (props) => { disabled={props.state.disabled} onChange={(ev) => props.setter('streamResponse', ev)} /> - + = (props) => { onChange={(ev) => props.setter('disableNameStops', ev)} /> - +
) diff --git a/web/shared/PresetSettings/Prompt.tsx b/web/shared/PresetSettings/Prompt.tsx index c71715ba5..3d7b158ec 100644 --- a/web/shared/PresetSettings/Prompt.tsx +++ b/web/shared/PresetSettings/Prompt.tsx @@ -11,7 +11,6 @@ import { ToggleButton } from '../Button' import { isChatPage } from '../hooks' import { Jailbreak, SystemPrompt } from './Fields' import { PresetTabProps } from './types' -import { hidePresetSetting } from '../util' export const PromptSettings: Component = (props) => { const character = chatStore((s) => s.active?.char) @@ -107,7 +106,7 @@ export const PromptSettings: Component = (props) => { disabled={props.state.disabled} service={props.state.service} format={props.state.thirdPartyFormat} - hide={hidePresetSetting(props.state, 'prefixNameAppend')} + hide={props.hides.prefixNameAppend} onChange={(ev) => props.setter('prefixNameAppend', ev)} /> = (props) => { value={props.state.prefill ?? ''} disabled={props.state.disabled} class="form-field focusable-field text-900 min-h-[8rem] w-full rounded-xl px-4 py-2 text-sm" - hide={hidePresetSetting(props.state, 'prefill')} + hide={props.hides.prefill} onChange={(ev) => props.setter('prefill', ev.currentTarget.value)} />
@@ -130,14 +129,14 @@ export const PromptSettings: Component = (props) => { label="Override Character System Prompt" value={props.state.ignoreCharacterSystemPrompt ?? false} disabled={props.state.disabled} - hide={hidePresetSetting(props.state, 'ignoreCharacterSystemPrompt')} + hide={props.hides.ignoreCharacterSystemPrompt} onChange={(ev) => props.setter('ignoreCharacterSystemPrompt', ev)} /> props.setter('ignoreCharacterUjb', ev)} />
diff --git a/web/shared/PresetSettings/Sliders.tsx b/web/shared/PresetSettings/Sliders.tsx index 556f62e82..da2b643da 100644 --- a/web/shared/PresetSettings/Sliders.tsx +++ b/web/shared/PresetSettings/Sliders.tsx @@ -4,41 +4,142 @@ import { defaultPresets } from '../../../common/presets' import { Card } from '../Card' import { A } from '@solidjs/router' import { PresetTabProps } from './types' -import { hidePresetSetting } from '../util' +import { HelpModal } from '../Modal' export const SliderSettings: Component = (props) => { return (
- - props.setter('dynatemp_range', ev)} - hide={hidePresetSetting(props.state, 'dynatemp_range')} - /> + +
Dynamic Temperature
+
+ The range to use for dynamic temperature. When used, the actual temperature is allowed to + be automatically adjusted dynamically between DynaTemp ± DynaTempRange. For example, + setting `temperature=0.4` and `dynatemp_range=0.1` will result in a minimum temp of 0.3 + and max of 0.5. +
+
+ props.setter('dynatemp_range', ev)} + hide={props.hides.dynatemp_range} + /> - props.setter('dynatemp_exponent', ev)} - hide={hidePresetSetting(props.state, 'dynatemp_exponent')} - /> + props.setter('dynatemp_exponent', ev)} + hide={props.hides.dynatemp_exponent} + /> +
+
+ + +
+ XTC (Exclude Top Choices) +
+
+ props.setter('xtcThreshold', ev)} + hide={props.hides.xtcThreshold} + /> + + props.setter('xtcProbability', ev)} + hide={props.hides.xtcThreshold} + /> +
+
+ + +
+ DRY Sampling{' '} + + Reference + +
+ +
+ props.setter('dryMultiplier', ev)} + hide={props.hides.dryMultiplier} + /> + + props.setter('dryBase', ev)} + hide={props.hides.dryBase} + /> + + props.setter('dryAllowedLength', ev)} + /> +
+
+ + = (props) => { min={0} max={10} step={0.01} - value={props.state.smoothingFactor || 0} + value={props.state.smoothingFactor ?? 0} disabled={props.state.disabled} aiSetting={'smoothingFactor'} recommended={props.sub?.preset.smoothingFactor} onChange={(ev) => props.setter('smoothingFactor', ev)} - hide={hidePresetSetting(props.state, 'smoothingFactor')} + hide={props.hides.smoothingFactor} /> = (props) => { min={1} max={5} step={0.01} - value={props.state.smoothingCurve || 1} + value={props.state.smoothingCurve ?? 1} disabled={props.state.disabled} aiSetting={'smoothingCurve'} recommended={props.sub?.preset.smoothingCurve} onChange={(ev) => props.setter('smoothingCurve', ev)} - hide={hidePresetSetting(props.state, 'smoothingCurve')} + hide={props.hides.smoothingCurve} /> = (props) => { min={1} max={3} step={0.05} - value={props.state.cfgScale || 1} + value={props.state.cfgScale ?? 1} disabled={props.state.disabled} aiSetting={'cfgScale'} onChange={(ev) => props.setter('cfgScale', ev)} - hide={hidePresetSetting(props.state, 'cfgScale')} + hide={props.hides.cfgScale} /> = (props) => { aiSetting={'topP'} recommended={props.sub?.preset.topP} onChange={(ev) => props.setter('topP', ev)} - hide={hidePresetSetting(props.state, 'topP')} + hide={props.hides.topP} /> = (props) => { aiSetting={'topK'} recommended={props.sub?.preset.topK} onChange={(ev) => props.setter('topK', ev)} - hide={hidePresetSetting(props.state, 'topK')} + hide={props.hides.topK} /> = (props) => { aiSetting={'topA'} recommended={props.sub?.preset.topA} onChange={(ev) => props.setter('topA', ev)} - hide={hidePresetSetting(props.state, 'topA')} + hide={props.hides.topA} /> = (props) => { disabled={props.state.disabled} aiSetting={'mirostatTau'} onChange={(ev) => props.setter('mirostatTau', ev)} - hide={hidePresetSetting(props.state, 'mirostatTau')} + hide={props.hides.mirostatTau} /> = (props) => { disabled={props.state.disabled} aiSetting={'mirostatLR'} onChange={(ev) => props.setter('mirostatLR', ev)} - hide={hidePresetSetting(props.state, 'mirostatLR')} + hide={props.hides.mirostatTau} /> = (props) => { aiSetting={'tailFreeSampling'} recommended={props.sub?.preset.tailFreeSampling} onChange={(ev) => props.setter('tailFreeSampling', ev)} - hide={hidePresetSetting(props.state, 'tailFreeSampling')} + hide={props.hides.tailFreeSampling} /> = (props) => { aiSetting={'typicalP'} recommended={props.sub?.preset.typicalP} onChange={(ev) => props.setter('typicalP', ev)} - hide={hidePresetSetting(props.state, 'typicalP')} + hide={props.hides.typicalP} /> = (props) => { aiSetting={'repetitionPenalty'} recommended={props.sub?.preset.repetitionPenalty} onChange={(ev) => props.setter('repetitionPenalty', ev)} - hide={hidePresetSetting(props.state, 'repetitionPenalty')} + hide={props.hides.repetitionPenalty} /> = (props) => { aiSetting={'repetitionPenaltyRange'} recommended={props.sub?.preset.repetitionPenaltyRange} onChange={(ev) => props.setter('repetitionPenaltyRange', ev)} - hide={hidePresetSetting(props.state, 'repetitionPenaltyRange')} + hide={props.hides.repetitionPenaltyRange} /> = (props) => { aiSetting={'repetitionPenaltySlope'} recommended={props.sub?.preset.repetitionPenaltySlope} onChange={(ev) => props.setter('repetitionPenaltySlope', ev)} - hide={hidePresetSetting(props.state, 'repetitionPenaltySlope')} + hide={props.hides.repetitionPenaltySlope} /> = (props) => { disabled={props.state.disabled} aiSetting={'etaCutoff'} onChange={(ev) => props.setter('etaCutoff', ev)} - hide={hidePresetSetting(props.state, 'etaCutoff')} + hide={props.hides.etaCutoff} /> = (props) => { disabled={props.state.disabled} aiSetting={'epsilonCutoff'} onChange={(ev) => props.setter('epsilonCutoff', ev)} - hide={hidePresetSetting(props.state, 'epsilonCutoff')} + hide={props.hides.epsilonCutoff} /> = (props) => { aiSetting={'frequencyPenalty'} recommended={props.sub?.preset.frequencyPenalty} onChange={(ev) => props.setter('frequencyPenalty', ev)} - hide={hidePresetSetting(props.state, 'frequencyPenalty')} + hide={props.hides.frequencyPenalty} /> = (props) => { aiSetting={'presencePenalty'} recommended={props.sub?.preset.presencePenalty} onChange={(ev) => props.setter('presencePenalty', ev)} - hide={hidePresetSetting(props.state, 'presencePenalty')} + hide={props.hides.presencePenalty} /> = (props) => { aiSetting={'encoderRepitionPenalty'} recommended={props.sub?.preset.encoderRepitionPenalty} onChange={(ev) => props.setter('encoderRepitionPenalty', ev)} - hide={hidePresetSetting(props.state, 'encoderRepitionPenalty')} + hide={props.hides.encoderRepitionPenalty} /> = (props) => { aiSetting={'penaltyAlpha'} recommended={props.sub?.preset.penaltyAlpha} onChange={(ev) => props.setter('penaltyAlpha', ev)} - hide={hidePresetSetting(props.state, 'penaltyAlpha')} + hide={props.hides.penaltyAlpha} /> = (props) => { disabled={props.state.disabled} aiSetting={'numBeams'} onChange={(ev) => props.setter('numBeams', ev)} - hide={hidePresetSetting(props.state, 'numBeams')} + hide={props.hides.numBeams} />
) } + +const XTCHelpModal = () => ( + Reference} title="Exclude Top Choices"> +

+ XTC is a novel sampler that turns truncation on its head: Instead of pruning the least likely + tokens, under certain circumstances, it removes the most likely tokens from consideration. +

+

+ More precisely, it removes all except the least likely token meeting a given threshold, with a + given probability +

+

+ + Reference + +

+
+) diff --git a/web/shared/PresetSettings/Toggles.tsx b/web/shared/PresetSettings/Toggles.tsx index 5c0b1827e..43e2d6d3e 100644 --- a/web/shared/PresetSettings/Toggles.tsx +++ b/web/shared/PresetSettings/Toggles.tsx @@ -8,7 +8,6 @@ import Sortable, { SortItem } from '../Sortable' import { A } from '@solidjs/router' import { inverseSamplerServiceMap, samplerServiceMap } from '/common/sampler-order' import { PresetState, PresetTabProps, SetPresetState } from './types' -import { hidePresetSetting } from '../util' export const ToggleSettings: Component = (props) => { return ( @@ -29,7 +28,7 @@ export const ToggleSettings: Component = (props) => { } value={props.state.cfgOppose || ''} disabled={props.state.disabled} - hide={hidePresetSetting(props.state, 'cfgScale')} + hide={props.hides.cfgScale} onChange={(ev) => props.setter('cfgOppose', ev.currentTarget.value)} /> @@ -48,7 +47,7 @@ export const ToggleSettings: Component = (props) => { { label: 'Off', value: 'off' }, ]} value={props.state.phraseRepPenalty || 'aggressive'} - hide={hidePresetSetting(props.state, 'phraseRepPenalty')} + hide={props.hides.phraseRepPenalty} onChange={(ev) => props.setter('phraseRepPenalty', ev.value)} /> @@ -59,7 +58,7 @@ export const ToggleSettings: Component = (props) => { value={props.state.tempLast ?? false} service={props.state.service} format={props.state.thirdPartyFormat} - hide={hidePresetSetting(props.state, 'tempLast')} + hide={props.hides.tempLast} recommended={props.sub?.preset.tempLast} onChange={(ev) => props.setter('tempLast', ev)} /> @@ -79,7 +78,7 @@ export const ToggleSettings: Component = (props) => { value={props.state.mirostatToggle ?? false} disabled={props.state.disabled} service={props.state.service} - hide={hidePresetSetting(props.state, 'mirostatLR')} + hide={props.hides.mirostatLR} format={props.state.thirdPartyFormat} recommended={props.sub?.preset.mirostatToggle} onChange={(ev) => props.setter('mirostatToggle', ev)} @@ -92,7 +91,7 @@ export const ToggleSettings: Component = (props) => { value={props.state.tokenHealing ?? true} disabled={props.state.disabled} service={props.state.service} - hide={hidePresetSetting(props.state, 'tokenHealing')} + hide={props.hides.tokenHealing} format={props.state.thirdPartyFormat} recommended={props.sub?.preset.tokenHealing} onChange={(ev) => props.setter('tokenHealing', ev)} @@ -104,7 +103,7 @@ export const ToggleSettings: Component = (props) => { value={props.state.addBosToken ?? true} disabled={props.state.disabled} service={props.state.service} - hide={hidePresetSetting(props.state, 'addBosToken')} + hide={props.hides.addBosToken} format={props.state.thirdPartyFormat} recommended={props.sub?.preset.addBosToken} onChange={(ev) => props.setter('addBosToken', ev)} @@ -116,7 +115,7 @@ export const ToggleSettings: Component = (props) => { value={props.state.banEosToken ?? false} disabled={props.state.disabled} service={props.state.service} - hide={hidePresetSetting(props.state, 'banEosToken')} + hide={props.hides.banEosToken} format={props.state.thirdPartyFormat} recommended={props.sub?.preset.banEosToken} onChange={(ev) => props.setter('banEosToken', ev)} @@ -128,7 +127,7 @@ export const ToggleSettings: Component = (props) => { value={props.state.skipSpecialTokens ?? true} disabled={props.state.disabled} service={props.state.service} - hide={hidePresetSetting(props.state, 'skipSpecialTokens')} + hide={props.hides.skipSpecialTokens} format={props.state.thirdPartyFormat} recommended={props.sub?.preset.skipSpecialTokens} onChange={(ev) => props.setter('skipSpecialTokens', ev)} @@ -141,7 +140,7 @@ export const ToggleSettings: Component = (props) => { value={props.state.doSample ?? true} disabled={props.state.disabled} service={props.state.service} - hide={hidePresetSetting(props.state, 'doSample')} + hide={props.hides.doSample} format={props.state.thirdPartyFormat} onChange={(ev) => props.setter('doSample', ev)} /> @@ -153,7 +152,7 @@ export const ToggleSettings: Component = (props) => { value={props.state.earlyStopping ?? false} disabled={props.state.disabled} service={props.state.service} - hide={hidePresetSetting(props.state, 'earlyStopping')} + hide={props.hides.earlyStopping} format={props.state.thirdPartyFormat} onChange={(ev) => props.setter('earlyStopping', ev)} /> diff --git a/web/shared/PresetSettings/index.tsx b/web/shared/PresetSettings/index.tsx index fbe77d002..f5ee00729 100644 --- a/web/shared/PresetSettings/index.tsx +++ b/web/shared/PresetSettings/index.tsx @@ -17,7 +17,7 @@ import Tabs from '../Tabs' import { useSearchParams } from '@solidjs/router' import { AgnaisticSettings } from './Agnaistic' import { usePaneManager } from '../hooks' -import { PresetProps, PresetState, PresetTab, SetPresetState } from './types' +import { HideState, PresetProps, PresetState, PresetTab, SetPresetState } from './types' import { GeneralSettings } from './General' import { RegisteredSettings } from './Registered' import { PromptSettings } from './Prompt' @@ -31,7 +31,7 @@ export { PresetSettings as default } type TempSetting = AdapterSetting & { value: any } const PresetSettings: Component< - PresetProps & { noSave: boolean; store: PresetState; setter: SetPresetState } + PresetProps & { noSave: boolean; store: PresetState; setter: SetPresetState; hides: HideState } > = (props) => { const settings = settingStore() const pane = usePaneManager() @@ -103,6 +103,7 @@ const PresetSettings: Component< props.setter('thirdPartyFormat', ev.value as ThirdPartyFormat)} /> - + - + - + - + - + - +
) } diff --git a/web/shared/PresetSettings/settings.ts b/web/shared/PresetSettings/settings.ts index abc4dee63..09ab39b2d 100644 --- a/web/shared/PresetSettings/settings.ts +++ b/web/shared/PresetSettings/settings.ts @@ -21,6 +21,7 @@ export const ADAPTER_SETTINGS: { tempLast: ['agnaistic', 'tabby', 'exllamav2'], dynatemp_range: ['kobold', 'ooba', 'tabby', 'agnaistic', 'aphrodite', 'ollama'], dynatemp_exponent: ['kobold', 'aphrodite', 'ooba', 'tabby', 'agnaistic', 'ollama'], + dryMultiplier: ['agnaistic', 'ooba', 'tabby'], smoothingFactor: ['kobold', 'aphrodite', 'ooba', 'tabby', 'agnaistic'], smoothingCurve: ['kobold', 'aphrodite'], maxTokens: AI_ADAPTERS.slice(), @@ -32,6 +33,9 @@ export const ADAPTER_SETTINGS: { epsilonCutoff: ['aphrodite'], etaCutoff: ['aphrodite'], + xtcProbability: ['agnaistic', 'tabby', 'ooba'], + xtcThreshold: ['agnaistic', 'tabby', 'ooba'], + prefill: ['claude', 'openrouter', 'openai', 'openai-chat', 'gemini'], topP: [ diff --git a/web/shared/PresetSettings/types.ts b/web/shared/PresetSettings/types.ts index 33e7c6b0a..13c073cb2 100644 --- a/web/shared/PresetSettings/types.ts +++ b/web/shared/PresetSettings/types.ts @@ -1,8 +1,11 @@ import { SetStoreFunction, createStore } from 'solid-js/store' -import { AIAdapter } from '/common/adapters' +import { AIAdapter, MODE_SETTINGS, PresetAISettings } from '/common/adapters' import { AppSchema } from '/common/types' import { SubscriptionModelOption } from '/common/types/presets' import { agnaiPresets } from '/common/presets/agnaistic' +import { createEffect, on } from 'solid-js' +import { ADAPTER_SETTINGS } from './settings' +import { isValidServiceSetting } from '../util' export type PresetProps = { disabled?: boolean @@ -16,6 +19,7 @@ export type PresetTab = 'General' | 'Prompt' | 'Memory' | 'Samplers' | 'Toggles' export type PresetTabProps = { state: PresetState setter: SetPresetState + hides: HideState sub: SubscriptionModelOption | undefined tab: string } @@ -24,6 +28,8 @@ export type PresetState = Omit & { disabled?: boolean } +export type HideState = ReturnType[2] + export type SetPresetState = SetStoreFunction export function getPresetForm(state: PresetState) { @@ -43,7 +49,7 @@ export function getPresetForm(state: PresetState) { } export function getSubPresetForm(state: PresetState) { - const { disabled, ...form } = state + const { disabled, subApiKeySet, ...form } = state return { ...form, kind: 'subscription-setting' as const } } @@ -65,9 +71,49 @@ export const initPreset: Omit & { userId: '', allowGuestUsage: false, disabled: false, + xtcThreshold: 0, + xtcProbability: 0, + dryAllowedLength: 2, + dryBase: 1.75, + dryMultiplier: 0, } export function getPresetEditor() { const [store, setStore] = createStore(initPreset) - return [store, setStore] as const + const [hide, setHides] = createStore<{ [key in keyof AppSchema.GenSettings]?: boolean }>({}) + + createEffect( + on( + () => [store.service, store.thirdPartyFormat, store.presetMode], + () => { + const keys = Object.keys(ADAPTER_SETTINGS) as Array + + for (const key of keys) { + setHides(key, hidePresetSetting(store, key as any)) + } + } + ) + ) + + return [store, setStore, hide] as const +} + +function hidePresetSetting( + state: Pick, + prop?: keyof PresetAISettings +) { + let hide = false + if (!prop) { + hide = false + } else if (!isValidServiceSetting(state, prop)) { + console.log('eval:invalid', prop) + hide = true + } else if (state.presetMode && state.presetMode !== 'advanced') { + const enabled = MODE_SETTINGS[state.presetMode]?.[prop] + if (!enabled) { + hide = true + } + } + + return hide } diff --git a/web/shared/PromptEditor/index.tsx b/web/shared/PromptEditor/index.tsx index 49c37476f..7415a6071 100644 --- a/web/shared/PromptEditor/index.tsx +++ b/web/shared/PromptEditor/index.tsx @@ -8,6 +8,7 @@ import { createEffect, createMemo, createSignal, + on, onMount, } from 'solid-js' import { FormLabel } from '../FormLabel' @@ -162,9 +163,6 @@ const PromptEditor: Component< const presets = presetStore() - // const [templateId, setTemplateId] = createSignal('') - // const [input, setInput] = createSignal(props.value || '') - const [autoOpen, setAutoOpen] = createSignal(false) const [template, setTemplate] = createSignal('') @@ -211,10 +209,25 @@ const PromptEditor: Component< props.onChange?.({ prompt: ev.currentTarget.value, templateId: props.state?.promptTemplateId }) } - createEffect(() => { - if (props.value === undefined) return - ref.value = props.value - }) + /** + * Specifically for the .gaslight property only: + * If there is a `promptTemplateId` set, we need to assign the ref.value to the template prompt + * Otherwise use the `props.value` which is the gaslight template. + */ + createEffect( + on( + () => [props.value, template(), props.state?.promptTemplateId, presets.templates], + () => { + if (props.state?.promptTemplateId) { + const template = presets.templates.find((t) => t._id === props.state?.promptTemplateId) + ref.value = template?.template || 'Loading...' + return + } + + ref.value = props.value + } + ) + ) const usable = createMemo(() => { type Entry = [Interp, Placeholder] diff --git a/web/shared/RangeInput.tsx b/web/shared/RangeInput.tsx index 9467a9454..1e1f856da 100644 --- a/web/shared/RangeInput.tsx +++ b/web/shared/RangeInput.tsx @@ -1,11 +1,12 @@ -import { Component, Show, createSignal, createEffect } from 'solid-js' +import { Component, Show, createEffect, createSignal, on } from 'solid-js' import type { JSX } from 'solid-js' import { PresetAISettings, samplerDisableValues } from '../../common/adapters' import { markdown } from './markdown' +import { round } from '/common/util' const RangeInput: Component<{ label: string | JSX.Element - fieldName: string + fieldName?: string value: number helperText?: string | JSX.Element helperMarkdown?: string @@ -15,38 +16,70 @@ const RangeInput: Component<{ disabled?: boolean recommended?: number | string recommendLabel?: string | JSX.Element - onChange?: (value: number) => void + onChange: (value: number) => void parentClass?: string aiSetting?: keyof PresetAISettings hide?: boolean }> = (props) => { - const [previousPropsValue, setPreviousPropsValue] = createSignal(props.value) - const [value, setValue] = createSignal(props.value) let input: HTMLInputElement | undefined + let slider: HTMLInputElement | undefined + + const [display, setDisplay] = createSignal(props.value.toString()) + + function updateRangeSliders(next?: string) { + if (!input || !slider) return + + if (next === undefined && props.value === +display()) { + return + } - function updateRangeSliders() { - if (props.value !== previousPropsValue()) { - setValue(props.value) - setPreviousPropsValue(props.value) + const parsed = next !== undefined ? next || '0' : props.value.toString() + if (isNaN(+parsed)) { + input.value = display() + slider.value = display() + return } - if (!input) return - const value = Math.min(+input.value, +input.max) - const nextSize = ((value - +input.min) * 100) / (+input.max - +input.min) + '% 100%' + input.value = parsed + slider.value = parsed + setDisplay(parsed) + + const percent = Math.min(+parsed, +input.max) + const nextSize = ((percent - +input.min) * 100) / (+input.max - +input.min) + '% 100%' input.style.backgroundSize = nextSize + + if (next !== undefined) { + props.onChange(+parsed) + } + + // const value = next ?? props.value + // if (value === undefined) return + // if (!input || !slider) return + + // input.value = value as any + // slider.value = value as any + + // if (next !== undefined) { + // props.onChange(next) + // } } const onInput: JSX.EventHandler = (event) => { - setValue(+event.currentTarget.value) - updateRangeSliders() - props.onChange?.(+event.currentTarget.value) + updateRangeSliders(event.currentTarget.value as any) + props.onChange(+event.currentTarget.value) } - createEffect(updateRangeSliders) + createEffect( + on( + () => props.value, + () => updateRangeSliders() + ) + ) const disableSampler = () => { + if (!props.aiSetting) return + const value = samplerDisableValues[props.aiSetting] if (value === undefined) return - - setValue(value) + updateRangeSliders(value.toString()) } return ( @@ -57,7 +90,7 @@ const RangeInput: Component<{ -  ({props.recommendLabel || 'Recommended'}: {props.recommended?.toString()}) +  ({props.recommendLabel || 'Eg.'}: {props.recommended?.toString()}) @@ -68,18 +101,6 @@ const RangeInput: Component<{
-

{props.helperText}

@@ -87,10 +108,11 @@ const RangeInput: Component<{

- + + " + min={props.min} + max={props.max} + step={props.step} + onInput={onInput} + value={props.value} + disabled={props.disabled} + /> + { + if (!props.step) return + if (ev.key !== 'ArrowDown' && ev.key !== 'ArrowUp') return + + const places = (props.step.toString().split('.')[1] || '').length + const dir = ev.key === 'ArrowDown' ? -props.step : props.step + let value = round(props.value + dir, places) + if (props.max !== undefined) value = Math.min(value, props.max) + if (props.min !== undefined) value = Math.max(value, props.min) + updateRangeSliders(value.toString()) + }} + disabled={props.disabled} + /> +
) } @@ -120,32 +166,58 @@ export const InlineRangeInput: Component<{ max: number step: number disabled?: boolean - onChange?: (value: number) => void + onChange: (value: number) => void hide?: boolean parentClass?: string + label?: string + aiSetting?: keyof PresetAISettings }> = (props) => { - const [value, setValue] = createSignal(props.value) let input: HTMLInputElement | undefined + let slider: HTMLInputElement | undefined + + function updateRangeSliders(next?: number) { + const value = next ?? props.value + if (!input || !slider) return - function updateRangeSliders() { - if (!input) return - const value = Math.min(+input.value, +input.max) - const nextSize = ((value - +input.min) * 100) / (+input.max - +input.min) + '% 100%' + input.value = value as any + slider.value = value as any + + const percent = Math.min(+input.value, +input.max) + const nextSize = ((percent - +input.min) * 100) / (+input.max - +input.min) + '% 100%' input.style.backgroundSize = nextSize + + if (next !== undefined) { + props.onChange(next) + } } const onInput: JSX.EventHandler = (event) => { - setValue(+event.currentTarget.value) updateRangeSliders() props.onChange?.(+event.currentTarget.value) } - createEffect(updateRangeSliders) + createEffect( + on( + () => props.value, + () => updateRangeSliders() + ) + ) + + const disableSampler = () => { + if (!props.aiSetting) return + const value = samplerDisableValues[props.aiSetting] + if (value === undefined) return + updateRangeSliders(value) + } + return (
+ +
{props.label}
+
+ + + + Disable + +
) } diff --git a/web/shared/Select.tsx b/web/shared/Select.tsx index d1cb2c3df..ee079428e 100644 --- a/web/shared/Select.tsx +++ b/web/shared/Select.tsx @@ -64,7 +64,7 @@ const Select: Component<{ -  ({props.recommendLabel || 'Recommended'}: {recommend()?.toString()}) +  ({props.recommendLabel || 'Eg.'}: {recommend()?.toString()}) diff --git a/web/shared/TextInput.tsx b/web/shared/TextInput.tsx index ac498d37b..a2e55364a 100644 --- a/web/shared/TextInput.tsx +++ b/web/shared/TextInput.tsx @@ -43,6 +43,7 @@ type Props = { children?: any initialValue?: number | string hide?: boolean + variant?: 'outline' /** Do not update the input value if the value property receives a new value */ static?: boolean @@ -158,14 +159,14 @@ const TextInput: Component = (props) => { return (
-
+
{props.prelabel}
@@ -205,11 +206,16 @@ const TextInput: Component = (props) => { aria-placeholder={placeholder()} value={props.initialValue ?? value()} class={ - 'form-field focusable-field text-900 min-h-[40px] w-full rounded-md px-4 ' + + 'form-field focusable-field text-900 box-border min-h-[40px] w-full rounded-md px-4 hover:border-white/20 ' + (props.class || '') } style={{ transition: 'height 0.2s ease-in-out', height: height() }} - classList={{ 'py-2': !props.class?.includes('py-'), ...props.classList }} + classList={{ + 'py-2': !props.class?.includes('py-'), + 'border-0.25': props.variant === 'outline', + 'border-[var(--bg-600)]': props.variant === 'outline', + ...props.classList, + }} disabled={props.disabled} spellcheck={props.spellcheck} lang={props.lang} @@ -230,10 +236,16 @@ const TextInput: Component = (props) => { placeholder={placeholder()} aria-placeholder={placeholder()} value={props.initialValue ?? value()} - class={'form-field focusable-field rounded-md px-4 ' + (props.class || '')} + class={ + 'form-field focusable-field box-border rounded-md px-4 hover:border-white/20 ' + + (props.class || '') + } classList={{ 'w-full': !props.class?.includes('w-'), + 'border-[1px]': props.variant === 'outline', + 'border-[var(--bg-600)]': props.variant === 'outline', 'py-2': !props.class?.includes('p-') && !props.class?.includes('py-'), + 'rounded-l-none': !!props.prelabel, ...props.classList, }} onkeyup={(ev) => { @@ -266,6 +278,8 @@ const TextInput: Component = (props) => { class={'form-field focusable-field rounded-xl px-4 py-2 ' + (props.class || '')} classList={{ 'w-full': !props.class?.includes('w-'), + 'border-[1px]': props.variant === 'outline', + 'border-[var(--bg-600)]': props.variant === 'outline', ...props.classList, }} onkeyup={(ev) => { diff --git a/web/shared/Toggle.tsx b/web/shared/Toggle.tsx index d15e14065..4536666b4 100644 --- a/web/shared/Toggle.tsx +++ b/web/shared/Toggle.tsx @@ -53,7 +53,7 @@ export const Toggle: Component<{ -  (Recommended: {props.recommended?.toString()}) +  (Rec.: {props.recommended?.toString()}) diff --git a/web/shared/util.ts b/web/shared/util.ts index 5265999b8..cb96f7c1b 100644 --- a/web/shared/util.ts +++ b/web/shared/util.ts @@ -1,9 +1,9 @@ import { createHooks, recommended } from '@css-hooks/solid' import * as lf from 'localforage' import { UnwrapBody, Validator, assertValid } from '/common/valid' -import { AIAdapter, MODE_SETTINGS, PresetAISettings, ThirdPartyFormat } from '/common/adapters' +import { AIAdapter, PresetAISettings, ThirdPartyFormat } from '/common/adapters' import type { Option } from './Select' -import { Component, createEffect, createSignal, JSX, on, onCleanup } from 'solid-js' +import { Component, createEffect, JSX, onCleanup } from 'solid-js' import type { UserState } from '../store' import { AppSchema, UI } from '/common/types' import { deepClone } from '/common/util' @@ -402,6 +402,45 @@ export function formatDate(value: string | number | Date) { return `${month} ${day} ${time}` } +export function toShortDuration(valueSecs: number | Date | string, parts?: number) { + if (valueSecs instanceof Date) { + valueSecs = Math.round((Date.now() - valueSecs.valueOf()) / 1000) + } else if (typeof valueSecs === 'string') { + valueSecs = Math.round((Date.now() - new Date(valueSecs).valueOf()) / 1000) + } + + if (valueSecs < 60) { + return '<1m' + } + const { + duration: [days, hours, minutes, seconds], + } = toRawDuration(valueSecs) + + if (parts) { + const sects: string[] = [] + if (days) sects.push(`${days}d`) + if (hours) sects.push(`${hours}h`) + if (minutes) sects.push(`${minutes}m`) + if (seconds) sects.push(`${seconds}s`) + + return sects.slice(0, parts).join(' ') + } + + if (days) { + return `${days}d` + } + + if (hours) { + return `${hours}h` + } + + if (minutes) { + return `${minutes}m` + } + + return `${seconds}s` +} + export function toDuration(valueSecs: number | Date, full?: boolean) { if (valueSecs instanceof Date) { valueSecs = Math.round((Date.now() - valueSecs.valueOf()) / 1000) @@ -641,48 +680,6 @@ function isPresetSetting(key: string): key is keyof PresetAISettings { return key in ADAPTER_SETTINGS === true } -export function hidePresetSetting( - state: Pick, - prop?: keyof PresetAISettings -) { - let initial = false - if (!prop) { - initial = false - } else if (state.presetMode && state.presetMode !== 'advanced') { - const enabled = MODE_SETTINGS[state.presetMode]?.[prop] - if (!enabled) initial = true - } else { - const valid = isValidServiceSetting(state, prop) - if (valid) initial = false - else initial = true - } - - const [hide, setHide] = createSignal(initial) - - createEffect( - on( - () => (state.service || '') + (state.thirdPartyFormat || '') + (state.presetMode || ''), - () => { - let next = false - if (!prop) { - next = false - } else if (state.presetMode && state.presetMode !== 'advanced') { - const enabled = MODE_SETTINGS[state.presetMode]?.[prop] - if (!enabled) next = true - } else { - const valid = isValidServiceSetting(state, prop) - if (valid) next = false - else next = true - } - - setHide(next) - } - ) - ) - - return hide() -} - export function isValidServiceSetting( state: Pick, prop?: keyof PresetAISettings @@ -727,7 +724,7 @@ export function applyDotProperty(obj: T, property: string, value: any) { export function applyStoreProperty(obj: T, property: string, value: any) { const props = property.split('.') - let base: any = JSON.parse(JSON.stringify(obj)) + let base: any = JSON.parse(JSON.stringify(obj || {})) let ref: any = base for (let i = 0; i < props.length; i++) { @@ -932,7 +929,9 @@ export function useRowHelper(opts: { const updateItem = (index: number, field: string, value: any) => { const prev = items() - const item = setProperty(prev[index], field, value) + const base = getProperty(opts.empty(), field) + const parsed = typeof base === 'number' ? +value : value + const item = setProperty(prev[index], field, parsed) const next = prev .slice(0, index) @@ -949,6 +948,7 @@ export function useRowHelper(opts: { } // Textarea and Input fields + if ('currentTarget' in ev) { return updateItem(index, field, ev.currentTarget.value) } @@ -981,6 +981,20 @@ function setProperty(obj: any, path: string, value: any): any { } } +function getProperty(obj: any, path: string) { + const [head, ...props] = path.split('.') + + let curr = obj[head] + if (curr === undefined) return + + for (const prop of props) { + if (curr === undefined) return + curr = curr[prop] + } + + return curr +} + export const sticky = { interval: null as any as NodeJS.Timer, monitor: (ref: HTMLElement) => { diff --git a/web/store/api.ts b/web/store/api.ts index df3c3eacc..84ef9b92c 100644 --- a/web/store/api.ts +++ b/web/store/api.ts @@ -1,5 +1,6 @@ import Cookies from 'js-cookie' import { EVENTS, events } from '../emitter' +import { jwtDecode } from 'jwt-decode' let socketId = '' @@ -226,8 +227,7 @@ export function getUserId() { } function getTokenBody(jwt: string) { - const [_head, body, _sign] = jwt.split('.') - const data = JSON.parse(window.atob(body)) + const data = jwtDecode(jwt) return data as { admin: boolean; exp: number; iat: number; userId: string; username: string } } diff --git a/web/store/embeddings/index.ts b/web/store/embeddings/index.ts index d9416cd86..231578dd6 100644 --- a/web/store/embeddings/index.ts +++ b/web/store/embeddings/index.ts @@ -110,18 +110,20 @@ const handlers: { decodeCallbacks.delete(msg.id) }, init: (type) => { - const user = getStore('user').getState() - const disableLTM = user.user?.disableLTM ?? true - if (type === 'embed') { - if (disableLTM) return - post('initSimilarity', { model: models.embedding }) - } + try { + const user = getStore('user').getState() + const disableLTM = user.user?.disableLTM ?? true + if (type === 'embed') { + if (disableLTM) return + post('initSimilarity', { model: models.embedding }) + } - if (type === 'image' && window.flags.caption) { - const httpCaptioning = models.captioning.startsWith('http') - if (disableLTM && !httpCaptioning) return - post('initCaptioning', { model: models.captioning }) - } + if (type === 'image' && window.flags.caption) { + const httpCaptioning = models.captioning.startsWith('http') + if (disableLTM && !httpCaptioning) return + post('initCaptioning', { model: models.captioning }) + } + } catch (ex) {} }, embedLoaded: async () => { EMBED_READY = true