Skip to content

Commit

Permalink
llm: make model selection a dropdown, show more of the explanations, …
Browse files Browse the repository at this point in the history
…add ollama description, and fix bug with ai-formula (no project context)
  • Loading branch information
haraldschilly committed Feb 27, 2024
1 parent e06be12 commit 34824c9
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 83 deletions.
14 changes: 6 additions & 8 deletions src/packages/frontend/account/useLanguageModelSetting.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import { redux, useMemo, useTypedRedux } from "@cocalc/frontend/app-framework";
import { EnabledLLMs } from "@cocalc/frontend/project/context";
import {
LanguageModel,
USER_SELECTABLE_LANGUAGE_MODELS,
fromOllamaModel,
getValidLanguageModelName,
isOllamaLLM,
} from "@cocalc/util/db-schema/llm";
import { useProjectContext } from "../project/context";

export const SETTINGS_LANGUAGE_MODEL_KEY = "language_model";

// ATTN: requires the project context
export function useLanguageModelSetting(): [
LanguageModel | string,
(llm: LanguageModel | string) => void,
] {
// ATTN: it is tempting to use the `useProjectContext` hook here, but it is not possible
// The "AI Formula" dialog is outside the project context (unfortunately)
export function useLanguageModelSetting(
enabledLLMs: EnabledLLMs,
): [LanguageModel | string, (llm: LanguageModel | string) => void] {
const other_settings = useTypedRedux("account", "other_settings");
const ollama = useTypedRedux("customize", "ollama");

const { enabledLLMs } = useProjectContext();

const llm = useMemo(() => {
return getValidLanguageModelName(
other_settings?.get("language_model"),
Expand Down
4 changes: 3 additions & 1 deletion src/packages/frontend/codemirror/extensions/ai-formula.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ interface Props extends Opts {
}

function AiGenFormula({ mode, text = "", project_id, cb }: Props) {
const [model, setModel] = useLanguageModelSetting();
const projectsStore = redux.getStore("projects");
const enabledLLMs = projectsStore.whichLLMareEnabled(project_id);
const [model, setModel] = useLanguageModelSetting(enabledLLMs);
const [input, setInput] = useState<string>(text);
const [formula, setFormula] = useState<string>("");
const [generating, setGenerating] = useState<boolean>(false);
Expand Down
4 changes: 3 additions & 1 deletion src/packages/frontend/frame-editors/llm/help-me-fix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ export default function HelpMeFix({
const { redux, project_id, path } = useFrameContext();
const [gettingHelp, setGettingHelp] = useState<boolean>(false);
const [errorGettingHelp, setErrorGettingHelp] = useState<string>("");
const [model, setModel] = useLanguageModelSetting();
const projectsStore = redux.getStore("projects");
const enabledLLMs = projectsStore.whichLLMareEnabled(project_id);
const [model, setModel] = useLanguageModelSetting(enabledLLMs);
if (
redux == null ||
!(redux.getStore("projects") as ProjectsStore).hasLanguageModelEnabled(
Expand Down
138 changes: 80 additions & 58 deletions src/packages/frontend/frame-editors/llm/model-switch.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Radio, Tooltip } from "antd";
import type { SelectProps } from "antd";
import { Select } from "antd";

import { CSS, redux, useTypedRedux } from "@cocalc/frontend/app-framework";
import { LanguageModelVendorAvatar } from "@cocalc/frontend/components/language-model-icon";
import {
DEFAULT_MODEL,
LLM_USERNAMES,
Expand All @@ -12,6 +14,7 @@ import {
model2service,
toOllamaModel,
} from "@cocalc/util/db-schema/llm";
import type { OllamaPublic } from "@cocalc/util/types/llm";

export { DEFAULT_MODEL };
export type { LanguageModel };
Expand Down Expand Up @@ -55,87 +58,106 @@ export default function ModelSwitch({
);
const ollama = useTypedRedux("customize", "ollama");

function renderLLMButton(btnModel: LanguageModel, title: string) {
function getPrice(btnModel): string {
return isFreeModel(btnModel) ? "free" : "NOT free";
}

function makeLLMOption(
ret: NonNullable<SelectProps["options"]>,
btnModel: LanguageModel,
title: string,
) {
if (!USER_SELECTABLE_LANGUAGE_MODELS.includes(btnModel)) return;
const prefix = isFreeModel(btnModel) ? "FREE" : "NOT FREE";
return (
<Tooltip title={`${prefix}: ${title}`}>
<Radio.Button value={btnModel}>
{modelToName(btnModel)}
{btnModel === model
? !isFreeModel(btnModel)
? " (not free)"
: " (free)"
: undefined}
</Radio.Button>
</Tooltip>
);

const display = modelToName(btnModel);
ret.push({
value: btnModel,
display,
label: (
<>
<LanguageModelVendorAvatar model={btnModel} />{" "}
<strong>{display}</strong> ({getPrice(btnModel)}): {title}
</>
),
});
}

function renderOpenAI() {
function appendOpenAI(ret: NonNullable<SelectProps["options"]>) {
if (!showOpenAI) return null;
return (
<>
{renderLLMButton(
"gpt-3.5-turbo",
"OpenAI's fastest model, great for most everyday tasks (4k token context)",
)}
{renderLLMButton(
"gpt-3.5-turbo-16k",
`Same as ${modelToName(
"gpt-3.5-turbo",
)} but with much larger context size (16k token context)`,
)}
{renderLLMButton(
"gpt-4",
"OpenAI's most capable model, great for tasks that require creativity and advanced reasoning (8k token context)",
)}
</>

makeLLMOption(
ret,
"gpt-3.5-turbo",
"OpenAI's fastest model, great for most everyday tasks (4k token context)",
);
makeLLMOption(
ret,
"gpt-3.5-turbo-16k",
`Same as ${modelToName(
"gpt-3.5-turbo",
)} but with much larger context size (16k token context)`,
);
makeLLMOption(
ret,
"gpt-4",
"OpenAI's most capable model, great for tasks that require creativity and advanced reasoning (8k token context)",
);
}

function renderGoogle() {
function appendGoogle(ret: NonNullable<SelectProps["options"]>) {
if (!showGoogle) return null;

return (
<>
{renderLLMButton(
{makeLLMOption(
ret,
GOOGLE_GEMINI,
`Google's Gemini Pro Generative AI model ('${GOOGLE_GEMINI}', 30k token context)`,
`Google's Gemini Pro Generative AI model (30k token context)`,
)}
</>
);
}

function renderOllama() {
function appendOllama(ret: NonNullable<SelectProps["options"]>) {
if (!showOllama || !ollama) return null;

return Object.entries(ollama.toJS()).map(([key, config]) => {
const { display } = config;
return (
<Tooltip key={key} title={`${display} (Ollama)`}>
<Radio.Button value={toOllamaModel(key)}>{display}</Radio.Button>
</Tooltip>
);
});
for (const [key, config] of Object.entries<OllamaPublic>(ollama.toJS())) {
const { display, desc } = config;
const ollamaModel = toOllamaModel(key);
ret.push({
value: ollamaModel,
display,
label: (
<>
<LanguageModelVendorAvatar model={ollamaModel} />{" "}
<strong>{display}</strong> ({getPrice(ollamaModel)}):{" "}
{desc ?? "Ollama"}
</>
),
});
}
}

function getOptions(): SelectProps["options"] {
const ret: NonNullable<SelectProps["options"]> = [];
appendOpenAI(ret);
appendGoogle(ret);
appendOllama(ret);
return ret;
}

// all models selectable here must be in util/db-schema/openai::USER_SELECTABLE_LANGUAGE_MODELS
// all models selectable here must be in util/db-schema/openai::USER_SELECTABLE_LANGUAGE_MODELS + the custom ones from the ollama configuration
return (
<Radio.Group
style={style}
<Select
dropdownStyle={style}
size={size}
value={model}
optionType="button"
buttonStyle="solid"
onChange={({ target: { value } }) => {
setModel(value);
}}
>
{renderOpenAI()}
{renderGoogle()}
{renderOllama()}
</Radio.Group>
onChange={setModel}
style={{ width: 300 }}
optionLabelProp={"display"}
popupMatchSelectWidth={false}
options={getOptions()}
/>
);
}

Expand Down
7 changes: 6 additions & 1 deletion src/packages/frontend/frame-editors/llm/title-bar-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ to do the work.

import { Alert, Button, Input, Popover, Radio, Space, Tooltip } from "antd";
import { useEffect, useMemo, useRef, useState } from "react";

import { useLanguageModelSetting } from "@cocalc/frontend/account/useLanguageModelSetting";
import { redux } from "@cocalc/frontend/app-framework";
import {
Icon,
IconName,
Expand Down Expand Up @@ -156,7 +158,10 @@ export default function LanguageModelTitleBarButton({
const scopeRef = useRef<any>(null);
const contextRef = useRef<any>(null);
const submitRef = useRef<any>(null);
const [model, setModel] = useLanguageModelSetting();

const projectsStore = redux.getStore("projects");
const enabledLLMs = projectsStore.whichLLMareEnabled(project_id);
const [model, setModel] = useLanguageModelSetting(enabledLLMs);

useEffect(() => {
if (showDialog) {
Expand Down
5 changes: 4 additions & 1 deletion src/packages/frontend/jupyter/chatgpt/explain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Alert, Button } from "antd";
import { CSSProperties, useState } from "react";

import { useLanguageModelSetting } from "@cocalc/frontend/account/useLanguageModelSetting";
import { redux } from "@cocalc/frontend/app-framework";
import getChatActions from "@cocalc/frontend/chat/get-actions";
import AIAvatar from "@cocalc/frontend/components/ai-avatar";
import { Icon } from "@cocalc/frontend/components/icon";
Expand All @@ -31,7 +32,9 @@ export default function ChatGPTExplain({ actions, id, style }: Props) {
const { project_id, path } = useFrameContext();
const [gettingExplanation, setGettingExplanation] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const [model, setModel] = useLanguageModelSetting();
const projectsStore = redux.getStore("projects");
const enabledLLMs = projectsStore.whichLLMareEnabled(project_id);
const [model, setModel] = useLanguageModelSetting(enabledLLMs);

if (
actions == null ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, { useMemo, useState } from "react";

import { useLanguageModelSetting } from "@cocalc/frontend/account/useLanguageModelSetting";
import { alert_message } from "@cocalc/frontend/alerts";
import { useFrameContext } from "@cocalc/frontend/app-framework";
import { redux, useFrameContext } from "@cocalc/frontend/app-framework";
import { Paragraph } from "@cocalc/frontend/components";
import { Icon } from "@cocalc/frontend/components/icon";
import { LanguageModelVendorAvatar } from "@cocalc/frontend/components/language-model-icon";
Expand Down Expand Up @@ -44,9 +44,12 @@ export default function AIGenerateCodeCell({
setShowChatGPT,
showChatGPT,
}: AIGenerateCodeCellProps) {
const [model, setModel] = useLanguageModelSetting();
const [querying, setQuerying] = useState<boolean>(false);
const { project_id, path } = useFrameContext();

const projectsStore = redux.getStore("projects");
const enabledLLMs = projectsStore.whichLLMareEnabled(project_id);
const [model, setModel] = useLanguageModelSetting(enabledLLMs);
const [querying, setQuerying] = useState<boolean>(false);
const [prompt, setPrompt] = useState<string>("");
const input = useMemo(() => {
if (!showChatGPT) return "";
Expand Down
11 changes: 6 additions & 5 deletions src/packages/frontend/project/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ import { useProjectStatus } from "./page/project-status-hook";
import { useProjectHasInternetAccess } from "./settings/has-internet-access-hook";
import { Project } from "./settings/types";

export interface EnabledLLMs {
openai: boolean;
google: boolean;
ollama: boolean;
}
export interface ProjectContextState {
actions?: ProjectActions;
active_project_tab?: string;
Expand All @@ -39,11 +44,7 @@ export interface ProjectContextState {
flipTabs: [number, React.Dispatch<React.SetStateAction<number>>];
onCoCalcCom: boolean;
onCoCalcDocker: boolean;
enabledLLMs: {
openai: boolean;
google: boolean;
ollama: boolean;
};
enabledLLMs: EnabledLLMs;
}

export const ProjectContext: Context<ProjectContextState> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ export default function AIGenerateJupyterNotebook({
onSuccess,
project_id,
}: Props) {
const [model, setModel] = useLanguageModelSetting();
const projectsStore = redux.getStore("projects");
const enabledLLMs = projectsStore.whichLLMareEnabled(project_id);
const [model, setModel] = useLanguageModelSetting(enabledLLMs);
const [kernelSpecs, setKernelSpecs] = useState<KernelSpec[] | null | string>(
null,
);
Expand Down
1 change: 1 addition & 0 deletions src/packages/hub/webapp-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export class WebappConfiguration {
model,
display: cocalc.display ?? `Ollama ${model}`,
icon: cocalc.icon, // fallback is the Ollama icon, frontend does that
desc: cocalc.desc ?? "",
};
}
return public_ollama;
Expand Down
Loading

0 comments on commit 34824c9

Please sign in to comment.