mop-agent 0.1.13 → 0.1.14
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/README.md +1 -1
- package/apps/web/app/assistant/page.tsx +134 -46
- package/apps/web/app/globals.css +136 -26
- package/apps/web/components/AppShell.tsx +41 -41
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ through MOP-FLOW. It stores project memory, performs semantic recall and
|
|
|
5
5
|
consolidation, serves grounded chat, and can request approved actions from a
|
|
6
6
|
linked FLOW node.
|
|
7
7
|
|
|
8
|
-
> **Release status:** release candidate `mop-agent@0.1.
|
|
8
|
+
> **Release status:** release candidate `mop-agent@0.1.14` contains the corrected VPS
|
|
9
9
|
> installer, one-time Admin setup/login flow, and simplified shared application shell
|
|
10
10
|
> with centered page titles and ChatGPT-inspired navigation.
|
|
11
11
|
> The canonical installation command is exactly `npx mop-agent`.
|
|
@@ -5,6 +5,9 @@ import { useEffect, useRef, useState } from "react";
|
|
|
5
5
|
import { useMemoryCore } from "@/components/AppShell";
|
|
6
6
|
|
|
7
7
|
type Turn = { role: "user" | "assistant"; content: string };
|
|
8
|
+
type SavedChat = { id: string; title: string; turns: Turn[]; updatedAt: number };
|
|
9
|
+
|
|
10
|
+
const CHAT_HISTORY_KEY = "mop-agent-chat-history-v1";
|
|
8
11
|
|
|
9
12
|
export default function AssistantPage() {
|
|
10
13
|
const { projects } = useMemoryCore();
|
|
@@ -13,6 +16,9 @@ export default function AssistantPage() {
|
|
|
13
16
|
const [input, setInput] = useState("");
|
|
14
17
|
const [busy, setBusy] = useState(false);
|
|
15
18
|
const [providerUsed, setProviderUsed] = useState("");
|
|
19
|
+
const [history, setHistory] = useState<SavedChat[]>([]);
|
|
20
|
+
const [historyReady, setHistoryReady] = useState(false);
|
|
21
|
+
const [activeChatId, setActiveChatId] = useState("");
|
|
16
22
|
const endRef = useRef<HTMLDivElement>(null);
|
|
17
23
|
|
|
18
24
|
useEffect(() => {
|
|
@@ -21,10 +27,61 @@ export default function AssistantPage() {
|
|
|
21
27
|
}).catch(() => {});
|
|
22
28
|
}, []);
|
|
23
29
|
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
try {
|
|
32
|
+
const stored = JSON.parse(window.localStorage.getItem(CHAT_HISTORY_KEY) ?? "[]");
|
|
33
|
+
if (Array.isArray(stored)) setHistory(stored.slice(0, 30));
|
|
34
|
+
} catch {
|
|
35
|
+
window.localStorage.removeItem(CHAT_HISTORY_KEY);
|
|
36
|
+
} finally {
|
|
37
|
+
setHistoryReady(true);
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!historyReady) return;
|
|
43
|
+
window.localStorage.setItem(CHAT_HISTORY_KEY, JSON.stringify(history.slice(0, 30)));
|
|
44
|
+
}, [history, historyReady]);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!activeChatId || turns.length === 0) return;
|
|
48
|
+
const firstMessage = turns.find((turn) => turn.role === "user")?.content ?? "New conversation";
|
|
49
|
+
setHistory((current) => {
|
|
50
|
+
const updated: SavedChat = {
|
|
51
|
+
id: activeChatId,
|
|
52
|
+
title: firstMessage.slice(0, 54),
|
|
53
|
+
turns,
|
|
54
|
+
updatedAt: Date.now(),
|
|
55
|
+
};
|
|
56
|
+
return [updated, ...current.filter((chat) => chat.id !== activeChatId)].slice(0, 30);
|
|
57
|
+
});
|
|
58
|
+
}, [activeChatId, turns]);
|
|
59
|
+
|
|
60
|
+
function startNewChat() {
|
|
61
|
+
if (busy) return;
|
|
62
|
+
setActiveChatId("");
|
|
63
|
+
setTurns([]);
|
|
64
|
+
setProviderUsed("");
|
|
65
|
+
setInput("");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function openChat(chat: SavedChat) {
|
|
69
|
+
if (busy) return;
|
|
70
|
+
setActiveChatId(chat.id);
|
|
71
|
+
setTurns(chat.turns);
|
|
72
|
+
setProviderUsed("");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function deleteChat(id: string) {
|
|
76
|
+
if (busy) return;
|
|
77
|
+
setHistory((current) => current.filter((chat) => chat.id !== id));
|
|
78
|
+
if (activeChatId === id) startNewChat();
|
|
79
|
+
}
|
|
24
80
|
|
|
25
81
|
async function send(prefill?: string) {
|
|
26
82
|
const message = (prefill ?? input).trim();
|
|
27
83
|
if (!message || busy) return;
|
|
84
|
+
if (!activeChatId) setActiveChatId(`chat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
28
85
|
setInput("");
|
|
29
86
|
setBusy(true);
|
|
30
87
|
setTurns((current) => [...current, { role: "user", content: message }, { role: "assistant", content: "" }]);
|
|
@@ -68,64 +125,95 @@ export default function AssistantPage() {
|
|
|
68
125
|
|
|
69
126
|
return (
|
|
70
127
|
<section className="mop-assistant-page">
|
|
71
|
-
<div className="mop-assistant-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<div
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
128
|
+
<div className="mop-assistant-workspace">
|
|
129
|
+
<div className="mop-assistant-conversation">
|
|
130
|
+
{turns.length === 0 ? (
|
|
131
|
+
<div className="mop-assistant-welcome">
|
|
132
|
+
<div style={assistantLogo}><img src="/icon.svg" alt="MOP-AGENT" /></div>
|
|
133
|
+
<p style={{ color: "#742220", fontSize: 11, fontWeight: 900, letterSpacing: ".16em" }}>MOP-AGENT IS READY</p>
|
|
134
|
+
<h1 style={{ fontFamily: '"SFMono-Regular", Consolas, monospace', fontSize: "clamp(26px, 4vw, 40px)", margin: "8px 0 12px" }}>
|
|
135
|
+
What are we working on, {name.split(" ")[0]}?
|
|
136
|
+
</h1>
|
|
137
|
+
<p style={{ color: "rgba(45,74,62,.7)", maxWidth: 610, lineHeight: 1.65 }}>
|
|
138
|
+
Start talking immediately. Link projects when you want MOP-AGENT to remember their state and work across them.
|
|
139
|
+
</p>
|
|
140
|
+
<div className="mop-prompt-grid" style={promptGrid}>
|
|
141
|
+
{["Help me plan today’s work", "What can MOP-AGENT do?", "Summarize what you remember", "Plan a new software project"].map((prompt) => (
|
|
142
|
+
<button key={prompt} onClick={() => send(prompt)} style={promptCard}>{prompt}<span>→</span></button>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
{projects.length === 0 && (
|
|
146
|
+
<p style={{ fontSize: 13, color: "rgba(45,74,62,.68)", marginTop: 24 }}>
|
|
147
|
+
No project linked yet—this does not block chat. <a href="/brain" style={{ color: "#742220" }}>Link one from Brain →</a>
|
|
148
|
+
</p>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
) : (
|
|
152
|
+
<div style={{ width: "min(100%, 820px)", margin: "0 auto", padding: "28px 0 160px" }}>
|
|
153
|
+
{turns.map((turn, index) => (
|
|
154
|
+
<article key={index} style={{ display: "grid", gridTemplateColumns: "34px 1fr", gap: 13, marginBottom: 26 }}>
|
|
155
|
+
<span style={turn.role === "assistant" ? botAvatar : userAvatar}>{turn.role === "assistant" ? "✦" : name.slice(0, 1).toUpperCase()}</span>
|
|
156
|
+
<div>
|
|
157
|
+
<strong style={{ fontSize: 13, color: turn.role === "assistant" ? "#742220" : "#2d4a3e" }}>{turn.role === "assistant" ? "MOP-AGENT" : "You"}</strong>
|
|
158
|
+
<div style={{ whiteSpace: "pre-wrap", lineHeight: 1.7, marginTop: 6, color: "#2d4a3e" }}>{turn.content || "Thinking…"}</div>
|
|
159
|
+
</div>
|
|
160
|
+
</article>
|
|
85
161
|
))}
|
|
162
|
+
<div ref={endRef} />
|
|
86
163
|
</div>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div className="mop-assistant-composer-wrap">
|
|
168
|
+
<div style={composer}>
|
|
169
|
+
<textarea
|
|
170
|
+
value={input}
|
|
171
|
+
onChange={(e) => setInput(e.target.value)}
|
|
172
|
+
onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }}
|
|
173
|
+
placeholder="Message MOP-AGENT…"
|
|
174
|
+
rows={1}
|
|
175
|
+
style={textarea}
|
|
176
|
+
/>
|
|
177
|
+
<button onClick={() => send()} disabled={busy || !input.trim()} style={{ ...sendButton, opacity: busy || !input.trim() ? .45 : 1 }}>↑</button>
|
|
92
178
|
</div>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{turns.map((turn, index) => (
|
|
96
|
-
<article key={index} style={{ display: "grid", gridTemplateColumns: "34px 1fr", gap: 13, marginBottom: 26 }}>
|
|
97
|
-
<span style={turn.role === "assistant" ? botAvatar : userAvatar}>{turn.role === "assistant" ? "✦" : name.slice(0, 1).toUpperCase()}</span>
|
|
98
|
-
<div>
|
|
99
|
-
<strong style={{ fontSize: 13, color: turn.role === "assistant" ? "#742220" : "#2d4a3e" }}>{turn.role === "assistant" ? "MOP-AGENT" : "You"}</strong>
|
|
100
|
-
<div style={{ whiteSpace: "pre-wrap", lineHeight: 1.7, marginTop: 6, color: "#2d4a3e" }}>{turn.content || "Thinking…"}</div>
|
|
101
|
-
</div>
|
|
102
|
-
</article>
|
|
103
|
-
))}
|
|
104
|
-
<div ref={endRef} />
|
|
179
|
+
<div style={{ textAlign: "center", color: "rgba(45,74,62,.62)", fontSize: 11, marginTop: 8 }}>
|
|
180
|
+
{providerUsed ? `Answered by ${providerUsed} · ` : ""}Cross-project memory
|
|
105
181
|
</div>
|
|
106
|
-
|
|
182
|
+
</div>
|
|
107
183
|
</div>
|
|
108
184
|
|
|
109
|
-
<
|
|
110
|
-
<div
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
rows={1}
|
|
117
|
-
style={textarea}
|
|
118
|
-
/>
|
|
119
|
-
<button onClick={() => send()} disabled={busy || !input.trim()} style={{ ...sendButton, opacity: busy || !input.trim() ? .45 : 1 }}>↑</button>
|
|
185
|
+
<aside className="mop-chat-history" aria-label="Chat history">
|
|
186
|
+
<div className="mop-chat-history-header">
|
|
187
|
+
<div>
|
|
188
|
+
<span>MEMORY LOG</span>
|
|
189
|
+
<strong>Chat history</strong>
|
|
190
|
+
</div>
|
|
191
|
+
<button type="button" onClick={startNewChat} disabled={busy} title="Start a new chat">+</button>
|
|
120
192
|
</div>
|
|
121
|
-
<div
|
|
122
|
-
{
|
|
193
|
+
<div className="mop-chat-history-list">
|
|
194
|
+
{history.length === 0 ? (
|
|
195
|
+
<p className="mop-chat-history-empty">Your conversations will appear here.</p>
|
|
196
|
+
) : history.map((chat) => (
|
|
197
|
+
<div className={`mop-chat-history-item${chat.id === activeChatId ? " is-active" : ""}`} key={chat.id}>
|
|
198
|
+
<button type="button" className="mop-chat-history-open" onClick={() => openChat(chat)} disabled={busy}>
|
|
199
|
+
<strong>{chat.title}</strong>
|
|
200
|
+
<span>{formatChatDate(chat.updatedAt)}</span>
|
|
201
|
+
</button>
|
|
202
|
+
<button type="button" className="mop-chat-history-delete" onClick={() => deleteChat(chat.id)} disabled={busy} aria-label={`Delete ${chat.title}`}>×</button>
|
|
203
|
+
</div>
|
|
204
|
+
))}
|
|
123
205
|
</div>
|
|
124
|
-
</
|
|
206
|
+
</aside>
|
|
125
207
|
</section>
|
|
126
208
|
);
|
|
127
209
|
}
|
|
128
210
|
|
|
211
|
+
function formatChatDate(timestamp: number) {
|
|
212
|
+
const date = new Date(timestamp);
|
|
213
|
+
if (Number.isNaN(date.getTime())) return "Saved conversation";
|
|
214
|
+
return new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }).format(date);
|
|
215
|
+
}
|
|
216
|
+
|
|
129
217
|
const assistantLogo: CSSProperties = { width: 86, height: 86, display: "grid", placeItems: "center" };
|
|
130
218
|
const promptGrid: CSSProperties = { width: "min(100%, 650px)", display: "grid", gridTemplateColumns: "repeat(2,minmax(0,1fr))", gap: 10, marginTop: 28 };
|
|
131
219
|
const promptCard: CSSProperties = { display: "flex", justifyContent: "space-between", padding: "14px 15px", border: "1px solid rgba(45,74,62,.38)", borderBottomWidth: 3, background: "#fffdf2", color: "#2d4a3e", cursor: "pointer", textAlign: "left" };
|
package/apps/web/app/globals.css
CHANGED
|
@@ -272,31 +272,7 @@ button {
|
|
|
272
272
|
font-size: 16px;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
.mop-project-memory {
|
|
276
|
-
min-height: 0;
|
|
277
|
-
overflow: hidden;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
.mop-project-memory nav {
|
|
281
|
-
max-height: min(34vh, 280px);
|
|
282
|
-
overflow-y: auto;
|
|
283
|
-
scrollbar-width: thin;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.mop-project-memory a { min-height: 34px; padding-block: 5px; font-size: 13px; }
|
|
287
|
-
.mop-project-dot {
|
|
288
|
-
flex: 0 0 auto;
|
|
289
|
-
width: 7px;
|
|
290
|
-
height: 7px;
|
|
291
|
-
background: rgba(254, 249, 225, .34);
|
|
292
|
-
}
|
|
293
|
-
.mop-project-dot[data-online="true"] { background: #78e19b; box-shadow: 0 0 0 2px rgba(120, 225, 155, .12); }
|
|
294
|
-
.mop-nav-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
295
|
-
.mop-sidebar-empty { display: block; padding: 7px 11px; color: rgba(254, 249, 225, .42); font-size: 12px; }
|
|
296
275
|
.mop-admin-nav { margin-top: 2px; }
|
|
297
|
-
.mop-settings-subnav { display: grid; gap: 1px; margin: 1px 0 2px 31px; border-left: 1px solid rgba(254, 249, 225, .18); }
|
|
298
|
-
.mop-settings-subnav button { min-height: 32px; padding: 5px 12px; color: rgba(254, 249, 225, .6); font-size: 12px; }
|
|
299
|
-
.mop-settings-subnav button.is-active { color: #ff9a56; border-color: transparent; background: transparent; box-shadow: none; }
|
|
300
276
|
|
|
301
277
|
.mop-sidebar-spacer { flex: 1; }
|
|
302
278
|
.mop-account-card {
|
|
@@ -389,7 +365,17 @@ button {
|
|
|
389
365
|
|
|
390
366
|
/* Assistant content now lives inside the shared shell. */
|
|
391
367
|
.mop-assistant-page {
|
|
392
|
-
|
|
368
|
+
height: calc(100vh - 70px);
|
|
369
|
+
min-height: 560px;
|
|
370
|
+
position: relative;
|
|
371
|
+
display: grid;
|
|
372
|
+
grid-template-columns: minmax(0, 1fr) 290px;
|
|
373
|
+
overflow: hidden;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.mop-assistant-workspace {
|
|
377
|
+
min-width: 0;
|
|
378
|
+
min-height: 0;
|
|
393
379
|
position: relative;
|
|
394
380
|
display: flex;
|
|
395
381
|
flex-direction: column;
|
|
@@ -417,6 +403,104 @@ button {
|
|
|
417
403
|
background: linear-gradient(transparent, var(--mop-cream) 28%);
|
|
418
404
|
}
|
|
419
405
|
|
|
406
|
+
.mop-chat-history {
|
|
407
|
+
min-width: 0;
|
|
408
|
+
min-height: 0;
|
|
409
|
+
display: flex;
|
|
410
|
+
flex-direction: column;
|
|
411
|
+
padding: 17px 13px 13px;
|
|
412
|
+
color: var(--mop-green);
|
|
413
|
+
border-left: 1px solid rgba(45, 74, 62, .3);
|
|
414
|
+
background:
|
|
415
|
+
linear-gradient(rgba(255, 253, 242, .84), rgba(254, 249, 225, .94)),
|
|
416
|
+
repeating-linear-gradient(0deg, transparent 0, transparent 7px, rgba(116, 34, 32, .04) 7px, rgba(116, 34, 32, .04) 8px);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.mop-chat-history-header {
|
|
420
|
+
display: flex;
|
|
421
|
+
align-items: center;
|
|
422
|
+
justify-content: space-between;
|
|
423
|
+
gap: 12px;
|
|
424
|
+
padding: 2px 2px 14px;
|
|
425
|
+
border-bottom: 1px solid rgba(45, 74, 62, .24);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.mop-chat-history-header > div { display: grid; gap: 3px; }
|
|
429
|
+
.mop-chat-history-header span {
|
|
430
|
+
color: var(--mop-red);
|
|
431
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
432
|
+
font-size: 8px;
|
|
433
|
+
font-weight: 900;
|
|
434
|
+
letter-spacing: .16em;
|
|
435
|
+
}
|
|
436
|
+
.mop-chat-history-header strong { font-family: "SFMono-Regular", Consolas, monospace; font-size: 15px; }
|
|
437
|
+
.mop-chat-history-header button {
|
|
438
|
+
width: 34px;
|
|
439
|
+
height: 34px;
|
|
440
|
+
border: 1px solid rgba(45, 74, 62, .38);
|
|
441
|
+
background: var(--mop-red);
|
|
442
|
+
color: var(--mop-cream);
|
|
443
|
+
font-size: 20px;
|
|
444
|
+
cursor: pointer;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.mop-chat-history-list {
|
|
448
|
+
min-height: 0;
|
|
449
|
+
display: grid;
|
|
450
|
+
align-content: start;
|
|
451
|
+
gap: 6px;
|
|
452
|
+
overflow-y: auto;
|
|
453
|
+
padding: 12px 1px;
|
|
454
|
+
scrollbar-width: thin;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.mop-chat-history-empty {
|
|
458
|
+
margin: 8px 5px;
|
|
459
|
+
color: rgba(45, 74, 62, .58);
|
|
460
|
+
font-size: 12px;
|
|
461
|
+
line-height: 1.55;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.mop-chat-history-item {
|
|
465
|
+
display: grid;
|
|
466
|
+
grid-template-columns: minmax(0, 1fr) 28px;
|
|
467
|
+
align-items: stretch;
|
|
468
|
+
border: 1px solid transparent;
|
|
469
|
+
}
|
|
470
|
+
.mop-chat-history-item:hover,
|
|
471
|
+
.mop-chat-history-item.is-active {
|
|
472
|
+
border-color: rgba(45, 74, 62, .24);
|
|
473
|
+
background: rgba(255, 253, 242, .78);
|
|
474
|
+
}
|
|
475
|
+
.mop-chat-history-item.is-active { border-left: 3px solid var(--mop-red); }
|
|
476
|
+
.mop-chat-history-open,
|
|
477
|
+
.mop-chat-history-delete {
|
|
478
|
+
min-width: 0;
|
|
479
|
+
border: 0;
|
|
480
|
+
box-shadow: none;
|
|
481
|
+
background: transparent;
|
|
482
|
+
color: var(--mop-green);
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
}
|
|
485
|
+
.mop-chat-history-open {
|
|
486
|
+
display: grid;
|
|
487
|
+
gap: 4px;
|
|
488
|
+
padding: 9px 7px 9px 9px;
|
|
489
|
+
text-align: left;
|
|
490
|
+
}
|
|
491
|
+
.mop-chat-history-open strong {
|
|
492
|
+
overflow: hidden;
|
|
493
|
+
text-overflow: ellipsis;
|
|
494
|
+
white-space: nowrap;
|
|
495
|
+
font-size: 12px;
|
|
496
|
+
font-weight: 760;
|
|
497
|
+
}
|
|
498
|
+
.mop-chat-history-open span { color: rgba(45, 74, 62, .5); font-size: 9px; }
|
|
499
|
+
.mop-chat-history-delete { opacity: 0; padding: 0; font-size: 17px; }
|
|
500
|
+
.mop-chat-history-item:hover .mop-chat-history-delete,
|
|
501
|
+
.mop-chat-history-item.is-active .mop-chat-history-delete { opacity: .65; }
|
|
502
|
+
.mop-chat-history-delete:hover { color: var(--mop-red); opacity: 1 !important; }
|
|
503
|
+
|
|
420
504
|
.mop-settings-grid {
|
|
421
505
|
display: grid;
|
|
422
506
|
grid-template-columns: 1fr;
|
|
@@ -484,9 +568,35 @@ button {
|
|
|
484
568
|
background: rgba(20, 34, 29, .56);
|
|
485
569
|
}
|
|
486
570
|
.mop-app-main { min-height: calc(100vh - 62px); }
|
|
487
|
-
.mop-assistant-page {
|
|
571
|
+
.mop-assistant-page { height: calc(100vh - 62px); min-height: 520px; grid-template-columns: minmax(0, 1fr); }
|
|
572
|
+
.mop-chat-history { display: none; }
|
|
488
573
|
.mop-assistant-conversation { padding: 0 16px; }
|
|
489
574
|
.mop-assistant-composer-wrap { padding: 26px 12px 12px; }
|
|
490
575
|
.mop-settings-grid { grid-template-columns: 1fr; }
|
|
491
576
|
.mop-user-invite-form { grid-template-columns: 1fr !important; }
|
|
492
577
|
}
|
|
578
|
+
|
|
579
|
+
.mop-back-workspace-btn {
|
|
580
|
+
display: flex;
|
|
581
|
+
align-items: center;
|
|
582
|
+
justify-content: center;
|
|
583
|
+
width: 100%;
|
|
584
|
+
min-height: 40px;
|
|
585
|
+
margin-bottom: 9px;
|
|
586
|
+
padding: 9px 12px;
|
|
587
|
+
border: 1px solid var(--mop-red);
|
|
588
|
+
background: var(--mop-red);
|
|
589
|
+
color: var(--mop-cream);
|
|
590
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
591
|
+
font-size: 11px;
|
|
592
|
+
font-weight: 900;
|
|
593
|
+
text-decoration: none;
|
|
594
|
+
box-shadow: 2px 2px 0 rgba(18, 38, 30, .28);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.mop-back-workspace-btn:hover { color: var(--mop-cream); transform: translate(-1px, -1px); }
|
|
598
|
+
|
|
599
|
+
@media (min-width: 761px) and (max-width: 1050px) {
|
|
600
|
+
.mop-assistant-page { grid-template-columns: minmax(0, 1fr) 235px; }
|
|
601
|
+
.mop-chat-history { padding-inline: 9px; }
|
|
602
|
+
}
|
|
@@ -94,53 +94,53 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
|
|
|
94
94
|
{menuOpen && <button className="mop-sidebar-scrim" aria-label="Close navigation" onClick={() => setMenuOpen(false)} />}
|
|
95
95
|
|
|
96
96
|
<aside className={`mop-app-sidebar${menuOpen ? " is-open" : ""}`}>
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
<span>New chat</span>
|
|
101
|
-
</a>
|
|
102
|
-
<a href="/brain" className={pathname.startsWith("/brain") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
|
|
103
|
-
<span className="mop-nav-icon">◉</span>
|
|
104
|
-
<span>Brain</span>
|
|
105
|
-
</a>
|
|
106
|
-
</nav>
|
|
107
|
-
|
|
108
|
-
<div className="mop-nav-section mop-project-memory">
|
|
109
|
-
<p>PROJECT MEMORY</p>
|
|
110
|
-
<nav>
|
|
111
|
-
{projects.slice(0, 8).map((project) => (
|
|
112
|
-
<a key={project.id} href={`/brain/${project.id}`} className={pathname === `/brain/${project.id}` ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
|
|
113
|
-
<span className="mop-project-dot" data-online={project.status === "online"} />
|
|
114
|
-
<span className="mop-nav-label">{project.name}</span>
|
|
115
|
-
</a>
|
|
116
|
-
))}
|
|
117
|
-
{projects.length === 0 && <span className="mop-sidebar-empty">No linked projects yet</span>}
|
|
118
|
-
</nav>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
{isAdmin && (
|
|
122
|
-
<div className="mop-nav-section mop-admin-nav">
|
|
123
|
-
<p>ADMIN</p>
|
|
97
|
+
{isSettings ? (
|
|
98
|
+
<div className="mop-nav-section">
|
|
99
|
+
<p>SETTINGS</p>
|
|
124
100
|
<nav>
|
|
125
|
-
<
|
|
126
|
-
<span className="mop-nav-icon"
|
|
127
|
-
<span>
|
|
128
|
-
</
|
|
129
|
-
{
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
</button>
|
|
134
|
-
<button className={settingsSection === "users" ? "is-active" : ""} onClick={() => { selectSection("users"); setMenuOpen(false); }}>
|
|
135
|
-
<span>Users</span>
|
|
136
|
-
</button>
|
|
137
|
-
</div>
|
|
138
|
-
)}
|
|
101
|
+
<button className={settingsSection === "providers" ? "is-active" : ""} onClick={() => { selectSection("providers"); setMenuOpen(false); }}>
|
|
102
|
+
<span className="mop-nav-icon">◇</span>
|
|
103
|
+
<span>Providers</span>
|
|
104
|
+
</button>
|
|
105
|
+
<button className={settingsSection === "users" ? "is-active" : ""} onClick={() => { selectSection("users"); setMenuOpen(false); }}>
|
|
106
|
+
<span className="mop-nav-icon">♙</span>
|
|
107
|
+
<span>Users</span>
|
|
108
|
+
</button>
|
|
139
109
|
</nav>
|
|
140
110
|
</div>
|
|
111
|
+
) : (
|
|
112
|
+
<>
|
|
113
|
+
<nav className="mop-sidebar-primary" aria-label="Workspace">
|
|
114
|
+
<a href="/assistant" className={pathname.startsWith("/assistant") || pathname.startsWith("/chat/") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
|
|
115
|
+
<span className="mop-nav-icon">✎</span>
|
|
116
|
+
<span>New chat</span>
|
|
117
|
+
</a>
|
|
118
|
+
<a href="/brain" className={pathname.startsWith("/brain") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
|
|
119
|
+
<span className="mop-nav-icon">◉</span>
|
|
120
|
+
<span>Brain</span>
|
|
121
|
+
</a>
|
|
122
|
+
</nav>
|
|
123
|
+
|
|
124
|
+
{isAdmin && (
|
|
125
|
+
<div className="mop-nav-section mop-admin-nav">
|
|
126
|
+
<p>ADMIN</p>
|
|
127
|
+
<nav>
|
|
128
|
+
<a href="/settings" onClick={() => setMenuOpen(false)}>
|
|
129
|
+
<span className="mop-nav-icon">⚙</span>
|
|
130
|
+
<span>Settings</span>
|
|
131
|
+
</a>
|
|
132
|
+
</nav>
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
</>
|
|
141
136
|
)}
|
|
142
137
|
|
|
143
138
|
<div className="mop-sidebar-spacer" />
|
|
139
|
+
{isSettings && (
|
|
140
|
+
<a href="/assistant" className="mop-back-workspace-btn" onClick={() => setMenuOpen(false)}>
|
|
141
|
+
<span>← BACK TO WORKSPACE</span>
|
|
142
|
+
</a>
|
|
143
|
+
)}
|
|
144
144
|
<button className="mop-account-card" type="button" onClick={logout} title="Sign out">
|
|
145
145
|
<span className="mop-account-avatar">{viewer.name.slice(0, 1).toUpperCase()}</span>
|
|
146
146
|
<span className="mop-account-copy">
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mop-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "mop-agent",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.14",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"packages/*",
|
package/package.json
CHANGED