mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-02 17:22:06 +03:00
fix(webui): nudge model setup on blocked submit
This commit is contained in:
@@ -90,6 +90,7 @@ interface ModelPresetBadgeProps {
|
|||||||
provider?: string | null;
|
provider?: string | null;
|
||||||
providerLabel?: string | null;
|
providerLabel?: string | null;
|
||||||
needsSetup?: boolean;
|
needsSetup?: boolean;
|
||||||
|
attentionRequest?: number;
|
||||||
fallbackModelName?: string | null;
|
fallbackModelName?: string | null;
|
||||||
isHero: boolean;
|
isHero: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@@ -106,6 +107,7 @@ export function ModelPresetBadge({
|
|||||||
provider,
|
provider,
|
||||||
providerLabel,
|
providerLabel,
|
||||||
needsSetup = false,
|
needsSetup = false,
|
||||||
|
attentionRequest = 0,
|
||||||
fallbackModelName,
|
fallbackModelName,
|
||||||
isHero,
|
isHero,
|
||||||
onClick,
|
onClick,
|
||||||
@@ -272,11 +274,13 @@ export function ModelPresetBadge({
|
|||||||
|
|
||||||
const pill = (
|
const pill = (
|
||||||
<PresetPill
|
<PresetPill
|
||||||
|
key={needsSetup ? attentionRequest : undefined}
|
||||||
label={displayLabel}
|
label={displayLabel}
|
||||||
modelDetail={displayModelDetail}
|
modelDetail={displayModelDetail}
|
||||||
provider={displayProvider}
|
provider={displayProvider}
|
||||||
providerLabel={fallbackModelName ? null : providerLabel}
|
providerLabel={fallbackModelName ? null : providerLabel}
|
||||||
needsSetup={needsSetup}
|
needsSetup={needsSetup}
|
||||||
|
needsAttention={needsSetup && attentionRequest > 0}
|
||||||
fallbackModelName={fallbackModelName}
|
fallbackModelName={fallbackModelName}
|
||||||
fallbackFromLabel={fallbackModelName ? label : null}
|
fallbackFromLabel={fallbackModelName ? label : null}
|
||||||
isHero={isHero}
|
isHero={isHero}
|
||||||
@@ -493,6 +497,7 @@ function PresetPill({
|
|||||||
provider,
|
provider,
|
||||||
providerLabel,
|
providerLabel,
|
||||||
needsSetup = false,
|
needsSetup = false,
|
||||||
|
needsAttention = false,
|
||||||
fallbackModelName,
|
fallbackModelName,
|
||||||
fallbackFromLabel,
|
fallbackFromLabel,
|
||||||
isHero,
|
isHero,
|
||||||
@@ -504,6 +509,7 @@ function PresetPill({
|
|||||||
provider?: string | null;
|
provider?: string | null;
|
||||||
providerLabel?: string | null;
|
providerLabel?: string | null;
|
||||||
needsSetup?: boolean;
|
needsSetup?: boolean;
|
||||||
|
needsAttention?: boolean;
|
||||||
fallbackModelName?: string | null;
|
fallbackModelName?: string | null;
|
||||||
fallbackFromLabel?: string | null;
|
fallbackFromLabel?: string | null;
|
||||||
isHero: boolean;
|
isHero: boolean;
|
||||||
@@ -542,6 +548,7 @@ function PresetPill({
|
|||||||
"transition-[color,background-color,border-color,transform] duration-150 ease-out group-focus-visible:ring-2 group-focus-visible:ring-ring/45",
|
"transition-[color,background-color,border-color,transform] duration-150 ease-out group-focus-visible:ring-2 group-focus-visible:ring-ring/45",
|
||||||
isHero ? "gap-1.5 px-2.5 text-[12px]" : "gap-2 px-3 text-[12.5px]",
|
isHero ? "gap-1.5 px-2.5 text-[12px]" : "gap-2 px-3 text-[12.5px]",
|
||||||
needsSetup && "composer-model-pill-setup",
|
needsSetup && "composer-model-pill-setup",
|
||||||
|
needsAttention && "composer-model-pill-setup-attention",
|
||||||
offset !== undefined && "composer-model-pill-dock",
|
offset !== undefined && "composer-model-pill-dock",
|
||||||
)}
|
)}
|
||||||
style={scale === undefined ? undefined : {
|
style={scale === undefined ? undefined : {
|
||||||
|
|||||||
@@ -1036,6 +1036,7 @@ export function ThreadComposer({
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [inlineError, setInlineError] = useState<string | null>(null);
|
const [inlineError, setInlineError] = useState<string | null>(null);
|
||||||
const [sendPending, setSendPending] = useState(false);
|
const [sendPending, setSendPending] = useState(false);
|
||||||
|
const [modelSetupAttentionRequest, setModelSetupAttentionRequest] = useState(0);
|
||||||
const interactionDisabled = !!disabled || sendPending;
|
const interactionDisabled = !!disabled || sendPending;
|
||||||
const [voiceErrorFading, setVoiceErrorFading] = useState(false);
|
const [voiceErrorFading, setVoiceErrorFading] = useState(false);
|
||||||
const [slashMenuDismissed, setSlashMenuDismissed] = useState(false);
|
const [slashMenuDismissed, setSlashMenuDismissed] = useState(false);
|
||||||
@@ -2008,7 +2009,9 @@ export function ThreadComposer({
|
|||||||
|
|
||||||
const submit = useCallback(() => {
|
const submit = useCallback(() => {
|
||||||
if (modelNeedsSetup) {
|
if (modelNeedsSetup) {
|
||||||
onModelBadgeClick?.();
|
if (hasComposerContent) {
|
||||||
|
setModelSetupAttentionRequest((request) => request + 1);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!canSend) return;
|
if (!canSend) return;
|
||||||
@@ -2112,11 +2115,11 @@ export function ThreadComposer({
|
|||||||
clear,
|
clear,
|
||||||
clearComposerText,
|
clearComposerText,
|
||||||
hasTouchPrimaryPointer,
|
hasTouchPrimaryPointer,
|
||||||
|
hasComposerContent,
|
||||||
handleStop,
|
handleStop,
|
||||||
isStreaming,
|
isStreaming,
|
||||||
maxTextBytes,
|
maxTextBytes,
|
||||||
modelNeedsSetup,
|
modelNeedsSetup,
|
||||||
onModelBadgeClick,
|
|
||||||
onSend,
|
onSend,
|
||||||
onStop,
|
onStop,
|
||||||
onQuotedContextChange,
|
onQuotedContextChange,
|
||||||
@@ -2546,6 +2549,7 @@ export function ThreadComposer({
|
|||||||
provider={modelProvider}
|
provider={modelProvider}
|
||||||
providerLabel={modelProviderLabel}
|
providerLabel={modelProviderLabel}
|
||||||
needsSetup={modelNeedsSetup}
|
needsSetup={modelNeedsSetup}
|
||||||
|
attentionRequest={modelSetupAttentionRequest}
|
||||||
fallbackModelName={fallbackModelName}
|
fallbackModelName={fallbackModelName}
|
||||||
isHero={isHero}
|
isHero={isHero}
|
||||||
onClick={modelNeedsSetup ? onModelBadgeClick : undefined}
|
onClick={modelNeedsSetup ? onModelBadgeClick : undefined}
|
||||||
|
|||||||
@@ -765,6 +765,33 @@
|
|||||||
0 2px 5px rgb(0 0 0 / 0.16);
|
0 2px 5px rgb(0 0 0 / 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes composer-model-pill-setup-attention {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
22% {
|
||||||
|
transform: translateX(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
46% {
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
68% {
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
86% {
|
||||||
|
transform: translateX(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-model-pill-setup-attention {
|
||||||
|
animation: composer-model-pill-setup-attention 280ms cubic-bezier(0.22, 0.8, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes composer-model-pill-viewport-enter {
|
@keyframes composer-model-pill-viewport-enter {
|
||||||
from {
|
from {
|
||||||
transform: scale(0.9074);
|
transform: scale(0.9074);
|
||||||
@@ -817,6 +844,10 @@
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.composer-model-pill-setup-attention {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
.composer-model-pill-track[data-settling="true"],
|
.composer-model-pill-track[data-settling="true"],
|
||||||
.composer-model-pill-dock {
|
.composer-model-pill-dock {
|
||||||
transition: none;
|
transition: none;
|
||||||
|
|||||||
@@ -895,11 +895,18 @@ describe("ThreadShell", () => {
|
|||||||
expect(client.sendMessage).not.toHaveBeenCalled();
|
expect(client.sendMessage).not.toHaveBeenCalled();
|
||||||
|
|
||||||
onOpenModelSettings.mockClear();
|
onOpenModelSettings.mockClear();
|
||||||
|
const firstSetupPill = badge.querySelector('[data-needs-setup="true"]');
|
||||||
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
||||||
|
|
||||||
expect(onOpenModelSettings).toHaveBeenCalledTimes(1);
|
const secondSetupPill = badge.querySelector('[data-needs-setup="true"]');
|
||||||
|
expect(onOpenModelSettings).not.toHaveBeenCalled();
|
||||||
|
expect(secondSetupPill).not.toBe(firstSetupPill);
|
||||||
|
expect(secondSetupPill).toHaveClass("composer-model-pill-setup-attention");
|
||||||
expect(input).toHaveValue("hello");
|
expect(input).toHaveValue("hello");
|
||||||
expect(client.sendMessage).not.toHaveBeenCalled();
|
expect(client.sendMessage).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
||||||
|
expect(badge.querySelector('[data-needs-setup="true"]')).not.toBe(secondSetupPill);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps image generation controls out of the composer", async () => {
|
it("keeps image generation controls out of the composer", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user