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

155 lines
5.6 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
import styles from './header.module.css';
import { Connection, HORDE_ANON_KEY, type IConnection, type IHordeModel } from '../../tools/connection';
import { useInputState } from '@common/hooks/useInputState';
import { useInputCallback } from '@common/hooks/useInputCallback';
import { Huggingface } from '../../tools/huggingface';
import { INSTRUCTS } from '../../contexts/state';
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 isOnline = useMemo(() => contextLength > 0, [contextLength]);
useEffect(() => {
setInstruct(connection.instruct);
connection.url && setConnectionUrl(connection.url);
connection.model && setModelName(connection.model);
setApiKey(connection.apiKey || HORDE_ANON_KEY);
if (connection.type === 'kobold') {
Connection.getContextLength(connection).then(setContextLength);
} else if (connection.type === 'horde') {
Connection.getHordeModels()
.then(m => setHordeModels(Array.from(
m.values())
.sort((a, b) =>
b.maxContext - a.maxContext || a.name.localeCompare(b.name)
)
));
}
}, [connection]);
useEffect(() => {
if (modelName) {
Huggingface.findModelTemplate(modelName)
.then(template => {
if (template) {
setModelTemplate(template);
}
});
}
}, [modelName]);
const setBackendType = useInputCallback((type) => {
switch (type) {
case 'kobold':
case 'horde':
setConnection({
type,
instruct,
url: connectionUrl,
apiKey,
model: modelName,
});
break;
}
}, [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({
type: 'kobold',
instruct,
url,
apiKey,
model: modelName,
});
}, [connectionUrl, instruct, setConnection]);
const handleBlurHorde = useCallback(() => {
setConnection({
type: 'horde',
instruct,
url: connectionUrl,
apiKey,
model: modelName,
});
}, [apiKey, modelName, instruct, setConnection]);
return (
<div class={styles.connectionEditor}>
<select value={connection.type} 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(INSTRUCTS).map(([label, value]) => (
<option value={value} key={value}>
{label}
</option>
))}
</optgroup>
{instruct !== modelTemplate
&& !Object.values(INSTRUCTS).includes(instruct)
&& <optgroup label='Custom'>
<option value={connection.instruct}>Custom</option>
</optgroup>
}
</select>
{connection.type === 'kobold' && <input
value={connectionUrl}
onInput={setConnectionUrl}
onBlur={handleBlurUrl}
class={isOnline ? styles.valid : styles.invalid}
/>}
{connection.type === 'horde' && <>
<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>
);
};