Frontend: Add Claude integration UI
- Add Claude models to dropdown with optgroup sections - Update context window calculation for Claude (200k tokens) - Add API key dialog modal for first-time Claude use - Check for API key existence before sending Claude requests - Auto-detect provider from model name (claude-*) - Update sendMessage to handle Claude provider - Store and retrieve API key via backend commands - Add visual separation between Anthropic and Ollama models
This commit is contained in:
@@ -19,7 +19,13 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
const [model, setModel] = useState("llama3.1"); // Default local model
|
||||
const [enableTools, setEnableTools] = useState(true);
|
||||
const [availableModels, setAvailableModels] = useState<string[]>([]);
|
||||
const [claudeModels] = useState<string[]>([
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-5-haiku-20241022",
|
||||
]);
|
||||
const [streamingContent, setStreamingContent] = useState("");
|
||||
const [showApiKeyDialog, setShowApiKeyDialog] = useState(false);
|
||||
const [apiKeyInput, setApiKeyInput] = useState("");
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -33,6 +39,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
};
|
||||
|
||||
const getContextWindowSize = (modelName: string): number => {
|
||||
if (modelName.startsWith("claude-")) return 200000;
|
||||
if (modelName.includes("llama3")) return 8192;
|
||||
if (modelName.includes("qwen2.5")) return 32768;
|
||||
if (modelName.includes("deepseek")) return 16384;
|
||||
@@ -190,6 +197,15 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
const sendMessage = async () => {
|
||||
if (!input.trim() || loading) return;
|
||||
|
||||
// Check if using Claude and API key is required
|
||||
if (model.startsWith("claude-")) {
|
||||
const hasKey = await invoke<boolean>("get_anthropic_api_key_exists");
|
||||
if (!hasKey) {
|
||||
setShowApiKeyDialog(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const userMsg: Message = { role: "user", content: input };
|
||||
const newHistory = [...messages, userMsg];
|
||||
|
||||
@@ -200,7 +216,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
|
||||
try {
|
||||
const config: ProviderConfig = {
|
||||
provider: "ollama",
|
||||
provider: model.startsWith("claude-") ? "anthropic" : "ollama",
|
||||
model: model,
|
||||
base_url: "http://localhost:11434",
|
||||
enable_tools: enableTools,
|
||||
@@ -227,6 +243,21 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveApiKey = async () => {
|
||||
if (!apiKeyInput.trim()) return;
|
||||
|
||||
try {
|
||||
await invoke("set_anthropic_api_key", { apiKey: apiKeyInput });
|
||||
setShowApiKeyDialog(false);
|
||||
setApiKeyInput("");
|
||||
// Retry sending the message
|
||||
sendMessage();
|
||||
} catch (e) {
|
||||
console.error("Failed to save API key:", e);
|
||||
alert(`Failed to save API key: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
const clearSession = async () => {
|
||||
const confirmed = await ask(
|
||||
"Are you sure? This will clear all messages and reset the conversation context.",
|
||||
@@ -380,7 +411,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
>
|
||||
🔄 New Session
|
||||
</button>
|
||||
{availableModels.length > 0 ? (
|
||||
{availableModels.length > 0 || claudeModels.length > 0 ? (
|
||||
<select
|
||||
value={model}
|
||||
onChange={(e) => {
|
||||
@@ -407,11 +438,24 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
backgroundSize: "10px",
|
||||
}}
|
||||
>
|
||||
{claudeModels.length > 0 && (
|
||||
<optgroup label="Anthropic">
|
||||
{claudeModels.map((m) => (
|
||||
<option key={m} value={m}>
|
||||
{m}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
{availableModels.length > 0 && (
|
||||
<optgroup label="Ollama">
|
||||
{availableModels.map((m) => (
|
||||
<option key={m} value={m}>
|
||||
{m}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
@@ -766,6 +810,105 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* API Key Dialog */}
|
||||
{showApiKeyDialog && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#2f2f2f",
|
||||
padding: "32px",
|
||||
borderRadius: "12px",
|
||||
maxWidth: "500px",
|
||||
width: "90%",
|
||||
border: "1px solid #444",
|
||||
}}
|
||||
>
|
||||
<h2 style={{ marginTop: 0, color: "#ececec" }}>
|
||||
Enter Anthropic API Key
|
||||
</h2>
|
||||
<p
|
||||
style={{ color: "#aaa", fontSize: "0.9em", marginBottom: "20px" }}
|
||||
>
|
||||
To use Claude models, please enter your Anthropic API key. Your
|
||||
key will be stored securely in your system keychain.
|
||||
</p>
|
||||
<input
|
||||
type="password"
|
||||
value={apiKeyInput}
|
||||
onChange={(e) => setApiKeyInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSaveApiKey()}
|
||||
placeholder="sk-ant-..."
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "12px",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #555",
|
||||
backgroundColor: "#1a1a1a",
|
||||
color: "#ececec",
|
||||
fontSize: "1em",
|
||||
marginBottom: "20px",
|
||||
outline: "none",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "12px",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowApiKeyDialog(false);
|
||||
setApiKeyInput("");
|
||||
}}
|
||||
style={{
|
||||
padding: "10px 20px",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #555",
|
||||
backgroundColor: "transparent",
|
||||
color: "#aaa",
|
||||
cursor: "pointer",
|
||||
fontSize: "0.9em",
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSaveApiKey}
|
||||
disabled={!apiKeyInput.trim()}
|
||||
style={{
|
||||
padding: "10px 20px",
|
||||
borderRadius: "8px",
|
||||
border: "none",
|
||||
backgroundColor: apiKeyInput.trim() ? "#ececec" : "#555",
|
||||
color: apiKeyInput.trim() ? "#000" : "#888",
|
||||
cursor: apiKeyInput.trim() ? "pointer" : "not-allowed",
|
||||
fontSize: "0.9em",
|
||||
}}
|
||||
>
|
||||
Save Key
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user