File size: 6,255 Bytes
921d377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
 * IdentityLibrary — Left-panel section showing character identities for the
 * current wizard session.
 *
 * Only shows identities that were:
 *   1. Created during this wizard session (sessionIds)
 *   2. Imported from the full library (importedIds)
 *
 * An "Import from Library" picker lets users bring in pre-existing identities
 * without cluttering the view with every identity ever created.
 */
import React, { useState } from 'react';
import { Plus, Download, X, Sparkles } from 'lucide-react';
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
export function IdentityLibrary({ items, sessionIds, importedIds, activeIdentityId, onSelectIdentity, onNewIdentity, onImportIdentity, resolveUrl, }) {
    const [showPicker, setShowPicker] = useState(false);
    // All root identities from the full gallery
    const allIdentities = items.filter((i) => i.role === 'anchor' && !i.parentId);
    // Session-scoped: created here + imported
    const visibleIds = new Set([...sessionIds, ...importedIds]);
    const sessionIdentities = allIdentities.filter((i) => visibleIds.has(i.id));
    // Library identities available for import (not already visible)
    const libraryIdentities = allIdentities.filter((i) => !visibleIds.has(i.id));
    return (<div className="space-y-2">
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-1.5">
          <Sparkles size={10} className="text-purple-400/60"/>
          <span className="text-[9px] text-white/35 font-semibold uppercase tracking-wider">
            My Identities
          </span>
        </div>
        <span className="text-[9px] text-white/20">{sessionIdentities.length}</span>
      </div>

      <div className="grid grid-cols-3 gap-1.5">
        {sessionIdentities.slice(0, 9).map((identity) => {
            const active = activeIdentityId === identity.id;
            const isImported = importedIds.has(identity.id);
            return (<button key={identity.id} onClick={() => onSelectIdentity(identity)} className={[
                    'relative aspect-square rounded-lg overflow-hidden border-2 transition-all group',
                    active
                        ? 'border-purple-500/60 ring-1 ring-purple-500/20 scale-105'
                        : 'border-white/[0.08] hover:border-white/20 hover:scale-105',
                ].join(' ')} title={identity.wizardMeta?.professionLabel || `Seed ${identity.seed ?? '?'}`}>
              <img src={resolveUrl(identity.url)} alt={identity.wizardMeta?.professionLabel || 'Identity'} className="w-full h-full object-cover" loading="lazy"/>
              {/* Seed overlay on hover */}
              <div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-end justify-center pb-1">
                <span className="text-[8px] text-white/70 font-mono">
                  {identity.seed ?? '—'}
                </span>
              </div>
              {/* Imported badge */}
              {isImported && (<div className="absolute top-0.5 left-0.5">
                  <Download size={8} className="text-cyan-400/80"/>
                </div>)}
              {/* Active indicator */}
              {active && (<div className="absolute top-0.5 right-0.5 w-2 h-2 rounded-full bg-purple-400 shadow-[0_0_4px_rgba(168,85,247,0.6)]"/>)}
            </button>);
        })}

        {/* New identity slot */}
        <button onClick={onNewIdentity} className="aspect-square rounded-lg border-2 border-dashed border-white/[0.08] hover:border-white/15 flex items-center justify-center transition-colors group" title="Create new identity">
          <Plus size={14} className="text-white/15 group-hover:text-white/35 transition-colors"/>
        </button>
      </div>

      {sessionIdentities.length === 0 && (<p className="text-[10px] text-white/15 text-center py-2">
          No identities yet
        </p>)}

      {/* Import from Library button */}
      {libraryIdentities.length > 0 && (<button onClick={() => setShowPicker(!showPicker)} className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 rounded-lg text-[9px] font-medium text-cyan-400/60 hover:text-cyan-300/80 hover:bg-cyan-500/[0.06] border border-cyan-500/10 hover:border-cyan-500/20 transition-all">
          <Download size={10}/>
          Import from Library ({libraryIdentities.length})
        </button>)}

      {/* Library Picker Overlay */}
      {showPicker && (<div className="rounded-xl border border-white/[0.08] bg-black/60 backdrop-blur-sm p-2 space-y-2">
          <div className="flex items-center justify-between">
            <span className="text-[9px] text-white/40 font-semibold uppercase tracking-wider">
              Library
            </span>
            <button onClick={() => setShowPicker(false)} className="text-white/25 hover:text-white/50 transition-colors">
              <X size={12}/>
            </button>
          </div>
          <div className="grid grid-cols-3 gap-1.5 max-h-[120px] overflow-y-auto scrollbar-hide">
            {libraryIdentities.map((identity) => (<button key={identity.id} onClick={() => {
                    onImportIdentity(identity);
                    setShowPicker(false);
                }} className="relative aspect-square rounded-lg overflow-hidden border-2 border-white/[0.06] hover:border-cyan-500/40 transition-all group" title={identity.wizardMeta?.professionLabel || `Seed ${identity.seed ?? '?'}`}>
                <img src={resolveUrl(identity.url)} alt={identity.wizardMeta?.professionLabel || 'Identity'} className="w-full h-full object-cover" loading="lazy"/>
                <div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-end justify-center pb-1">
                  <span className="text-[8px] text-white/70 font-mono">
                    {identity.seed ?? '—'}
                  </span>
                </div>
              </button>))}
          </div>
        </div>)}
    </div>);
}