Skip to content
This repository has been archived by the owner on Jun 2, 2024. It is now read-only.

Commit

Permalink
[web] Advanced Conversation Parameters and [lib] Proxy fix (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxijonson committed May 2, 2023
1 parent a92133b commit 7578c40
Show file tree
Hide file tree
Showing 29 changed files with 1,038 additions and 362 deletions.
7 changes: 6 additions & 1 deletion packages/lib/src/schemas/conversationConfig.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export const conversationConfigSchema = z.object({
max_tokens: z.number().optional(),
presence_penalty: z.number().optional(),
frequency_penalty: z.number().optional(),
logit_bias: z.record(z.number(), z.number()).optional(),
logit_bias: z
.record(
z.string().refine((val) => !isNaN(Number(val))),
z.number()
)
.optional(),
user: z.string().optional(),
});

Expand Down
6 changes: 1 addition & 5 deletions packages/lib/src/utils/createChatCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ export default async <T extends CreateChatCompletionRequest>(
): Promise<
T["stream"] extends true ? ReadableStream : CreateChatCompletionResponse
> => {
const headers: Record<string, string> = getRequestHeaders(
apiKey,
optHeaders,
proxy
);
const headers = getRequestHeaders(apiKey, optHeaders, proxy);
const url = getRequestUrl(ENDPOINT_CHATCOMPLETION, proxy);

const response = await fetch(url, {
Expand Down
6 changes: 1 addition & 5 deletions packages/lib/src/utils/createModeration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ export default async (
{ apiKey, input }: CreateModerationRequest,
{ headers: optHeaders = {}, proxy }: RequestOptions
): Promise<CreateModerationResponse> => {
const headers: Record<string, string> = getRequestHeaders(
apiKey,
optHeaders,
proxy
);
const headers = getRequestHeaders(apiKey, optHeaders, proxy);
const url = getRequestUrl(ENDPOINT_MODERATION, proxy);

const response = await fetch(url, {
Expand Down
6 changes: 3 additions & 3 deletions packages/lib/src/utils/getRequestHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ export default (
optHeaders: Record<string, string> = {},
proxy?: RequestOptionsProxy
) => {
const headers: Record<string, string> = {
const headers = new Headers({
...optHeaders,
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
};
});

if (proxy && proxy.auth) {
const auth = base64Encode(
`${proxy.auth.username}:${proxy.auth.password}`
);
headers["Proxy-Authorization"] = `Basic ${auth}`;
headers.set("Proxy-Authorization", `Basic ${auth}`);
}

return headers;
Expand Down
16 changes: 9 additions & 7 deletions packages/lib/src/utils/getRequestUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { RequestOptionsProxy } from "./types.js";
* Returns the URL object for a request, taking into account a proxy.
*/
export default (targetUrl: string, proxy?: RequestOptionsProxy) => {
let url = new URL(targetUrl);
if (proxy) {
url = new URL(
url.pathname,
`${proxy.protocol || "http"}://${proxy.host}:${proxy.port || 80}`
);
if (!proxy) return new URL(targetUrl);

const protocol = proxy.protocol || "http";
const host = proxy.host;
const port = proxy.port || (protocol === "https" ? 443 : 80);

if (!host) {
throw new Error("Proxy host is required.");
}

return url;
return new URL(`${protocol}://${host}:${port}/${targetUrl}`);
};
2 changes: 1 addition & 1 deletion packages/lib/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export interface RequestOptionsProxy {
/**
* The port number of the proxy server.
*
* @default 80
* @default 80 for HTTP, 443 for HTTPS
*/
port?: number;

Expand Down
190 changes: 12 additions & 178 deletions packages/web/src/components/AddConversationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,188 +1,22 @@
import { useForm } from "@mantine/form";
import useConversationManager from "../hooks/useConversationManager";
import {
Group,
PasswordInput,
Select,
Stack,
Text,
Switch,
Input,
SegmentedControl,
Button,
Textarea,
Tooltip,
Anchor,
useMantineTheme,
} from "@mantine/core";
import React from "react";
import useSettings from "../hooks/useSettings";
import usePersistence from "../hooks/usePersistence";

export const ModelSelectItem = React.forwardRef<
HTMLDivElement,
{ label: string; value: string; selected: boolean }
>(({ label, value, ...restProps }, ref) => {
const theme = useMantineTheme();
const { selected } = restProps;

const subColor = (() => {
const dark = theme.colorScheme === "dark";

if (dark && selected) {
return theme.colors.gray[4];
} else if (dark && !selected) {
return theme.colors.gray[6];
} else if (selected) {
return theme.colors.gray[3];
} else {
return theme.colors.gray[6];
}
})();

return (
<Stack ref={ref} spacing={0} p="xs" {...restProps}>
<Text>{label}</Text>
<Text size="xs" color={subColor}>
{value}
</Text>
</Stack>
);
});
import ConversationForm, { ConversationFormValues } from "./ConversationForm";

export default () => {
const { addConversation, setActiveConversation } = useConversationManager();
const { addPersistedConversationId } = usePersistence();
const { settings } = useSettings();
const form = useForm({
initialValues: {
apiKey: settings.apiKey,
model: settings.model,
context: settings.context,
dry: settings.dry,
disableModeration: settings.disableModeration,
stream: settings.stream,
save: settings.save,
},
transformValues: (values) => ({
...values,
disableModeration: (values.disableModeration === "soft"
? "soft"
: values.disableModeration === "off") as boolean | "soft",
dry: !values.apiKey || values.dry,
}),
});
const [modelOptions, setModelOptions] = React.useState([
{ label: "GPT 3.5", value: "gpt-3.5-turbo" },
{ label: "GPT 4", value: "gpt-4" },
{ label: "GPT 4 (32k)", value: "gpt-4-32k" },
]);

const handleSubmit = form.onSubmit(({ save, ...values }) => {
const newConversation = addConversation(values);
setActiveConversation(newConversation.id, true);
if (save) {
addPersistedConversationId(newConversation.id);
}
});

return (
<form onSubmit={handleSubmit}>
<Stack>
<PasswordInput
{...form.getInputProps("apiKey")}
label="OpenAI API Key"
description={
<Text>
You can find yours{" "}
<Anchor
href="https://platform.openai.com/account/api-keys"
target="_blank"
>
here
</Anchor>
</Text>
}
/>
<Group>
<Select
{...form.getInputProps("model")}
label="Model"
searchable
creatable
itemComponent={ModelSelectItem}
data={modelOptions}
getCreateLabel={(query) => query}
onCreate={(query) => {
const item = { value: query, label: query };
setModelOptions((current) => [...current, item]);
return item;
}}
sx={{ flexGrow: 1 }}
/>
<Group position="center" sx={{ flexGrow: 1 }}>
<Input.Wrapper label="Moderation">
<div>
<SegmentedControl
{...form.getInputProps("disableModeration")}
color="blue"
data={[
{ label: "On", value: "on" },
{ label: "Soft", value: "soft" },
{ label: "Off", value: "off" },
]}
/>
</div>
</Input.Wrapper>
</Group>
<Group position="center" noWrap sx={{ flexGrow: 1 }}>
<Tooltip
label="Dry mode is enabled when no API key is specified"
disabled={!!form.values.apiKey}
>
<Input.Wrapper label="Dry">
<Switch
{...form.getInputProps("dry")}
checked={
!form.values.apiKey || form.values.dry
}
readOnly={!form.values.apiKey}
/>
</Input.Wrapper>
</Tooltip>
<Input.Wrapper label="Stream">
<Switch
{...form.getInputProps("stream", {
type: "checkbox",
})}
/>
</Input.Wrapper>
<Input.Wrapper label="Save">
<Switch
{...form.getInputProps("save", {
type: "checkbox",
})}
/>
</Input.Wrapper>
</Group>
</Group>
<Textarea
{...form.getInputProps("context")}
autosize
minRows={3}
maxRows={5}
label="Context"
/>
<Button type="submit">Start</Button>
{form.values.save && (
<Text size="xs" italic align="center">
This conversation will be saved to your browser's local
storage, along with your API key, if specified. Make
sure that you trust the device you are using and that
you are not using a shared device.
</Text>
)}
</Stack>
</form>
const onSubmit = React.useCallback(
({ save, headers, proxy, ...values }: ConversationFormValues) => {
const newConversation = addConversation(values, { headers, proxy });
setActiveConversation(newConversation.id, true);
if (save) {
addPersistedConversationId(newConversation.id);
}
},
[addConversation, addPersistedConversationId, setActiveConversation]
);

return <ConversationForm onSubmit={onSubmit} />;
};
27 changes: 27 additions & 0 deletions packages/web/src/components/ApiKeyInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PasswordInput, Anchor, Text } from "@mantine/core";

interface ApiKeyInputProps {
value: string;
onChange: (value: string) => void;
}

export default ({ value, onChange }: ApiKeyInputProps) => {
return (
<PasswordInput
value={value || ""}
onChange={(event) => onChange(event.currentTarget.value)}
label="OpenAI API Key"
description={
<Text>
You can find yours{" "}
<Anchor
href="https://platform.openai.com/account/api-keys"
target="_blank"
>
here
</Anchor>
</Text>
}
/>
);
};
4 changes: 2 additions & 2 deletions packages/web/src/components/AppNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from "react-icons/bi";
import TippedActionIcon from "./TippedActionIcon";
import { openModal } from "@mantine/modals";
import Settings from "./Settings";
import SettingsForm from "./SettingsForm";
import NavbarConversation from "./NavbarConversation";
import React from "react";
import Usage from "./Usage";
Expand Down Expand Up @@ -110,7 +110,7 @@ export default () => {
tip="Settings"
onClick={() =>
openModal({
children: <Settings />,
children: <SettingsForm />,
centered: true,
size: "lg",
title: "Settings",
Expand Down
19 changes: 19 additions & 0 deletions packages/web/src/components/ContextInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Textarea } from "@mantine/core";

interface ContextInputProps {
value: string;
onChange: (value: string) => void;
}

export default ({ value, onChange }: ContextInputProps) => {
return (
<Textarea
value={value}
onChange={(event) => onChange(event.currentTarget.value)}
autosize
minRows={3}
maxRows={5}
label="Context"
/>
);
};
Loading

0 comments on commit 7578c40

Please sign in to comment.