card.svelte 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. <script lang="ts">
  2. import { LibraryBig, Toolbox } from '@lucide/svelte';
  3. import type { ClassValue } from 'svelte/elements';
  4. import { resolve } from '$app/paths';
  5. import { GlassPane } from '$lib/components/common/glass-pane';
  6. import { Icon } from '$lib/components/common/icon';
  7. import { StatusBadge } from '$lib/components/common/status-badge';
  8. import { Tags } from '$lib/components/common/tags';
  9. import type { Agent } from '$lib/types/entities';
  10. let {
  11. class: className = '',
  12. agent,
  13. selectedTags,
  14. onselect,
  15. }: {
  16. class?: ClassValue;
  17. agent: Agent;
  18. selectedTags?: Set<string>;
  19. onselect?: () => void;
  20. } = $props();
  21. </script>
  22. {#snippet content()}
  23. <GlassPane
  24. class={[
  25. 'relative flex h-64 flex-col gap-2 overflow-hidden rounded-xl p-4 hover:border-white/20 hover:bg-white/5',
  26. className,
  27. ]}
  28. reactive
  29. >
  30. <!-- header -->
  31. <div class="flex shrink-0 items-center gap-2">
  32. <Icon icon={agent.icon} size={36} />
  33. <div
  34. class="text-md line-clamp-2 flex-1 truncate leading-none font-medium text-wrap text-white/75 group-hover:text-white"
  35. >
  36. {agent.name}
  37. </div>
  38. {#if agent.status}
  39. <StatusBadge class="shrink-0" status={agent.status} />
  40. {/if}
  41. </div>
  42. {#if agent.description}
  43. <div class="line-clamp-3 shrink-0 text-sm text-white/50">
  44. {agent.description}
  45. </div>
  46. {:else}
  47. <div class="shrink-0 text-xs text-white/25">&lt;no description&gt;</div>
  48. {/if}
  49. <div class="mb-2 overflow-hidden">
  50. {#if agent.tags.length > 0}
  51. <Tags activeTags={selectedTags} tags={agent.tags} />
  52. {/if}
  53. </div>
  54. <div class="mt-auto flex shrink-0 flex-col gap-2">
  55. {#if agent.kbs.length > 0}
  56. {@const visible = agent.kbs.slice(0, 2)}
  57. {@const overflow = agent.kbs.length - visible.length}
  58. <div class="flex items-center gap-1 text-xs text-white/50">
  59. <LibraryBig class="shrink-0 text-primary/75" size={16} strokeWidth={2} />
  60. <span class="truncate">
  61. {visible.map((kb) => kb.name).join(', ')}{overflow > 0 ? ` +${overflow}` : ''}
  62. </span>
  63. </div>
  64. {/if}
  65. {#if agent.tools.length > 0}
  66. {@const visible = agent.tools.slice(0, 2)}
  67. {@const overflow = agent.tools.length - visible.length}
  68. <div class="flex items-center gap-1 text-xs text-white/50">
  69. <Toolbox class="shrink-0 text-primary/75" size={16} strokeWidth={2} />
  70. <span class="truncate">
  71. {visible.map((t) => t.name).join(', ')}{overflow > 0 ? ` +${overflow}` : ''}
  72. </span>
  73. </div>
  74. {/if}
  75. </div>
  76. </GlassPane>
  77. {/snippet}
  78. {#if onselect === undefined}
  79. <a class="group block" href={resolve('/workloads/agents/[id]', { id: agent.id })}>
  80. {@render content()}
  81. </a>
  82. {:else}
  83. <div
  84. class="group block"
  85. role="button"
  86. tabindex="0"
  87. onclick={() => onselect()}
  88. onkeydown={(e) => e.key === 'Enter' && onselect()}
  89. >
  90. {@render content()}
  91. </div>
  92. {/if}