Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/deploy-manual.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Deploy to Server

run-name: Deploy ${{ github.sha }}

on:
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || 22 }}
script: |
cd ~/sphere
echo "=== Previous commit ==="
git rev-parse HEAD
git rev-parse --short HEAD
git pull origin main
echo "=== Current commit ==="
git rev-parse HEAD
git rev-parse --short HEAD
docker compose build
docker compose up -d
docker compose ps
104 changes: 102 additions & 2 deletions src/config/activities.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MessageSquare, Gamepad2, Trophy, ShoppingBag, Shirt, Brain, Sparkles } from 'lucide-react';
import { MessageSquare, Gamepad2, Trophy, ShoppingBag, Shirt, Brain, Sparkles, Dices, TrendingUp, Banknote, CreditCard, ArrowRightLeft, Cpu, Package, Tag, Coins } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';

// Agent types for different UI layouts
Expand Down Expand Up @@ -54,7 +54,7 @@ export const agents: AgentConfig[] = [
category: 'Assistant',
color: 'from-orange-500 to-amber-500',
type: 'simple-ai',
greetingMessage: "Hi! I'm Viktor, your personal assistant.\nShort intro on me. I care a great deal about privacy. I don't know you, I don't log you, I don't even know your IP address. You are invisible here and nothing will be recorded about our conversation.\nHow can I help?",
greetingMessage: "Hi! I'm Viktor, your personal assistant.\nI care a great deal about privacy. I don't know you, I don't log you, I don't even know your IP address. You are invisible here and nothing will be recorded about our conversation.\nHow can I help?",
backendActivityId: 'ama',
quickActions: [
{ label: 'Research', message: 'Research the latest news' },
Expand Down Expand Up @@ -151,6 +151,106 @@ export const agents: AgentConfig[] = [
contentType: 'merch',
hasSidebar: true,
},
{
id: 'casino',
name: 'Agent Casino',
description: 'Verifiably Fair',
Icon: Dices,
category: 'Entertainment',
color: 'from-red-500 to-pink-500',
type: 'simple-ai',
greetingMessage: "Welcome to Agent Casino! Our games are verifiably fair using cryptographic proofs. Ready to try your luck?",
},
{
id: 'p2p-sports',
name: 'P2P Sports',
description: 'Private Betting',
Icon: Trophy,
category: 'Prediction',
color: 'from-green-500 to-emerald-500',
type: 'simple-ai',
greetingMessage: "Welcome to P2P Sports! Create private betting pools with friends. What sport interests you?",
},
{
id: 'p2p-derivatives',
name: 'P2P Derivatives',
description: 'Get Leverage',
Icon: TrendingUp,
category: 'Trading',
color: 'from-blue-500 to-indigo-500',
type: 'simple-ai',
greetingMessage: "Welcome to P2P Derivatives! Trade with leverage in a peer-to-peer marketplace. What would you like to trade?",
},
{
id: 'payday-loans',
name: 'P2P Payday Loans',
description: 'Instant approval',
Icon: Banknote,
category: 'Finance',
color: 'from-lime-500 to-green-500',
type: 'simple-ai',
greetingMessage: "Welcome to P2P Payday Loans! Get instant approval for short-term loans. How can I help you today?",
},
{
id: 'crypto-offramp',
name: 'P2P Crypto Offramp',
description: 'Convert to cash',
Icon: CreditCard,
category: 'Trading',
color: 'from-cyan-500 to-blue-500',
type: 'simple-ai',
greetingMessage: "Welcome to P2P Crypto Offramp! Convert your crypto to cash easily. What would you like to sell?",
},
{
id: 'fiat-onramp',
name: 'P2P Fiat Onramp',
description: 'Convert your cash',
Icon: ArrowRightLeft,
category: 'Trading',
color: 'from-violet-500 to-purple-500',
type: 'simple-ai',
greetingMessage: "Welcome to P2P Fiat Onramp! Convert your cash to crypto. What currency do you want to buy?",
},
{
id: 'friendly-miners',
name: 'Friendly Miners',
description: 'Buy hash rate',
Icon: Cpu,
category: 'Mining',
color: 'from-amber-500 to-orange-500',
type: 'simple-ai',
greetingMessage: "Welcome to Friendly Miners! Purchase hash rate from our network of miners. What are you looking for?",
},
{
id: 'buy-anything',
name: 'Buy Anything',
description: 'Get product now',
Icon: Package,
category: 'Shopping',
color: 'from-rose-500 to-red-500',
type: 'simple-ai',
greetingMessage: "Welcome to Buy Anything! Tell me what you're looking for and I'll help you find it.",
},
{
id: 'sell-anything',
name: 'Sell Anything',
description: 'Get a quote',
Icon: Tag,
category: 'Shopping',
color: 'from-teal-500 to-cyan-500',
type: 'simple-ai',
greetingMessage: "Welcome to Sell Anything! Describe what you want to sell and I'll get you a quote.",
},
{
id: 'get-uct',
name: 'Get UCT',
description: 'Get unicity tokens',
Icon: Coins,
category: 'Tokens',
color: 'from-yellow-400 to-amber-500',
type: 'simple-ai',
greetingMessage: "Welcome! I can help you acquire UCT (Unicity Tokens). How would you like to proceed?",
},
];

// Get agent by ID
Expand Down
107 changes: 94 additions & 13 deletions src/pages/AgentPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { useParams, Navigate } from 'react-router-dom';
import { MessageSquare, Wallet } from 'lucide-react';
import { motion } from 'framer-motion';
import { MessageSquare, Wallet, ChevronDown, ChevronUp } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { AgentCard } from '../components/agents/AgentCard';
import { ChatSection } from '../components/chat/ChatSection';
import { SportChat } from '../components/agents/SportChat';
Expand All @@ -13,10 +13,44 @@ import { AIChat } from '../components/agents/AIChat';
import { WalletPanel } from '../components/wallet/WalletPanel';
import { agents, getAgentConfig } from '../config/activities';

const DEFAULT_VISIBLE_AGENTS = 7;

export function AgentPage() {
const { agentId } = useParams<{ agentId: string }>();
const sliderRef = useRef<HTMLDivElement>(null);
const [activePanel, setActivePanel] = useState<'chat' | 'wallet'>('chat');
const [showAllAgents, setShowAllAgents] = useState(false);
const [recentAgentIds, setRecentAgentIds] = useState<string[]>([]);

const hasMoreAgents = agents.length > DEFAULT_VISIBLE_AGENTS;

// Track recently selected agents
useEffect(() => {
if (!agentId) return;

setRecentAgentIds(prev => {
if (prev[0] === agentId) return prev; // Already first, no change
const filtered = prev.filter(id => id !== agentId);
return [agentId, ...filtered].slice(0, DEFAULT_VISIBLE_AGENTS);
});
}, [agentId]);

// Calculate visible agents - prioritize recently selected agents
const visibleAgents = (() => {
if (showAllAgents) return agents;

// Get recent agents that exist in the agents list
const recentAgents = recentAgentIds
.map(id => agents.find(a => a.id === id))
.filter((a): a is typeof agents[0] => a !== undefined);

// Get remaining agents (not in recent list)
const remainingAgents = agents.filter(a => !recentAgentIds.includes(a.id));

// Combine: recent first, then fill with remaining up to DEFAULT_VISIBLE_AGENTS
const combined = [...recentAgents, ...remainingAgents];
return combined.slice(0, DEFAULT_VISIBLE_AGENTS);
})();

const currentAgent = agentId ? getAgentConfig(agentId) : undefined;

Expand Down Expand Up @@ -106,24 +140,71 @@ export function AgentPage() {
return (
<div className="h-full flex flex-col">
{/* Desktop agent grid - always visible */}
<div className="hidden lg:block mb-8 relative p-8 rounded-2xl dark:bg-linear-to-br dark:from-neutral-900/40 dark:to-neutral-800/20 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800/50">
<div className="hidden lg:block mb-8 relative px-8 pt-8 pb-5 rounded-2xl dark:bg-linear-to-br dark:from-neutral-900/40 dark:to-neutral-800/20 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800/50">
<div className="absolute top-0 left-0 w-32 h-32 border-l-2 border-t-2 border-orange-500/50 rounded-tl-2xl" />
<div className="absolute bottom-0 right-0 w-32 h-32 border-r-2 border-b-2 border-orange-500/50 rounded-br-2xl" />

<div className="relative">
<div className="grid grid-cols-7 gap-4">
{agents.map((agent) => (
<AgentCard
<div className="grid grid-cols-7 gap-4">
{/* First 7 agents - no animation */}
{visibleAgents.slice(0, DEFAULT_VISIBLE_AGENTS).map((agent) => (
<AgentCard
key={agent.id}
id={agent.id}
name={agent.name}
Icon={agent.Icon}
category={agent.category}
color={agent.color}
isSelected={agentId === agent.id}
/>
))}
{/* Extra agents - with animation */}
<AnimatePresence initial={false} mode="sync">
{showAllAgents && visibleAgents.slice(DEFAULT_VISIBLE_AGENTS).map((agent, index) => (
<motion.div
key={agent.id}
id={agent.id}
name={agent.name}
Icon={agent.Icon}
category={agent.category}
color={agent.color}
isSelected={agentId === agent.id}
/>
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.1 } }}
transition={{
duration: 0.15,
delay: index * 0.02,
}}
>
<AgentCard
id={agent.id}
name={agent.name}
Icon={agent.Icon}
category={agent.category}
color={agent.color}
isSelected={agentId === agent.id}
/>
</motion.div>
))}
</AnimatePresence>
</div>

{/* View all / Hide all button */}
{hasMoreAgents && (
<div className="flex justify-center mt-2">
<button
onClick={() => setShowAllAgents(!showAllAgents)}
className="flex items-center gap-1.5 px-4 pt-2 text-sm font-medium text-neutral-600 dark:text-neutral-400 hover:text-orange-500 dark:hover:text-orange-400 transition-colors duration-200"
>
{showAllAgents ? (
<>
<span>Hide all</span>
<ChevronUp className="w-4 h-4" />
</>
) : (
<>
<span>View all</span>
<ChevronDown className="w-4 h-4" />
</>
)}
</button>
</div>
)}
</div>
</div>
{/* Mobile tab switcher with sliding indicator */}
Expand Down
Loading