mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-19 10:42:57 +02:00
Pass 3 - add commands, remove run, formatting, mobile view
This commit is contained in:
@@ -38,7 +38,6 @@ type AiClientConfigCardProps = {
|
|||||||
keyAuth: AiClientAuthInput;
|
keyAuth: AiClientAuthInput;
|
||||||
description: string;
|
description: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
stackBlocks?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AiClientConfigCard({
|
export function AiClientConfigCard({
|
||||||
@@ -47,8 +46,7 @@ export function AiClientConfigCard({
|
|||||||
endpoint,
|
endpoint,
|
||||||
keyAuth,
|
keyAuth,
|
||||||
description,
|
description,
|
||||||
icon: Icon,
|
icon: Icon
|
||||||
stackBlocks = true
|
|
||||||
}: AiClientConfigCardProps) {
|
}: AiClientConfigCardProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -94,7 +92,7 @@ export function AiClientConfigCard({
|
|||||||
guide?.presets.find((p) => p.id === presetId) ?? guide?.presets[0];
|
guide?.presets.find((p) => p.id === presetId) ?? guide?.presets[0];
|
||||||
|
|
||||||
const manualContent = guide ? (
|
const manualContent = guide ? (
|
||||||
<div className="space-y-4">
|
<div className="min-w-0 space-y-4">
|
||||||
{guide.presets.length > 1 ? (
|
{guide.presets.length > 1 ? (
|
||||||
<Select
|
<Select
|
||||||
value={presetId}
|
value={presetId}
|
||||||
@@ -114,14 +112,7 @@ export function AiClientConfigCard({
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
) : null}
|
) : null}
|
||||||
<div
|
<div className="grid min-w-0 gap-4">
|
||||||
className={cn(
|
|
||||||
"grid gap-4",
|
|
||||||
!stackBlocks &&
|
|
||||||
(preset?.blocks.length ?? 0) > 1 &&
|
|
||||||
"@lg:grid-cols-2"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{preset?.blocks.map((block) => (
|
{preset?.blocks.map((block) => (
|
||||||
<AiConfigCodeBlock key={block.id} block={block} />
|
<AiConfigCodeBlock key={block.id} block={block} />
|
||||||
))}
|
))}
|
||||||
@@ -133,7 +124,7 @@ export function AiClientConfigCard({
|
|||||||
<Collapsible
|
<Collapsible
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
className="rounded-md border bg-card"
|
className="min-w-0 rounded-md border bg-card"
|
||||||
>
|
>
|
||||||
<CollapsibleTrigger className="flex w-full items-center gap-3 px-4 py-3 text-left cursor-pointer">
|
<CollapsibleTrigger className="flex w-full items-center gap-3 px-4 py-3 text-left cursor-pointer">
|
||||||
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
||||||
@@ -150,7 +141,7 @@ export function AiClientConfigCard({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent className="border-t px-4 py-4">
|
<CollapsibleContent className="min-w-0 border-t px-4 py-4">
|
||||||
{!guide && revealing ? (
|
{!guide && revealing ? (
|
||||||
<div className="flex items-center justify-center py-6 text-muted-foreground">
|
<div className="flex items-center justify-center py-6 text-muted-foreground">
|
||||||
<Loader2 className="size-5 animate-spin" />
|
<Loader2 className="size-5 animate-spin" />
|
||||||
@@ -179,27 +170,21 @@ export function AiClientConfigCard({
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent
|
<TabsContent
|
||||||
value="cli"
|
value="cli"
|
||||||
className={cn(
|
className="grid min-w-0 gap-4 mt-4"
|
||||||
"grid gap-4 mt-4",
|
|
||||||
!stackBlocks && "@lg:grid-cols-2"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<AiConfigCodeBlock
|
<AiConfigCodeBlock
|
||||||
block={guide.cli.configure}
|
block={guide.cli.configure}
|
||||||
/>
|
/>
|
||||||
<AiConfigCodeBlock block={guide.cli.run} />
|
|
||||||
{guide.cli.configureWithKey ? (
|
{guide.cli.configureWithKey ? (
|
||||||
<AiConfigCodeBlock
|
<AiConfigCodeBlock
|
||||||
block={guide.cli.configureWithKey}
|
block={guide.cli.configureWithKey}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{guide.cli.runWithKey ? (
|
|
||||||
<AiConfigCodeBlock
|
|
||||||
block={guide.cli.runWithKey}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="manual" className="mt-4">
|
<TabsContent
|
||||||
|
value="manual"
|
||||||
|
className="min-w-0 mt-4"
|
||||||
|
>
|
||||||
{manualContent}
|
{manualContent}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ type AiClientConfigSectionProps = {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
auth: AiClientAuthInput;
|
auth: AiClientAuthInput;
|
||||||
/**
|
/**
|
||||||
* "wide" lays the client cards out side by side and allows a card's
|
* "wide" lays the client cards out side by side once there's room
|
||||||
* code blocks to sit side by side once there's room (e.g. the Keys
|
* (e.g. the Keys page). "compact" always stacks them, which is what
|
||||||
* page). "compact" always stacks both, which is what fits the
|
* fits the Resource Launcher's side panel. A card's own code blocks
|
||||||
* Resource Launcher's side panel.
|
* always stack regardless of this setting.
|
||||||
*/
|
*/
|
||||||
layout?: "wide" | "compact";
|
layout?: "wide" | "compact";
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -64,10 +64,10 @@ export function AiClientConfigSection({
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<div className={cn("@container", className)}>
|
<div className={cn("@container min-w-0", className)}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid gap-3",
|
"grid min-w-0 gap-3",
|
||||||
isWide && "@3xl:grid-cols-2"
|
isWide && "@3xl:grid-cols-2"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -80,7 +80,6 @@ export function AiClientConfigSection({
|
|||||||
keyAuth={auth}
|
keyAuth={auth}
|
||||||
description={descriptions[clientId]}
|
description={descriptions[clientId]}
|
||||||
icon={CLIENT_ICONS[clientId]}
|
icon={CLIENT_ICONS[clientId]}
|
||||||
stackBlocks={!isWide}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,19 +2,21 @@
|
|||||||
|
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
import type { AiConfigBlock } from "@app/lib/aiClientConfig";
|
import type { AiConfigBlock } from "@app/lib/aiClientConfig";
|
||||||
|
import { cn } from "@app/lib/cn";
|
||||||
|
|
||||||
export function AiConfigCodeBlock({ block }: { block: AiConfigBlock }) {
|
export function AiConfigCodeBlock({ block }: { block: AiConfigBlock }) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1.5">
|
<div className="min-w-0 space-y-1.5">
|
||||||
<p className="font-mono text-xs text-muted-foreground">
|
<p className="font-mono text-xs text-muted-foreground">
|
||||||
{block.label}
|
{block.label}
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
className={
|
className={cn(
|
||||||
|
"min-w-0",
|
||||||
block.kind === "steps"
|
block.kind === "steps"
|
||||||
? "[&_pre]:text-sm"
|
? "[&_pre]:text-sm"
|
||||||
: "[&_pre]:text-xs [&_code]:font-mono"
|
: "[&_pre]:text-xs [&_code]:font-mono"
|
||||||
}
|
)}
|
||||||
>
|
>
|
||||||
<CopyTextBox
|
<CopyTextBox
|
||||||
text={block.displayText}
|
text={block.displayText}
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ export type AiConfigPreset = {
|
|||||||
export type AiCliCommands = {
|
export type AiCliCommands = {
|
||||||
configure: AiConfigBlock;
|
configure: AiConfigBlock;
|
||||||
configureWithKey?: AiConfigBlock;
|
configureWithKey?: AiConfigBlock;
|
||||||
run: AiConfigBlock;
|
|
||||||
runWithKey?: AiConfigBlock;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AiClientGuide = {
|
export type AiClientGuide = {
|
||||||
@@ -59,34 +57,26 @@ function block(
|
|||||||
return { id, label, kind, displayText: build(keyValue(auth)) };
|
return { id, label, kind, displayText: build(keyValue(auth)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCli(clientArg: "claude" | "codex", auth: AiClientAuth): AiCliCommands {
|
function buildCli(
|
||||||
|
clientArg: "claude" | "codex" | "opencode",
|
||||||
|
auth: AiClientAuth
|
||||||
|
): AiCliCommands {
|
||||||
const configure: AiConfigBlock = {
|
const configure: AiConfigBlock = {
|
||||||
id: `cli-configure-${clientArg}`,
|
id: `cli-configure-${clientArg}`,
|
||||||
label: "Configure",
|
label: "Configure",
|
||||||
displayText: `pangolin configure ${clientArg}`
|
displayText: `pangolin configure ${clientArg}`
|
||||||
};
|
};
|
||||||
const run: AiConfigBlock = {
|
|
||||||
id: `cli-run-${clientArg}`,
|
|
||||||
label: "Run",
|
|
||||||
displayText: `pangolin run ${clientArg}`
|
|
||||||
};
|
|
||||||
|
|
||||||
if (auth.mode !== "keyed") {
|
if (auth.mode !== "keyed") {
|
||||||
return { configure, run };
|
return { configure };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
configure,
|
configure,
|
||||||
run,
|
|
||||||
configureWithKey: {
|
configureWithKey: {
|
||||||
id: `cli-configure-key-${clientArg}`,
|
id: `cli-configure-key-${clientArg}`,
|
||||||
label: "Configure with an API key",
|
label: "Configure with an API key",
|
||||||
displayText: `pangolin configure ${clientArg} ${auth.key}`
|
displayText: `pangolin configure ${clientArg} ${auth.key}`
|
||||||
},
|
|
||||||
runWithKey: {
|
|
||||||
id: `cli-run-key-${clientArg}`,
|
|
||||||
label: "Run with an API key",
|
|
||||||
displayText: `pangolin run ${clientArg} ${auth.key}`
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -278,7 +268,7 @@ function buildOpencodeGuide(endpoint: string, auth: AiClientAuth): AiClientGuide
|
|||||||
return {
|
return {
|
||||||
id: "opencode",
|
id: "opencode",
|
||||||
name: AI_CLIENT_NAMES.opencode,
|
name: AI_CLIENT_NAMES.opencode,
|
||||||
cli: null,
|
cli: buildCli("opencode", auth),
|
||||||
presets: [
|
presets: [
|
||||||
{
|
{
|
||||||
id: "default",
|
id: "default",
|
||||||
|
|||||||
Reference in New Issue
Block a user