diff --git a/src/packages/frontend/account/avatar/avatar.tsx b/src/packages/frontend/account/avatar/avatar.tsx index 089634f57f..7e4677e682 100644 --- a/src/packages/frontend/account/avatar/avatar.tsx +++ b/src/packages/frontend/account/avatar/avatar.tsx @@ -18,7 +18,7 @@ import { LanguageModelVendorAvatar } from "@cocalc/frontend/components/language- import { ProjectTitle } from "@cocalc/frontend/projects/project-title"; import { DEFAULT_COLOR } from "@cocalc/frontend/users/store"; import { webapp_client } from "@cocalc/frontend/webapp-client"; -import { service2model } from "@cocalc/util/db-schema/openai"; +import { service2model } from "@cocalc/util/db-schema/llm-utils"; import { ensure_bound, startswith, trunc_middle } from "@cocalc/util/misc"; import { avatar_fontcolor } from "./font-color"; diff --git a/src/packages/frontend/account/chatbot.ts b/src/packages/frontend/account/chatbot.ts index d3f10280dd..fdf0e1a26b 100644 --- a/src/packages/frontend/account/chatbot.ts +++ b/src/packages/frontend/account/chatbot.ts @@ -7,39 +7,46 @@ When new models are added, e.g., Claude soon (!), they will go here. */ +import { redux } from "@cocalc/frontend/app-framework"; import { + LANGUAGE_MODELS, LANGUAGE_MODEL_PREFIXES, LLM_USERNAMES, - MODELS, - Vendor, - model2vendor, -} from "@cocalc/util/db-schema/openai"; + fromMistralService, + fromOllamaModel, + isMistralService, + isOllamaLLM, +} from "@cocalc/util/db-schema/llm-utils"; // we either check if the prefix is one of the known ones (used in some circumstances) // or if the account id is exactly one of the language models (more precise) -export function isChatBot(account_id?: string) { +export function isChatBot(account_id?: string): boolean { + if (typeof account_id !== "string") return false; return ( LANGUAGE_MODEL_PREFIXES.some((prefix) => account_id?.startsWith(prefix)) || - MODELS.some((model) => account_id === model) + LANGUAGE_MODELS.some((model) => account_id === model) || + isOllamaLLM(account_id) ); } -export function getChatBotVendor(account_id?: string): Vendor { - if (account_id == null) { - return "openai"; - } - return model2vendor(account_id as any); -} - export function chatBotName(account_id?: string): string { - if (account_id?.startsWith("chatgpt")) { + if (typeof account_id !== "string") return "ChatBot"; + if (account_id.startsWith("chatgpt")) { return LLM_USERNAMES[account_id] ?? "ChatGPT"; } - if (account_id?.startsWith("openai-")) { + if (account_id.startsWith("openai-")) { return LLM_USERNAMES[account_id.slice("openai-".length)] ?? "ChatGPT"; } - if (account_id?.startsWith("google-")) { + if (account_id.startsWith("google-")) { return LLM_USERNAMES[account_id.slice("google-".length)] ?? "Gemini"; } + if (isMistralService(account_id)) { + return LLM_USERNAMES[fromMistralService(account_id)] ?? "Mistral"; + } + if (isOllamaLLM(account_id)) { + const ollama = redux.getStore("customize").get("ollama")?.toJS() ?? {}; + const key = fromOllamaModel(account_id); + return ollama[key]?.display ?? "Ollama"; + } return "ChatBot"; } diff --git a/src/packages/frontend/account/other-settings.tsx b/src/packages/frontend/account/other-settings.tsx index f1c0428156..018fa5fdeb 100644 --- a/src/packages/frontend/account/other-settings.tsx +++ b/src/packages/frontend/account/other-settings.tsx @@ -7,7 +7,7 @@ import { Card, InputNumber } from "antd"; import { Map } from "immutable"; import { Checkbox, Panel } from "@cocalc/frontend/antd-bootstrap"; -import { Component, Rendered, redux } from "@cocalc/frontend/app-framework"; +import { Rendered, redux } from "@cocalc/frontend/app-framework"; import { A, Icon, @@ -19,17 +19,11 @@ import { } from "@cocalc/frontend/components"; import AIAvatar from "@cocalc/frontend/components/ai-avatar"; import { IS_MOBILE, IS_TOUCH } from "@cocalc/frontend/feature"; +import ModelSwitch from "@cocalc/frontend/frame-editors/llm/model-switch"; import { NewFilenameFamilies } from "@cocalc/frontend/project/utils"; import track from "@cocalc/frontend/user-tracking"; import { webapp_client } from "@cocalc/frontend/webapp-client"; import { DEFAULT_NEW_FILENAMES, NEW_FILENAMES } from "@cocalc/util/db-schema"; -import { - LLM_USERNAMES, - USER_SELECTABLE_LANGUAGE_MODELS, - getValidLanguageModelName, - isFreeModel, - model2vendor, -} from "@cocalc/util/db-schema/openai"; import { VBAR_EXPLANATION, VBAR_KEY, @@ -38,7 +32,7 @@ import { } from "../project/page/vbar"; import { dark_mode_mins, get_dark_mode_config } from "./dark-mode"; import Tours from "./tours"; -import { SETTINGS_LANGUAGE_MODEL_KEY } from "./useLanguageModelSetting"; +import { useLanguageModelSetting } from "./useLanguageModelSetting"; interface Props { other_settings: Map; @@ -46,37 +40,39 @@ interface Props { kucalc: string; } -export class OtherSettings extends Component { - private on_change(name: string, value: any): void { +export function OtherSettings(props: Readonly): JSX.Element { + const [model, setModel] = useLanguageModelSetting(); + + function on_change(name: string, value: any): void { redux.getActions("account").set_other_settings(name, value); } - private toggle_global_banner(val: boolean): void { + function toggle_global_banner(val: boolean): void { if (val) { // this must be "null", not "undefined" – otherwise the data isn't stored in the DB. - this.on_change("show_global_info2", null); + on_change("show_global_info2", null); } else { - this.on_change("show_global_info2", webapp_client.server_time()); + on_change("show_global_info2", webapp_client.server_time()); } } // private render_first_steps(): Rendered { - // if (this.props.kucalc !== KUCALC_COCALC_COM) return; + // if (props.kucalc !== KUCALC_COCALC_COM) return; // return ( // this.on_change("first_steps", e.target.checked)} + // checked={!!props.other_settings.get("first_steps")} + // onChange={(e) => on_change("first_steps", e.target.checked)} // > // Offer the First Steps guide // // ); // } - private render_global_banner(): Rendered { + function render_global_banner(): Rendered { return ( this.toggle_global_banner(e.target.checked)} + checked={!props.other_settings.get("show_global_info2")} + onChange={(e) => toggle_global_banner(e.target.checked)} > Show announcement banner: only shows up if there is a message @@ -84,11 +80,11 @@ export class OtherSettings extends Component { ); } - private render_time_ago_absolute(): Rendered { + function render_time_ago_absolute(): Rendered { return ( this.on_change("time_ago_absolute", e.target.checked)} + checked={!!props.other_settings.get("time_ago_absolute")} + onChange={(e) => on_change("time_ago_absolute", e.target.checked)} > Display timestamps as absolute points in time instead of relative to the current time @@ -96,12 +92,12 @@ export class OtherSettings extends Component { ); } - private render_confirm(): Rendered { + function render_confirm(): Rendered { if (!IS_MOBILE) { return ( this.on_change("confirm_close", e.target.checked)} + checked={!!props.other_settings.get("confirm_close")} + onChange={(e) => on_change("confirm_close", e.target.checked)} > Confirm Close: always ask for confirmation before closing the browser window @@ -110,11 +106,11 @@ export class OtherSettings extends Component { } } - private render_katex(): Rendered { + function render_katex(): Rendered { return ( this.on_change("katex", e.target.checked)} + checked={!!props.other_settings.get("katex")} + onChange={(e) => on_change("katex", e.target.checked)} > KaTeX: attempt to render formulas with{" "} KaTeX (much faster, but missing @@ -123,28 +119,28 @@ export class OtherSettings extends Component { ); } - private render_standby_timeout(): Rendered { + function render_standby_timeout(): Rendered { if (IS_TOUCH) { return; } return ( this.on_change("standby_timeout_m", n)} + on_change={(n) => on_change("standby_timeout_m", n)} min={1} max={180} unit="minutes" - number={this.props.other_settings.get("standby_timeout_m")} + number={props.other_settings.get("standby_timeout_m")} /> ); } - private render_mask_files(): Rendered { + function render_mask_files(): Rendered { return ( this.on_change("mask_files", e.target.checked)} + checked={!!props.other_settings.get("mask_files")} + onChange={(e) => on_change("mask_files", e.target.checked)} > Mask files: grey out files in the files viewer that you probably do not want to open @@ -152,13 +148,11 @@ export class OtherSettings extends Component { ); } - private render_hide_project_popovers(): Rendered { + function render_hide_project_popovers(): Rendered { return ( - this.on_change("hide_project_popovers", e.target.checked) - } + checked={!!props.other_settings.get("hide_project_popovers")} + onChange={(e) => on_change("hide_project_popovers", e.target.checked)} > Hide Project Tab Popovers: do not show the popovers over the project tabs @@ -166,11 +160,11 @@ export class OtherSettings extends Component { ); } - private render_hide_file_popovers(): Rendered { + function render_hide_file_popovers(): Rendered { return ( this.on_change("hide_file_popovers", e.target.checked)} + checked={!!props.other_settings.get("hide_file_popovers")} + onChange={(e) => on_change("hide_file_popovers", e.target.checked)} > Hide File Tab Popovers: do not show the popovers over file tabs @@ -178,13 +172,11 @@ export class OtherSettings extends Component { ); } - private render_hide_button_tooltips(): Rendered { + function render_hide_button_tooltips(): Rendered { return ( - this.on_change("hide_button_tooltips", e.target.checked) - } + checked={!!props.other_settings.get("hide_button_tooltips")} + onChange={(e) => on_change("hide_button_tooltips", e.target.checked)} > Hide Button Tooltips: hides some button tooltips (this is only partial) @@ -192,57 +184,57 @@ export class OtherSettings extends Component { ); } - private render_default_file_sort(): Rendered { + function render_default_file_sort(): Rendered { return ( this.on_change("default_file_sort", value)} + on_change={(value) => on_change("default_file_sort", value)} /> ); } - private render_new_filenames(): Rendered { + function render_new_filenames(): Rendered { const selected = - this.props.other_settings.get(NEW_FILENAMES) ?? DEFAULT_NEW_FILENAMES; + props.other_settings.get(NEW_FILENAMES) ?? DEFAULT_NEW_FILENAMES; return ( this.on_change(NEW_FILENAMES, value)} + on_change={(value) => on_change(NEW_FILENAMES, value)} /> ); } - private render_page_size(): Rendered { + function render_page_size(): Rendered { return ( this.on_change("page_size", n)} + on_change={(n) => on_change("page_size", n)} min={1} max={10000} - number={this.props.other_settings.get("page_size")} + number={props.other_settings.get("page_size")} /> ); } - private render_no_free_warnings(): Rendered { + function render_no_free_warnings(): Rendered { let extra; - if (!this.props.is_stripe_customer) { + if (!props.is_stripe_customer) { extra = (only available to customers); } else { extra = (thanks for being a customer); } return ( this.on_change("no_free_warnings", e.target.checked)} + disabled={!props.is_stripe_customer} + checked={!!props.other_settings.get("no_free_warnings")} + onChange={(e) => on_change("no_free_warnings", e.target.checked)} > Hide free warnings: do{" "} @@ -253,15 +245,15 @@ export class OtherSettings extends Component { ); } - private render_dark_mode(): Rendered { - const checked = !!this.props.other_settings.get("dark_mode"); - const config = get_dark_mode_config(this.props.other_settings.toJS()); + function render_dark_mode(): Rendered { + const checked = !!props.other_settings.get("dark_mode"); + const config = get_dark_mode_config(props.other_settings.toJS()); const label_style = { width: "100px", display: "inline-block" } as const; return (
this.on_change("dark_mode", e.target.checked)} + onChange={(e) => on_change("dark_mode", e.target.checked)} style={{ color: "rgba(229, 224, 216)", backgroundColor: "rgb(36, 37, 37)", @@ -286,7 +278,7 @@ export class OtherSettings extends Component { min={dark_mode_mins.brightness} max={100} value={config.brightness} - onChange={(x) => this.on_change("dark_mode_brightness", x)} + onChange={(x) => on_change("dark_mode_brightness", x)} />
Contrast @@ -294,7 +286,7 @@ export class OtherSettings extends Component { min={dark_mode_mins.contrast} max={100} value={config.contrast} - onChange={(x) => this.on_change("dark_mode_contrast", x)} + onChange={(x) => on_change("dark_mode_contrast", x)} />
Sepia @@ -302,7 +294,7 @@ export class OtherSettings extends Component { min={dark_mode_mins.sepia} max={100} value={config.sepia} - onChange={(x) => this.on_change("dark_mode_sepia", x)} + onChange={(x) => on_change("dark_mode_sepia", x)} />
Grayscale @@ -310,7 +302,7 @@ export class OtherSettings extends Component { min={dark_mode_mins.grayscale} max={100} value={config.grayscale} - onChange={(x) => this.on_change("dark_mode_grayscale", x)} + onChange={(x) => on_change("dark_mode_grayscale", x)} /> )} @@ -318,30 +310,30 @@ export class OtherSettings extends Component { ); } - private render_antd(): Rendered { + function render_antd(): Rendered { return ( <> this.on_change("antd_rounded", e.target.checked)} + checked={props.other_settings.get("antd_rounded", true)} + onChange={(e) => on_change("antd_rounded", e.target.checked)} > Rounded Design: use rounded corners for buttons, etc. this.on_change("antd_animate", e.target.checked)} + checked={props.other_settings.get("antd_animate", true)} + onChange={(e) => on_change("antd_animate", e.target.checked)} > Animations: briefly animate some aspects, e.g. buttons this.on_change("antd_brandcolors", e.target.checked)} + checked={props.other_settings.get("antd_brandcolors", false)} + onChange={(e) => on_change("antd_brandcolors", e.target.checked)} > Color Scheme: use brand colors instead of default colors this.on_change("antd_compact", e.target.checked)} + checked={props.other_settings.get("antd_compact", false)} + onChange={(e) => on_change("antd_compact", e.target.checked)} > Compact Design: use a more compact design @@ -349,10 +341,8 @@ export class OtherSettings extends Component { ); } - render_vertical_fixed_bar_options(): Rendered { - const selected = getValidVBAROption( - this.props.other_settings.get(VBAR_KEY), - ); + function render_vertical_fixed_bar_options(): Rendered { + const selected = getValidVBAROption(props.other_settings.get(VBAR_KEY)); return (
@@ -361,7 +351,7 @@ export class OtherSettings extends Component { selected={selected} options={VBAR_OPTIONS} on_change={(value) => { - this.on_change(VBAR_KEY, value); + on_change(VBAR_KEY, value); track("flyout", { aspect: "layout", how: "account", value }); }} /> @@ -376,40 +366,7 @@ export class OtherSettings extends Component { ); } - render_language_model(): Rendered { - const projectsStore = redux.getStore("projects"); - const haveOpenAI = projectsStore.hasLanguageModelEnabled( - undefined, - undefined, - "openai", - ); - const haveGoogle = projectsStore.hasLanguageModelEnabled( - undefined, - undefined, - "google", - ); - - const defaultModel = getValidLanguageModelName( - this.props.other_settings.get(SETTINGS_LANGUAGE_MODEL_KEY), - { openai: haveOpenAI, google: haveGoogle }, - ); - - const options: { value: string; display: JSX.Element }[] = []; - - for (const key of USER_SELECTABLE_LANGUAGE_MODELS) { - const vendor = model2vendor(key); - if (vendor === "google" && !haveGoogle) continue; - if (vendor === "openai" && !haveOpenAI) continue; - - const txt = isFreeModel(key) ? " (free)" : ""; - const display = ( - <> - {LLM_USERNAMES[key]} {txt} - - ); - options.push({ value: key, display }); - } - + function render_language_model(): Rendered { return ( { } > - { - this.on_change(SETTINGS_LANGUAGE_MODEL_KEY, value); - }} - /> + ); } - render() { - if (this.props.other_settings == null) { - return ; - } - return ( - <> - - Theme - - } - > - {this.render_dark_mode()} - {this.render_antd()} - + if (props.other_settings == null) { + return ; + } + return ( + <> + + Theme + + } + > + {render_dark_mode()} + {render_antd()} + - - Other - - } - > - {this.render_confirm()} - {this.render_katex()} - {this.render_time_ago_absolute()} - {this.render_global_banner()} - {this.render_mask_files()} - {this.render_hide_project_popovers()} - {this.render_hide_file_popovers()} - {this.render_hide_button_tooltips()} - {this.render_no_free_warnings()} - {redux.getStore("customize").get("openai_enabled") && ( - { - this.on_change("openai_disabled", e.target.checked); - redux.getStore("projects").clearOpenAICache(); - }} - > - Disable all AI integrations, e.g., - code generation or explanation buttons in Jupyter, @chatgpt - mentions, etc. - - )} - {this.render_language_model()} + + Other + + } + > + {render_confirm()} + {render_katex()} + {render_time_ago_absolute()} + {render_global_banner()} + {render_mask_files()} + {render_hide_project_popovers()} + {render_hide_file_popovers()} + {render_hide_button_tooltips()} + {render_no_free_warnings()} + {redux.getStore("customize").get("openai_enabled") && ( { - this.on_change("disable_markdown_codebar", e.target.checked); + on_change("openai_disabled", e.target.checked); + redux.getStore("projects").clearOpenAICache(); }} > - Disable the markdown code bar in all markdown - documents. Checking this hides the extra run, copy, and explain - buttons in fenced code blocks. + Disable all AI integrations, e.g., code generation + or explanation buttons in Jupyter, @chatgpt mentions, etc. - {this.render_vertical_fixed_bar_options()} - {this.render_new_filenames()} - {this.render_default_file_sort()} - {this.render_page_size()} - {this.render_standby_timeout()} -
- - - - ); - } + )} + {render_language_model()} + { + on_change("disable_markdown_codebar", e.target.checked); + }} + > + Disable the markdown code bar in all markdown + documents. Checking this hides the extra run, copy, and explain + buttons in fenced code blocks. + + {render_vertical_fixed_bar_options()} + {render_new_filenames()} + {render_default_file_sort()} + {render_page_size()} + {render_standby_timeout()} +
+ + + + ); } diff --git a/src/packages/frontend/account/useLanguageModelSetting.tsx b/src/packages/frontend/account/useLanguageModelSetting.tsx index 3c79ff3ef5..e61fbd00b3 100644 --- a/src/packages/frontend/account/useLanguageModelSetting.tsx +++ b/src/packages/frontend/account/useLanguageModelSetting.tsx @@ -1,27 +1,54 @@ import { redux, useMemo, useTypedRedux } from "@cocalc/frontend/app-framework"; import { - LanguageModel, + LLMServicesAvailable, + LanguageService, USER_SELECTABLE_LANGUAGE_MODELS, + fromOllamaModel, getValidLanguageModelName, -} from "@cocalc/util/db-schema/openai"; + isOllamaLLM, +} from "@cocalc/util/db-schema/llm-utils"; export const SETTINGS_LANGUAGE_MODEL_KEY = "language_model"; -export function useLanguageModelSetting(): [ - LanguageModel, - (llm: LanguageModel) => 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( + project_id?: string, +): [LanguageService, (llm: LanguageService) => void] { const other_settings = useTypedRedux("account", "other_settings"); - const llm = useMemo(() => { - return getValidLanguageModelName(other_settings?.get("language_model")); + const ollama = useTypedRedux("customize", "ollama"); + + const haveOpenAI = useTypedRedux("customize", "openai_enabled"); + const haveGoogle = useTypedRedux("customize", "google_vertexai_enabled"); + const haveOllama = useTypedRedux("customize", "ollama_enabled"); + const haveMistral = useTypedRedux("customize", "mistral_enabled"); + + const enabledLLMs: LLMServicesAvailable = useMemo(() => { + const projectsStore = redux.getStore("projects"); + return projectsStore.whichLLMareEnabled(project_id); + }, [haveOpenAI, haveGoogle, haveOllama, haveMistral]); + + const llm: LanguageService = useMemo(() => { + return getValidLanguageModelName( + other_settings?.get("language_model"), + enabledLLMs, + Object.keys(ollama?.toJS() ?? {}), + ); }, [other_settings]); - function setLLM(llm: LanguageModel) { + function setLLM(llm: LanguageService) { if (USER_SELECTABLE_LANGUAGE_MODELS.includes(llm as any)) { redux .getActions("account") .set_other_settings(SETTINGS_LANGUAGE_MODEL_KEY, llm); } + + // check if llm is a key in the ollama typedmap + if (isOllamaLLM(llm) && ollama?.get(fromOllamaModel(llm))) { + redux + .getActions("account") + .set_other_settings(SETTINGS_LANGUAGE_MODEL_KEY, llm); + } } return [llm, setLLM]; diff --git a/src/packages/frontend/admin/site-settings/row-entry.tsx b/src/packages/frontend/admin/site-settings/row-entry.tsx index aa4f3864fc..1597623bf0 100644 --- a/src/packages/frontend/admin/site-settings/row-entry.tsx +++ b/src/packages/frontend/admin/site-settings/row-entry.tsx @@ -120,7 +120,8 @@ export function RowEntry({ {displayed_val != null && ( {" "} - Interpreted as {displayed_val}.{" "} + {valid ? "Interpreted as" : "Invalid:"}{" "} + {displayed_val}.{" "} )} {valid != null && Array.isArray(valid) && ( diff --git a/src/packages/frontend/chat/actions.ts b/src/packages/frontend/chat/actions.ts index fb1a8f786a..fb874079b0 100644 --- a/src/packages/frontend/chat/actions.ts +++ b/src/packages/frontend/chat/actions.ts @@ -6,23 +6,24 @@ import { fromJS, Map as immutableMap } from "immutable"; import { Actions, redux } from "@cocalc/frontend/app-framework"; +import { History as LanguageModelHistory } from "@cocalc/frontend/client/types"; import type { HashtagState, SelectedHashtags, } from "@cocalc/frontend/editors/task-editor/types"; import { open_new_tab } from "@cocalc/frontend/misc"; -import { History as LanguageModelHistory } from "@cocalc/frontend/misc/openai"; import enableSearchEmbeddings from "@cocalc/frontend/search/embeddings"; import track from "@cocalc/frontend/user-tracking"; import { webapp_client } from "@cocalc/frontend/webapp-client"; import { SyncDB } from "@cocalc/sync/editor/db"; import { + LANGUAGE_MODEL_PREFIXES, getVendorStatusCheckMD, model2service, model2vendor, + toOllamaModel, type LanguageModel, - LANGUAGE_MODEL_PREFIXES, -} from "@cocalc/util/db-schema/openai"; +} from "@cocalc/util/db-schema/llm-utils"; import { cmp, isValidUUID, parse_hashtags, uuid } from "@cocalc/util/misc"; import { getSortedDates } from "./chat-log"; import { message_to_markdown } from "./message"; @@ -553,7 +554,13 @@ export class ChatActions extends Actions { input = stripMentions(input); // also important to strip details, since they tend to confuse chatgpt: //input = stripDetails(input); - const sender_id = model2service(model); + const sender_id = (function () { + try { + return model2service(model); + } catch { + return model; + } + })(); let date: string = this.send_reply({ message, reply: ":robot: Thinking...", @@ -606,7 +613,7 @@ export class ChatActions extends Actions { setTimeout(() => { this.chatStreams.delete(id); }, 3 * 60 * 1000); - const chatStream = webapp_client.openai_client.languageModelStream({ + const chatStream = webapp_client.openai_client.queryStream({ input, history: reply_to ? this.getChatGPTHistory(reply_to) : undefined, project_id, @@ -735,9 +742,9 @@ function getReplyToRoot(message, messages): Date | undefined { return date ? new Date(date) : undefined; } +// We strip out any cased version of the string @chatgpt and also all mentions. function stripMentions(value: string): string { - // We strip out any cased version of the string @chatgpt and also all mentions. - for (const name of ["@chatgpt4", "@chatgpt", "@palm"]) { + for (const name of ["@chatgpt4", "@chatgpt"]) { while (true) { const i = value.toLowerCase().indexOf(name); if (i == -1) break; @@ -780,11 +787,17 @@ function getLanguageModel(input?: string): false | LanguageModel { return "gpt-3.5-turbo"; } // these prefexes should come from util/db-schema/openai::model2service - for (const prefix of ["account-id=openai-", "account-id=google-"]) { + for (const vendorprefix of LANGUAGE_MODEL_PREFIXES) { + const prefix = `account-id=${vendorprefix}`; const i = x.indexOf(prefix); if (i != -1) { const j = x.indexOf(">", i); - return x.slice(i + prefix.length, j).trim() as any; + const model = x.slice(i + prefix.length, j).trim() as LanguageModel; + // for now, ollama must be prefixed – in the future, all model names will have a vendor prefix! + if (vendorprefix.startsWith("ollama")) { + return toOllamaModel(model); + } + return model; } } return false; diff --git a/src/packages/frontend/chat/input.tsx b/src/packages/frontend/chat/input.tsx index 50d92f9b15..413157d091 100644 --- a/src/packages/frontend/chat/input.tsx +++ b/src/packages/frontend/chat/input.tsx @@ -181,12 +181,6 @@ export default function ChatInput({ }} editBarStyle={editBarStyle} overflowEllipsis={true} - chatGPT={redux - .getStore("projects") - .hasLanguageModelEnabled(project_id, undefined, "openai")} - vertexAI={redux - .getStore("projects") - .hasLanguageModelEnabled(project_id, undefined, "google")} /> ); } @@ -194,7 +188,7 @@ export default function ChatInput({ function getPlaceholder(project_id, placeholder?: string): string { if (placeholder != null) return placeholder; if (redux.getStore("projects").hasLanguageModelEnabled(project_id)) { - return "Type a new message (use @chatgpt for ChatGPT)..."; + return "Type a new message (mention a LLM via @chatgpt, @gemini, …)..."; } return "Type a new message..."; } diff --git a/src/packages/frontend/chat/message.tsx b/src/packages/frontend/chat/message.tsx index 104b2ce585..3b30e9a8f9 100644 --- a/src/packages/frontend/chat/message.tsx +++ b/src/packages/frontend/chat/message.tsx @@ -3,7 +3,7 @@ * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ -import { Button, Col, Row, Popconfirm, Tooltip } from "antd"; +import { Button, Col, Popconfirm, Row, Tooltip } from "antd"; import { Map } from "immutable"; import { CSSProperties } from "react"; @@ -17,7 +17,7 @@ import { import { Gap, Icon, TimeAgo, Tip } from "@cocalc/frontend/components"; import MostlyStaticMarkdown from "@cocalc/frontend/editors/slate/mostly-static-markdown"; import { IS_TOUCH } from "@cocalc/frontend/feature"; -import { modelToName } from "@cocalc/frontend/frame-editors/chatgpt/model-switch"; +import { modelToName } from "@cocalc/frontend/frame-editors/llm/model-switch"; import { COLORS } from "@cocalc/util/theme"; import { ChatActions } from "./actions"; import { getUserName } from "./chat-log"; @@ -127,7 +127,7 @@ export default function Message(props: Props) { const is_viewers_message = sender_is_viewer(props.account_id, props.message); const verb = show_history ? "Hide" : "Show"; - const isChatGPTThread = useMemo( + const isLLMThread = useMemo( () => props.actions?.isLanguageModelThread(props.message.get("date")), [props.message], ); @@ -632,9 +632,9 @@ export default function Message(props: Props) { {!generating && ( Reply - {isChatGPTThread ? ` to ${modelToName(isChatGPTThread)}` : ""} - {isChatGPTThread && ( + {isLLMThread ? ` to ${modelToName(isLLMThread)}` : ""} + {isLLMThread && ( @@ -657,7 +657,7 @@ export default function Message(props: Props) { )} {!generating && - isChatGPTThread && + isLLMThread && props.actions && Date.now() - date <= regenerateCutoff && ( {formula ? ( - <> - {formula} - - Preview: - - - + {formula}, + }, + { + key: "2", + label: "Preview", + children: , + }, + ...(fullReply + ? [ + { + key: "3", + label: "Full reply", + children: , + }, + ] + : []), + ]} + /> ) : undefined} {error ? {error} : undefined} {mode === "tex" ? ( @@ -246,6 +300,13 @@ function AiGenFormula({ mode, text = "", project_id, cb }: Props) { return (
+