Skip to content

Commit 9f94eef

Browse files
authored
Fix folder rename input (#1098)
- fix persona squish/unsquish - fix change folder entry - prevent duplicate subscriptions - do not add char names from trim if not in end tokens - model categories by tier
1 parent 532dd0a commit 9f94eef

File tree

19 files changed

+272
-46
lines changed

19 files changed

+272
-46
lines changed

common/requests/util.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,14 @@ export function trimResponseV2(
6666
generated = generated.split(`${member.handle} :`).join(`${member.handle}:`)
6767
}
6868

69-
if (bots) {
70-
for (const bot of Object.values(bots)) {
71-
if (!bot) continue
72-
if (bot?._id === char._id) continue
73-
endTokens.push(`${bot.name}:`)
74-
}
75-
}
69+
/** Do not always add character names as stop tokens here */
70+
// if (bots) {
71+
// for (const bot of Object.values(bots)) {
72+
// if (!bot) continue
73+
// if (bot?._id === char._id) continue
74+
// endTokens.push(`${bot.name}:`)
75+
// }
76+
// }
7677

7778
let index = -1
7879
let trimmed = allEndTokens.concat(...endTokens).reduce((prev, endToken) => {

common/types/ui.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export type CustomUI = {
4242
chatQuoteColor: string
4343
}
4444

45-
export type MessageOption = 'edit' | 'regen' | 'trash' | 'fork' | 'prompt'
45+
export type MessageOption = 'edit' | 'regen' | 'trash' | 'fork' | 'prompt' | 'schema-regen'
4646

4747
export type UISettings = {
4848
theme: string
@@ -147,5 +147,6 @@ export const defaultUIsettings: UISettings = {
147147
fork: { outer: false, pos: 2 },
148148
regen: { outer: true, pos: 1 },
149149
trash: { outer: false, pos: 4 },
150+
'schema-regen': { outer: false, pos: 5 },
150151
},
151152
}

srv/adapter/agnaistic.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,15 @@ export const handleAgnaistic: ModelAdapter = async function* (opts) {
251251

252252
log.debug(`Prompt:\n${prompt}`)
253253

254-
const [submodel, override] = subPreset.subModel.split(',')
255-
256-
let params = [`type=text`, `id=${opts.user._id}`, `model=${submodel}`, `level=${level}`]
254+
const [submodel, override = ''] = subPreset.subModel.split(',')
255+
256+
let params = [
257+
`type=text`,
258+
`id=${opts.user._id}`,
259+
`model=${submodel}`,
260+
`level=${level}`,
261+
`sub_model=${override}`,
262+
]
257263
.filter((p) => !!p)
258264
.join('&')
259265

srv/adapter/generate.ts

+1
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ export async function createChatStream(
438438
lastMessage: opts.lastMessage,
439439
imageData: opts.imageData,
440440
jsonSchema: jsonSchema || opts.jsonSchema,
441+
reschemaPrompt: opts.reschemaPrompt,
441442
subscription,
442443
encoder,
443444
jsonValues: opts.jsonValues,

srv/adapter/payloads.ts

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ function getBasePayload(opts: AdapterProps, stops: string[] = []) {
114114
lists: opts.lists,
115115
previous: opts.previous,
116116
json_schema_v2: ensureSafeSchema(json_schema),
117+
reschema_prompt: opts.reschemaPrompt,
117118
json_schema,
118119
imageData: opts.imageData,
119120
context_size: opts.contextSize,

srv/adapter/type.ts

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export type GenerateRequestV2 = {
6969
impersonate?: AppSchema.Character
7070

7171
jsonSchema?: JsonField[]
72+
reschemaPrompt?: string
7273
jsonValues?: Record<string, any>
7374

7475
/** Base64 */
@@ -122,6 +123,7 @@ export type AdapterProps = {
122123
encoder?: TokenCounter
123124

124125
jsonSchema?: any
126+
reschemaPrompt?: string
125127
jsonValues: Record<string, any> | undefined
126128

127129
imageData?: string

srv/api/billing/checkout.ts

+24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { v4 } from 'uuid'
66
import { config } from '../../config'
77
import { billingCmd, domain } from '../../domains'
88
import { subsCmd } from '../../domains/subs/cmd'
9+
import { AppSchema } from '/common/types'
910

1011
export const startCheckout = handle(async ({ body, userId }) => {
1112
assertValid({ tierId: 'string', callback: 'string' }, body)
@@ -180,6 +181,10 @@ export const finishCheckout = handle(async ({ body, userId }) => {
180181
subscriptionId: subscription.id,
181182
})
182183

184+
if (user && subscription.id) {
185+
await ensureOnlyActiveSubscription(user, subscription.id)
186+
}
187+
183188
const config = await store.users.updateUser(userId, {
184189
sub: {
185190
tierId: agg.tierId,
@@ -205,3 +210,22 @@ export const finishCheckout = handle(async ({ body, userId }) => {
205210
await billingCmd.cancel(body.sessionId, { userId })
206211
}
207212
})
213+
214+
async function ensureOnlyActiveSubscription(user: AppSchema.User, subscriptionId: string) {
215+
// The user isn't an existing customer -- ignore
216+
if (!user.billing?.customerId) return
217+
218+
const subs = await stripe.subscriptions
219+
.list({ customer: user.billing.customerId, status: 'active' })
220+
.then((res) => res.data)
221+
.catch(() => [])
222+
223+
for (const sub of subs) {
224+
if (sub.id !== subscriptionId) {
225+
await subsCmd.cancelDuplicate(user._id, { subscriptionId, replacementId: subscriptionId })
226+
await stripe.subscriptions.cancel(sub.id, {
227+
cancellation_details: { comment: 'duplicate detected during checkout' },
228+
})
229+
}
230+
}
231+
}

srv/domains/subs/agg.ts

+8
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ export const subsAgg = createAggregate<SubsEvt, SubsAgg, 'subscriptions'>({
5555
}
5656
}
5757

58+
case 'cancelled-duplicate': {
59+
return {
60+
state: prev.state,
61+
downgrade: prev.downgrade,
62+
history,
63+
}
64+
}
65+
5866
case 'cancelled': {
5967
const endAt = new Date(prev.periodStart)
6068
endAt.setFullYear(meta.timestamp.getFullYear())

srv/domains/subs/cmd.ts

+9
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ export const subsCmd = createCommands<SubsEvt, SubsAgg, SubsCmd>(domain.subscrip
9696

9797
return { type: 'resumed' }
9898
},
99+
100+
async cancelDuplicate(cmd, agg) {
101+
return {
102+
type: 'cancelled-duplicate',
103+
subscriptionId: cmd.subscriptionId,
104+
replacementId: cmd.replacementId,
105+
}
106+
},
107+
99108
async cancel(cmd, agg) {
100109
if (agg.state !== 'active') {
101110
throw new CommandError('Cannot cancel subscription - Subscription not active', 'NOT_ACTIVE')

srv/domains/subs/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type SubsEvt =
1010
periodStart: string
1111
}
1212
| { type: 'cancelled' }
13+
| { type: 'cancelled-duplicate'; subscriptionId: string; replacementId?: string }
1314
| { type: 'upgraded'; tierId: string; priceId: string }
1415
| { type: 'downgraded'; tierId: string; priceId: string; activeAt: string }
1516
| { type: 'session-started'; sessionId: string; tierId: string }
@@ -36,6 +37,7 @@ export type SubsCmd =
3637
productId: string
3738
subscription: Stripe.Subscription
3839
}
40+
| { type: 'cancelDuplicate'; subscriptionId: string; replacementId?: string }
3941
| { type: 'cancel' }
4042
| { type: 'resume' }
4143
| { type: 'upgrade'; tierId: string; priceId: string }

web/pages/Character/components/CharacterFolderView.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -442,15 +442,15 @@ function getChildFolders(tree: FolderTree, path: string, sort: SortDirection) {
442442
}
443443

444444
const ChangeFolder: Component<{ char?: AppSchema.Character; close: () => void }> = (props) => {
445-
const [actual, setActual] = createSignal('/')
445+
const [name, setName] = createSignal(props.char?.folder || '')
446+
const actual = createMemo(() => toFolderSlug(name()))
446447

447448
createEffect(
448449
on(
449450
() => props.char,
450451
() => {
451452
if (!props.char) return
452-
453-
setActual(toFolderSlug(props.char.folder || '/'))
453+
setName(props.char.folder || '/')
454454
}
455455
)
456456
)
@@ -489,10 +489,10 @@ const ChangeFolder: Component<{ char?: AppSchema.Character; close: () => void }>
489489
/>
490490

491491
<TextInput
492-
helperMarkdown={`Folder names are 'normalized'.\nNoramlized name: ${actual()}`}
492+
helperMarkdown={`Folder names are 'normalized'.\nNormalized name: ${actual()}`}
493493
label="New Folder"
494-
value={props.char?.folder || '/'}
495-
onChange={(ev) => setActual(toFolderSlug(ev.currentTarget.value))}
494+
value={name()}
495+
onChange={(ev) => setName(ev.currentTarget.value)}
496496
/>
497497
</div>
498498
</RootModal>

web/pages/Chat/components/Message.tsx

+28-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Zap,
1717
Split,
1818
MoreHorizontal,
19+
Braces,
1920
} from 'lucide-solid'
2021
import {
2122
Accessor,
@@ -548,10 +549,6 @@ const MessageOptions: Component<{
548549
onRemove: () => void
549550
showMore: Signal<boolean>
550551
}> = (props) => {
551-
const showInner = createMemo(() =>
552-
Object.values(props.ui.msgOptsInline || {}).some((v) => !v?.outer)
553-
)
554-
555552
const closer = (action: () => void) => {
556553
return () => {
557554
action()
@@ -617,6 +614,20 @@ const MessageOptions: Component<{
617614
icon: RefreshCw,
618615
},
619616

617+
'schema-regen': {
618+
key: 'schema-regen',
619+
class: 'refresh-btn',
620+
label: 'Schema Regen',
621+
outer: props.ui.msgOptsInline['schema-regen'],
622+
show:
623+
window.flags.reschema &&
624+
((props.msg.json && props.last) ||
625+
(props.msg.adapter === 'image' && !!props.msg.imagePrompt)) &&
626+
!!props.msg.characterId,
627+
onClick: () => !props.partial && retryJsonSchema(props.msg, props.msg),
628+
icon: Braces,
629+
},
630+
620631
trash: {
621632
key: 'trash',
622633
label: 'Delete',
@@ -632,6 +643,14 @@ const MessageOptions: Component<{
632643
return items
633644
})
634645

646+
const showInner = createMemo(() => {
647+
for (const opt of Object.values(logic())) {
648+
if (!opt.outer && opt.show) return true
649+
}
650+
651+
return false
652+
})
653+
635654
const order = createMemo(() => {
636655
open()
637656
logic()
@@ -736,7 +755,7 @@ const MessageOption: Component<{
736755

737756
<Show when={!props.outer}>
738757
<Button
739-
class={`${props.class || ''} w-full`}
758+
class={`${props.class || ''} w-full min-w-max`}
740759
schema={props.schema || 'secondary'}
741760
onClick={props.onClick}
742761
size="sm"
@@ -758,6 +777,10 @@ function retryMessage(original: AppSchema.ChatMessage, split: SplitMessage) {
758777
}
759778
}
760779

780+
function retryJsonSchema(original: AppSchema.ChatMessage, split: SplitMessage) {
781+
msgStore.retrySchema(split.chatId, original._id)
782+
}
783+
761784
function renderMessage(ctx: ContextState, text: string, isUser: boolean, adapter?: string) {
762785
// Address unfortunate Showdown bug where spaces in code blocks are replaced with nbsp, except
763786
// it also encodes the ampersand, which results in them actually being rendered as `&amp;nbsp;`

web/pages/Settings/UISettings.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const msgInlineLabels: Record<UI.MessageOption, string> = {
2727
prompt: 'Prompt View',
2828
fork: 'Fork',
2929
trash: 'Delete',
30+
'schema-regen': 'Retry Schema',
3031
}
3132

3233
const UISettings: Component<{}> = () => {

web/shared/CustomSelect.tsx

+29-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export type CustomOption = {
1414
export const CustomSelect: Component<{
1515
buttonLabel: string | JSX.Element | ((opt: CustomOption) => JSX.Element | string)
1616
onSelect: (opt: CustomOption) => void
17-
options: CustomOption[]
17+
options?: CustomOption[]
18+
categories?: Array<{ name: string; options: CustomOption[] }>
1819
value: any
1920

2021
header?: JSX.Element
@@ -74,15 +75,33 @@ export const CustomSelect: Component<{
7475
</div>
7576
<RootModal show={open()} close={() => setOpen(false)} title={props.modalTitle}>
7677
<div class="flex flex-col gap-4">
77-
<div class="flex flex-wrap gap-2 pr-3">
78-
<OptionList
79-
header={props.header}
80-
options={props.options}
81-
onSelect={onSelect}
82-
selected={props.selected}
83-
search={props.search}
84-
/>
85-
</div>
78+
<Show when={props.categories}>
79+
<For each={props.categories}>
80+
{(category) => (
81+
<div class="flex flex-wrap gap-2 pr-3">
82+
<div class="bold text-md">{category.name}</div>
83+
<OptionList
84+
header={props.header}
85+
options={category.options}
86+
onSelect={onSelect}
87+
selected={props.selected}
88+
search={props.search}
89+
/>
90+
</div>
91+
)}
92+
</For>
93+
</Show>
94+
<Show when={props.options}>
95+
<div class="flex flex-wrap gap-2 pr-3">
96+
<OptionList
97+
header={props.header}
98+
options={props.options!}
99+
onSelect={onSelect}
100+
selected={props.selected}
101+
search={props.search}
102+
/>
103+
</div>
104+
</Show>
86105
</div>
87106
</RootModal>
88107
</div>

web/shared/PersonaAttributes.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const PersonaAttributes: Component<{
7070
if (key === 'text') {
7171
squished.push(values)
7272
} else {
73+
if (!values.trim()) continue
7374
squished.push(`${key}:\n${values}`)
7475
}
7576
}
@@ -82,6 +83,11 @@ const PersonaAttributes: Component<{
8283
const text = props.state.find((s) => s.key === 'text')
8384
if (!text) return
8485

86+
if (props.state.length === 1) {
87+
props.setter([{ key: 'personality', values: props.state[0].values }])
88+
return
89+
}
90+
8591
let matching = true
8692
for (const { values } of props.state) {
8793
if (!text.values.includes(values)) matching = false

0 commit comments

Comments
 (0)