diff --git a/common/requests/util.ts b/common/requests/util.ts index c2461368d..fcf908f52 100644 --- a/common/requests/util.ts +++ b/common/requests/util.ts @@ -66,13 +66,14 @@ export function trimResponseV2( generated = generated.split(`${member.handle} :`).join(`${member.handle}:`) } - if (bots) { - for (const bot of Object.values(bots)) { - if (!bot) continue - if (bot?._id === char._id) continue - endTokens.push(`${bot.name}:`) - } - } + /** Do not always add character names as stop tokens here */ + // if (bots) { + // for (const bot of Object.values(bots)) { + // if (!bot) continue + // if (bot?._id === char._id) continue + // endTokens.push(`${bot.name}:`) + // } + // } let index = -1 let trimmed = allEndTokens.concat(...endTokens).reduce((prev, endToken) => { diff --git a/common/types/ui.ts b/common/types/ui.ts index 42a98ec2e..f424bb5d8 100644 --- a/common/types/ui.ts +++ b/common/types/ui.ts @@ -42,7 +42,7 @@ export type CustomUI = { chatQuoteColor: string } -export type MessageOption = 'edit' | 'regen' | 'trash' | 'fork' | 'prompt' +export type MessageOption = 'edit' | 'regen' | 'trash' | 'fork' | 'prompt' | 'schema-regen' export type UISettings = { theme: string @@ -147,5 +147,6 @@ export const defaultUIsettings: UISettings = { fork: { outer: false, pos: 2 }, regen: { outer: true, pos: 1 }, trash: { outer: false, pos: 4 }, + 'schema-regen': { outer: false, pos: 5 }, }, } diff --git a/srv/adapter/agnaistic.ts b/srv/adapter/agnaistic.ts index 97635b695..3979d95e3 100644 --- a/srv/adapter/agnaistic.ts +++ b/srv/adapter/agnaistic.ts @@ -251,9 +251,15 @@ export const handleAgnaistic: ModelAdapter = async function* (opts) { log.debug(`Prompt:\n${prompt}`) - const [submodel, override] = subPreset.subModel.split(',') - - let params = [`type=text`, `id=${opts.user._id}`, `model=${submodel}`, `level=${level}`] + const [submodel, override = ''] = subPreset.subModel.split(',') + + let params = [ + `type=text`, + `id=${opts.user._id}`, + `model=${submodel}`, + `level=${level}`, + `sub_model=${override}`, + ] .filter((p) => !!p) .join('&') diff --git a/srv/adapter/generate.ts b/srv/adapter/generate.ts index 56789e0c9..b3c0e500a 100644 --- a/srv/adapter/generate.ts +++ b/srv/adapter/generate.ts @@ -438,6 +438,7 @@ export async function createChatStream( lastMessage: opts.lastMessage, imageData: opts.imageData, jsonSchema: jsonSchema || opts.jsonSchema, + reschemaPrompt: opts.reschemaPrompt, subscription, encoder, jsonValues: opts.jsonValues, diff --git a/srv/adapter/payloads.ts b/srv/adapter/payloads.ts index 37b417ca0..5497d8060 100644 --- a/srv/adapter/payloads.ts +++ b/srv/adapter/payloads.ts @@ -114,6 +114,7 @@ function getBasePayload(opts: AdapterProps, stops: string[] = []) { lists: opts.lists, previous: opts.previous, json_schema_v2: ensureSafeSchema(json_schema), + reschema_prompt: opts.reschemaPrompt, json_schema, imageData: opts.imageData, context_size: opts.contextSize, diff --git a/srv/adapter/type.ts b/srv/adapter/type.ts index 7bc46ec07..36d11a0c5 100644 --- a/srv/adapter/type.ts +++ b/srv/adapter/type.ts @@ -69,6 +69,7 @@ export type GenerateRequestV2 = { impersonate?: AppSchema.Character jsonSchema?: JsonField[] + reschemaPrompt?: string jsonValues?: Record /** Base64 */ @@ -122,6 +123,7 @@ export type AdapterProps = { encoder?: TokenCounter jsonSchema?: any + reschemaPrompt?: string jsonValues: Record | undefined imageData?: string diff --git a/srv/api/billing/checkout.ts b/srv/api/billing/checkout.ts index e71d8c13c..8b9dcdedf 100644 --- a/srv/api/billing/checkout.ts +++ b/srv/api/billing/checkout.ts @@ -6,6 +6,7 @@ import { v4 } from 'uuid' import { config } from '../../config' import { billingCmd, domain } from '../../domains' import { subsCmd } from '../../domains/subs/cmd' +import { AppSchema } from '/common/types' export const startCheckout = handle(async ({ body, userId }) => { assertValid({ tierId: 'string', callback: 'string' }, body) @@ -180,6 +181,10 @@ export const finishCheckout = handle(async ({ body, userId }) => { subscriptionId: subscription.id, }) + if (user && subscription.id) { + await ensureOnlyActiveSubscription(user, subscription.id) + } + const config = await store.users.updateUser(userId, { sub: { tierId: agg.tierId, @@ -205,3 +210,22 @@ export const finishCheckout = handle(async ({ body, userId }) => { await billingCmd.cancel(body.sessionId, { userId }) } }) + +async function ensureOnlyActiveSubscription(user: AppSchema.User, subscriptionId: string) { + // The user isn't an existing customer -- ignore + if (!user.billing?.customerId) return + + const subs = await stripe.subscriptions + .list({ customer: user.billing.customerId, status: 'active' }) + .then((res) => res.data) + .catch(() => []) + + for (const sub of subs) { + if (sub.id !== subscriptionId) { + await subsCmd.cancelDuplicate(user._id, { subscriptionId, replacementId: subscriptionId }) + await stripe.subscriptions.cancel(sub.id, { + cancellation_details: { comment: 'duplicate detected during checkout' }, + }) + } + } +} diff --git a/srv/domains/subs/agg.ts b/srv/domains/subs/agg.ts index c8c617628..6d7a11d4e 100644 --- a/srv/domains/subs/agg.ts +++ b/srv/domains/subs/agg.ts @@ -55,6 +55,14 @@ export const subsAgg = createAggregate({ } } + case 'cancelled-duplicate': { + return { + state: prev.state, + downgrade: prev.downgrade, + history, + } + } + case 'cancelled': { const endAt = new Date(prev.periodStart) endAt.setFullYear(meta.timestamp.getFullYear()) diff --git a/srv/domains/subs/cmd.ts b/srv/domains/subs/cmd.ts index ffb11bf54..10aba2c06 100644 --- a/srv/domains/subs/cmd.ts +++ b/srv/domains/subs/cmd.ts @@ -96,6 +96,15 @@ export const subsCmd = createCommands(domain.subscrip return { type: 'resumed' } }, + + async cancelDuplicate(cmd, agg) { + return { + type: 'cancelled-duplicate', + subscriptionId: cmd.subscriptionId, + replacementId: cmd.replacementId, + } + }, + async cancel(cmd, agg) { if (agg.state !== 'active') { throw new CommandError('Cannot cancel subscription - Subscription not active', 'NOT_ACTIVE') diff --git a/srv/domains/subs/types.ts b/srv/domains/subs/types.ts index 5502ad5b0..2a075935b 100644 --- a/srv/domains/subs/types.ts +++ b/srv/domains/subs/types.ts @@ -10,6 +10,7 @@ export type SubsEvt = periodStart: string } | { type: 'cancelled' } + | { type: 'cancelled-duplicate'; subscriptionId: string; replacementId?: string } | { type: 'upgraded'; tierId: string; priceId: string } | { type: 'downgraded'; tierId: string; priceId: string; activeAt: string } | { type: 'session-started'; sessionId: string; tierId: string } @@ -36,6 +37,7 @@ export type SubsCmd = productId: string subscription: Stripe.Subscription } + | { type: 'cancelDuplicate'; subscriptionId: string; replacementId?: string } | { type: 'cancel' } | { type: 'resume' } | { type: 'upgrade'; tierId: string; priceId: string } diff --git a/web/pages/Character/components/CharacterFolderView.tsx b/web/pages/Character/components/CharacterFolderView.tsx index eb5a7ff1e..353434020 100644 --- a/web/pages/Character/components/CharacterFolderView.tsx +++ b/web/pages/Character/components/CharacterFolderView.tsx @@ -442,15 +442,15 @@ function getChildFolders(tree: FolderTree, path: string, sort: SortDirection) { } const ChangeFolder: Component<{ char?: AppSchema.Character; close: () => void }> = (props) => { - const [actual, setActual] = createSignal('/') + const [name, setName] = createSignal(props.char?.folder || '') + const actual = createMemo(() => toFolderSlug(name())) createEffect( on( () => props.char, () => { if (!props.char) return - - setActual(toFolderSlug(props.char.folder || '/')) + setName(props.char.folder || '/') } ) ) @@ -489,10 +489,10 @@ const ChangeFolder: Component<{ char?: AppSchema.Character; close: () => void }> /> setActual(toFolderSlug(ev.currentTarget.value))} + value={name()} + onChange={(ev) => setName(ev.currentTarget.value)} /> diff --git a/web/pages/Chat/components/Message.tsx b/web/pages/Chat/components/Message.tsx index c0fa42733..3ce6312d6 100644 --- a/web/pages/Chat/components/Message.tsx +++ b/web/pages/Chat/components/Message.tsx @@ -16,6 +16,7 @@ import { Zap, Split, MoreHorizontal, + Braces, } from 'lucide-solid' import { Accessor, @@ -548,10 +549,6 @@ const MessageOptions: Component<{ onRemove: () => void showMore: Signal }> = (props) => { - const showInner = createMemo(() => - Object.values(props.ui.msgOptsInline || {}).some((v) => !v?.outer) - ) - const closer = (action: () => void) => { return () => { action() @@ -617,6 +614,20 @@ const MessageOptions: Component<{ icon: RefreshCw, }, + 'schema-regen': { + key: 'schema-regen', + class: 'refresh-btn', + label: 'Schema Regen', + outer: props.ui.msgOptsInline['schema-regen'], + show: + window.flags.reschema && + ((props.msg.json && props.last) || + (props.msg.adapter === 'image' && !!props.msg.imagePrompt)) && + !!props.msg.characterId, + onClick: () => !props.partial && retryJsonSchema(props.msg, props.msg), + icon: Braces, + }, + trash: { key: 'trash', label: 'Delete', @@ -632,6 +643,14 @@ const MessageOptions: Component<{ return items }) + const showInner = createMemo(() => { + for (const opt of Object.values(logic())) { + if (!opt.outer && opt.show) return true + } + + return false + }) + const order = createMemo(() => { open() logic() @@ -736,7 +755,7 @@ const MessageOption: Component<{