ideaco 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.dockerignore +33 -0
  2. package/.nvmrc +1 -0
  3. package/ARCHITECTURE.md +394 -0
  4. package/Dockerfile +50 -0
  5. package/LICENSE +29 -0
  6. package/README.md +206 -0
  7. package/bin/i18n.js +46 -0
  8. package/bin/ideaco.js +494 -0
  9. package/deploy.sh +15 -0
  10. package/docker-compose.yml +30 -0
  11. package/electron/main.cjs +986 -0
  12. package/electron/preload.cjs +14 -0
  13. package/electron/web-backends.cjs +854 -0
  14. package/jsconfig.json +8 -0
  15. package/next.config.mjs +34 -0
  16. package/package.json +134 -0
  17. package/postcss.config.mjs +6 -0
  18. package/public/demo/dashboard.png +0 -0
  19. package/public/demo/employee.png +0 -0
  20. package/public/demo/messages.png +0 -0
  21. package/public/demo/office.png +0 -0
  22. package/public/demo/requirement.png +0 -0
  23. package/public/logo.jpeg +0 -0
  24. package/public/logo.png +0 -0
  25. package/scripts/prepare-electron.js +67 -0
  26. package/scripts/release.js +76 -0
  27. package/src/app/api/agents/[agentId]/chat/route.js +70 -0
  28. package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
  29. package/src/app/api/agents/[agentId]/route.js +106 -0
  30. package/src/app/api/avatar/route.js +104 -0
  31. package/src/app/api/browse-dir/route.js +44 -0
  32. package/src/app/api/chat/route.js +265 -0
  33. package/src/app/api/company/factory-reset/route.js +43 -0
  34. package/src/app/api/company/route.js +82 -0
  35. package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
  36. package/src/app/api/departments/route.js +92 -0
  37. package/src/app/api/group-chat-loop/events/route.js +70 -0
  38. package/src/app/api/group-chat-loop/route.js +94 -0
  39. package/src/app/api/mailbox/route.js +100 -0
  40. package/src/app/api/messages/route.js +14 -0
  41. package/src/app/api/providers/[id]/configure/route.js +21 -0
  42. package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
  43. package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
  44. package/src/app/api/providers/route.js +11 -0
  45. package/src/app/api/requirements/route.js +242 -0
  46. package/src/app/api/secretary/route.js +65 -0
  47. package/src/app/api/system/cli-backends/route.js +91 -0
  48. package/src/app/api/system/cron/route.js +110 -0
  49. package/src/app/api/system/knowledge/route.js +104 -0
  50. package/src/app/api/system/plugins/route.js +40 -0
  51. package/src/app/api/system/skills/route.js +46 -0
  52. package/src/app/api/system/status/route.js +46 -0
  53. package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
  54. package/src/app/api/talent-market/[profileId]/route.js +17 -0
  55. package/src/app/api/talent-market/route.js +26 -0
  56. package/src/app/api/teams/route.js +773 -0
  57. package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
  58. package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
  59. package/src/app/globals.css +130 -0
  60. package/src/app/layout.jsx +40 -0
  61. package/src/app/page.jsx +97 -0
  62. package/src/components/AgentChatModal.jsx +164 -0
  63. package/src/components/AgentDetailModal.jsx +425 -0
  64. package/src/components/AgentSpyModal.jsx +481 -0
  65. package/src/components/AvatarGrid.jsx +29 -0
  66. package/src/components/BossProfileModal.jsx +162 -0
  67. package/src/components/CachedAvatar.jsx +77 -0
  68. package/src/components/ChatPanel.jsx +219 -0
  69. package/src/components/ChatShared.jsx +255 -0
  70. package/src/components/DepartmentDetail.jsx +842 -0
  71. package/src/components/DepartmentView.jsx +367 -0
  72. package/src/components/FileReference.jsx +260 -0
  73. package/src/components/FilesView.jsx +465 -0
  74. package/src/components/GroupChatView.jsx +799 -0
  75. package/src/components/Mailbox.jsx +926 -0
  76. package/src/components/MessagesView.jsx +112 -0
  77. package/src/components/OnboardingGuide.jsx +209 -0
  78. package/src/components/OrgTree.jsx +151 -0
  79. package/src/components/Overview.jsx +391 -0
  80. package/src/components/PixelOffice.jsx +2281 -0
  81. package/src/components/ProviderGrid.jsx +551 -0
  82. package/src/components/ProvidersBoard.jsx +16 -0
  83. package/src/components/RequirementDetail.jsx +1279 -0
  84. package/src/components/RequirementsBoard.jsx +187 -0
  85. package/src/components/SecretarySettings.jsx +295 -0
  86. package/src/components/SetupWizard.jsx +388 -0
  87. package/src/components/Sidebar.jsx +169 -0
  88. package/src/components/SystemMonitor.jsx +808 -0
  89. package/src/components/TalentMarket.jsx +183 -0
  90. package/src/components/TeamDetail.jsx +697 -0
  91. package/src/core/agent/base-agent.js +104 -0
  92. package/src/core/agent/chat-store.js +602 -0
  93. package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
  94. package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
  95. package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
  96. package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
  97. package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
  98. package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
  99. package/src/core/agent/cli-agent/backends/index.js +27 -0
  100. package/src/core/agent/cli-agent/backends/registry.js +580 -0
  101. package/src/core/agent/cli-agent/index.js +154 -0
  102. package/src/core/agent/index.js +60 -0
  103. package/src/core/agent/llm-agent/client.js +320 -0
  104. package/src/core/agent/llm-agent/index.js +97 -0
  105. package/src/core/agent/message-bus.js +211 -0
  106. package/src/core/agent/session.js +608 -0
  107. package/src/core/agent/tools.js +596 -0
  108. package/src/core/agent/web-agent/backends/base-backend.js +180 -0
  109. package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
  110. package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
  111. package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
  112. package/src/core/agent/web-agent/backends/index.js +91 -0
  113. package/src/core/agent/web-agent/index.js +278 -0
  114. package/src/core/agent/web-agent/web-client.js +407 -0
  115. package/src/core/employee/base-employee.js +1088 -0
  116. package/src/core/employee/index.js +35 -0
  117. package/src/core/employee/knowledge.js +327 -0
  118. package/src/core/employee/lifecycle.js +990 -0
  119. package/src/core/employee/memory/index.js +642 -0
  120. package/src/core/employee/memory/store.js +143 -0
  121. package/src/core/employee/performance.js +224 -0
  122. package/src/core/employee/secretary.js +625 -0
  123. package/src/core/employee/skills.js +398 -0
  124. package/src/core/index.js +38 -0
  125. package/src/core/organization/company.js +2600 -0
  126. package/src/core/organization/department.js +737 -0
  127. package/src/core/organization/group-chat-loop.js +264 -0
  128. package/src/core/organization/index.js +8 -0
  129. package/src/core/organization/persistence.js +111 -0
  130. package/src/core/organization/team.js +267 -0
  131. package/src/core/organization/workforce/hr.js +377 -0
  132. package/src/core/organization/workforce/providers.js +468 -0
  133. package/src/core/organization/workforce/role-archetypes.js +805 -0
  134. package/src/core/organization/workforce/talent-market.js +205 -0
  135. package/src/core/prompts.js +532 -0
  136. package/src/core/requirement.js +1789 -0
  137. package/src/core/system/audit.js +483 -0
  138. package/src/core/system/cron.js +449 -0
  139. package/src/core/system/index.js +7 -0
  140. package/src/core/system/plugin.js +2183 -0
  141. package/src/core/utils/json-parse.js +188 -0
  142. package/src/core/workspace.js +239 -0
  143. package/src/lib/api-i18n.js +211 -0
  144. package/src/lib/avatar.js +268 -0
  145. package/src/lib/client-store.js +1025 -0
  146. package/src/lib/config-validator.js +483 -0
  147. package/src/lib/format-time.js +22 -0
  148. package/src/lib/hooks.js +414 -0
  149. package/src/lib/i18n.js +134 -0
  150. package/src/lib/paths.js +23 -0
  151. package/src/lib/store.js +72 -0
  152. package/src/locales/de.js +393 -0
  153. package/src/locales/en.js +1054 -0
  154. package/src/locales/es.js +393 -0
  155. package/src/locales/fr.js +393 -0
  156. package/src/locales/ja.js +501 -0
  157. package/src/locales/ko.js +513 -0
  158. package/src/locales/zh.js +828 -0
  159. package/tailwind.config.mjs +11 -0
