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.
- package/.dockerignore +33 -0
- package/.nvmrc +1 -0
- package/ARCHITECTURE.md +394 -0
- package/Dockerfile +50 -0
- package/LICENSE +29 -0
- package/README.md +206 -0
- package/bin/i18n.js +46 -0
- package/bin/ideaco.js +494 -0
- package/deploy.sh +15 -0
- package/docker-compose.yml +30 -0
- package/electron/main.cjs +986 -0
- package/electron/preload.cjs +14 -0
- package/electron/web-backends.cjs +854 -0
- package/jsconfig.json +8 -0
- package/next.config.mjs +34 -0
- package/package.json +134 -0
- package/postcss.config.mjs +6 -0
- package/public/demo/dashboard.png +0 -0
- package/public/demo/employee.png +0 -0
- package/public/demo/messages.png +0 -0
- package/public/demo/office.png +0 -0
- package/public/demo/requirement.png +0 -0
- package/public/logo.jpeg +0 -0
- package/public/logo.png +0 -0
- package/scripts/prepare-electron.js +67 -0
- package/scripts/release.js +76 -0
- package/src/app/api/agents/[agentId]/chat/route.js +70 -0
- package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
- package/src/app/api/agents/[agentId]/route.js +106 -0
- package/src/app/api/avatar/route.js +104 -0
- package/src/app/api/browse-dir/route.js +44 -0
- package/src/app/api/chat/route.js +265 -0
- package/src/app/api/company/factory-reset/route.js +43 -0
- package/src/app/api/company/route.js +82 -0
- package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
- package/src/app/api/departments/route.js +92 -0
- package/src/app/api/group-chat-loop/events/route.js +70 -0
- package/src/app/api/group-chat-loop/route.js +94 -0
- package/src/app/api/mailbox/route.js +100 -0
- package/src/app/api/messages/route.js +14 -0
- package/src/app/api/providers/[id]/configure/route.js +21 -0
- package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
- package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
- package/src/app/api/providers/route.js +11 -0
- package/src/app/api/requirements/route.js +242 -0
- package/src/app/api/secretary/route.js +65 -0
- package/src/app/api/system/cli-backends/route.js +91 -0
- package/src/app/api/system/cron/route.js +110 -0
- package/src/app/api/system/knowledge/route.js +104 -0
- package/src/app/api/system/plugins/route.js +40 -0
- package/src/app/api/system/skills/route.js +46 -0
- package/src/app/api/system/status/route.js +46 -0
- package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
- package/src/app/api/talent-market/[profileId]/route.js +17 -0
- package/src/app/api/talent-market/route.js +26 -0
- package/src/app/api/teams/route.js +773 -0
- package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
- package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
- package/src/app/globals.css +130 -0
- package/src/app/layout.jsx +40 -0
- package/src/app/page.jsx +97 -0
- package/src/components/AgentChatModal.jsx +164 -0
- package/src/components/AgentDetailModal.jsx +425 -0
- package/src/components/AgentSpyModal.jsx +481 -0
- package/src/components/AvatarGrid.jsx +29 -0
- package/src/components/BossProfileModal.jsx +162 -0
- package/src/components/CachedAvatar.jsx +77 -0
- package/src/components/ChatPanel.jsx +219 -0
- package/src/components/ChatShared.jsx +255 -0
- package/src/components/DepartmentDetail.jsx +842 -0
- package/src/components/DepartmentView.jsx +367 -0
- package/src/components/FileReference.jsx +260 -0
- package/src/components/FilesView.jsx +465 -0
- package/src/components/GroupChatView.jsx +799 -0
- package/src/components/Mailbox.jsx +926 -0
- package/src/components/MessagesView.jsx +112 -0
- package/src/components/OnboardingGuide.jsx +209 -0
- package/src/components/OrgTree.jsx +151 -0
- package/src/components/Overview.jsx +391 -0
- package/src/components/PixelOffice.jsx +2281 -0
- package/src/components/ProviderGrid.jsx +551 -0
- package/src/components/ProvidersBoard.jsx +16 -0
- package/src/components/RequirementDetail.jsx +1279 -0
- package/src/components/RequirementsBoard.jsx +187 -0
- package/src/components/SecretarySettings.jsx +295 -0
- package/src/components/SetupWizard.jsx +388 -0
- package/src/components/Sidebar.jsx +169 -0
- package/src/components/SystemMonitor.jsx +808 -0
- package/src/components/TalentMarket.jsx +183 -0
- package/src/components/TeamDetail.jsx +697 -0
- package/src/core/agent/base-agent.js +104 -0
- package/src/core/agent/chat-store.js +602 -0
- package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
- package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
- package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
- package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
- package/src/core/agent/cli-agent/backends/index.js +27 -0
- package/src/core/agent/cli-agent/backends/registry.js +580 -0
- package/src/core/agent/cli-agent/index.js +154 -0
- package/src/core/agent/index.js +60 -0
- package/src/core/agent/llm-agent/client.js +320 -0
- package/src/core/agent/llm-agent/index.js +97 -0
- package/src/core/agent/message-bus.js +211 -0
- package/src/core/agent/session.js +608 -0
- package/src/core/agent/tools.js +596 -0
- package/src/core/agent/web-agent/backends/base-backend.js +180 -0
- package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
- package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
- package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
- package/src/core/agent/web-agent/backends/index.js +91 -0
- package/src/core/agent/web-agent/index.js +278 -0
- package/src/core/agent/web-agent/web-client.js +407 -0
- package/src/core/employee/base-employee.js +1088 -0
- package/src/core/employee/index.js +35 -0
- package/src/core/employee/knowledge.js +327 -0
- package/src/core/employee/lifecycle.js +990 -0
- package/src/core/employee/memory/index.js +642 -0
- package/src/core/employee/memory/store.js +143 -0
- package/src/core/employee/performance.js +224 -0
- package/src/core/employee/secretary.js +625 -0
- package/src/core/employee/skills.js +398 -0
- package/src/core/index.js +38 -0
- package/src/core/organization/company.js +2600 -0
- package/src/core/organization/department.js +737 -0
- package/src/core/organization/group-chat-loop.js +264 -0
- package/src/core/organization/index.js +8 -0
- package/src/core/organization/persistence.js +111 -0
- package/src/core/organization/team.js +267 -0
- package/src/core/organization/workforce/hr.js +377 -0
- package/src/core/organization/workforce/providers.js +468 -0
- package/src/core/organization/workforce/role-archetypes.js +805 -0
- package/src/core/organization/workforce/talent-market.js +205 -0
- package/src/core/prompts.js +532 -0
- package/src/core/requirement.js +1789 -0
- package/src/core/system/audit.js +483 -0
- package/src/core/system/cron.js +449 -0
- package/src/core/system/index.js +7 -0
- package/src/core/system/plugin.js +2183 -0
- package/src/core/utils/json-parse.js +188 -0
- package/src/core/workspace.js +239 -0
- package/src/lib/api-i18n.js +211 -0
- package/src/lib/avatar.js +268 -0
- package/src/lib/client-store.js +1025 -0
- package/src/lib/config-validator.js +483 -0
- package/src/lib/format-time.js +22 -0
- package/src/lib/hooks.js +414 -0
- package/src/lib/i18n.js +134 -0
- package/src/lib/paths.js +23 -0
- package/src/lib/store.js +72 -0
- package/src/locales/de.js +393 -0
- package/src/locales/en.js +1054 -0
- package/src/locales/es.js +393 -0
- package/src/locales/fr.js +393 -0
- package/src/locales/ja.js +501 -0
- package/src/locales/ko.js +513 -0
- package/src/locales/zh.js +828 -0
- 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
|
+
}
|