opencompany 0.1.0

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.
@@ -0,0 +1,266 @@
1
+ import { state, chatEl, inputEl, sendBtnEl } from './state.js';
2
+
3
+ export async function getConfig() {
4
+ if (state.cachedConfig) return state.cachedConfig;
5
+ const res = await fetch('/api/config');
6
+ state.cachedConfig = await res.json();
7
+ return state.cachedConfig;
8
+ }
9
+
10
+ function getTimeStr() {
11
+ const now = new Date();
12
+ return now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
13
+ }
14
+
15
+ export function appendMsg(role, text) {
16
+ const row = document.createElement('div');
17
+ row.className = 'msg-row ' + role;
18
+
19
+ if (role === 'assistant') {
20
+ const avatar = document.createElement('div');
21
+ avatar.className = 'msg-avatar';
22
+ avatar.innerHTML = '<i data-lucide="bot"></i>';
23
+
24
+ const wrap = document.createElement('div');
25
+ wrap.className = 'msg-wrap';
26
+
27
+ const bubble = document.createElement('div');
28
+ bubble.className = 'msg-bubble';
29
+ bubble.textContent = text;
30
+
31
+ const time = document.createElement('div');
32
+ time.className = 'msg-time';
33
+ time.textContent = getTimeStr();
34
+
35
+ wrap.appendChild(bubble);
36
+ wrap.appendChild(time);
37
+ row.appendChild(avatar);
38
+ row.appendChild(wrap);
39
+ } else {
40
+ const wrap = document.createElement('div');
41
+ wrap.className = 'msg-wrap';
42
+
43
+ const bubble = document.createElement('div');
44
+ bubble.className = 'msg-bubble';
45
+ bubble.textContent = text;
46
+
47
+ const time = document.createElement('div');
48
+ time.className = 'msg-time';
49
+ time.textContent = getTimeStr();
50
+
51
+ wrap.appendChild(bubble);
52
+ wrap.appendChild(time);
53
+ row.appendChild(wrap);
54
+ }
55
+
56
+ chatEl.appendChild(row);
57
+ chatEl.scrollTop = chatEl.scrollHeight;
58
+
59
+ if (typeof lucide !== 'undefined') {
60
+ lucide.createIcons({ nodes: [row] });
61
+ }
62
+
63
+ return row;
64
+ }
65
+
66
+ export async function loadHistoryFromBackend(name) {
67
+ try {
68
+ const res = await fetch(`/api/agents/${encodeURIComponent(name)}/history`);
69
+ const data = await res.json();
70
+ const entries = data.messages || [];
71
+ state.agentMessages[name] = entries.map(e => ({ role: e.role, text: e.content }));
72
+ if (state.currentAgent === name) {
73
+ chatEl.innerHTML = '';
74
+ state.agentMessages[name].forEach(m => appendMsg(m.role, m.text));
75
+ }
76
+ } catch (e) {
77
+ console.error('Failed to load history:', e);
78
+ }
79
+ }
80
+
81
+ export function updateProgress() {
82
+ const progressText = document.querySelector('.progress-text');
83
+ const progressFill = document.querySelector('.progress-fill');
84
+ if (!progressText || !progressFill) return;
85
+
86
+ const agent = state.agents.find(a => a.name === state.currentAgent);
87
+ if (agent) {
88
+ progressText.textContent = agent.tools.length + ' tools';
89
+ const pct = Math.min(100, agent.tools.length * 25);
90
+ progressFill.style.width = pct + '%';
91
+ }
92
+ }
93
+
94
+ function createStepEl(step) {
95
+ const el = document.createElement('div');
96
+ el.className = 'tool-step';
97
+ el.dataset.name = step.name;
98
+ el.dataset.status = step.status || 'done';
99
+
100
+ const nameEl = document.createElement('span');
101
+ nameEl.className = 'tool-step-name';
102
+ nameEl.textContent = step.name;
103
+
104
+ const detailEl = document.createElement('span');
105
+ detailEl.className = 'tool-step-detail';
106
+ try {
107
+ const args = JSON.parse(step.input);
108
+ const summary = args.query || args.path || args.command || step.input;
109
+ detailEl.textContent = summary.length > 60 ? summary.slice(0, 60) + '...' : summary;
110
+ } catch {
111
+ detailEl.textContent = step.input.length > 60 ? step.input.slice(0, 60) + '...' : step.input;
112
+ }
113
+
114
+ el.appendChild(nameEl);
115
+ el.appendChild(detailEl);
116
+ return el;
117
+ }
118
+
119
+ async function sendMessage() {
120
+ if (state.agentSending[state.currentAgent]) return;
121
+ if (!state.currentAgent) {
122
+ alert('Please select or create an agent first.');
123
+ return;
124
+ }
125
+
126
+ const text = inputEl.value.trim();
127
+ if (!text) return;
128
+
129
+ state.agentSending[state.currentAgent] = true;
130
+ const thisAgent = state.currentAgent;
131
+ sendBtnEl.disabled = true;
132
+ inputEl.disabled = true;
133
+ inputEl.value = '';
134
+
135
+ appendMsg('user', text);
136
+
137
+ if (!state.agentMessages[state.currentAgent]) state.agentMessages[state.currentAgent] = [];
138
+ state.agentMessages[state.currentAgent].push({ role: 'user', text });
139
+
140
+ const messages = state.agentMessages[state.currentAgent]
141
+ .map(m => ({ role: m.role, content: m.text }));
142
+
143
+ const loadingEntry = { role: 'assistant', text: '...', loading: true };
144
+ state.agentMessages[state.currentAgent].push(loadingEntry);
145
+
146
+ const loadingRow = appendMsg('assistant', '...');
147
+ loadingRow.dataset.loading = 'true';
148
+
149
+ function findLoadingBubble() {
150
+ const row = chatEl.querySelector('.msg-row[data-loading="true"]');
151
+ if (!row) return { row: null, bubble: null, wrap: null };
152
+ return {
153
+ row,
154
+ bubble: row.querySelector('.msg-bubble'),
155
+ wrap: row.querySelector('.msg-wrap'),
156
+ };
157
+ }
158
+
159
+ let stepsEl = null;
160
+
161
+ try {
162
+ const res = await fetch(`/api/agents/${encodeURIComponent(state.currentAgent)}/chat`, {
163
+ method: 'POST',
164
+ headers: { 'Content-Type': 'application/json' },
165
+ body: JSON.stringify({ messages }),
166
+ });
167
+
168
+ const reader = res.body.getReader();
169
+ const decoder = new TextDecoder();
170
+ let buffer = '';
171
+
172
+ while (true) {
173
+ const { done, value } = await reader.read();
174
+ if (done) break;
175
+
176
+ buffer += decoder.decode(value, { stream: true });
177
+
178
+ let boundary;
179
+ while ((boundary = buffer.indexOf('\n\n')) !== -1) {
180
+ const raw = buffer.substring(0, boundary);
181
+ buffer = buffer.substring(boundary + 2);
182
+
183
+ let eventType = '';
184
+ let data = '';
185
+ for (const line of raw.split('\n')) {
186
+ if (line.startsWith('event:')) eventType = line.substring(6).trim();
187
+ else if (line.startsWith('data:')) data = line.substring(5).trim();
188
+ }
189
+
190
+ if (!data) continue;
191
+
192
+ let parsed;
193
+ try { parsed = JSON.parse(data); } catch { continue; }
194
+
195
+ if (eventType === 'step') {
196
+ if (state.currentAgent === thisAgent) {
197
+ const dom = findLoadingBubble();
198
+ if (dom.wrap && dom.bubble) {
199
+ if (!stepsEl) {
200
+ stepsEl = document.createElement('div');
201
+ stepsEl.className = 'tool-steps';
202
+ dom.wrap.insertBefore(stepsEl, dom.bubble);
203
+ }
204
+
205
+ if (parsed.status === 'running') {
206
+ const stepEl = createStepEl(parsed);
207
+ stepsEl.appendChild(stepEl);
208
+ } else if (parsed.status === 'done') {
209
+ const stepEl = stepsEl.querySelector(
210
+ `.tool-step[data-name="${parsed.name}"][data-status="running"]`
211
+ );
212
+ if (stepEl) {
213
+ stepEl.dataset.status = 'done';
214
+ } else {
215
+ const el = createStepEl(parsed);
216
+ stepsEl.appendChild(el);
217
+ }
218
+ }
219
+
220
+ chatEl.scrollTop = chatEl.scrollHeight;
221
+ }
222
+ }
223
+ } else if (eventType === 'done') {
224
+ loadingEntry.text = parsed.reply || '(empty)';
225
+ loadingEntry.loading = false;
226
+ if (state.currentAgent === thisAgent) {
227
+ const dom = findLoadingBubble();
228
+ if (dom.bubble) dom.bubble.textContent = loadingEntry.text;
229
+ if (dom.row) delete dom.row.dataset.loading;
230
+ }
231
+ } else if (eventType === 'error') {
232
+ loadingEntry.text = 'Error: ' + (parsed.error || 'Unknown error');
233
+ loadingEntry.loading = false;
234
+ if (state.currentAgent === thisAgent) {
235
+ const dom = findLoadingBubble();
236
+ if (dom.bubble) dom.bubble.textContent = loadingEntry.text;
237
+ if (dom.row) delete dom.row.dataset.loading;
238
+ }
239
+ }
240
+ }
241
+ }
242
+ } catch (e) {
243
+ loadingEntry.text = 'Error: ' + e.message;
244
+ loadingEntry.loading = false;
245
+ if (state.currentAgent === thisAgent) {
246
+ const dom = findLoadingBubble();
247
+ if (dom.bubble) dom.bubble.textContent = loadingEntry.text;
248
+ if (dom.row) delete dom.row.dataset.loading;
249
+ }
250
+ }
251
+
252
+ chatEl.scrollTop = chatEl.scrollHeight;
253
+ state.agentSending[thisAgent] = false;
254
+ if (state.currentAgent === thisAgent) {
255
+ sendBtnEl.disabled = false;
256
+ inputEl.disabled = false;
257
+ inputEl.focus();
258
+ }
259
+ }
260
+
261
+ export function initChat() {
262
+ sendBtnEl.addEventListener('click', sendMessage);
263
+ inputEl.addEventListener('keydown', e => {
264
+ if (e.key === 'Enter') sendMessage();
265
+ });
266
+ }
@@ -0,0 +1,10 @@
1
+ export function initContactBar() {
2
+ const btn = document.getElementById('contact-copy-btn');
3
+ if (!btn) return;
4
+ btn.addEventListener('click', () => {
5
+ navigator.clipboard.writeText('eulerguy9137@163.com').then(() => {
6
+ btn.classList.add('copied');
7
+ setTimeout(() => btn.classList.remove('copied'), 1500);
8
+ });
9
+ });
10
+ }
@@ -0,0 +1,42 @@
1
+ export function initInteraction() {
2
+ const btns = document.querySelectorAll('.interaction-btn');
3
+ const panels = document.querySelectorAll('.interaction-panel');
4
+
5
+ function showPanel(name) {
6
+ panels.forEach(p => p.style.display = 'none');
7
+ const target = document.getElementById('panel-' + name);
8
+ if (target) target.style.display = 'flex';
9
+ btns.forEach(b => b.classList.toggle('active', b.dataset.panel === name));
10
+ }
11
+
12
+ btns.forEach(b => {
13
+ b.addEventListener('click', () => showPanel(b.dataset.panel));
14
+ });
15
+
16
+ // Sign In submit — placeholder
17
+ document.getElementById('signin-submit')?.addEventListener('click', () => {
18
+ const email = document.getElementById('signin-email')?.value?.trim();
19
+ const password = document.getElementById('signin-password')?.value;
20
+ if (!email || !password) {
21
+ alert('Please fill in email and password.');
22
+ return;
23
+ }
24
+ alert('Sign in is not yet connected to a backend. Coming soon!');
25
+ });
26
+
27
+ // Sign Up submit — placeholder
28
+ document.getElementById('signup-submit')?.addEventListener('click', () => {
29
+ const email = document.getElementById('signup-email')?.value?.trim();
30
+ const password = document.getElementById('signup-password')?.value;
31
+ const confirm = document.getElementById('signup-confirm')?.value;
32
+ if (!email || !password || !confirm) {
33
+ alert('Please fill in all fields.');
34
+ return;
35
+ }
36
+ if (password !== confirm) {
37
+ alert('Passwords do not match.');
38
+ return;
39
+ }
40
+ alert('Sign up is not yet connected to a backend. Coming soon!');
41
+ });
42
+ }
@@ -0,0 +1,103 @@
1
+ let cachedLogs = [];
2
+
3
+ export async function loadLogs() {
4
+ const container = document.getElementById('log-list');
5
+ if (!container) return;
6
+
7
+ try {
8
+ const res = await fetch('/api/logs');
9
+ const data = await res.json();
10
+ cachedLogs = data.logs || [];
11
+ renderLogs();
12
+ } catch (e) {
13
+ container.innerHTML = '<div class="log-empty">Failed to load logs.</div>';
14
+ }
15
+ }
16
+
17
+ function renderLogs(filter = '') {
18
+ const container = document.getElementById('log-list');
19
+ if (!container) return;
20
+
21
+ let logs = cachedLogs;
22
+ const keyword = filter.trim().toLowerCase();
23
+ if (keyword) {
24
+ logs = logs.filter(e =>
25
+ e.agent.toLowerCase().includes(keyword) ||
26
+ e.path.toLowerCase().includes(keyword) ||
27
+ e.action.toLowerCase().includes(keyword)
28
+ );
29
+ }
30
+
31
+ container.innerHTML = '';
32
+
33
+ if (logs.length === 0) {
34
+ container.innerHTML = '<div class="log-empty">' + (keyword ? 'No matching logs.' : 'No activity yet. Agent file modifications will appear here.') + '</div>';
35
+ return;
36
+ }
37
+
38
+ logs.forEach(entry => {
39
+ const row = document.createElement('div');
40
+ row.className = 'log-entry';
41
+
42
+ const dot = document.createElement('span');
43
+ dot.className = 'log-dot';
44
+
45
+ const agent = document.createElement('span');
46
+ agent.className = 'log-agent';
47
+ agent.textContent = entry.agent;
48
+
49
+ const action = document.createElement('span');
50
+ action.className = 'log-action';
51
+ if (entry.action === 'write_file') {
52
+ action.textContent = 'modified';
53
+ } else if (entry.action === 'exec') {
54
+ const cmd = entry.path || '';
55
+ if (cmd.includes('mkdir')) {
56
+ action.textContent = 'created folder';
57
+ } else if (cmd.includes('touch') || cmd.includes('cat >') || cmd.includes('> ')) {
58
+ action.textContent = 'created file';
59
+ } else {
60
+ action.textContent = 'ran command';
61
+ }
62
+ } else {
63
+ action.textContent = entry.action;
64
+ }
65
+
66
+ const path = document.createElement('span');
67
+ path.className = 'log-path';
68
+ path.textContent = entry.path;
69
+
70
+ const time = document.createElement('span');
71
+ time.className = 'log-time';
72
+ time.textContent = formatTime(entry.timestamp);
73
+
74
+ row.appendChild(dot);
75
+ row.appendChild(agent);
76
+ row.appendChild(action);
77
+ row.appendChild(path);
78
+ row.appendChild(time);
79
+ container.appendChild(row);
80
+ });
81
+ }
82
+
83
+ function formatTime(iso) {
84
+ try {
85
+ const match = iso.match(/T(\d{2}):(\d{2})/);
86
+ if (match) {
87
+ const date = iso.match(/(\d{4}-\d{2}-\d{2})/)?.[1] || '';
88
+ return `${date} ${match[1]}:${match[2]}`;
89
+ }
90
+ } catch {}
91
+ return iso;
92
+ }
93
+
94
+ export function initLog() {
95
+ document.getElementById('log-refresh-btn')?.addEventListener('click', loadLogs);
96
+
97
+ const searchInput = document.getElementById('log-search');
98
+ if (searchInput) {
99
+ searchInput.addEventListener('input', () => {
100
+ renderLogs(searchInput.value);
101
+ });
102
+ }
103
+ }