@@ -0,0 +1,112 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { useStore } from '@/lib/client-store';
5
+ import { useI18n } from '@/lib/i18n';
6
+
7
+ const TYPE_COLORS = {
8
+ task: 'text-blue-400 bg-blue-900/20',
9
+ report: 'text-green-400 bg-green-900/20',
10
+ question: 'text-yellow-400 bg-yellow-900/20',
11
+ review: 'text-purple-400 bg-purple-900/20',
12
+ feedback: 'text-orange-400 bg-orange-900/20',
13
+ broadcast: 'text-cyan-400 bg-cyan-900/20',
14
+ };
15
+
16
+ export default function MessagesView() {
17
+ const { t } = useI18n();
18
+ const { company, fetchMessages } = useStore();
19
+ const [messages, setMessages] = useState([]);
20
+ const [loading, setLoading] = useState(true);
21
+
22
+ const loadMessages = useCallback(async () => {
23
+ setLoading(true);
24
+ const msgs = await fetchMessages(50);
25
+ setMessages(msgs || []);
26
+ setLoading(false);
27
+ }, [fetchMessages]);
28
+
29
+ useEffect(() => {
30
+ loadMessages();
31
+ }, [loadMessages]);
32
+
33
+ // Build agentId -> name mapping
34
+ const agentNameMap = {};
35
+ if (company) {
36
+ for (const dept of company.departments) {
37
+ for (const m of dept.members) {
38
+ agentNameMap[m.id] = m.name;
39
+ }
40
+ }
41
+ }
42
+
43
+ return (
44
+ <div className="p-6 space-y-6 animate-fade-in">
45
+ <div className="flex items-center justify-between">
46
+ <div>
47
+ <h1 className="text-2xl font-bold">{t('messages.title')}</h1>
48
+ <p className="text-sm text-[var(--muted)] mt-1">{t('messages.subtitle')}</p>
49
+ </div>
50
+ <button onClick={loadMessages} className="btn-secondary text-sm" disabled={loading}>
51
+ {t('messages.refresh')}
52
+ </button>
53
+ </div>
54
+
55
+ {/* Statistics */}
56
+ {company?.messageBusStats && (
57
+ <div className="grid grid-cols-3 gap-3">
58
+ <div className="card text-center">
59
+ <div className="text-2xl font-bold text-[var(--accent)]">{company.messageBusStats.totalMessages}</div>
60
+ <div className="text-xs text-[var(--muted)]">{t('messages.totalMessages')}</div>
61
+ </div>
62
+ <div className="card text-center">
63
+ <div className="text-2xl font-bold text-green-400">{company.messageBusStats.activeAgents || 0}</div>
64
+ <div className="text-xs text-[var(--muted)]">{t('messages.activeAgents')}</div>
65
+ </div>
66
+ <div className="card text-center">
67
+ <div className="text-2xl font-bold text-purple-400">
68
+ {Object.keys(company.messageBusStats.byType || {}).length}
69
+ </div>
70
+ <div className="text-xs text-[var(--muted)]">{t('messages.messageTypes')}</div>
71
+ </div>
72
+ </div>
73
+ )}
74
+
75
+ {/* Message list */}
76
+ {loading ? (
77
+ <div className="text-center py-8 text-[var(--muted)]">{t('common.loading')}</div>
78
+ ) : messages.length === 0 ? (
79
+ <div className="card text-center py-12 text-[var(--muted)]">
80
+ <div className="text-5xl mb-4">๐Ÿ’ฌ</div>
81
+ <p>{t('messages.noRecords')}</p>
82
+ <p className="text-sm mt-1">{t('messages.noRecordsHint')}</p>
83
+ </div>
84
+ ) : (
85
+ <div className="space-y-2">
86
+ {messages.slice().reverse().map((msg) => (
87
+ <div key={msg.id} className="card py-3 flex items-start gap-3">
88
+ <div className={`text-xs px-2 py-0.5 rounded shrink-0 ${TYPE_COLORS[msg.type] || 'text-gray-400 bg-gray-900/20'}`}>
89
+ {msg.type}
90
+ </div>
91
+ <div className="flex-1 min-w-0">
92
+ <div className="text-sm">
93
+ <span className="font-medium text-[var(--accent)]">
94
+ {agentNameMap[msg.from] || msg.from?.slice(0, 8) || '?'}
95
+ </span>
96
+ <span className="text-[var(--muted)]"> โ†’ </span>
97
+ <span className="font-medium text-green-400">
98
+ {agentNameMap[msg.to] || msg.to?.slice(0, 8) || t('messages.broadcast')}
99
+ </span>
100
+ </div>
101
+ <div className="text-xs text-[var(--muted)] mt-1 break-all">{msg.content}</div>
102
+ </div>
103
+ <div className="text-[10px] text-[var(--muted)] shrink-0">
104
+ {msg.timestamp && new Date(msg.timestamp).toLocaleTimeString('zh', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
105
+ </div>
106
+ </div>
107
+ ))}
108
+ </div>
109
+ )}
110
+ </div>
111
+ );
112
+ }
@@ -0,0 +1,209 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef, useCallback } from 'react';
4
+ import { useStore } from '@/lib/client-store';
5
+ import { useI18n } from '@/lib/i18n';
6
+ import CachedAvatar from './CachedAvatar';
7
+ import ProvidersBoard from './ProvidersBoard';
8
+
9
+ /**
10
+ * OnboardingGuide โ€” Game-style onboarding with secretary avatar and speech bubbles.
11
+ *
12
+ * Flow:
13
+ * Step 0: Secretary introduces herself, greets the boss
14
+ * Step 1: Open providers board in a modal, let user configure providers
15
+ * Step 2: Tell user they can create departments and assign tasks
16
+ * Step 3: Done โ€” dismiss guide
17
+ */
18
+ export default function OnboardingGuide({ onComplete }) {
19
+ const { company, setActiveTab } = useStore();
20
+ const { t } = useI18n();
21
+ const [step, setStep] = useState(0);
22
+ const [showProviders, setShowProviders] = useState(false);
23
+ const [typedText, setTypedText] = useState('');
24
+ const [isTyping, setIsTyping] = useState(true);
25
+ const typeTimerRef = useRef(null);
26
+
27
+ const secretary = company?.secretary;
28
+ const secretaryName = secretary?.name || t('chat.secretary');
29
+ const secretaryAvatar = secretary?.avatar || '';
30
+
31
+ // Get current step's dialogue text
32
+ const getDialogue = useCallback((s) => {
33
+ const dialogues = t('onboarding.dialogues');
34
+ if (Array.isArray(dialogues) && dialogues[s]) {
35
+ return dialogues[s].replace('{bossName}', company?.bossName || 'Boss').replace('{secretaryName}', secretaryName);
36
+ }
37
+ return '';
38
+ }, [t, company?.bossName, secretaryName]);
39
+
40
+ // Typewriter effect
41
+ useEffect(() => {
42
+ const text = getDialogue(step);
43
+ if (!text) return;
44
+
45
+ setTypedText('');
46
+ setIsTyping(true);
47
+ let index = 0;
48
+
49
+ const type = () => {
50
+ if (index < text.length) {
51
+ setTypedText(text.slice(0, index + 1));
52
+ index++;
53
+ typeTimerRef.current = setTimeout(type, 30);
54
+ } else {
55
+ setIsTyping(false);
56
+ }
57
+ };
58
+
59
+ typeTimerRef.current = setTimeout(type, 500);
60
+ return () => { if (typeTimerRef.current) clearTimeout(typeTimerRef.current); };
61
+ }, [step, getDialogue]);
62
+
63
+ // Skip typewriter โ€” show full text
64
+ const skipTyping = () => {
65
+ if (typeTimerRef.current) clearTimeout(typeTimerRef.current);
66
+ setTypedText(getDialogue(step));
67
+ setIsTyping(false);
68
+ };
69
+
70
+ const handleNext = () => {
71
+ if (isTyping) {
72
+ skipTyping();
73
+ return;
74
+ }
75
+ if (step === 1) {
76
+ // Open providers modal
77
+ setShowProviders(true);
78
+ return;
79
+ }
80
+ if (step === 2) {
81
+ // Final step โ€” done
82
+ onComplete();
83
+ return;
84
+ }
85
+ setStep(s => s + 1);
86
+ };
87
+
88
+ const handleProvidersClose = () => {
89
+ setShowProviders(false);
90
+ setStep(2);
91
+ };
92
+
93
+ const getButtonText = () => {
94
+ if (isTyping) return t('onboarding.clickToContinue');
95
+ if (step === 0) return t('onboarding.nextBtn');
96
+ if (step === 1) return t('onboarding.configureBtn');
97
+ if (step === 2) return t('onboarding.startBtn');
98
+ return t('common.next');
99
+ };
100
+
101
+ return (
102
+ <>
103
+ {/* Overlay */}
104
+ <div className="fixed inset-0 bg-black/70 z-[100] flex flex-col items-center justify-end transition-all">
105
+ {/* Speech bubble area */}
106
+ <div className="w-full max-w-2xl px-6 mb-4 animate-fade-in">
107
+ {/* Bubble */}
108
+ <div
109
+ className="relative bg-[var(--card)] border border-[var(--border)] rounded-2xl rounded-bl-md p-5 shadow-2xl cursor-pointer select-none"
110
+ onClick={handleNext}
111
+ >
112
+ {/* Secretary name tag */}
113
+ <div className="absolute -top-3 left-4 px-3 py-0.5 bg-[var(--accent)] text-white text-xs rounded-full font-medium shadow-lg">
114
+ {secretaryName}
115
+ </div>
116
+ {/* Dialogue text */}
117
+ <p className="text-sm leading-relaxed whitespace-pre-line mt-1 min-h-[3rem]">
118
+ {typedText}
119
+ {isTyping && <span className="inline-block w-0.5 h-4 bg-[var(--accent)] ml-0.5 animate-pulse align-middle" />}
120
+ </p>
121
+ {/* Action button */}
122
+ <div className="flex justify-end mt-3">
123
+ <button
124
+ onClick={(e) => { e.stopPropagation(); handleNext(); }}
125
+ className="px-4 py-1.5 bg-[var(--accent)] text-white text-sm rounded-lg hover:opacity-90 transition-all shadow-lg"
126
+ >
127
+ {getButtonText()}
128
+ </button>
129
+ </div>
130
+ {/* Skip */}
131
+ {step < 2 && (
132
+ <button
133
+ onClick={(e) => { e.stopPropagation(); onComplete(); }}
134
+ className="absolute top-2 right-3 text-[10px] text-[var(--muted)] hover:text-white transition-all"
135
+ >
136
+ {t('onboarding.skip')}
137
+ </button>
138
+ )}
139
+ </div>
140
+ {/* Bubble tail */}
141
+ <div className="ml-10 w-0 h-0 border-l-[12px] border-l-transparent border-r-[12px] border-r-transparent border-t-[12px] border-t-[var(--card)]" />
142
+ </div>
143
+
144
+ {/* Secretary avatar at the bottom */}
145
+ <div className="flex items-end gap-3 pb-8 animate-bounce-in">
146
+ <div className="relative">
147
+ {/* Glow effect */}
148
+ <div className="absolute -inset-2 rounded-full bg-[var(--accent)]/20 blur-lg animate-pulse" />
149
+ <CachedAvatar
150
+ src={secretaryAvatar}
151
+ alt={secretaryName}
152
+ className="w-28 h-28 rounded-full border-3 border-[var(--accent)] shadow-2xl relative z-10 bg-[var(--card)]"
153
+ />
154
+ {/* Online indicator */}
155
+ <div className="absolute bottom-1 right-1 w-4 h-4 bg-green-500 rounded-full border-2 border-[var(--card)] z-20" />
156
+ </div>
157
+ </div>
158
+
159
+ {/* Step dots */}
160
+ <div className="flex gap-2 pb-4">
161
+ {[0, 1, 2].map(i => (
162
+ <div
163
+ key={i}
164
+ className={`w-2 h-2 rounded-full transition-all ${
165
+ step === i ? 'bg-[var(--accent)] w-5' : step > i ? 'bg-[var(--accent)]/50' : 'bg-white/20'
166
+ }`}
167
+ />
168
+ ))}
169
+ </div>
170
+ </div>
171
+
172
+ {/* Providers Board Modal */}
173
+ {showProviders && (
174
+ <div className="fixed inset-0 bg-black/80 z-[110] flex items-center justify-center !m-0" onClick={handleProvidersClose}>
175
+ <div
176
+ className="bg-[var(--background)] rounded-2xl max-w-4xl w-full mx-4 max-h-[85vh] overflow-y-auto shadow-2xl border border-[var(--border)] animate-fade-in"
177
+ onClick={e => e.stopPropagation()}
178
+ >
179
+ {/* Header with close button */}
180
+ <div className="sticky top-0 bg-[var(--background)] border-b border-[var(--border)] px-6 py-3 flex items-center justify-between z-10 rounded-t-2xl">
181
+ <div>
182
+ <h2 className="text-lg font-bold">{t('onboarding.providersTitle')}</h2>
183
+ <p className="text-xs text-[var(--muted)]">{t('onboarding.providersDesc')}</p>
184
+ </div>
185
+ <button
186
+ onClick={handleProvidersClose}
187
+ className="px-4 py-1.5 bg-[var(--accent)] text-white text-sm rounded-lg hover:opacity-90 transition-all"
188
+ >
189
+ {t('onboarding.providersDone')}
190
+ </button>
191
+ </div>
192
+ <ProvidersBoard />
193
+ </div>
194
+ </div>
195
+ )}
196
+
197
+ <style jsx>{`
198
+ @keyframes bounce-in {
199
+ 0% { transform: translateY(100px); opacity: 0; }
200
+ 50% { transform: translateY(-10px); opacity: 1; }
201
+ 100% { transform: translateY(0); opacity: 1; }
202
+ }
203
+ .animate-bounce-in {
204
+ animation: bounce-in 0.6s ease-out forwards;
205
+ }
206
+ `}</style>
207
+ </>
208
+ );
209
+ }
@@ -0,0 +1,151 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useStore } from '@/lib/client-store';
5
+ import AgentDetailModal from './AgentDetailModal';
6
+ import { useI18n } from '@/lib/i18n';
7
+ import CachedAvatar from './CachedAvatar';
8
+
9
+ /**
10
+ * Tree node - recursive render (clickable for detail)
11
+ */
12
+ function TreeNode({ node, allMembers, depth = 0, onClickAgent }) {
13
+ const subs = allMembers.filter(m => m.reportsTo === node.id);
14
+
15
+ return (
16
+ <div className="flex flex-col items-center">
17
+ {/* Node */}
18
+ <div
19
+ onClick={() => onClickAgent?.(node.id)}
20
+ className={`flex flex-col items-center p-3 rounded-xl border transition-all hover:scale-105 cursor-pointer ${
21
+ depth === 0
22
+ ? 'bg-gradient-to-br from-red-900/30 to-orange-900/20 border-red-500/30 shadow-lg shadow-red-500/10'
23
+ : depth === 1
24
+ ? 'bg-gradient-to-br from-blue-900/20 to-indigo-900/20 border-blue-500/20'
25
+ : 'bg-[var(--card)] border-[var(--border)]'
26
+ }`}
27
+ >
28
+ <CachedAvatar src={node.avatar} alt={node.name} className="w-12 h-12 rounded-full bg-[var(--border)] mb-1" />
29
+ <div className="text-sm font-medium text-center">{node.name}</div>
30
+ <div className="text-[10px] text-[var(--muted)]">
31
+ {node.gender === 'female' ? '๐Ÿ‘ฉ' : '๐Ÿ‘จ'}{node.age ? ` ${node.age}` : ''} ยท {node.role}
32
+ </div>
33
+ <div className="text-[10px] text-[var(--muted)] italic mt-0.5 max-w-[120px] truncate text-center" title={node.signature}>
34
+ "{node.signature}"
35
+ </div>
36
+ {node.tokenUsage?.totalCost > 0 && (
37
+ <div className="text-[10px] text-red-400 mt-0.5">๐Ÿ”ฅ ${node.tokenUsage.totalCost.toFixed(4)}</div>
38
+ )}
39
+ </div>
40
+
41
+ {/* Subordinate connections and child nodes */}
42
+ {subs.length > 0 && (
43
+ <>
44
+ <div className="w-px h-6 bg-[var(--border)]" />
45
+ <div className="flex gap-6 relative">
46
+ {/* Horizontal connector */}
47
+ {subs.length > 1 && (
48
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 h-px bg-[var(--border)]"
49
+ style={{ width: `calc(100% - 60px)` }} />
50
+ )}
51
+ {subs.map(sub => (
52
+ <div key={sub.id} className="flex flex-col items-center">
53
+ <div className="w-px h-4 bg-[var(--border)]" />
54
+ <TreeNode node={sub} allMembers={allMembers} depth={depth + 1} onClickAgent={onClickAgent} />
55
+ </div>
56
+ ))}
57
+ </div>
58
+ </>
59
+ )}
60
+ </div>
61
+ );
62
+ }
63
+
64
+ export default function OrgTree({ embedded = false }) {
65
+ const { t } = useI18n();
66
+ const { company } = useStore();
67
+ const [selectedAgent, setSelectedAgent] = useState(null);
68
+
69
+ if (!company) return null;
70
+
71
+ return (
72
+ <div className={`${embedded ? 'p-4' : 'p-6 space-y-6'} animate-fade-in`}>
73
+ {!embedded && (
74
+ <div>
75
+ <h1 className="text-2xl font-bold">{t('orgTree.title')}</h1>
76
+ <p className="text-sm text-[var(--muted)] mt-1">{t('orgTree.subtitle')}</p>
77
+ </div>
78
+ )}
79
+
80
+ {/* Company level */}
81
+ <div className="flex flex-col items-center">
82
+ {/* Boss */}
83
+ <div className="flex flex-col items-center p-4 rounded-xl bg-gradient-to-br from-yellow-900/30 to-red-900/20 border border-yellow-500/30 shadow-lg shadow-yellow-500/10 mb-2">
84
+ {company.bossAvatar ? (
85
+ <CachedAvatar src={company.bossAvatar} alt="boss" className="w-12 h-12 rounded-full bg-[var(--border)] mb-1" />
86
+ ) : (
87
+ <div className="text-3xl mb-1">๐Ÿ‘‘</div>
88
+ )}
89
+ <div className="text-sm font-bold">{company.boss}</div>
90
+ <div className="text-[10px] text-yellow-400">{t('orgTree.boss')}</div>
91
+ </div>
92
+ <div className="w-px h-6 bg-[var(--border)]" />
93
+
94
+ {/* Secretary */}
95
+ <div className="flex flex-col items-center p-3 rounded-xl bg-gradient-to-br from-purple-900/20 to-pink-900/20 border border-purple-500/20 mb-2">
96
+ <CachedAvatar src={company.secretary?.avatar} alt="secretary" className="w-10 h-10 rounded-full bg-[var(--border)] mb-1" />
97
+ <div className="text-sm font-medium">{company.secretary?.name}</div>
98
+ <div className="text-[10px] text-purple-400">
99
+ {company.secretary?.gender === 'female' ? '๐Ÿ‘ฉ' : '๐Ÿ‘จ'}{company.secretary?.age ? ` ${t('display.ageYears', { n: company.secretary.age })}` : ''} ยท {t('orgTree.secretary')}
100
+ </div>
101
+ </div>
102
+ <div className="w-px h-6 bg-[var(--border)]" />
103
+
104
+ {/* Departments */}
105
+ {company.departments?.length > 0 ? (
106
+ <div className="flex gap-12 flex-wrap justify-center">
107
+ {company.departments.map(dept => {
108
+ // Find department leader (top-level node)
109
+ const leader = dept.members.find(m => m.id === dept.leader);
110
+ const others = dept.members.filter(m => !m.reportsTo && m.id !== dept.leader);
111
+
112
+ return (
113
+ <div key={dept.id} className="flex flex-col items-center">
114
+ <div className="text-xs text-[var(--muted)] mb-2 px-3 py-1 rounded-full bg-[var(--card)] border border-[var(--border)]">
115
+ ๐Ÿญ {dept.name}
116
+ </div>
117
+ <div className="w-px h-4 bg-[var(--border)]" />
118
+
119
+ {leader ? (
120
+ <TreeNode node={leader} allMembers={dept.members} depth={0} onClickAgent={setSelectedAgent} />
121
+ ) : (
122
+ <div className="text-sm text-[var(--muted)]">{t('orgTree.noLeader')}</div>
123
+ )}
124
+
125
+ {/* Non-leader members without a superior */}
126
+ {others.length > 0 && (
127
+ <div className="flex gap-4 mt-4">
128
+ {others.map(m => (
129
+ <TreeNode key={m.id} node={m} allMembers={dept.members} depth={1} onClickAgent={setSelectedAgent} />
130
+ ))}
131
+ </div>
132
+ )}
133
+ </div>
134
+ );
135
+ })}
136
+ </div>
137
+ ) : (
138
+ <div className="card text-center py-8 text-[var(--muted)]">
139
+ <div className="text-4xl mb-3">๐Ÿ•ณ๏ธ</div>
140
+ <p>{t('orgTree.empty')}</p>
141
+ </div>
142
+ )}
143
+ </div>
144
+
145
+ {/* Agent detail modal */}
146
+ {selectedAgent && (
147
+ <AgentDetailModal agentId={selectedAgent} onClose={() => setSelectedAgent(null)} />
148
+ )}
149
+ </div>
150
+ );
151
+ }