mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(panel): dense tooltip pass — projects, products, social, KB, business, settings, notifications (178 tips)
Density bar: every dialog field, column header, filter, badge, and status pill explains itself — grounded in the backend (ladder normalization, rate-limit park/probe, self-hosted fallback, reindex scope). Disabled buttons span-wrap so tips fire; tabs re-assert data-state.
This commit is contained in:
@@ -527,6 +527,7 @@ export function AIRoutingCard() {
|
||||
active={currentMode === "self_hosted"}
|
||||
onClick={flipToSelfHosted}
|
||||
disabled={applyMode.isPending || !isSelfHostedConnected}
|
||||
labelHint="Unlike Ollama/Grok, self-hosted has no built-in fallback model — pick one in the picker below or the switch fails server-side."
|
||||
/>
|
||||
<ModeButton
|
||||
icon={<Cpu className="h-4 w-4" />}
|
||||
@@ -561,9 +562,11 @@ export function AIRoutingCard() {
|
||||
<>
|
||||
<Separator />
|
||||
<section className="space-y-2">
|
||||
<Label className="text-sm font-medium">
|
||||
Self-Hosted default model
|
||||
</Label>
|
||||
<HelpTip label="Self-hosted has no automatic fallback — clearing this back to '(use server default)' will fail the next time Self-Hosted mode is applied.">
|
||||
<Label className="text-sm font-medium">
|
||||
Self-Hosted default model
|
||||
</Label>
|
||||
</HelpTip>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Choose which discovered model all agents should use in
|
||||
self-hosted mode.
|
||||
@@ -782,6 +785,7 @@ function ModeButton({
|
||||
disabled,
|
||||
onClick,
|
||||
highlight,
|
||||
labelHint,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
@@ -790,6 +794,8 @@ function ModeButton({
|
||||
disabled: boolean;
|
||||
onClick: () => void | Promise<void>;
|
||||
highlight?: boolean;
|
||||
/** Extra hover context on the label, e.g. a gotcha the always-visible description doesn't cover. */
|
||||
labelHint?: string;
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
@@ -804,11 +810,15 @@ function ModeButton({
|
||||
>
|
||||
<div className="flex w-full items-center gap-2 text-sm font-medium">
|
||||
{icon}
|
||||
<span>{label}</span>
|
||||
<HelpTip label={labelHint}>
|
||||
<span>{label}</span>
|
||||
</HelpTip>
|
||||
{active ? (
|
||||
<Badge className="ml-auto bg-primary/15 text-primary border-0 text-xs">
|
||||
active
|
||||
</Badge>
|
||||
<HelpTip label="Currently applied fleet-wide — every agent spawns on this provider until the mode changes again.">
|
||||
<Badge className="ml-auto bg-primary/15 text-primary border-0 text-xs">
|
||||
active
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground font-normal">
|
||||
|
||||
@@ -289,7 +289,13 @@ export function FeatureFlagsCard() {
|
||||
size="sm"
|
||||
className="w-full justify-between px-2 text-muted-foreground"
|
||||
>
|
||||
<span className="text-sm">X (Twitter) credentials</span>
|
||||
{/* Tip goes on the inner span, not the
|
||||
CollapsibleTrigger asChild Button — wrapping the
|
||||
trigger itself would clobber its open/closed
|
||||
data-state (same trap as Switch/TabsTrigger). */}
|
||||
<HelpTip label="Only takes effect once x_engine_enabled above is on.">
|
||||
<span className="text-sm">X (Twitter) credentials</span>
|
||||
</HelpTip>
|
||||
{xCredsOpen ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
@@ -314,7 +320,9 @@ export function FeatureFlagsCard() {
|
||||
size="sm"
|
||||
className="w-full justify-between px-2 text-muted-foreground"
|
||||
>
|
||||
<span className="text-sm">TikTok credentials</span>
|
||||
<HelpTip label="Only used for the TikTok distribution leg — needs video_engine_enabled above on, and video only posts once you approve a rendered draft.">
|
||||
<span className="text-sm">TikTok credentials</span>
|
||||
</HelpTip>
|
||||
{tiktokCredsOpen ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
@@ -339,7 +347,9 @@ export function FeatureFlagsCard() {
|
||||
size="sm"
|
||||
className="w-full justify-between px-2 text-muted-foreground"
|
||||
>
|
||||
<span className="text-sm">Telegram credentials</span>
|
||||
<HelpTip label="Only takes effect once telegram_enabled above is on — in-app notifications keep working either way.">
|
||||
<span className="text-sm">Telegram credentials</span>
|
||||
</HelpTip>
|
||||
{telegramCredsOpen ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
|
||||
@@ -145,16 +145,22 @@ export function SelfHostedSection({
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Server className="h-4 w-4 text-muted-foreground" />
|
||||
<Label className="text-sm font-medium">Self-Hosted LLM</Label>
|
||||
<HelpTip label="Routes agents to any locally-run OpenAI-compatible endpoint (Ollama, vLLM, LM Studio) instead of a cloud provider.">
|
||||
<Label className="text-sm font-medium">Self-Hosted LLM</Label>
|
||||
</HelpTip>
|
||||
{testResult?.ok === true && (
|
||||
<Badge className="bg-emerald-500/10 text-emerald-600 border-0">
|
||||
<CheckCircle2 className="h-3 w-3" /> connected
|
||||
</Badge>
|
||||
<HelpTip label="The last Test Connection call succeeded — the model list below reflects this endpoint.">
|
||||
<Badge className="bg-emerald-500/10 text-emerald-600 border-0">
|
||||
<CheckCircle2 className="h-3 w-3" /> connected
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
{testResult?.ok === false && (
|
||||
<Badge className="bg-red-500/10 text-red-600 border-0">
|
||||
<XCircle className="h-3 w-3" /> error
|
||||
</Badge>
|
||||
<HelpTip label="The last Test Connection call failed — see the error detail below.">
|
||||
<Badge className="bg-red-500/10 text-red-600 border-0">
|
||||
<XCircle className="h-3 w-3" /> error
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -216,9 +222,11 @@ export function SelfHostedSection({
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</div>
|
||||
<Button onClick={handleSave} disabled={saveConfig.isPending}>
|
||||
{saveConfig.isPending ? "Saving…" : "Save"}
|
||||
</Button>
|
||||
<HelpTip label="Saves the Base URL above together with any token typed here, in one combined config — not just this token.">
|
||||
<Button onClick={handleSave} disabled={saveConfig.isPending}>
|
||||
{saveConfig.isPending ? "Saving…" : "Save"}
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</div>
|
||||
{config?.has_token && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -336,24 +344,26 @@ export function SelfHostedSection({
|
||||
</span>
|
||||
</HelpTip>
|
||||
</p>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRefresh}
|
||||
disabled={refreshModels.isPending}
|
||||
>
|
||||
{refreshModels.isPending ? (
|
||||
<>
|
||||
<RefreshCw className="mr-1.5 h-3 w-3 animate-spin" />
|
||||
Refreshing…
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="mr-1.5 h-3 w-3" />
|
||||
Refresh Models
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<HelpTip label="Re-queries the endpoint's model list only — doesn't re-run the connection test above.">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRefresh}
|
||||
disabled={refreshModels.isPending}
|
||||
>
|
||||
{refreshModels.isPending ? (
|
||||
<>
|
||||
<RefreshCw className="mr-1.5 h-3 w-3 animate-spin" />
|
||||
Refreshing…
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="mr-1.5 h-3 w-3" />
|
||||
Refresh Models
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</div>
|
||||
<div className="divide-y rounded-md border">
|
||||
{models.map((m) => (
|
||||
|
||||
@@ -69,19 +69,22 @@ export function TelegramCredentialsForm() {
|
||||
|
||||
<div className="flex items-center gap-2 rounded-md border p-3">
|
||||
{status?.has_credentials ? (
|
||||
<>
|
||||
<HelpTip label="Bot token + chat id are both stored — CEO notifications also DM via Telegram alongside the in-app bell.">
|
||||
<Key className="h-4 w-4 text-green-500" />
|
||||
<span className="text-sm text-green-600 dark:text-green-400">
|
||||
Credentials are set
|
||||
</span>
|
||||
</>
|
||||
</HelpTip>
|
||||
) : (
|
||||
<>
|
||||
<HelpTip label="Missing — notifications stay in-app only; nothing sends over Telegram until both fields below are set.">
|
||||
<KeyRound className="h-4 w-4 text-amber-500" />
|
||||
<span className="text-sm text-amber-600 dark:text-amber-400">
|
||||
{isLoading ? "Checking..." : "No credentials configured"}
|
||||
</span>
|
||||
</>
|
||||
</HelpTip>
|
||||
)}
|
||||
{status?.has_credentials ? (
|
||||
<span className="text-sm text-green-600 dark:text-green-400">
|
||||
Credentials are set
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-amber-600 dark:text-amber-400">
|
||||
{isLoading ? "Checking..." : "No credentials configured"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -88,19 +88,22 @@ export function TikTokCredentialsForm() {
|
||||
|
||||
<div className="flex items-center gap-2 rounded-md border p-3">
|
||||
{status?.has_credentials ? (
|
||||
<>
|
||||
<HelpTip label="All 4 OAuth2 secrets are stored — approved video drafts can post to TikTok's inbox-upload.">
|
||||
<Key className="h-4 w-4 text-green-500" />
|
||||
<span className="text-sm text-green-600 dark:text-green-400">
|
||||
Credentials are set
|
||||
</span>
|
||||
</>
|
||||
</HelpTip>
|
||||
) : (
|
||||
<>
|
||||
<HelpTip label="Missing at least one secret — video drafts still render and queue but can't post to TikTok until all 4 are set.">
|
||||
<KeyRound className="h-4 w-4 text-amber-500" />
|
||||
<span className="text-sm text-amber-600 dark:text-amber-400">
|
||||
{isLoading ? "Checking..." : "No credentials configured"}
|
||||
</span>
|
||||
</>
|
||||
</HelpTip>
|
||||
)}
|
||||
{status?.has_credentials ? (
|
||||
<span className="text-sm text-green-600 dark:text-green-400">
|
||||
Credentials are set
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-amber-600 dark:text-amber-400">
|
||||
{isLoading ? "Checking..." : "No credentials configured"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -86,19 +86,22 @@ export function XCredentialsForm() {
|
||||
|
||||
<div className="flex items-center gap-2 rounded-md border p-3">
|
||||
{status?.has_credentials ? (
|
||||
<>
|
||||
<HelpTip label="All 4 secrets are stored — approved posts in the X queue can actually publish.">
|
||||
<Key className="h-4 w-4 text-green-500" />
|
||||
<span className="text-sm text-green-600 dark:text-green-400">
|
||||
Credentials are set
|
||||
</span>
|
||||
</>
|
||||
</HelpTip>
|
||||
) : (
|
||||
<>
|
||||
<HelpTip label="Missing at least one secret — drafts still queue for review but can't post until all 4 are set.">
|
||||
<KeyRound className="h-4 w-4 text-amber-500" />
|
||||
<span className="text-sm text-amber-600 dark:text-amber-400">
|
||||
{isLoading ? "Checking..." : "No credentials configured"}
|
||||
</span>
|
||||
</>
|
||||
</HelpTip>
|
||||
)}
|
||||
{status?.has_credentials ? (
|
||||
<span className="text-sm text-green-600 dark:text-green-400">
|
||||
Credentials are set
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-amber-600 dark:text-amber-400">
|
||||
{isLoading ? "Checking..." : "No credentials configured"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user