Skip to content

Commit

Permalink
Fix json schema auto-complete (#992)
Browse files Browse the repository at this point in the history
- Automatically update references in history and response on field name change
- Fix mobile view of json fields
- Add help window
- Char form search params for tabs
  • Loading branch information
sceuick authored Aug 4, 2024
1 parent 842787f commit 94e000d
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 70 deletions.
3 changes: 2 additions & 1 deletion srv/adapter/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,8 @@ export async function createChatStream(
opts.char?.json?.enabled &&
opts.char.json?.history &&
opts.char.json?.response &&
opts.char.json?.schema?.length
opts.char.json?.schema?.length &&
opts.char.json.schema.some((s) => !s.disabled)

if (subscription?.preset?.jsonSchemaCapable && isUsableSchema) {
jsonSchema = opts.char.json?.schema
Expand Down
4 changes: 2 additions & 2 deletions srv/api/chat/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export const generateMessageV2 = handle(async (req, res) => {
let error = false
let meta = { ctx: metadata.settings.maxContextLength, char: metadata.size, len: metadata.length }

const hydrator = entities.char.json ? jsonHydrator(entities.char.json) : undefined
const hydrator = entities.char.json?.enabled ? jsonHydrator(entities.char.json) : undefined
let hydration: HydratedJson | undefined
let jsonPartial: any

Expand Down Expand Up @@ -553,7 +553,7 @@ async function handleGuestGenerate(body: GenRequest, req: AppRequest, res: Respo
let error = false
let meta = { ctx: entities.settings.maxContextLength, char: entities.size, len: entities.length }

const hydrator = body.char.json ? jsonHydrator(body.char.json) : undefined
const hydrator = body.char.json?.enabled ? jsonHydrator(body.char.json) : undefined
let hydration: HydratedJson | undefined
let jsonPartial: any

Expand Down
72 changes: 64 additions & 8 deletions web/pages/Character/CharacterSchema.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
import { Component, Show, createEffect, createMemo, createSignal, on } from 'solid-js'
import { Component, Setter, Show, createEffect, createMemo, createSignal, on } from 'solid-js'
import Button from '/web/shared/Button'
import { RootModal } from '/web/shared/Modal'
import { HelpModal, RootModal } from '/web/shared/Modal'
import { JsonSchema } from '/web/shared/JsonSchema'
import TextInput from '/web/shared/TextInput'
import { FormLabel } from '/web/shared/FormLabel'
import { Toggle } from '/web/shared/Toggle'
import { CharacterJsonSchema } from '/common/types/library'
import { Card, Pill, TitleCard } from '/web/shared/Card'
import { JSON_NAME_RE } from '/common/util'
import { JSON_NAME_RE, neat } from '/common/util'
import { JsonField } from '/common/prompt'
import { AutoComplete } from '/web/shared/AutoComplete'
import { characterStore } from '/web/store'
import { CircleHelp } from 'lucide-solid'

const helpMarkdown = neat`
You can return many values using JSON schemas and control the structure of your response.
For example you could define the following fields:
- **response**: string
- **character health percentage**: number
You can then reference these fields in your **response** and **history** templates:
Response Template
\`\`\`
{{response}}
\`HP: {{character health percentage}}%\`
\`\`\`
History Template
\`\`\`
{{response}}
(User's health: {{character health percentage}}%)
\`\`\`
**Tips**:
- Use instructive fields names. This tells the AI how to generate that field.
- If a field is disabled, it won't be included in the generation.
- Order may be important. For example, if you have a multi-character schema you may name the fields like this:
-- **Steve's message to Linda**
-- **Linda's response to Steve**
`

export const CharacterSchema: Component<{
characterId?: string
Expand Down Expand Up @@ -80,7 +109,17 @@ export const CharacterSchema: Component<{
}
})

const onAutoComplete = (opt: { label: string }) => {
const onFieldNameChange = (from: string, to: string) => {
const res = response().split(`{{${from}}}`).join(`{{${to}}}`)
const his = hist().split(`{{${from}}}`).join(`{{${to}}}`)

histRef.value = his
respRef.value = res
setResponse(res)
setHistory(his)
}

const onAutoComplete = (setter: Setter<string>) => (opt: { label: string }) => {
const ref = auto() === 'history' ? histRef : auto() === 'response' ? respRef : undefined

if (ref) {
Expand All @@ -102,6 +141,7 @@ export const CharacterSchema: Component<{

const next = `${before}${opt.label}${after}`
ref.value = next
setter(next)
ref.focus()
ref.setSelectionRange(
before.length + opt.label.length,
Expand Down Expand Up @@ -130,6 +170,8 @@ export const CharacterSchema: Component<{
}
}

setHistErr('')
setResErr('')
setShow(false)
}

Expand Down Expand Up @@ -165,18 +207,28 @@ export const CharacterSchema: Component<{
}
>
<div class="flex flex-col gap-2 text-sm">
<div class="flex w-full justify-center">
<div class="flex w-full justify-center gap-2">
<Pill type="premium">
This feature is in beta. Please raise bugs on Discord or GitHub.
</Pill>

<HelpModal
title="Information"
cta={
<Button size="sm">
<CircleHelp size={24} />
</Button>
}
markdown={helpMarkdown}
/>
</div>
<Card class="relative">
<Show when={auto() === 'response'}>
<AutoComplete
options={vars()}
close={() => setAuto('')}
dir="down"
onSelect={onAutoComplete}
onSelect={onAutoComplete(setResponse)}
/>
</Show>
<TextInput
Expand Down Expand Up @@ -213,7 +265,7 @@ export const CharacterSchema: Component<{
options={vars()}
close={() => setAuto('')}
dir="down"
onSelect={onAutoComplete}
onSelect={onAutoComplete(setHistory)}
/>
</Show>
<TextInput
Expand Down Expand Up @@ -246,7 +298,11 @@ export const CharacterSchema: Component<{
/>
</Card>

<JsonSchema inherit={schema()} update={(ev) => setCandidate(ev)} />
<JsonSchema
inherit={schema()}
update={(ev) => setCandidate(ev)}
onNameChange={onFieldNameChange}
/>
</div>
</RootModal>
</div>
Expand Down
14 changes: 11 additions & 3 deletions web/pages/Character/CreateCharacterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
chatStore,
userStore,
} from '../../store'
import { useNavigate } from '@solidjs/router'
import { useNavigate, useSearchParams } from '@solidjs/router'
import PersonaAttributes from '../../shared/PersonaAttributes'
import AvatarIcon from '../../shared/AvatarIcon'
import Select, { Option } from '../../shared/Select'
Expand Down Expand Up @@ -99,6 +99,7 @@ export const CreateCharacterForm: Component<{
onSuccess?: (char: AppSchema.Character) => void
}> = (props) => {
let personaRef: any
const [search, setSearch] = useSearchParams()
const nav = useNavigate()
const user = userStore()

Expand Down Expand Up @@ -304,7 +305,7 @@ export const CreateCharacterForm: Component<{
() => !!props.chat?.overrides && props.chat.characterId === props.editId
)

const tabs = useTabs(['Persona', 'Voice', 'Images', 'Advanced'], 0)
const tabs = useTabs(['Persona', 'Voice', 'Images', 'Advanced'], +(search.char_tab || '0'))

let spriteRef: any

Expand Down Expand Up @@ -386,7 +387,14 @@ export const CreateCharacterForm: Component<{
</Show>
</div>

<Tabs select={tabs.select} selected={tabs.selected} tabs={tabs.tabs} />
<Tabs
select={(id) => {
tabs.select(id)
setSearch({ char_tab: id })
}}
selected={tabs.selected}
tabs={tabs.tabs}
/>

<div class="flex flex-col gap-2" classList={{ hidden: tabs.current() !== 'Persona' }}>
<Button
Expand Down
5 changes: 3 additions & 2 deletions web/shared/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,17 @@ export const Pill: Component<{
return {
'background-color': rgba.background || '',
'border-color': `var(--${type}-${base})`,
color: `var(--text-${text})`,
color: (props.class || '').includes('text-') ? undefined : `var(--text-${text})`,
}
})

return (
<span
class={`flex w-fit items-center border-[1px] px-2 py-1 ${props.class || ''}`}
class={`flex items-center border-[1px] px-2 py-1 ${props.class || ''}`}
style={bg()}
onClick={props.onClick}
classList={{
'w-fit': !props.class?.includes('w-'),
'px-2': !props.class?.includes('px-'),
'border-[1px]': !props.small,
'border-0': props.small,
Expand Down
Loading

0 comments on commit 94e000d

Please sign in to comment.