1
0
Fork 0
tsgames/src/games/ai-story/components/header/connectionEditor.tsx

150 lines
5.5 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
import styles from './header.module.css';
import { Connection, HORDE_ANON_KEY, isHordeConnection, isKoboldConnection, type IConnection, type IHordeModel } from '../../tools/connection';
import { Instruct } from '../../contexts/state';
import { useInputState } from '@common/hooks/useInputState';
import { useInputCallback } from '@common/hooks/useInputCallback';
import { Huggingface } from '../../tools/huggingface';
interface IProps {
connection: IConnection;
setConnection: (c: IConnection) => void;
}
export const ConnectionEditor = ({ connection, setConnection }: IProps) => {
// kobold
const [connectionUrl, setConnectionUrl] = useInputState('');
// horde
const [apiKey, setApiKey] = useInputState(HORDE_ANON_KEY);
const [modelName, setModelName] = useInputState('');
const [instruct, setInstruct] = useInputState('');
const [modelTemplate, setModelTemplate] = useInputState('');
const [hordeModels, setHordeModels] = useState<IHordeModel[]>([]);
const [contextLength, setContextLength] = useState<number>(0);
const backendType = useMemo(() => {
if (isKoboldConnection(connection)) return 'kobold';
if (isHordeConnection(connection)) return 'horde';
return 'unknown';
}, [connection]);
const isOnline = useMemo(() => contextLength > 0, [contextLength]);
useEffect(() => {
setInstruct(connection.instruct);
if (isKoboldConnection(connection)) {
setConnectionUrl(connection.url);
Connection.getContextLength(connection).then(setContextLength);
} else if (isHordeConnection(connection)) {
setModelName(connection.model);
setApiKey(connection.apiKey || HORDE_ANON_KEY);
Connection.getHordeModels()
.then(m => setHordeModels(Array.from(m.values()).sort((a, b) => a.name.localeCompare(b.name))));
}
}, [connection]);
useEffect(() => {
if (modelName) {
Huggingface.findModelTemplate(modelName)
.then(template => {
if (template) {
setModelTemplate(template);
setInstruct(template);
}
});
}
}, [modelName]);
const setBackendType = useInputCallback((type) => {
if (type === 'kobold') {
setConnection({
instruct,
url: connectionUrl,
});
} else if (type === 'horde') {
setConnection({
instruct,
apiKey,
model: modelName,
});
}
}, [setConnection, connectionUrl, apiKey, modelName, instruct]);
const handleSetInstruct = useInputCallback((instruct: string) => {
setConnection({...connection, instruct});
}, [setConnection, connection]);
const handleBlurUrl = useCallback(() => {
const regex = /^(?:http(s?):\/\/)?(.*?)\/?$/i;
const url = connectionUrl.replace(regex, 'http$1://$2');
setConnection({
instruct,
url,
});
}, [connectionUrl, instruct, setConnection]);
const handleBlurHorde = useCallback(() => {
setConnection({
instruct,
apiKey,
model: modelName,
});
}, [apiKey, modelName, instruct, setConnection]);
return (
<div class={styles.connectionEditor}>
<select value={backendType} onChange={setBackendType}>
<option value='kobold'>Kobold CPP</option>
<option value='horde'>Horde</option>
</select>
<select value={instruct} onChange={handleSetInstruct} title='Instruct template'>
{modelName && modelTemplate && <optgroup label='Native model template'>
<option value={modelTemplate} title='Native for model'>{modelName}</option>
</optgroup>}
<optgroup label='Manual templates'>
{Object.entries(Instruct).map(([label, value]) => (
<option value={value} key={value}>
{label.toLowerCase()}
</option>
))}
</optgroup>
{instruct !== modelTemplate && <optgroup label='Custom'>
<option value={connection.instruct}>Custom</option>
</optgroup>}
</select>
{isKoboldConnection(connection) && <input
value={connectionUrl}
onInput={setConnectionUrl}
onBlur={handleBlurUrl}
class={isOnline ? styles.valid : styles.invalid}
/>}
{isHordeConnection(connection) && <>
<input
placeholder='Horde API key'
title='Horde API key'
value={apiKey}
onInput={setApiKey}
onBlur={handleBlurHorde}
/>
<select
value={modelName}
onChange={setModelName}
onBlur={handleBlurHorde}
title='Horde model'
>
{hordeModels.map((m) => (
<option value={m.name} key={m.name}>
{m.name} ({m.maxLength}/{m.maxContext})
</option>
))}
</select>
</>}
</div>
);
};