Add a guided setup after signup, and a no-model notice in conversations
The first super admin now lands on Settings > Setup after signing up: a skippable walk through the app name, connecting a model provider and picking models, ending with where to go next. It stays under Settings to come back to. While a bot has no text model, its conversation shows a notice in the thread (centred when empty): super admins get Connect a provider and Guided setup, everyone else is told the super admin sets it up. The models modal keeps it current. The Models page's data and save logic move into useModelsOverview, shared with the setup. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -8,7 +8,7 @@ import Dropdown from "@moduletrace/twui/elements/Dropdown";
|
||||
import useStatus from "@moduletrace/twui/hooks/useStatus";
|
||||
import { CpuAi, EllipsisVertical, Monitor } from "lucide-react";
|
||||
import type { BUN_SQLITE_COBBLE_WORKSPACES } from "@/db/types/db";
|
||||
import type { BotWithLastMessage } from "@/src/types";
|
||||
import type { BotTextModel, BotWithLastMessage } from "@/src/types";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import WorkspaceAvatar from "@/src/components/general/workspace-avatar";
|
||||
import RailDrawerButton from "@/src/layouts/bots/(partials)/rail-drawer-button";
|
||||
@@ -27,6 +27,12 @@ type Props = {
|
||||
*/
|
||||
workspace?: BUN_SQLITE_COBBLE_WORKSPACES | null;
|
||||
has_other_unread?: boolean;
|
||||
/**
|
||||
* The text model the bot runs on, kept by the conversation (which
|
||||
* shows a notice while there's none).
|
||||
*/
|
||||
model?: BotTextModel | null;
|
||||
onModelChange: (model: BotTextModel | null) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -40,10 +46,11 @@ export default function ConversationHeader({
|
||||
bot,
|
||||
workspace,
|
||||
has_other_unread,
|
||||
model,
|
||||
onModelChange,
|
||||
}: Props) {
|
||||
const { computers } = useContext(BotComputersContext);
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const [model, setModel] = useState(pageProps?.bot_text_model);
|
||||
/**
|
||||
* The menu's toggle shows its change at once; a push (here or from
|
||||
* another tab) brings the live bot's.
|
||||
@@ -143,7 +150,7 @@ export default function ConversationHeader({
|
||||
(h) => h.harness == "text",
|
||||
)?.current;
|
||||
|
||||
setModel(
|
||||
onModelChange(
|
||||
current
|
||||
? {
|
||||
name: current.name,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo, type RefObject } from "react";
|
||||
import { useMemo, type ReactNode, type RefObject } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import isAwaitingReply from "../(utils)/is-awaiting-reply";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import type {
|
||||
@@ -37,6 +38,11 @@ type Props = {
|
||||
* The conversation went on in another session: no reply is due here.
|
||||
*/
|
||||
moved_on?: boolean;
|
||||
/**
|
||||
* Shown after the messages, or in the middle of an empty thread, e.g.
|
||||
* that the bot has no model to answer with.
|
||||
*/
|
||||
notice?: ReactNode;
|
||||
/**
|
||||
* Quotes text selected in a message in the composer.
|
||||
*/
|
||||
@@ -78,6 +84,7 @@ export default function MessageThread({
|
||||
receiving,
|
||||
has_task,
|
||||
moved_on,
|
||||
notice,
|
||||
onQuote,
|
||||
older,
|
||||
}: Props) {
|
||||
@@ -97,7 +104,10 @@ export default function MessageThread({
|
||||
role="log"
|
||||
aria-live="polite"
|
||||
aria-label={`Conversation with ${bot.name}`}
|
||||
className="w-full max-w-container mx-auto gap-4! items-stretch px-3 md:px-6 pt-6 pb-6"
|
||||
className={twMerge(
|
||||
"w-full max-w-container mx-auto gap-4! items-stretch px-3 md:px-6 pt-6 pb-6",
|
||||
notice && !messages.length && "grow justify-center",
|
||||
)}
|
||||
>
|
||||
{older?.has_more ? (
|
||||
<OlderMessagesLoader
|
||||
@@ -121,6 +131,7 @@ export default function MessageThread({
|
||||
group={group}
|
||||
/>
|
||||
))}
|
||||
{notice}
|
||||
<BotThinkingRow
|
||||
bot={bot}
|
||||
progress={progress}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useContext } from "react";
|
||||
import Button from "@moduletrace/twui/layout/Button";
|
||||
import Row from "@moduletrace/twui/layout/Row";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import { ArrowRight, CpuAi } from "lucide-react";
|
||||
import { SettingsModelsPath, SettingsSetupPath } from "@/src/dict/settings-pages-dict";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import IconTile from "@/src/components/general/icon-tile";
|
||||
import isPassThroughUser from "@/src/utils/rbac/is-pass-through-user";
|
||||
|
||||
type Props = {
|
||||
bot_name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shown in the conversation while the bot has no text model to run on
|
||||
* (nothing would answer a message). A super admin is sent to connect a
|
||||
* provider, or to the guided setup; anyone else is told who can.
|
||||
*/
|
||||
export default function NoModelNotice({ bot_name }: Props) {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const can_set_up = isPassThroughUser({ user_types: pageProps?.user_types });
|
||||
|
||||
return (
|
||||
<Stack
|
||||
role="status"
|
||||
className="w-full max-w-[440px] mx-auto gap-3! items-start rounded-default border border-solid border-hairline bg-surface px-5 py-4"
|
||||
>
|
||||
<Row className="gap-3! flex-nowrap items-center">
|
||||
<IconTile Icon={CpuAi} />
|
||||
<Span className="text-sm font-bold leading-5 text-ink">
|
||||
{can_set_up ? "Connect a model provider" : "No model set up yet"}
|
||||
</Span>
|
||||
</Row>
|
||||
<Span className="text-[13px] leading-5 text-ink-secondary">
|
||||
{can_set_up
|
||||
? `${bot_name} has no model to think with, so nothing would answer yet. Add a provider key (OpenRouter has free models) and pick what your bots run on.`
|
||||
: `${bot_name} has no model to think with, so nothing would answer yet. The super admin connects one in Settings.`}
|
||||
</Span>
|
||||
{can_set_up ? (
|
||||
<Row className="w-full gap-2! justify-end">
|
||||
<Button
|
||||
title="Open the guided setup"
|
||||
type="button"
|
||||
variant="outlined"
|
||||
color="gray"
|
||||
size="small"
|
||||
href={SettingsSetupPath}
|
||||
>
|
||||
Guided setup
|
||||
</Button>
|
||||
<Button
|
||||
title="Connect a provider"
|
||||
type="button"
|
||||
size="small"
|
||||
href={SettingsModelsPath}
|
||||
afterIcon={<ArrowRight size={14} aria-hidden />}
|
||||
>
|
||||
Connect a provider
|
||||
</Button>
|
||||
</Row>
|
||||
) : null}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import useConversationHistory from "../(hooks)/use-conversation-history";
|
||||
import mergeLoadedHistory from "../(utils)/merge-loaded-history";
|
||||
import LoadingLine from "@/src/components/general/loading-line";
|
||||
import SessionMovedBar from "../(partials)/session-moved-bar";
|
||||
import NoModelNotice from "../(partials)/no-model-notice";
|
||||
import syncSessionQuery from "../(utils)/sync-session-query";
|
||||
import announceShownSession from "@/src/functions/frontend/announce-shown-session";
|
||||
|
||||
@@ -85,6 +86,11 @@ export default function Conversation() {
|
||||
*/
|
||||
const [moved_to_session_id, setMovedToSessionId] = useState<number | null>(null);
|
||||
const [has_other_unread, setHasOtherUnread] = useState(false);
|
||||
/**
|
||||
* The text model the bot runs on here; none shows a notice in the
|
||||
* thread. A save in the models modal updates it.
|
||||
*/
|
||||
const [text_model, setTextModel] = useState(pageProps?.bot_text_model || null);
|
||||
const [reply_to, setReplyTo] = useState<BotMessageWithAttachments | null>(
|
||||
null,
|
||||
);
|
||||
@@ -391,6 +397,8 @@ export default function Conversation() {
|
||||
bot={live_bot}
|
||||
workspace={workspace}
|
||||
has_other_unread={has_other_unread}
|
||||
model={text_model}
|
||||
onModelChange={setTextModel}
|
||||
/>
|
||||
{history.loading ? (
|
||||
<LoadingLine label="Loading the conversation" />
|
||||
@@ -409,6 +417,11 @@ export default function Conversation() {
|
||||
receiving={pending_messages.length > 0}
|
||||
has_task={Boolean(progress.task_id)}
|
||||
moved_on={Boolean(moved_to_session_id)}
|
||||
notice={
|
||||
!text_model && !history.loading ? (
|
||||
<NoModelNotice bot_name={live_bot.name || "This bot"} />
|
||||
) : null
|
||||
}
|
||||
older={older}
|
||||
onQuote={({ text }) =>
|
||||
composer.current?.insertQuote({ text })
|
||||
|
||||
Reference in New Issue
Block a user