mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-10 16:37:40 +00:00
Compare commits
2 Commits
feat/impro
...
fix/duplic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
481dbaef67 | ||
|
|
e581a0e1cc |
19
README.md
19
README.md
@@ -60,6 +60,7 @@
|
|||||||
- **File Explorer** - Interactive file tree with syntax highlighting and live editing
|
- **File Explorer** - Interactive file tree with syntax highlighting and live editing
|
||||||
- **Git Explorer** - View, stage and commit your changes. You can also switch branches
|
- **Git Explorer** - View, stage and commit your changes. You can also switch branches
|
||||||
- **Session Management** - Resume conversations, manage multiple sessions, and track history
|
- **Session Management** - Resume conversations, manage multiple sessions, and track history
|
||||||
|
- **Plugin System** - Extend CloudCLI with custom plugins — add new tabs, backend services, and integrations. [Build your own →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
|
||||||
- **TaskMaster AI Integration** *(Optional)* - Advanced project management with AI-powered task planning, PRD parsing, and workflow automation
|
- **TaskMaster AI Integration** *(Optional)* - Advanced project management with AI-powered task planning, PRD parsing, and workflow automation
|
||||||
- **Model Compatibility** - Works with Claude, GPT, and Gemini model families (see [`shared/modelConstants.js`](shared/modelConstants.js) for the full list of supported models)
|
- **Model Compatibility** - Works with Claude, GPT, and Gemini model families (see [`shared/modelConstants.js`](shared/modelConstants.js) for the full list of supported models)
|
||||||
|
|
||||||
@@ -141,6 +142,24 @@ To use Claude Code's full functionality, you'll need to manually enable tools:
|
|||||||
|
|
||||||
**Recommended approach**: Start with basic tools enabled and add more as needed. You can always adjust these settings later.
|
**Recommended approach**: Start with basic tools enabled and add more as needed. You can always adjust these settings later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
CloudCLI has a plugin system that lets you add custom tabs with their own frontend UI and optional Node.js backend. Install plugins from git repos directly in **Settings > Plugins**, or build your own.
|
||||||
|
|
||||||
|
### Available Plugins
|
||||||
|
|
||||||
|
| Plugin | Description |
|
||||||
|
|---|---|
|
||||||
|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Shows file counts, lines of code, file-type breakdown, largest files, and recently modified files for your current project |
|
||||||
|
|
||||||
|
### Build Your Own
|
||||||
|
|
||||||
|
**[Plugin Starter Template →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** — fork this repo to create your own plugin. It includes a working example with frontend rendering, live context updates, and RPC communication to a backend server.
|
||||||
|
|
||||||
|
**[Plugin Documentation →](https://cloudcli.ai/docs/plugin-overview)** — full guide to the plugin API, manifest format, security model, and more.
|
||||||
|
|
||||||
---
|
---
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
export const CLAUDE_MODELS = {
|
export const CLAUDE_MODELS = {
|
||||||
// Models in SDK format (what the actual SDK accepts)
|
// Models in SDK format (what the actual SDK accepts)
|
||||||
OPTIONS: [
|
OPTIONS: [
|
||||||
{ value: 'sonnet', label: 'Sonnet' },
|
{ value: "sonnet", label: "Sonnet" },
|
||||||
{ value: 'opus', label: 'Opus' },
|
{ value: "opus", label: "Opus" },
|
||||||
{ value: 'haiku', label: 'Haiku' },
|
{ value: "haiku", label: "Haiku" },
|
||||||
{ value: 'opusplan', label: 'Opus Plan' },
|
{ value: "opusplan", label: "Opus Plan" },
|
||||||
{ value: 'sonnet[1m]', label: 'Sonnet [1M]' }
|
{ value: "sonnet[1m]", label: "Sonnet [1M]" },
|
||||||
],
|
],
|
||||||
|
|
||||||
DEFAULT: 'sonnet'
|
DEFAULT: "sonnet",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,28 +28,28 @@ export const CLAUDE_MODELS = {
|
|||||||
*/
|
*/
|
||||||
export const CURSOR_MODELS = {
|
export const CURSOR_MODELS = {
|
||||||
OPTIONS: [
|
OPTIONS: [
|
||||||
{ value: 'opus-4.6-thinking', label: 'Claude 4.6 Opus (Thinking)' },
|
{ value: "opus-4.6-thinking", label: "Claude 4.6 Opus (Thinking)" },
|
||||||
{ value: 'gpt-5.3-codex', label: 'GPT-5.3' },
|
{ value: "gpt-5.3-codex", label: "GPT-5.3" },
|
||||||
{ value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
|
{ value: "gpt-5.2-high", label: "GPT-5.2 High" },
|
||||||
{ value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
|
{ value: "gemini-3-pro", label: "Gemini 3 Pro" },
|
||||||
{ value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' },
|
{ value: "opus-4.5-thinking", label: "Claude 4.5 Opus (Thinking)" },
|
||||||
{ value: 'gpt-5.2', label: 'GPT-5.2' },
|
{ value: "gpt-5.2", label: "GPT-5.2" },
|
||||||
{ value: 'gpt-5.1', label: 'GPT-5.1' },
|
{ value: "gpt-5.1", label: "GPT-5.1" },
|
||||||
{ value: 'gpt-5.1-high', label: 'GPT-5.1 High' },
|
{ value: "gpt-5.1-high", label: "GPT-5.1 High" },
|
||||||
{ value: 'composer-1', label: 'Composer 1' },
|
{ value: "composer-1", label: "Composer 1" },
|
||||||
{ value: 'auto', label: 'Auto' },
|
{ value: "auto", label: "Auto" },
|
||||||
{ value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' },
|
{ value: "sonnet-4.5", label: "Claude 4.5 Sonnet" },
|
||||||
{ value: 'sonnet-4.5-thinking', label: 'Claude 4.5 Sonnet (Thinking)' },
|
{ value: "sonnet-4.5-thinking", label: "Claude 4.5 Sonnet (Thinking)" },
|
||||||
{ value: 'opus-4.5', label: 'Claude 4.5 Opus' },
|
{ value: "opus-4.5", label: "Claude 4.5 Opus" },
|
||||||
{ value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
|
{ value: "gpt-5.1-codex", label: "GPT-5.1 Codex" },
|
||||||
{ value: 'gpt-5.1-codex-high', label: 'GPT-5.1 Codex High' },
|
{ value: "gpt-5.1-codex-high", label: "GPT-5.1 Codex High" },
|
||||||
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
{ value: "gpt-5.1-codex-max", label: "GPT-5.1 Codex Max" },
|
||||||
{ value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' },
|
{ value: "gpt-5.1-codex-max-high", label: "GPT-5.1 Codex Max High" },
|
||||||
{ value: 'opus-4.1', label: 'Claude 4.1 Opus' },
|
{ value: "opus-4.1", label: "Claude 4.1 Opus" },
|
||||||
{ value: 'grok', label: 'Grok' }
|
{ value: "grok", label: "Grok" },
|
||||||
],
|
],
|
||||||
|
|
||||||
DEFAULT: 'gpt-5-3-codex'
|
DEFAULT: "gpt-5-3-codex",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,17 +57,16 @@ export const CURSOR_MODELS = {
|
|||||||
*/
|
*/
|
||||||
export const CODEX_MODELS = {
|
export const CODEX_MODELS = {
|
||||||
OPTIONS: [
|
OPTIONS: [
|
||||||
{ value: 'gpt-5.4', label: 'GPT-5.4' },
|
{ value: "gpt-5.4", label: "GPT-5.4" },
|
||||||
{ value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
|
{ value: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
|
||||||
{ value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
|
{ value: "gpt-5.2-codex", label: "GPT-5.2 Codex" },
|
||||||
{ value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
|
{ value: "gpt-5.2", label: "GPT-5.2" },
|
||||||
{ value: 'gpt-5.2', label: 'GPT-5.2' },
|
{ value: "gpt-5.1-codex-max", label: "GPT-5.1 Codex Max" },
|
||||||
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
{ value: "o3", label: "O3" },
|
||||||
{ value: 'o3', label: 'O3' },
|
{ value: "o4-mini", label: "O4-mini" },
|
||||||
{ value: 'o4-mini', label: 'O4-mini' }
|
|
||||||
],
|
],
|
||||||
|
|
||||||
DEFAULT: 'gpt-5.4'
|
DEFAULT: "gpt-5.4",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,16 +74,19 @@ export const CODEX_MODELS = {
|
|||||||
*/
|
*/
|
||||||
export const GEMINI_MODELS = {
|
export const GEMINI_MODELS = {
|
||||||
OPTIONS: [
|
OPTIONS: [
|
||||||
{ value: 'gemini-3.1-pro-preview', label: 'Gemini 3.1 Pro Preview' },
|
{ value: "gemini-3.1-pro-preview", label: "Gemini 3.1 Pro Preview" },
|
||||||
{ value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
|
{ value: "gemini-3-pro-preview", label: "Gemini 3 Pro Preview" },
|
||||||
{ value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' },
|
{ value: "gemini-3-flash-preview", label: "Gemini 3 Flash Preview" },
|
||||||
{ value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
|
{ value: "gemini-2.5-flash", label: "Gemini 2.5 Flash" },
|
||||||
{ value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
|
{ value: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
|
||||||
{ value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash Lite' },
|
{ value: "gemini-2.0-flash-lite", label: "Gemini 2.0 Flash Lite" },
|
||||||
{ value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash' },
|
{ value: "gemini-2.0-flash", label: "Gemini 2.0 Flash" },
|
||||||
{ value: 'gemini-2.0-pro-exp', label: 'Gemini 2.0 Pro Experimental' },
|
{ value: "gemini-2.0-pro-exp", label: "Gemini 2.0 Pro Experimental" },
|
||||||
{ value: 'gemini-2.0-flash-thinking-exp', label: 'Gemini 2.0 Flash Thinking' }
|
{
|
||||||
|
value: "gemini-2.0-flash-thinking-exp",
|
||||||
|
label: "Gemini 2.0 Flash Thinking",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
DEFAULT: 'gemini-2.5-flash'
|
DEFAULT: "gemini-2.5-flash",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Check, ChevronDown } from 'lucide-react';
|
import { Check, ChevronDown } from "lucide-react";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from "react-i18next";
|
||||||
import SessionProviderLogo from '../../../llm-logo-provider/SessionProviderLogo';
|
import SessionProviderLogo from "../../../llm-logo-provider/SessionProviderLogo";
|
||||||
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS, GEMINI_MODELS } from '../../../../../shared/modelConstants';
|
import {
|
||||||
import type { ProjectSession, SessionProvider } from '../../../../types/app';
|
CLAUDE_MODELS,
|
||||||
import { NextTaskBanner } from '../../../task-master';
|
CURSOR_MODELS,
|
||||||
|
CODEX_MODELS,
|
||||||
|
GEMINI_MODELS,
|
||||||
|
} from "../../../../../shared/modelConstants";
|
||||||
|
import type { ProjectSession, SessionProvider } from "../../../../types/app";
|
||||||
|
import { NextTaskBanner } from "../../../task-master";
|
||||||
|
|
||||||
interface ProviderSelectionEmptyStateProps {
|
type ProviderSelectionEmptyStateProps = {
|
||||||
selectedSession: ProjectSession | null;
|
selectedSession: ProjectSession | null;
|
||||||
currentSessionId: string | null;
|
currentSessionId: string | null;
|
||||||
provider: SessionProvider;
|
provider: SessionProvider;
|
||||||
@@ -24,7 +29,7 @@ interface ProviderSelectionEmptyStateProps {
|
|||||||
isTaskMasterInstalled: boolean | null;
|
isTaskMasterInstalled: boolean | null;
|
||||||
onShowAllTasks?: (() => void) | null;
|
onShowAllTasks?: (() => void) | null;
|
||||||
setInput: React.Dispatch<React.SetStateAction<string>>;
|
setInput: React.Dispatch<React.SetStateAction<string>>;
|
||||||
}
|
};
|
||||||
|
|
||||||
type ProviderDef = {
|
type ProviderDef = {
|
||||||
id: SessionProvider;
|
id: SessionProvider;
|
||||||
@@ -37,50 +42,56 @@ type ProviderDef = {
|
|||||||
|
|
||||||
const PROVIDERS: ProviderDef[] = [
|
const PROVIDERS: ProviderDef[] = [
|
||||||
{
|
{
|
||||||
id: 'claude',
|
id: "claude",
|
||||||
name: 'Claude Code',
|
name: "Claude Code",
|
||||||
infoKey: 'providerSelection.providerInfo.anthropic',
|
infoKey: "providerSelection.providerInfo.anthropic",
|
||||||
accent: 'border-primary',
|
accent: "border-primary",
|
||||||
ring: 'ring-primary/15',
|
ring: "ring-primary/15",
|
||||||
check: 'bg-primary text-primary-foreground',
|
check: "bg-primary text-primary-foreground",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'cursor',
|
id: "cursor",
|
||||||
name: 'Cursor',
|
name: "Cursor",
|
||||||
infoKey: 'providerSelection.providerInfo.cursorEditor',
|
infoKey: "providerSelection.providerInfo.cursorEditor",
|
||||||
accent: 'border-violet-500 dark:border-violet-400',
|
accent: "border-violet-500 dark:border-violet-400",
|
||||||
ring: 'ring-violet-500/15',
|
ring: "ring-violet-500/15",
|
||||||
check: 'bg-violet-500 text-white',
|
check: "bg-violet-500 text-white",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'codex',
|
id: "codex",
|
||||||
name: 'Codex',
|
name: "Codex",
|
||||||
infoKey: 'providerSelection.providerInfo.openai',
|
infoKey: "providerSelection.providerInfo.openai",
|
||||||
accent: 'border-emerald-600 dark:border-emerald-400',
|
accent: "border-emerald-600 dark:border-emerald-400",
|
||||||
ring: 'ring-emerald-600/15',
|
ring: "ring-emerald-600/15",
|
||||||
check: 'bg-emerald-600 dark:bg-emerald-500 text-white',
|
check: "bg-emerald-600 dark:bg-emerald-500 text-white",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gemini',
|
id: "gemini",
|
||||||
name: 'Gemini',
|
name: "Gemini",
|
||||||
infoKey: 'providerSelection.providerInfo.google',
|
infoKey: "providerSelection.providerInfo.google",
|
||||||
accent: 'border-blue-500 dark:border-blue-400',
|
accent: "border-blue-500 dark:border-blue-400",
|
||||||
ring: 'ring-blue-500/15',
|
ring: "ring-blue-500/15",
|
||||||
check: 'bg-blue-500 text-white',
|
check: "bg-blue-500 text-white",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getModelConfig(p: SessionProvider) {
|
function getModelConfig(p: SessionProvider) {
|
||||||
if (p === 'claude') return CLAUDE_MODELS;
|
if (p === "claude") return CLAUDE_MODELS;
|
||||||
if (p === 'codex') return CODEX_MODELS;
|
if (p === "codex") return CODEX_MODELS;
|
||||||
if (p === 'gemini') return GEMINI_MODELS;
|
if (p === "gemini") return GEMINI_MODELS;
|
||||||
return CURSOR_MODELS;
|
return CURSOR_MODELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getModelValue(p: SessionProvider, c: string, cu: string, co: string, g: string) {
|
function getModelValue(
|
||||||
if (p === 'claude') return c;
|
p: SessionProvider,
|
||||||
if (p === 'codex') return co;
|
c: string,
|
||||||
if (p === 'gemini') return g;
|
cu: string,
|
||||||
|
co: string,
|
||||||
|
g: string,
|
||||||
|
) {
|
||||||
|
if (p === "claude") return c;
|
||||||
|
if (p === "codex") return co;
|
||||||
|
if (p === "gemini") return g;
|
||||||
return cu;
|
return cu;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,24 +114,41 @@ export default function ProviderSelectionEmptyState({
|
|||||||
onShowAllTasks,
|
onShowAllTasks,
|
||||||
setInput,
|
setInput,
|
||||||
}: ProviderSelectionEmptyStateProps) {
|
}: ProviderSelectionEmptyStateProps) {
|
||||||
const { t } = useTranslation('chat');
|
const { t } = useTranslation("chat");
|
||||||
const nextTaskPrompt = t('tasks.nextTaskPrompt', { defaultValue: 'Start the next task' });
|
const nextTaskPrompt = t("tasks.nextTaskPrompt", {
|
||||||
|
defaultValue: "Start the next task",
|
||||||
|
});
|
||||||
|
|
||||||
const selectProvider = (next: SessionProvider) => {
|
const selectProvider = (next: SessionProvider) => {
|
||||||
setProvider(next);
|
setProvider(next);
|
||||||
localStorage.setItem('selected-provider', next);
|
localStorage.setItem("selected-provider", next);
|
||||||
setTimeout(() => textareaRef.current?.focus(), 100);
|
setTimeout(() => textareaRef.current?.focus(), 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModelChange = (value: string) => {
|
const handleModelChange = (value: string) => {
|
||||||
if (provider === 'claude') { setClaudeModel(value); localStorage.setItem('claude-model', value); }
|
if (provider === "claude") {
|
||||||
else if (provider === 'codex') { setCodexModel(value); localStorage.setItem('codex-model', value); }
|
setClaudeModel(value);
|
||||||
else if (provider === 'gemini') { setGeminiModel(value); localStorage.setItem('gemini-model', value); }
|
localStorage.setItem("claude-model", value);
|
||||||
else { setCursorModel(value); localStorage.setItem('cursor-model', value); }
|
} else if (provider === "codex") {
|
||||||
|
setCodexModel(value);
|
||||||
|
localStorage.setItem("codex-model", value);
|
||||||
|
} else if (provider === "gemini") {
|
||||||
|
setGeminiModel(value);
|
||||||
|
localStorage.setItem("gemini-model", value);
|
||||||
|
} else {
|
||||||
|
setCursorModel(value);
|
||||||
|
localStorage.setItem("cursor-model", value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const modelConfig = getModelConfig(provider);
|
const modelConfig = getModelConfig(provider);
|
||||||
const currentModel = getModelValue(provider, claudeModel, cursorModel, codexModel, geminiModel);
|
const currentModel = getModelValue(
|
||||||
|
provider,
|
||||||
|
claudeModel,
|
||||||
|
cursorModel,
|
||||||
|
codexModel,
|
||||||
|
geminiModel,
|
||||||
|
);
|
||||||
|
|
||||||
/* ── New session — provider picker ── */
|
/* ── New session — provider picker ── */
|
||||||
if (!selectedSession && !currentSessionId) {
|
if (!selectedSession && !currentSessionId) {
|
||||||
@@ -130,10 +158,10 @@ export default function ProviderSelectionEmptyState({
|
|||||||
{/* Heading */}
|
{/* Heading */}
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<h2 className="text-lg font-semibold tracking-tight text-foreground sm:text-xl">
|
<h2 className="text-lg font-semibold tracking-tight text-foreground sm:text-xl">
|
||||||
{t('providerSelection.title')}
|
{t("providerSelection.title")}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1 text-[13px] text-muted-foreground">
|
<p className="mt-1 text-[13px] text-muted-foreground">
|
||||||
{t('providerSelection.description')}
|
{t("providerSelection.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -149,23 +177,30 @@ export default function ProviderSelectionEmptyState({
|
|||||||
relative flex flex-col items-center gap-2.5 rounded-xl border-[1.5px] px-2
|
relative flex flex-col items-center gap-2.5 rounded-xl border-[1.5px] px-2
|
||||||
pb-4 pt-5 transition-all duration-150
|
pb-4 pt-5 transition-all duration-150
|
||||||
active:scale-[0.97]
|
active:scale-[0.97]
|
||||||
${active
|
${
|
||||||
? `${p.accent} ${p.ring} bg-card shadow-sm ring-2`
|
active
|
||||||
: 'border-border bg-card/60 hover:border-border/80 hover:bg-card'
|
? `${p.accent} ${p.ring} bg-card shadow-sm ring-2`
|
||||||
|
: "border-border bg-card/60 hover:border-border/80 hover:bg-card"
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<SessionProviderLogo
|
<SessionProviderLogo
|
||||||
provider={p.id}
|
provider={p.id}
|
||||||
className={`h-9 w-9 transition-transform duration-150 ${active ? 'scale-110' : ''}`}
|
className={`h-9 w-9 transition-transform duration-150 ${active ? "scale-110" : ""}`}
|
||||||
/>
|
/>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-[13px] font-semibold leading-none text-foreground">{p.name}</p>
|
<p className="text-[13px] font-semibold leading-none text-foreground">
|
||||||
<p className="mt-1 text-[10px] leading-tight text-muted-foreground">{t(p.infoKey)}</p>
|
{p.name}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-[10px] leading-tight text-muted-foreground">
|
||||||
|
{t(p.infoKey)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Check badge */}
|
{/* Check badge */}
|
||||||
{active && (
|
{active && (
|
||||||
<div className={`absolute -right-1 -top-1 h-[18px] w-[18px] rounded-full ${p.check} flex items-center justify-center shadow-sm`}>
|
<div
|
||||||
|
className={`absolute -right-1 -top-1 h-[18px] w-[18px] rounded-full ${p.check} flex items-center justify-center shadow-sm`}
|
||||||
|
>
|
||||||
<Check className="h-2.5 w-2.5" strokeWidth={3} />
|
<Check className="h-2.5 w-2.5" strokeWidth={3} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -175,9 +210,13 @@ export default function ProviderSelectionEmptyState({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Model picker — appears after provider is chosen */}
|
{/* Model picker — appears after provider is chosen */}
|
||||||
<div className={`transition-all duration-200 ${provider ? 'translate-y-0 opacity-100' : 'pointer-events-none translate-y-1 opacity-0'}`}>
|
<div
|
||||||
|
className={`transition-all duration-200 ${provider ? "translate-y-0 opacity-100" : "pointer-events-none translate-y-1 opacity-0"}`}
|
||||||
|
>
|
||||||
<div className="mb-5 flex items-center justify-center gap-2">
|
<div className="mb-5 flex items-center justify-center gap-2">
|
||||||
<span className="text-sm text-muted-foreground">{t('providerSelection.selectModel')}</span>
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{t("providerSelection.selectModel")}
|
||||||
|
</span>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<select
|
<select
|
||||||
value={currentModel}
|
value={currentModel}
|
||||||
@@ -185,9 +224,13 @@ export default function ProviderSelectionEmptyState({
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className="cursor-pointer appearance-none rounded-lg border border-border/60 bg-muted/50 py-1.5 pl-3 pr-7 text-sm font-medium text-foreground transition-colors hover:bg-muted focus:outline-none focus:ring-2 focus:ring-primary/20"
|
className="cursor-pointer appearance-none rounded-lg border border-border/60 bg-muted/50 py-1.5 pl-3 pr-7 text-sm font-medium text-foreground transition-colors hover:bg-muted focus:outline-none focus:ring-2 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
{modelConfig.OPTIONS.map(({ value, label }: { value: string; label: string }) => (
|
{modelConfig.OPTIONS.map(
|
||||||
<option key={value} value={value}>{label}</option>
|
({ value, label }: { value: string; label: string }) => (
|
||||||
))}
|
<option key={value + label} value={value}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
|
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
@@ -196,10 +239,18 @@ export default function ProviderSelectionEmptyState({
|
|||||||
<p className="text-center text-sm text-muted-foreground/70">
|
<p className="text-center text-sm text-muted-foreground/70">
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
claude: t('providerSelection.readyPrompt.claude', { model: claudeModel }),
|
claude: t("providerSelection.readyPrompt.claude", {
|
||||||
cursor: t('providerSelection.readyPrompt.cursor', { model: cursorModel }),
|
model: claudeModel,
|
||||||
codex: t('providerSelection.readyPrompt.codex', { model: codexModel }),
|
}),
|
||||||
gemini: t('providerSelection.readyPrompt.gemini', { model: geminiModel }),
|
cursor: t("providerSelection.readyPrompt.cursor", {
|
||||||
|
model: cursorModel,
|
||||||
|
}),
|
||||||
|
codex: t("providerSelection.readyPrompt.codex", {
|
||||||
|
model: codexModel,
|
||||||
|
}),
|
||||||
|
gemini: t("providerSelection.readyPrompt.gemini", {
|
||||||
|
model: geminiModel,
|
||||||
|
}),
|
||||||
}[provider]
|
}[provider]
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
@@ -208,7 +259,10 @@ export default function ProviderSelectionEmptyState({
|
|||||||
{/* Task banner */}
|
{/* Task banner */}
|
||||||
{provider && tasksEnabled && isTaskMasterInstalled && (
|
{provider && tasksEnabled && isTaskMasterInstalled && (
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<NextTaskBanner onStartTask={() => setInput(nextTaskPrompt)} onShowAllTasks={onShowAllTasks} />
|
<NextTaskBanner
|
||||||
|
onStartTask={() => setInput(nextTaskPrompt)}
|
||||||
|
onShowAllTasks={onShowAllTasks}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -221,12 +275,19 @@ export default function ProviderSelectionEmptyState({
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
<div className="max-w-md px-6 text-center">
|
<div className="max-w-md px-6 text-center">
|
||||||
<p className="mb-1.5 text-lg font-semibold text-foreground">{t('session.continue.title')}</p>
|
<p className="mb-1.5 text-lg font-semibold text-foreground">
|
||||||
<p className="text-sm leading-relaxed text-muted-foreground">{t('session.continue.description')}</p>
|
{t("session.continue.title")}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm leading-relaxed text-muted-foreground">
|
||||||
|
{t("session.continue.description")}
|
||||||
|
</p>
|
||||||
|
|
||||||
{tasksEnabled && isTaskMasterInstalled && (
|
{tasksEnabled && isTaskMasterInstalled && (
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<NextTaskBanner onStartTask={() => setInput(nextTaskPrompt)} onShowAllTasks={onShowAllTasks} />
|
<NextTaskBanner
|
||||||
|
onStartTask={() => setInput(nextTaskPrompt)}
|
||||||
|
onShowAllTasks={onShowAllTasks}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user