LLM settings from url

This commit is contained in:
Daniel O'Connell
2023-10-18 22:37:03 +02:00
parent 1cd14cd26b
commit 9d1e9022b8
6 changed files with 459 additions and 363 deletions
+4 -5
View File
@@ -44,20 +44,19 @@ def semantic():
@app.route('/chat', methods=['POST'])
@cross_origin()
def chat():
query = request.json.get('query')
session_id = request.json.get('sessionId')
history = request.json.get('history', [])
settings = Settings(**request.json.get('settings', {}))
def run(callback):
return run_query(session_id, query, history, settings, callback)
settings = request.json.get('settings', {})
def formatter(item):
if isinstance(item, Exception):
item = {'state': 'error', 'error': str(item)}
return json.dumps(item)
def run(callback):
return run_query(session_id, query, history, Settings(**settings), callback)
return Response(stream_with_context(stream(stream_callback(run, formatter))), mimetype='text/event-stream')
+6 -1
View File
@@ -191,7 +191,12 @@ def run_query(session_id: str, query: str, history: List[Dict], settings: Settin
callbacks = [LoggerCallbackHandler(session_id=session_id, query=query, history=history)]
if callback:
callbacks += [BroadcastCallbackHandler(callback)]
chat_model = get_model(streaming=True, callbacks=callbacks, max_tokens=settings.max_response_tokens)
chat_model = get_model(
streaming=True,
callbacks=callbacks,
max_tokens=settings.max_response_tokens,
model=settings.completions
)
chain = LLMChain(
llm=chat_model,
+89
View File
@@ -1,8 +1,22 @@
import { ChangeEvent } from "react";
import type { Parseable } from "../types";
// const Colours = ["blue", "cyan", "teal", "green", "amber"].map(
// colour => `bg-${colour}-100 border-${colour}-300 text-${colour}-800`
// );
// this would be nice, but Tailwind needs te actual string of the class to be in
// the source file for it to be included in the build
type NumberParser = (v: Parseable) => number;
type InputFields = {
field: string;
label: string;
value?: Parseable;
min?: string | number;
max?: string | number;
step?: string | number;
parser?: NumberParser;
updater: (v: any) => any;
};
export const Colours = [
"bg-red-100 border-red-300 text-red-800",
@@ -30,3 +44,78 @@ export const A: React.FC<{
<a className={className}>{children}</a>
);
};
const between =
(
min: Parseable,
max: Parseable,
parser: NumberParser,
updater: (v: any) => any
) =>
(event: ChangeEvent) => {
let num = parser((event.target as HTMLInputElement).value);
if (isNaN(num)) {
return;
} else if (min !== undefined && num < parser(min)) {
num = parser(min);
} else if (max !== undefined && num > parser(max)) {
num = parser(max);
}
updater(num);
};
export const SectionHeader = ({ text }: { text: string }) => (
<h4 className="col-span-4 text-lg font-semibold">{text}</h4>
);
export const NumberInput = ({
field,
value,
label,
min,
max,
updater,
// this cast is just to satisfy typescript - it can handle numbers, strings and undefined just fine
parser = (v) => parseInt(v as string, 10),
}: InputFields) => (
<>
<label htmlFor={field} className="col-span-3 inline-block">
{label}:{" "}
</label>
<input
name={field}
value={value}
className="w-20"
onChange={between(min, max, parser, updater)}
type="number"
/>
</>
);
export const Slider = ({
field,
value,
label,
min = 0,
max = 1,
step = 0.01,
// this cast is just to satisfy typescript - it can handle numbers, strings and undefined just fine
parser = (v) => parseFloat(v as string),
updater,
}: InputFields) => (
<>
<label htmlFor={field} className="col-span-2">
{label}:
</label>
<input
name={field}
className="col-span-2"
value={value}
onChange={between(min, max, parser, updater)}
type="range"
min={min}
max={max}
step={step}
/>
</>
);
+326
View File
@@ -0,0 +1,326 @@
import { ChangeEvent } from "react";
import TextareaAutosize from "react-textarea-autosize";
import type { Parseable, LLMSettings, Entry, Mode } from "../types";
import { SectionHeader, NumberInput, Slider } from "../components/html";
type LLMSettingsParsers = {
[key: string]:
| ((v: number | undefined) => any)
| ((v: string | undefined) => any)
| ((v: object | undefined) => any);
};
const DEFAULT_PROMPTS = {
context:
"You are a helpful assistant knowledgeable about AI Alignment and Safety. " +
'Please give a clear and coherent answer to the user\'s questions.(written after "Q:") ' +
"using the following sources. Each source is labeled with a letter. Feel free to " +
"use the sources in any order, and try to use multiple sources in your answers.\n\n",
history:
"\n\n" +
'Before the question ("Q: "), there will be a history of previous questions and answers. ' +
"These sources only apply to the last question. any sources used in previous answers " +
"are invalid.",
question:
"In your answer, please cite any claims you make back to each source " +
"using the format: [a], [b], etc. If you use multiple sources to make a claim " +
'cite all of them. For example: "AGI is concerning [c, d, e]."\n\n',
modes: {
default: "",
concise:
"Answer very concisely, getting to the crux of the matter in as " +
"few words as possible. Limit your answer to 1-2 sentences.\n\n",
rookie:
"This user is new to the field of AI Alignment and Safety - don't " +
"assume they know any technical terms or jargon. Still give a complete answer " +
"without patronizing the user, but take any extra time needed to " +
"explain new concepts or to illustrate your answer with examples. " +
"Put extra effort into explaining the intuition behind concepts " +
"rather than just giving a formal definition.\n\n",
},
};
export const MODELS = {
"gpt-3.5-turbo": { maxNumTokens: 4095, topKBlocks: 10 },
"gpt-3.5-turbo-16k": { maxNumTokens: 16385, topKBlocks: 30 },
"gpt-4": { maxNumTokens: 8192, topKBlocks: 20 },
/* 'gpt-4-32k': {maxNumTokens: 32768, topKBlocks: 30}, */
};
export const ENCODERS = ["cl100k_base"];
/** Update the given `obj` so that it has `val` at the given path.
*
* e.g.
* updateIn({a: {b: 123}}, ['a', 'b', 'c'], 42) == {a: {b: 123, c: 42}}
* updateIn({a: {b: 123}}, ['z', 'y', 'x'], 42) == {a: {b: 123}, z: {y: {x: 42}}}
*/
export const updateIn = (
obj: { [key: string]: any },
[head, ...rest]: string[],
val: any
) => {
if (!head) {
// No path provided - do nothing
} else if (!rest || rest.length == 0) {
obj[head] = val;
} else {
if (obj[head] === undefined) {
obj[head] = {};
}
updateIn(obj[head], rest, val);
}
return obj;
};
/** Create a settings object in which all items in the `overrides` object will be parsed appropriately
*
* `parsers` should be an object mapping settings fields to functions that will return a valid setting.
* The parser functions should have default values that will be used if the provided value is undefined.
*/
const parseSettings = (overrides: LLMSettings, parsers: LLMSettingsParsers) =>
Object.entries(parsers).reduce(
(settings, [key, parser]) =>
updateIn(settings, [key], parser(overrides[key])),
{}
);
/** Make a parser function from the provided `defaultVal`.
*
* If the parsed value is undefined, `defaultVal` will be returned, otherwise it will be parsed as
* a value of the same type as `defaultVal`.
* If `defaultVal` is an object, it will return a parser that will recursively search for appropriate keys.
*/
const withDefault = (defaultVal: any) => {
if (typeof defaultVal === "number" && defaultVal % 1 === 0) {
return (v: string | undefined): number =>
v !== undefined ? parseInt(v, 10) : defaultVal;
} else if (typeof defaultVal === "number") {
return (v: string | undefined): number =>
v !== undefined ? parseFloat(v) : defaultVal;
} else if (typeof defaultVal === "object") {
const parsers = Object.entries(defaultVal).reduce(
(parsers, [key, val]) => updateIn(parsers, [key], withDefault(val)),
{}
);
return (v: object | undefined): object => parseSettings(v || {}, parsers);
} else {
return (v: any | undefined): any => v || defaultVal;
}
};
const SETTINGS_PARSERS = {
prompts: withDefault(DEFAULT_PROMPTS),
mode: (v: string | undefined) => (v || "default") as Mode,
completions: withDefault("gpt-3.5-turbo"),
encoder: withDefault("cl100k_base"),
topKBlocks: withDefault(MODELS["gpt-3.5-turbo"].topKBlocks), // the number of blocks to use as citations
maxNumTokens: withDefault(MODELS["gpt-3.5-turbo"].maxNumTokens),
tokensBuffer: withDefault(50), // the number of tokens to leave as a buffer when calculating remaining tokens
maxHistory: withDefault(10), // the max number of previous items to use as history
historyFraction: withDefault(0.25), // the (approximate) fraction of num_tokens to use for history text before truncating
contextFraction: withDefault(0.5), // the (approximate) fraction of num_tokens to use for context text before truncating
};
export const makeSettings = (overrides: LLMSettings) =>
parseSettings(
Object.entries(overrides).reduce(
(acc, [key, val]) => updateIn(acc, key.split("."), val),
{}
),
SETTINGS_PARSERS
);
type ChatSettingsParams = {
settings: LLMSettings;
changeSetting: (path: string[], value: any) => void;
};
export const ChatSettings = ({
settings,
changeSetting,
}: ChatSettingsParams) => {
const changeVal = (field: string, value: any) =>
changeSetting([field], value);
const update = (field: string) => (event: ChangeEvent) =>
changeVal(field, (event.target as HTMLInputElement).value);
const updateNum = (field: string) => (num: Parseable) =>
changeVal(field, num);
return (
<div
className="chat-settings mx-5 grid w-[400px] flex-none grid-cols-4 gap-4 border-2 outline-black"
style={{ height: "fit-content" }}
>
<SectionHeader text="Models" />
<label htmlFor="completions-model" className="col-span-2">
Completions model:
</label>
<select
name="completions-model"
className="col-span-2"
value={settings.completions}
onChange={(event: ChangeEvent) => {
const value = (event.target as HTMLInputElement).value;
const { maxNumTokens, topKBlocks } =
MODELS[value as keyof typeof MODELS];
const prevNumTokens =
MODELS[settings.completions as keyof typeof MODELS].maxNumTokens;
const prevTopKBlocks =
MODELS[settings.completions as keyof typeof MODELS].topKBlocks;
if (settings.maxNumTokens === prevNumTokens) {
changeVal("maxNumTokens", maxNumTokens);
} else {
changeVal(
"maxNumTokens",
Math.min(settings.maxNumTokens || 0, maxNumTokens)
);
}
if (settings.topKBlocks === prevTopKBlocks) {
changeVal("topKBlocks", topKBlocks);
}
changeVal("completions", value);
}}
>
{Object.keys(MODELS).map((name) => (
<option value={name} key={name}>
{name}
</option>
))}
</select>
<label htmlFor="encoder" className="col-span-2">
Encoder:
</label>
<select
name="encoder"
className="col-span-2"
value={settings.encoder}
onChange={update("encoder")}
>
{ENCODERS.map((name) => (
<option value={name} key={name}>
{name}
</option>
))}
</select>
<SectionHeader text="Token options" />
<NumberInput
value={settings.maxNumTokens}
field="maxNumTokens"
label="Tokens"
min="1"
max={MODELS[settings.completions as keyof typeof MODELS].maxNumTokens}
updater={updateNum("maxNumTokens")}
/>
<NumberInput
field="tokensBuffer"
value={settings.tokensBuffer}
label="Number of tokens to leave as a buffer when calculating remaining tokens"
min="0"
max={settings.maxNumTokens}
updater={updateNum("tokensBuffer")}
/>
<SectionHeader text="Prompt options" />
<NumberInput
value={settings.topKBlocks}
field="topKBlocks"
label="Number of blocks to use as citations"
min="1"
updater={updateNum("topKBlocks")}
/>
<NumberInput
value={settings.maxHistory}
field="maxHistory"
label="The max number of previous interactions to use"
min="0"
updater={updateNum("maxHistory")}
/>
<Slider
value={settings.contextFraction}
field="contextFraction"
label="Approximate fraction of num_tokens to use for citations text before truncating"
updater={updateNum("contextFraction")}
/>
<Slider
value={settings.historyFraction}
field="historyFraction"
label="Approximate fraction of num_tokens to use for history text before truncating"
updater={updateNum("historyFraction")}
/>
</div>
);
};
type ChatPromptParams = {
settings: LLMSettings;
query: string;
history: Entry[];
changeSetting: (path: string[], value: any) => void;
};
export const ChatPrompts = ({
settings,
query,
history,
changeSetting,
}: ChatPromptParams) => {
const updatePrompt =
(...path: string[]) =>
(event: ChangeEvent) =>
changeSetting(
["prompts", ...path],
(event.target as HTMLInputElement).value
);
return (
<div className="chat-prompts mx-5 w-[400px] flex-none border-2 p-5 outline-black">
<details open>
<summary>Source prompt</summary>
<TextareaAutosize
className="border-gray w-full border px-1"
value={settings?.prompts?.context}
onChange={updatePrompt("context")}
/>
<div>(This is where sources will be injected)</div>
</details>
{history.length > 0 && (
<details open>
<summary>History prompt</summary>
<TextareaAutosize
className="border-gray w-full border px-1"
value={settings?.prompts?.history}
onChange={updatePrompt("history")}
/>
<details>
<summary>History</summary>
{history
.slice(Math.max(0, history.length - (settings.maxHistory || 0)))
.map((entry, i) => (
<div className="history-entry" key={i}>
{entry.content}
</div>
))}
</details>
</details>
)}
<details open>
<summary>Question prompt</summary>
<TextareaAutosize
className="border-gray w-full border px-1"
value={settings?.prompts?.question}
onChange={updatePrompt("question")}
/>
<TextareaAutosize
className="border-gray w-full border px-1"
value={settings?.prompts?.modes[settings.mode || "default"]}
onChange={updatePrompt("modes", settings.mode || "default")}
/>
</details>
<div>Q: {query}</div>
</div>
);
};
+32 -357
View File
@@ -1,373 +1,29 @@
import type { NextPage } from "next";
import { useState, useEffect, ChangeEvent } from "react";
import TextareaAutosize from "react-textarea-autosize";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import Head from "next/head";
import Link from "next/link";
import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch";
import type { Mode, Entry, LLMSettings } from "../types";
import Header from "../components/header";
import Chat from "../components/chat";
import { Controls } from "../components/controls";
const MAX_FOLLOWUPS = 4;
const DEFAULT_PROMPTS = {
context:
"You are a helpful assistant knowledgeable about AI Alignment and Safety. " +
'Please give a clear and coherent answer to the user\'s questions.(written after "Q:") ' +
"using the following sources. Each source is labeled with a letter. Feel free to " +
"use the sources in any order, and try to use multiple sources in your answers.\n\n",
history:
"\n\n" +
'Before the question ("Q: "), there will be a history of previous questions and answers. ' +
"These sources only apply to the last question. Any sources used in previous answers " +
"are invalid.",
question:
"In your answer, please cite any claims you make back to each source " +
"using the format: [a], [b], etc. If you use multiple sources to make a claim " +
'cite all of them. For example: "AGI is concerning [c, d, e]."\n\n',
modes: {
default: "",
concise:
"Answer very concisely, getting to the crux of the matter in as " +
"few words as possible. Limit your answer to 1-2 sentences.\n\n",
rookie:
"This user is new to the field of AI Alignment and Safety - don't " +
"assume they know any technical terms or jargon. Still give a complete answer " +
"without patronizing the user, but take any extra time needed to " +
"explain new concepts or to illustrate your answer with examples. " +
"Put extra effort into explaining the intuition behind concepts " +
"rather than just giving a formal definition.\n\n",
},
};
const MODELS = {
"gpt-3.5-turbo": { maxNumTokens: 4095, topKBlocks: 10 },
"gpt-3.5-turbo-16k": { maxNumTokens: 16385, topKBlocks: 30 },
"gpt-4": { maxNumTokens: 8192, topKBlocks: 20 },
/* 'gpt-4-32k': {maxNumTokens: 32768, topKBlocks: 30}, */
};
const DEFAULT_SETTINGS = {
prompts: DEFAULT_PROMPTS,
mode: "default" as Mode,
completions: "gpt-3.5-turbo",
encoder: "cl100k_base",
topKBlocks: MODELS["gpt-3.5-turbo"].topKBlocks, // the number of blocks to use as citations
maxNumTokens: MODELS["gpt-3.5-turbo"].maxNumTokens,
tokensBuffer: 50, // the number of tokens to leave as a buffer when calculating remaining tokens
maxHistory: 10, // the max number of previous items to use as history
historyFraction: 0.25, // the (approximate) fraction of num_tokens to use for history text before truncating
contextFraction: 0.5, // the (approximate) fraction of num_tokens to use for context text before truncating
};
const ENCODERS = ["cl100k_base"];
const updateIn = (
obj: { [key: string]: any },
[head, ...rest]: string[],
val: any
) => {
if (!head) {
// No path provided - do nothing
} else if (!rest || rest.length == 0) {
obj[head] = val;
} else {
updateIn(obj[head], rest, val);
}
return obj;
};
type Parseable = string | number | undefined;
type NumberParser = (v: Parseable) => number;
type InputFields = {
field: string;
label: string;
value?: Parseable;
min?: string | number;
max?: string | number;
step?: string | number;
parser?: NumberParser;
updater: (v: any) => any;
};
const between =
(
min: Parseable,
max: Parseable,
parser: NumberParser,
updater: (v: any) => any
) =>
(event: ChangeEvent) => {
let num = parser((event.target as HTMLInputElement).value);
if (isNaN(num)) {
return;
} else if (min !== undefined && num < parser(min)) {
num = parser(min);
} else if (max !== undefined && num > parser(max)) {
num = parser(max);
}
updater(num);
};
const SectionHeader = ({ text }: { text: string }) => (
<h4 className="col-span-4 text-lg font-semibold">{text}</h4>
);
const NumberInput = ({
field,
value,
label,
min,
max,
updater,
// this cast is just to satisfy typescript - it can handle numbers, strings and undefined just fine
parser = (v) => parseInt(v as string, 10),
}: InputFields) => (
<>
<label htmlFor={field} className="col-span-3 inline-block">
{label}:{" "}
</label>
<input
name={field}
value={value}
className="w-20"
onChange={between(min, max, parser, updater)}
type="number"
/>
</>
);
const Slider = ({
field,
value,
label,
min = 0,
max = 1,
step = 0.01,
// this cast is just to satisfy typescript - it can handle numbers, strings and undefined just fine
parser = (v) => parseFloat(v as string),
updater,
}: InputFields) => (
<>
<label htmlFor={field} className="col-span-2">
{label}:
</label>
<input
name={field}
className="col-span-2"
value={value}
onChange={between(min, max, parser, updater)}
type="range"
min={min}
max={max}
step={step}
/>
</>
);
type ChatSettingsParams = {
settings: LLMSettings;
updateSettings: (updater: (settings: LLMSettings) => LLMSettings) => void;
};
const ChatSettings = ({ settings, updateSettings }: ChatSettingsParams) => {
const changeVal = (field: string, value: any) =>
updateSettings((prev) => ({ ...prev, [field]: value }));
const update = (setting: string) => (event: ChangeEvent) => {
changeVal(setting, (event.target as HTMLInputElement).value);
};
const updateNum = (field: string) => (num: Parseable) =>
changeVal(field, num);
return (
<div
className="chat-settings mx-5 grid w-[400px] flex-none grid-cols-4 gap-4 border-2 outline-black"
style={{ height: "fit-content" }}
>
<SectionHeader text="Models" />
<label htmlFor="completions-model" className="col-span-2">
Completions model:
</label>
<select
name="completions-model"
className="col-span-2"
value={settings.completions}
onChange={(event: ChangeEvent) => {
const value = (event.target as HTMLInputElement).value;
const { maxNumTokens, topKBlocks } =
MODELS[value as keyof typeof MODELS];
const prevNumTokens =
MODELS[settings.completions as keyof typeof MODELS].maxNumTokens;
const prevTopKBlocks =
MODELS[settings.completions as keyof typeof MODELS].topKBlocks;
if (settings.maxNumTokens === prevNumTokens) {
changeVal("maxNumTokens", maxNumTokens);
} else {
changeVal(
"maxNumTokens",
Math.min(settings.maxNumTokens || 0, maxNumTokens)
);
}
if (settings.topKBlocks === prevTopKBlocks) {
changeVal("topKBlocks", topKBlocks);
}
changeVal("completions", value);
}}
>
{Object.keys(MODELS).map((name) => (
<option value={name} key={name}>
{name}
</option>
))}
</select>
<label htmlFor="encoder" className="col-span-2">
Encoder:
</label>
<select
name="encoder"
className="col-span-2"
value={settings.encoder}
onChange={update("encoder")}
>
{ENCODERS.map((name) => (
<option value={name} key={name}>
{name}
</option>
))}
</select>
<SectionHeader text="Token options" />
<NumberInput
value={settings.maxNumTokens}
field="maxNumTokens"
label="Tokens"
min="1"
max={MODELS[settings.completions as keyof typeof MODELS].maxNumTokens}
updater={updateNum("maxNumTokens")}
/>
<NumberInput
field="tokensBuffer"
value={settings.tokensBuffer}
label="Number of tokens to leave as a buffer when calculating remaining tokens"
min="0"
max={settings.maxNumTokens}
updater={updateNum("tokensBuffer")}
/>
<SectionHeader text="Prompt options" />
<NumberInput
value={settings.topKBlocks}
field="topKBlocks"
label="Number of blocks to use as citations"
min="1"
updater={updateNum("topKBlocks")}
/>
<NumberInput
value={settings.maxHistory}
field="maxHistory"
label="The max number of previous interactions to use"
min="0"
updater={updateNum("maxHistory")}
/>
<Slider
value={settings.contextFraction}
field="contextFraction"
label="Approximate fraction of num_tokens to use for citations text before truncating"
updater={updateNum("contextFraction")}
/>
<Slider
value={settings.historyFraction}
field="historyFraction"
label="Approximate fraction of num_tokens to use for history text before truncating"
updater={updateNum("historyFraction")}
/>
</div>
);
};
type ChatPromptParams = {
settings: LLMSettings;
query: string;
history: Entry[];
updateSettings: (updater: (settings: LLMSettings) => LLMSettings) => void;
};
const ChatPrompts = ({
settings,
query,
history,
updateSettings,
}: ChatPromptParams) => {
const updatePrompt =
(...path: string[]) =>
(event: ChangeEvent) => {
const newPrompts = {
...updateIn(
settings.prompts || {},
path,
(event.target as HTMLInputElement).value
),
};
updateSettings((settings) => ({ ...settings, prompts: newPrompts }));
};
return (
<div className="chat-prompts mx-5 w-[400px] flex-none border-2 p-5 outline-black">
<details open>
<summary>Source prompt</summary>
<TextareaAutosize
className="border-gray w-full border px-1"
value={settings?.prompts?.context}
onChange={updatePrompt("context")}
/>
<div>(This is where sources will be injected)</div>
</details>
{history.length > 0 && (
<details open>
<summary>History prompt</summary>
<TextareaAutosize
className="border-gray w-full border px-1"
value={settings?.prompts?.history}
onChange={updatePrompt("history")}
/>
<details>
<summary>History</summary>
{history
.slice(Math.max(0, history.length - (settings.maxHistory || 0)))
.map((entry, i) => (
<div className="history-entry" key={i}>
{entry.content}
</div>
))}
</details>
</details>
)}
<details open>
<summary>Question prompt</summary>
<TextareaAutosize
className="border-gray w-full border px-1"
value={settings?.prompts?.question}
onChange={updatePrompt("question")}
/>
<TextareaAutosize
className="border-gray w-full border px-1"
value={settings?.prompts?.modes[settings.mode || "default"]}
onChange={updatePrompt("modes", settings.mode || "default")}
/>
</details>
<div>Q: {query}</div>
</div>
);
};
import {
ChatSettings,
ChatPrompts,
updateIn,
makeSettings,
} from "../components/settings";
const Playground: NextPage = () => {
const [sessionId, setSessionId] = useState("");
const [settings, updateSettings] = useState<LLMSettings>(DEFAULT_SETTINGS);
const [settings, updateSettings] = useState<LLMSettings>(makeSettings({}));
const [query, setQuery] = useState<string>("");
const [history, setHistory] = useState<Entry[]>([]);
const router = useRouter();
const setMode = (mode: [Mode, boolean]) => {
if (mode[1]) {
localStorage.setItem("chat_mode", mode[0]);
@@ -375,6 +31,21 @@ const Playground: NextPage = () => {
}
};
const changeSetting = (path: string[], value: any) => {
router.replace(
{
pathname: router.pathname,
query: {
...router.query,
[path.join(".")]: value.toString(),
},
},
undefined,
{ scroll: false, shallow: true }
);
updateSettings((settings) => ({ ...updateIn(settings, path, value) }));
};
// initial load
useEffect(() => {
const mode = (localStorage.getItem("chat_mode") as Mode) || "default";
@@ -382,6 +53,10 @@ const Playground: NextPage = () => {
setSessionId(crypto.randomUUID());
}, []);
useEffect(() => {
updateSettings(makeSettings(router.query));
}, [updateSettings, router]);
return (
<>
<Head>
@@ -395,7 +70,7 @@ const Playground: NextPage = () => {
settings={settings}
query={query}
history={history}
updateSettings={updateSettings}
changeSetting={changeSetting}
/>
<Chat
sessionId={sessionId}
@@ -403,7 +78,7 @@ const Playground: NextPage = () => {
onQuery={setQuery}
onNewEntry={setHistory}
/>
<ChatSettings settings={settings} updateSettings={updateSettings} />
<ChatSettings settings={settings} changeSetting={changeSetting} />
</div>
</main>
</>
+2
View File
@@ -64,3 +64,5 @@ export type LLMSettings = {
contextFraction?: number;
[key: string]: any;
};
export type Parseable = string | number | undefined;