mirror of
https://github.com/aljazceru/goose.git
synced 2026-02-20 14:04:32 +01:00
easter egg (#1201)
This commit is contained in:
BIN
ui/desktop/src/assets/battle-game/background.png
Normal file
BIN
ui/desktop/src/assets/battle-game/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
BIN
ui/desktop/src/assets/battle-game/battle.mp3
Normal file
BIN
ui/desktop/src/assets/battle-game/battle.mp3
Normal file
Binary file not shown.
BIN
ui/desktop/src/assets/battle-game/goose.png
Normal file
BIN
ui/desktop/src/assets/battle-game/goose.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 522 KiB |
BIN
ui/desktop/src/assets/battle-game/llama.png
Normal file
BIN
ui/desktop/src/assets/battle-game/llama.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 626 KiB |
467
ui/desktop/src/components/settings/OllamaBattleGame.tsx
Normal file
467
ui/desktop/src/components/settings/OllamaBattleGame.tsx
Normal file
@@ -0,0 +1,467 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
|
||||
// Import actual PNG images
|
||||
import llamaSprite from '../../assets/battle-game/llama.png';
|
||||
import gooseSprite from '../../assets/battle-game/goose.png';
|
||||
import battleBackground from '../../assets/battle-game/background.png';
|
||||
import battleMusic from '../../assets/battle-game/battle.mp3';
|
||||
|
||||
interface BattleState {
|
||||
currentStep: number;
|
||||
gooseHp: number;
|
||||
llamaHp: number;
|
||||
message: string;
|
||||
animation: string | null;
|
||||
lastChoice?: string;
|
||||
showHostInput?: boolean;
|
||||
processingAction?: boolean;
|
||||
}
|
||||
|
||||
interface OllamaBattleGameProps {
|
||||
onComplete: (configValues: { [key: string]: string }) => void;
|
||||
requiredKeys: string[];
|
||||
}
|
||||
|
||||
export function OllamaBattleGame({ onComplete, _requiredKeys }: OllamaBattleGameProps) {
|
||||
// Use type assertion for audioRef to avoid DOM lib dependency
|
||||
const audioRef = useRef<any>(null);
|
||||
const [isMuted, setIsMuted] = useState(false);
|
||||
|
||||
const [battleState, setBattleState] = useState<BattleState>({
|
||||
currentStep: 0,
|
||||
gooseHp: 100,
|
||||
llamaHp: 100,
|
||||
message: 'A wild Ollama appeared!',
|
||||
animation: null,
|
||||
processingAction: false,
|
||||
});
|
||||
|
||||
const [configValues, setConfigValues] = useState<{ [key: string]: string }>({});
|
||||
|
||||
// Initialize audio when component mounts
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
audioRef.current = new window.Audio(battleMusic);
|
||||
audioRef.current.loop = true;
|
||||
audioRef.current.volume = 0.2;
|
||||
audioRef.current.play().catch((e) => console.log('Audio autoplay prevented:', e));
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.pause();
|
||||
audioRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const toggleMute = () => {
|
||||
if (audioRef.current) {
|
||||
if (isMuted) {
|
||||
audioRef.current.volume = 0.2;
|
||||
} else {
|
||||
audioRef.current.volume = 0;
|
||||
}
|
||||
setIsMuted(!isMuted);
|
||||
}
|
||||
};
|
||||
|
||||
const battleSteps = [
|
||||
{
|
||||
message: 'A wild Ollama appeared!',
|
||||
action: null,
|
||||
animation: 'appear',
|
||||
},
|
||||
{
|
||||
message: 'What will GOOSE do?',
|
||||
action: 'choice',
|
||||
choices: ['Pacify', 'HONK!'],
|
||||
animation: 'attack',
|
||||
followUpMessages: ["It's not very effective...", 'But OLLAMA is confused!'],
|
||||
},
|
||||
{
|
||||
message: 'OLLAMA used YAML Confusion!',
|
||||
action: null,
|
||||
animation: 'counter',
|
||||
followUpMessages: ['OLLAMA hurt itself in confusion!', 'GOOSE maintained composure!'],
|
||||
},
|
||||
{
|
||||
message: 'What will GOOSE do?',
|
||||
action: 'final_choice',
|
||||
choices: (previousChoice: string) => [
|
||||
previousChoice === 'Pacify' ? 'HONK!' : 'Pacify',
|
||||
'Configure Host',
|
||||
],
|
||||
animation: 'attack',
|
||||
},
|
||||
{
|
||||
message: 'OLLAMA used Docker Dependency!',
|
||||
action: null,
|
||||
animation: 'counter',
|
||||
followUpMessages: ["It's not very effective...", 'GOOSE knows containerization!'],
|
||||
},
|
||||
{
|
||||
message: 'What will GOOSE do?',
|
||||
action: 'host_choice',
|
||||
choices: ['Configure Host'],
|
||||
animation: 'finish',
|
||||
},
|
||||
{
|
||||
message: '', // Will be set dynamically based on choice
|
||||
action: 'host_input',
|
||||
prompt: 'Enter your Ollama host address:',
|
||||
configKey: 'OLLAMA_HOST',
|
||||
animation: 'finish',
|
||||
followUpMessages: [
|
||||
"It's super effective!",
|
||||
'OLLAMA has been configured!',
|
||||
'OLLAMA joined your team!',
|
||||
],
|
||||
},
|
||||
{
|
||||
message: 'Configuration complete!\nOLLAMA will remember this friendship!',
|
||||
action: 'complete',
|
||||
},
|
||||
];
|
||||
|
||||
const animateHit = (isLlama: boolean) => {
|
||||
const element = document.querySelector(isLlama ? '.llama-sprite' : '.goose-sprite');
|
||||
if (element) {
|
||||
element.classList.add('hit-flash');
|
||||
setTimeout(() => {
|
||||
element.classList.remove('hit-flash');
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Add CSS for the hit animation and defeat animation
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes hitFlash {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
.hit-flash {
|
||||
animation: hitFlash 0.5s;
|
||||
}
|
||||
@keyframes defeat {
|
||||
0% { transform: translateY(0); opacity: 1; }
|
||||
20% { transform: translateY(-30px); opacity: 1; }
|
||||
100% { transform: translateY(500px); opacity: 0; }
|
||||
}
|
||||
.defeated {
|
||||
animation: defeat 1.3s cubic-bezier(.36,.07,.19,.97) both;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
return () => {
|
||||
document.head.removeChild(style);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleAction = async (value: string) => {
|
||||
const currentStep =
|
||||
battleState.currentStep < battleSteps.length ? battleSteps[battleState.currentStep] : null;
|
||||
|
||||
if (!currentStep) return;
|
||||
|
||||
// Handle host input
|
||||
if (currentStep.action === 'host_input' && value) {
|
||||
setConfigValues((prev) => ({
|
||||
...prev,
|
||||
[currentStep.configKey]: value,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle host submit
|
||||
if (currentStep.action === 'host_input' && !value) {
|
||||
setBattleState((prev) => ({
|
||||
...prev,
|
||||
processingAction: true,
|
||||
llamaHp: 0,
|
||||
message: "It's super effective!",
|
||||
}));
|
||||
animateHit(true);
|
||||
|
||||
// Add defeat class to llama sprite and health bar
|
||||
const llamaContainer = document.querySelector('.llama-container');
|
||||
if (llamaContainer) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
llamaContainer.classList.add('defeated');
|
||||
}
|
||||
|
||||
// Show victory messages with delays
|
||||
if (currentStep.followUpMessages) {
|
||||
for (const msg of currentStep.followUpMessages) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
setBattleState((prev) => ({ ...prev, message: msg }));
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
onComplete(configValues);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle continue button for messages
|
||||
if (!currentStep.action) {
|
||||
setBattleState((prev) => ({
|
||||
...prev,
|
||||
currentStep: prev.currentStep + 1,
|
||||
message: battleSteps[prev.currentStep + 1]?.message || prev.message,
|
||||
processingAction: false,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle choices (Pacify/HONK/Configure Host)
|
||||
if (
|
||||
(currentStep.action === 'choice' ||
|
||||
currentStep.action === 'final_choice' ||
|
||||
currentStep.action === 'host_choice') &&
|
||||
value
|
||||
) {
|
||||
// Set processing flag to hide buttons
|
||||
setBattleState((prev) => ({
|
||||
...prev,
|
||||
processingAction: true,
|
||||
}));
|
||||
|
||||
if (value === 'Configure Host') {
|
||||
setBattleState((prev) => ({
|
||||
...prev,
|
||||
message: 'GOOSE used Configure Host!',
|
||||
showHostInput: true,
|
||||
currentStep: battleSteps.findIndex((step) => step.action === 'host_input'),
|
||||
processingAction: false,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Pacify or HONK attacks
|
||||
setBattleState((prev) => ({
|
||||
...prev,
|
||||
lastChoice: value,
|
||||
llamaHp: Math.max(0, prev.llamaHp - 25),
|
||||
message: `GOOSE used ${value}!`,
|
||||
}));
|
||||
animateHit(true);
|
||||
|
||||
// Show follow-up messages
|
||||
if (currentStep.followUpMessages) {
|
||||
for (const msg of currentStep.followUpMessages) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
setBattleState((prev) => ({ ...prev, message: msg }));
|
||||
}
|
||||
}
|
||||
|
||||
// Proceed to counter-attack
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
const isFirstCycle = currentStep.action === 'choice';
|
||||
const nextStep = battleSteps[battleState.currentStep + 1];
|
||||
setBattleState((prev) => ({
|
||||
...prev,
|
||||
gooseHp: Math.max(0, prev.gooseHp - 25),
|
||||
message: isFirstCycle ? 'OLLAMA used YAML Confusion!' : 'OLLAMA used Docker Dependency!',
|
||||
currentStep: prev.currentStep + 1,
|
||||
processingAction: false,
|
||||
}));
|
||||
animateHit(false);
|
||||
|
||||
// Show counter-attack messages
|
||||
if (nextStep?.followUpMessages) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
for (const msg of nextStep.followUpMessages) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
setBattleState((prev) => ({ ...prev, message: msg }));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for battle completion
|
||||
if (battleState.currentStep === battleSteps.length - 2) {
|
||||
onComplete(configValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full px-4 py-6">
|
||||
{/* Battle Scene */}
|
||||
<div
|
||||
className="relative w-full h-[300px] rounded-lg mb-4 bg-cover bg-center border-4 border-[#2C3E50] overflow-hidden"
|
||||
style={{
|
||||
backgroundImage: `url(${battleBackground})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center bottom',
|
||||
}}
|
||||
>
|
||||
{/* Llama sprite */}
|
||||
<div className="absolute right-24 top-8 llama-container">
|
||||
<div className="mb-2">
|
||||
<div className="bg-[#1F2937] rounded-lg px-3 py-1 text-white font-pokemon mb-1">
|
||||
OLLAMA
|
||||
<span className="text-xs ml-2">Lv.1</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-2 bg-[#374151] rounded-full flex-grow">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${battleState.llamaHp}%`,
|
||||
backgroundColor:
|
||||
battleState.llamaHp > 50
|
||||
? '#10B981'
|
||||
: battleState.llamaHp > 20
|
||||
? '#F59E0B'
|
||||
: '#EF4444',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-pokemon text-[#1F2937]">
|
||||
{Math.floor(battleState.llamaHp)}/100
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src={llamaSprite}
|
||||
alt="Llama"
|
||||
className="w-40 h-40 object-contain llama-sprite pixelated"
|
||||
style={{
|
||||
transform: `translateY(${battleState.currentStep % 2 === 1 ? '-4px' : '0'})`,
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
imageRendering: 'pixelated',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Goose sprite */}
|
||||
<div className="absolute left-24 bottom-4">
|
||||
<img
|
||||
src={gooseSprite}
|
||||
alt="Goose"
|
||||
className="w-40 h-40 object-contain mb-2 goose-sprite pixelated"
|
||||
style={{
|
||||
transform: `translateY(${battleState.currentStep % 2 === 0 ? '-4px' : '0'})`,
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
imageRendering: 'pixelated',
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<div className="bg-[#1F2937] rounded-lg px-3 py-1 text-white font-pokemon mb-1">
|
||||
GOOSE
|
||||
<span className="text-xs ml-2">Lv.99</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-2 bg-[#374151] rounded-full flex-grow">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${battleState.gooseHp}%`,
|
||||
backgroundColor:
|
||||
battleState.gooseHp > 50
|
||||
? '#10B981'
|
||||
: battleState.gooseHp > 20
|
||||
? '#F59E0B'
|
||||
: '#EF4444',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-pokemon text-[#1F2937]">
|
||||
{Math.floor(battleState.gooseHp)}/100
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dialog Box */}
|
||||
<div className="relative w-full">
|
||||
<div className="w-full bg-[#1F2937] rounded-lg p-6 border-4 border-[#4B5563] shadow-lg">
|
||||
<div className="absolute top-4 right-4">
|
||||
<button
|
||||
onClick={toggleMute}
|
||||
className="text-white hover:text-gray-300 transition-colors"
|
||||
>
|
||||
{isMuted ? '🔇' : '🔊'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-lg mb-4 text-white font-pokemon leading-relaxed">
|
||||
{battleState.message}
|
||||
</p>
|
||||
|
||||
{battleState.currentStep < battleSteps.length && (
|
||||
<div className="space-y-4">
|
||||
{/* Show battle choices */}
|
||||
{(battleSteps[battleState.currentStep].action === 'choice' ||
|
||||
battleSteps[battleState.currentStep].action === 'final_choice' ||
|
||||
battleSteps[battleState.currentStep].action === 'host_choice') &&
|
||||
!battleState.showHostInput &&
|
||||
!battleState.processingAction && (
|
||||
<div className="space-y-2">
|
||||
{(typeof battleSteps[battleState.currentStep].choices === 'function'
|
||||
? battleSteps[battleState.currentStep].choices(battleState.lastChoice || '')
|
||||
: battleSteps[battleState.currentStep].choices
|
||||
)?.map((choice: string) => (
|
||||
<button
|
||||
key={choice}
|
||||
onClick={() => handleAction(choice)}
|
||||
className="w-full text-left px-4 py-2 text-white font-pokemon hover:bg-[#2563EB] transition-colors rounded-lg group flex items-center"
|
||||
>
|
||||
<span className="opacity-0 group-hover:opacity-100 transition-opacity mr-2">
|
||||
▶
|
||||
</span>
|
||||
{choice}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show host input when needed */}
|
||||
{battleState.showHostInput && !battleState.processingAction && (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-300 font-pokemon">
|
||||
Enter your Ollama host address:
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
className="flex-grow px-4 py-2 bg-[#374151] border-2 border-[#4B5563] rounded-lg text-white font-pokemon placeholder-gray-400 focus:outline-none focus:border-[#60A5FA]"
|
||||
placeholder="http://localhost:11434"
|
||||
onChange={(e) => handleAction(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleAction('')}
|
||||
className="px-6 py-2 bg-[#2563EB] text-white font-pokemon rounded-lg hover:bg-[#1D4ED8] transition-colors focus:outline-none focus:ring-2 focus:ring-[#60A5FA] focus:ring-opacity-50"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Continue button for messages */}
|
||||
{!battleSteps[battleState.currentStep].action && !battleState.processingAction && (
|
||||
<button
|
||||
onClick={() => handleAction('')}
|
||||
className="mt-2 px-6 py-2 bg-[#2563EB] text-white font-pokemon rounded-lg hover:bg-[#1D4ED8] transition-colors focus:outline-none focus:ring-2 focus:ring-[#60A5FA] focus:ring-opacity-50"
|
||||
>
|
||||
▶ Continue
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Black corners for that classic Pokemon feel */}
|
||||
<div className="absolute top-0 left-0 w-4 h-4 bg-black rounded-tl-lg"></div>
|
||||
<div className="absolute top-0 right-0 w-4 h-4 bg-black rounded-tr-lg"></div>
|
||||
<div className="absolute bottom-0 left-0 w-4 h-4 bg-black rounded-bl-lg"></div>
|
||||
<div className="absolute bottom-0 right-0 w-4 h-4 bg-black rounded-br-lg"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,29 +5,39 @@ import { Input } from '../ui/input';
|
||||
import { Button } from '../ui/button';
|
||||
import { required_keys } from './models/hardcoded_stuff';
|
||||
import { isSecretKey } from './api_keys/utils';
|
||||
// import UnionIcon from "../images/Union@2x.svg";
|
||||
import { OllamaBattleGame } from './OllamaBattleGame';
|
||||
|
||||
interface ProviderSetupModalProps {
|
||||
provider: string;
|
||||
model: string;
|
||||
endpoint: string;
|
||||
_model: string;
|
||||
_endpoint: string;
|
||||
title?: string;
|
||||
onSubmit: (configValues: { [key: string]: string }) => void;
|
||||
onCancel: () => void;
|
||||
forceBattle?: boolean;
|
||||
}
|
||||
|
||||
export function ProviderSetupModal({
|
||||
provider,
|
||||
model,
|
||||
endpoint,
|
||||
_model,
|
||||
_endpoint,
|
||||
title,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
forceBattle = false,
|
||||
}: ProviderSetupModalProps) {
|
||||
const [configValues, setConfigValues] = React.useState<{ [key: string]: string }>({});
|
||||
const requiredKeys = required_keys[provider] || ['API Key'];
|
||||
const headerText = title || `Setup ${provider}`;
|
||||
|
||||
const shouldShowBattle = React.useMemo(() => {
|
||||
if (forceBattle) return true;
|
||||
if (provider.toLowerCase() !== 'ollama') return false;
|
||||
|
||||
const now = new Date();
|
||||
return now.getMinutes() === 0;
|
||||
}, [provider, forceBattle]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onSubmit(configValues);
|
||||
@@ -35,62 +45,69 @@ export function ProviderSetupModal({
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/20 dark:bg-white/20 backdrop-blur-sm transition-colors animate-[fadein_200ms_ease-in_forwards]">
|
||||
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] bg-bgApp rounded-xl overflow-hidden shadow-none p-[16px] pt-[24px] pb-0">
|
||||
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] bg-bgApp rounded-xl overflow-hidden shadow-none p-[16px] pt-[24px] pb-0">
|
||||
<div className="px-4 pb-0 space-y-8">
|
||||
{/* Header */}
|
||||
<div className="flex">
|
||||
{/* Purple icon */}
|
||||
{/* <div className="w-[24px] h-[24px] flex items-center justify-center">
|
||||
<img src={UnionIcon} alt="Union icon" />
|
||||
</div> */}
|
||||
<h2 className="text-2xl font-regular text-textStandard">{headerText}</h2>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mt-[24px] space-y-4">
|
||||
{requiredKeys.map((keyName) => (
|
||||
<div key={keyName}>
|
||||
<Input
|
||||
type={isSecretKey(keyName) ? 'password' : 'text'}
|
||||
value={configValues[keyName] || ''}
|
||||
onChange={(e) =>
|
||||
setConfigValues((prev) => ({
|
||||
...prev,
|
||||
[keyName]: e.target.value,
|
||||
}))
|
||||
{provider.toLowerCase() === 'ollama' && shouldShowBattle ? (
|
||||
<OllamaBattleGame onComplete={onSubmit} requiredKeys={requiredKeys} />
|
||||
) : (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mt-[24px] space-y-4">
|
||||
{requiredKeys.map((keyName) => (
|
||||
<div key={keyName}>
|
||||
<Input
|
||||
type={isSecretKey(keyName) ? 'password' : 'text'}
|
||||
value={configValues[keyName] || ''}
|
||||
onChange={(e) =>
|
||||
setConfigValues((prev) => ({
|
||||
...prev,
|
||||
[keyName]: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder={keyName}
|
||||
className="w-full h-14 px-4 font-regular rounded-lg border shadow-none border-gray-300 bg-white text-lg placeholder:text-gray-400 font-regular text-gray-900"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
className="flex text-gray-600 dark:text-gray-300"
|
||||
onClick={() => {
|
||||
if (provider.toLowerCase() === 'ollama') {
|
||||
onCancel();
|
||||
onSubmit({ forceBattle: 'true' });
|
||||
}
|
||||
placeholder={keyName}
|
||||
className="w-full h-14 px-4 font-regular rounded-lg border shadow-none border-gray-300 bg-white text-lg placeholder:text-gray-400 font-regular text-gray-900"
|
||||
required
|
||||
/>
|
||||
}}
|
||||
>
|
||||
<Lock className="w-6 h-6" />
|
||||
<span className="text-sm font-light ml-4 mt-[2px]">{`Your configuration values will be stored securely in the keychain and used only for making requests to ${provider}`}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex text-gray-600 dark:text-gray-300">
|
||||
<Lock className="w-6 h-6" />
|
||||
<span className="text-sm font-light ml-4 mt-[2px]">{`Your configuration values will be stored securely in the keychain and used only for making requests to ${provider}`}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="mt-[8px] -ml-8 -mr-8 pt-8">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="ghost"
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle text-md hover:bg-bgSubtle text-textProminent font-regular"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onCancel}
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle hover:text-textStandard text-textSubtle hover:bg-bgSubtle text-md font-regular"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
{/* Actions */}
|
||||
<div className="mt-[8px] -ml-8 -mr-8 pt-8">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="ghost"
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle text-md hover:bg-bgSubtle text-textProminent font-regular"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onCancel}
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle hover:text-textStandard text-textSubtle hover:bg-bgSubtle text-md font-regular"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Check, Plus, Settings, X, Rocket, RefreshCw, AlertCircle } from 'lucide-react';
|
||||
import { Check, Plus, Settings, X, Rocket } from 'lucide-react';
|
||||
import { Button } from '../../ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/Tooltip';
|
||||
import { Portal } from '@radix-ui/react-portal';
|
||||
import { required_keys } from '../models/hardcoded_stuff';
|
||||
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
|
||||
import { useActiveKeys } from '../api_keys/ActiveKeysContext';
|
||||
import { getActiveProviders } from '../api_keys/utils';
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export function ConfigureProvidersGrid() {
|
||||
const { activeKeys, setActiveKeys } = useActiveKeys();
|
||||
const [showSetupModal, setShowSetupModal] = useState(false);
|
||||
const [selectedForSetup, setSelectedForSetup] = useState<string | null>(null);
|
||||
const [modalMode, setModalMode] = useState<'edit' | 'setup'>('setup');
|
||||
const [modalMode, setModalMode] = useState<'edit' | 'setup' | 'battle'>('setup');
|
||||
const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
|
||||
const [providerToDelete, setProviderToDelete] = useState(null);
|
||||
const { currentModel } = useModel();
|
||||
@@ -242,11 +242,21 @@ export function ConfigureProvidersGrid() {
|
||||
? `Edit ${providers.find((p) => p.id === selectedForSetup)?.name} Configuration`
|
||||
: undefined
|
||||
}
|
||||
onSubmit={handleModalSubmit}
|
||||
onSubmit={(configValues) => {
|
||||
if (configValues.forceBattle === 'true') {
|
||||
setSelectedForSetup(selectedForSetup);
|
||||
setModalMode('battle');
|
||||
setShowSetupModal(true);
|
||||
return;
|
||||
}
|
||||
handleModalSubmit(configValues);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setShowSetupModal(false);
|
||||
setSelectedForSetup(null);
|
||||
setModalMode('setup');
|
||||
}}
|
||||
forceBattle={modalMode === 'battle'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
21
ui/desktop/src/styles/pokemon.css
Normal file
21
ui/desktop/src/styles/pokemon.css
Normal file
@@ -0,0 +1,21 @@
|
||||
@font-face {
|
||||
font-family: 'Pokemon';
|
||||
src: url('../assets/fonts/pokemon.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.font-pokemon {
|
||||
font-family:
|
||||
'Pokemon',
|
||||
system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.pixelated {
|
||||
image-rendering: pixelated;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
Reference in New Issue
Block a user