Hwandji's picture
feat: initial HuggingFace Space deployment
4343907
raw
history blame
11 kB
<template>
<div class="agent-card" :class="[
`agent-card--${agent.status}`,
{ 'agent-card--featured': featured }
]" :style="cardStyles">
<!-- Agent Header -->
<div class="agent-card__header">
<div class="agent-card__avatar">
<img
:src="agent.appearance.avatar"
:alt="agent.appearance.displayName"
class="agent-card__avatar-image"
/>
<div class="agent-card__status-indicator" :class="`status--${agent.status}`">
<div class="status__dot"></div>
</div>
</div>
<div class="agent-card__info">
<h3 class="agent-card__name">{{ agent.appearance.displayName }}</h3>
<p class="agent-card__subtitle">{{ agent.appearance.subtitle }}</p>
<div class="agent-card__meta">
<span class="agent-card__type">{{ formatAgentType(agent.type) }}</span>
<span class="agent-card__id">{{ agent.id }}</span>
</div>
</div>
</div>
<!-- Agent Capabilities -->
<div class="agent-card__capabilities">
<div class="capabilities__label">Capabilities</div>
<div class="capabilities__tags">
<span
v-for="capability in agent.capabilities.slice(0, 3)"
:key="capability"
class="capability-tag"
>
{{ formatCapability(capability) }}
</span>
<span v-if="agent.capabilities.length > 3" class="capability-tag capability-tag--more">
+{{ agent.capabilities.length - 3 }}
</span>
</div>
</div>
<!-- Agent Stats -->
<div class="agent-card__stats">
<div class="stat-item">
<div class="stat-item__value">{{ stats.messagesProcessed || 0 }}</div>
<div class="stat-item__label">Messages</div>
</div>
<div class="stat-item">
<div class="stat-item__value">{{ formatUptime(stats.uptime) }}</div>
<div class="stat-item__label">Uptime</div>
</div>
<div class="stat-item">
<div class="stat-item__value">{{ stats.responseTime || 0 }}ms</div>
<div class="stat-item__label">Response</div>
</div>
</div>
<!-- Agent Actions -->
<div class="agent-card__actions">
<button
class="btn btn--secondary btn--sm"
@click="$emit('view-details', agent)"
>
<Icon name="eye" size="16" />
Details
</button>
<button
class="btn btn--primary btn--sm"
@click="$emit('send-message', agent)"
:disabled="agent.status !== 'active'"
>
<Icon name="message-circle" size="16" />
Message
</button>
<div class="agent-card__menu">
<button class="btn btn--ghost btn--sm" @click="toggleMenu">
<Icon name="more-horizontal" size="16" />
</button>
<div v-if="menuOpen" class="dropdown-menu" @click.stop>
<button @click="$emit('configure', agent)" class="dropdown-item">
<Icon name="settings" size="16" />
Configure
</button>
<button @click="$emit('restart', agent)" class="dropdown-item">
<Icon name="refresh-cw" size="16" />
Restart
</button>
<hr class="dropdown-divider" />
<button @click="$emit('stop', agent)" class="dropdown-item dropdown-item--danger">
<Icon name="stop-circle" size="16" />
Stop Agent
</button>
</div>
</div>
</div>
<!-- Real-time Activity Indicator -->
<div v-if="agent.status === 'active'" class="agent-card__activity">
<div class="activity-pulse"></div>
<span class="activity-text">Processing...</span>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import Icon from './Icon.vue'
export default {
name: 'AgentCard',
components: { Icon },
props: {
agent: {
type: Object,
required: true,
validator: (agent) => {
return agent.id && agent.name && agent.type && agent.status
}
},
stats: {
type: Object,
default: () => ({
messagesProcessed: 0,
uptime: 0,
responseTime: 0
})
},
featured: {
type: Boolean,
default: false
}
},
emits: [
'view-details',
'send-message',
'configure',
'restart',
'stop'
],
setup(props) {
const menuOpen = ref(false)
const cardStyles = computed(() => ({
'--agent-color': props.agent.appearance?.color || '#6B7280',
'--agent-color-light': props.agent.appearance?.color + '20' || '#6B728020',
}))
const formatAgentType = (type) => {
return type.charAt(0).toUpperCase() + type.slice(1).replace('_', ' ')
}
const formatCapability = (capability) => {
return capability.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
}
const formatUptime = (seconds) => {
if (!seconds) return '0m'
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
if (hours > 0) return `${hours}h ${minutes}m`
return `${minutes}m`
}
const toggleMenu = () => {
menuOpen.value = !menuOpen.value
}
return {
menuOpen,
cardStyles,
formatAgentType,
formatCapability,
formatUptime,
toggleMenu
}
}
}
</script>
<style scoped>
/* Agent Card Base Styles */
.agent-card {
--agent-color: #6B7280;
--agent-color-light: #6B728020;
background: #ffffff;
border: 1px solid #E5E7EB;
border-radius: 12px;
padding: 20px;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.agent-card:hover {
border-color: var(--agent-color);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.agent-card--featured {
border-color: var(--agent-color);
background: linear-gradient(135deg, #ffffff 0%, var(--agent-color-light) 100%);
}
.agent-card--active {
border-left: 4px solid var(--agent-color);
}
.agent-card--inactive {
opacity: 0.6;
border-left: 4px solid #9CA3AF;
}
.agent-card--error {
border-left: 4px solid #EF4444;
}
/* Header Section */
.agent-card__header {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
}
.agent-card__avatar {
position: relative;
flex-shrink: 0;
}
.agent-card__avatar-image {
width: 48px;
height: 48px;
border-radius: 50%;
border: 3px solid var(--agent-color);
object-fit: cover;
}
.agent-card__status-indicator {
position: absolute;
bottom: 0;
right: 0;
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid white;
display: flex;
align-items: center;
justify-content: center;
}
.status__dot {
width: 8px;
height: 8px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.status--active .status__dot {
background: #10B981;
}
.status--inactive .status__dot {
background: #6B7280;
}
.status--error .status__dot {
background: #EF4444;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.agent-card__info {
flex: 1;
min-width: 0;
}
.agent-card__name {
font-size: 16px;
font-weight: 600;
color: #111827;
margin: 0 0 4px 0;
line-height: 1.3;
}
.agent-card__subtitle {
font-size: 13px;
color: #6B7280;
margin: 0 0 8px 0;
line-height: 1.4;
}
.agent-card__meta {
display: flex;
gap: 8px;
align-items: center;
}
.agent-card__type {
font-size: 11px;
font-weight: 500;
color: var(--agent-color);
background: var(--agent-color-light);
padding: 2px 6px;
border-radius: 4px;
}
.agent-card__id {
font-size: 10px;
color: #9CA3AF;
font-family: 'Fira Code', monospace;
}
/* Capabilities Section */
.agent-card__capabilities {
margin-bottom: 16px;
}
.capabilities__label {
font-size: 11px;
font-weight: 500;
color: #6B7280;
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.capabilities__tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.capability-tag {
font-size: 10px;
color: #4B5563;
background: #F3F4F6;
padding: 2px 6px;
border-radius: 4px;
border: 1px solid #E5E7EB;
}
.capability-tag--more {
color: #6B7280;
font-weight: 500;
}
/* Stats Section */
.agent-card__stats {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
margin-bottom: 16px;
padding: 12px;
background: #F9FAFB;
border-radius: 8px;
}
.stat-item {
text-align: center;
}
.stat-item__value {
font-size: 14px;
font-weight: 600;
color: #111827;
line-height: 1.2;
}
.stat-item__label {
font-size: 10px;
color: #6B7280;
margin-top: 2px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Actions Section */
.agent-card__actions {
display: flex;
gap: 8px;
align-items: center;
position: relative;
}
.agent-card__menu {
position: relative;
margin-left: auto;
}
.dropdown-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
background: white;
border: 1px solid #E5E7EB;
border-radius: 8px;
padding: 4px 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10;
min-width: 140px;
}
.dropdown-item {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 12px;
font-size: 13px;
color: #374151;
background: none;
border: none;
cursor: pointer;
text-align: left;
}
.dropdown-item:hover {
background: #F3F4F6;
}
.dropdown-item--danger {
color: #EF4444;
}
.dropdown-item--danger:hover {
background: #FEF2F2;
}
.dropdown-divider {
margin: 4px 0;
border: none;
border-top: 1px solid #E5E7EB;
}
/* Activity Indicator */
.agent-card__activity {
position: absolute;
bottom: 8px;
right: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.activity-pulse {
width: 8px;
height: 8px;
background: var(--agent-color);
border-radius: 50%;
animation: pulse 1.5s ease-in-out infinite;
}
.activity-text {
font-size: 10px;
color: var(--agent-color);
font-weight: 500;
}
/* Button Styles */
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
border: 1px solid transparent;
cursor: pointer;
transition: all 0.2s ease;
background: none;
}
.btn--sm {
font-size: 12px;
padding: 6px 10px;
}
.btn--primary {
background: var(--agent-color);
color: white;
border-color: var(--agent-color);
}
.btn--primary:hover:not(:disabled) {
opacity: 0.9;
transform: translateY(-1px);
}
.btn--primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn--secondary {
background: #F9FAFB;
color: #374151;
border-color: #E5E7EB;
}
.btn--secondary:hover {
background: #F3F4F6;
border-color: #D1D5DB;
}
.btn--ghost {
color: #6B7280;
}
.btn--ghost:hover {
color: #374151;
background: #F3F4F6;
}
/* Responsive Design */
@media (max-width: 640px) {
.agent-card {
padding: 16px;
}
.agent-card__stats {
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.agent-card__actions {
flex-wrap: wrap;
}
}
</style>