mop-agent 0.1.11 → 0.1.13
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 +3 -2
- package/apps/web/app/assistant/page.tsx +3 -5
- package/apps/web/app/globals.css +90 -167
- package/apps/web/components/AppShell.tsx +63 -112
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@ 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:**
|
|
9
|
-
> installer, one-time Admin setup/login flow, and shared
|
|
8
|
+
> **Release status:** release candidate `mop-agent@0.1.13` contains the corrected VPS
|
|
9
|
+
> installer, one-time Admin setup/login flow, and simplified shared application shell
|
|
10
|
+
> with centered page titles and ChatGPT-inspired navigation.
|
|
10
11
|
> The canonical installation command is exactly `npx mop-agent`.
|
|
11
12
|
|
|
12
13
|
## Current status
|
|
@@ -7,7 +7,7 @@ import { useMemoryCore } from "@/components/AppShell";
|
|
|
7
7
|
type Turn = { role: "user" | "assistant"; content: string };
|
|
8
8
|
|
|
9
9
|
export default function AssistantPage() {
|
|
10
|
-
const {
|
|
10
|
+
const { projects } = useMemoryCore();
|
|
11
11
|
const [turns, setTurns] = useState<Turn[]>([]);
|
|
12
12
|
const [name, setName] = useState("Admin");
|
|
13
13
|
const [input, setInput] = useState("");
|
|
@@ -34,8 +34,7 @@ export default function AssistantPage() {
|
|
|
34
34
|
headers: { "content-type": "application/json" },
|
|
35
35
|
body: JSON.stringify({
|
|
36
36
|
message,
|
|
37
|
-
|
|
38
|
-
allowCrossProject: !selectedProject,
|
|
37
|
+
allowCrossProject: true,
|
|
39
38
|
}),
|
|
40
39
|
});
|
|
41
40
|
|
|
@@ -120,14 +119,13 @@ export default function AssistantPage() {
|
|
|
120
119
|
<button onClick={() => send()} disabled={busy || !input.trim()} style={{ ...sendButton, opacity: busy || !input.trim() ? .45 : 1 }}>↑</button>
|
|
121
120
|
</div>
|
|
122
121
|
<div style={{ textAlign: "center", color: "rgba(45,74,62,.62)", fontSize: 11, marginTop: 8 }}>
|
|
123
|
-
{providerUsed ? `Answered by ${providerUsed} · ` : ""}
|
|
122
|
+
{providerUsed ? `Answered by ${providerUsed} · ` : ""}Cross-project memory
|
|
124
123
|
</div>
|
|
125
124
|
</div>
|
|
126
125
|
</section>
|
|
127
126
|
);
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
const selectStyle: CSSProperties = { color: "#2d4a3e", border: "1px solid rgba(45,74,62,.42)", padding: "6px 8px", background: "#fffdf2" };
|
|
131
129
|
const assistantLogo: CSSProperties = { width: 86, height: 86, display: "grid", placeItems: "center" };
|
|
132
130
|
const promptGrid: CSSProperties = { width: "min(100%, 650px)", display: "grid", gridTemplateColumns: "repeat(2,minmax(0,1fr))", gap: 10, marginTop: 28 };
|
|
133
131
|
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
|
@@ -97,7 +97,7 @@ button {
|
|
|
97
97
|
.mop-app-frame {
|
|
98
98
|
min-height: 100vh;
|
|
99
99
|
display: grid;
|
|
100
|
-
grid-template-columns:
|
|
100
|
+
grid-template-columns: 260px minmax(0, 1fr);
|
|
101
101
|
grid-template-rows: 70px minmax(0, 1fr);
|
|
102
102
|
grid-template-areas:
|
|
103
103
|
"topbar topbar"
|
|
@@ -111,7 +111,7 @@ button {
|
|
|
111
111
|
top: 0;
|
|
112
112
|
z-index: 50;
|
|
113
113
|
display: grid;
|
|
114
|
-
grid-template-columns:
|
|
114
|
+
grid-template-columns: 260px minmax(0, 1fr);
|
|
115
115
|
min-width: 0;
|
|
116
116
|
color: var(--mop-cream);
|
|
117
117
|
background:
|
|
@@ -153,6 +153,7 @@ button {
|
|
|
153
153
|
min-width: 0;
|
|
154
154
|
display: flex;
|
|
155
155
|
align-items: center;
|
|
156
|
+
justify-content: center;
|
|
156
157
|
gap: 14px;
|
|
157
158
|
padding: 0 18px;
|
|
158
159
|
}
|
|
@@ -167,18 +168,8 @@ button {
|
|
|
167
168
|
cursor: pointer;
|
|
168
169
|
}
|
|
169
170
|
|
|
170
|
-
.mop-topbar-title {
|
|
171
|
-
display: flex;
|
|
172
|
-
align-items: center;
|
|
173
|
-
gap: 8px;
|
|
174
|
-
min-width: 130px;
|
|
175
|
-
font-family: "SFMono-Regular", Consolas, monospace;
|
|
176
|
-
font-size: 13px;
|
|
177
|
-
letter-spacing: .08em;
|
|
178
|
-
text-transform: uppercase;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
171
|
.mop-live-dot {
|
|
172
|
+
flex: 0 0 auto;
|
|
182
173
|
width: 8px;
|
|
183
174
|
height: 8px;
|
|
184
175
|
background: #78e19b;
|
|
@@ -186,37 +177,21 @@ button {
|
|
|
186
177
|
}
|
|
187
178
|
|
|
188
179
|
.mop-topbar-center {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
180
|
+
min-width: 240px;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
gap: 10px;
|
|
194
185
|
padding: 9px 28px;
|
|
195
186
|
text-align: center;
|
|
196
187
|
color: rgba(254, 249, 225, .86);
|
|
197
188
|
border: 1px solid rgba(254, 249, 225, .12);
|
|
198
189
|
background: rgba(254, 249, 225, .07);
|
|
199
190
|
font-family: "SFMono-Regular", Consolas, monospace;
|
|
200
|
-
font-size:
|
|
201
|
-
font-weight: 800;
|
|
202
|
-
letter-spacing: .18em;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.mop-topbar-meta {
|
|
206
|
-
display: flex;
|
|
207
|
-
align-items: center;
|
|
208
|
-
gap: 9px;
|
|
209
|
-
margin-left: auto;
|
|
210
|
-
font-family: "SFMono-Regular", Consolas, monospace;
|
|
211
|
-
font-size: 10px;
|
|
191
|
+
font-size: 12px;
|
|
212
192
|
font-weight: 800;
|
|
213
|
-
letter-spacing: .
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
.mop-version {
|
|
217
|
-
padding: 4px 6px;
|
|
218
|
-
border: 1px solid rgba(254, 249, 225, .22);
|
|
219
|
-
background: rgba(254, 249, 225, .07);
|
|
193
|
+
letter-spacing: .14em;
|
|
194
|
+
text-transform: uppercase;
|
|
220
195
|
}
|
|
221
196
|
|
|
222
197
|
.mop-app-sidebar {
|
|
@@ -228,53 +203,68 @@ button {
|
|
|
228
203
|
display: flex;
|
|
229
204
|
flex-direction: column;
|
|
230
205
|
overflow-y: auto;
|
|
231
|
-
padding:
|
|
206
|
+
padding: 10px 9px 9px;
|
|
232
207
|
color: var(--mop-cream);
|
|
233
208
|
background:
|
|
234
|
-
linear-gradient(rgba(255,255,255,.
|
|
209
|
+
linear-gradient(rgba(255,255,255,.028), rgba(0,0,0,.035)),
|
|
235
210
|
var(--mop-green);
|
|
236
211
|
border-right: 2px solid #20362e;
|
|
237
212
|
}
|
|
238
213
|
|
|
239
|
-
.mop-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
font-family: "SFMono-Regular", Consolas, monospace;
|
|
244
|
-
font-size: 9px;
|
|
245
|
-
font-weight: 900;
|
|
246
|
-
letter-spacing: .22em;
|
|
214
|
+
.mop-sidebar-primary {
|
|
215
|
+
display: grid;
|
|
216
|
+
gap: 3px;
|
|
217
|
+
margin-bottom: 22px;
|
|
247
218
|
}
|
|
248
219
|
|
|
249
|
-
.mop-
|
|
250
|
-
.mop-nav-section a
|
|
220
|
+
.mop-sidebar-primary a,
|
|
221
|
+
.mop-nav-section a,
|
|
222
|
+
.mop-nav-section button {
|
|
251
223
|
display: flex;
|
|
252
224
|
align-items: center;
|
|
253
225
|
gap: 11px;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
226
|
+
width: 100%;
|
|
227
|
+
min-height: 40px;
|
|
228
|
+
padding: 8px 11px;
|
|
229
|
+
color: rgba(254, 249, 225, .82);
|
|
257
230
|
border: 1px solid transparent;
|
|
231
|
+
background: transparent;
|
|
232
|
+
text-align: left;
|
|
258
233
|
text-decoration: none;
|
|
259
|
-
font-
|
|
260
|
-
font-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
text-transform: uppercase;
|
|
234
|
+
font-size: 14px;
|
|
235
|
+
font-weight: 540;
|
|
236
|
+
letter-spacing: 0;
|
|
237
|
+
cursor: pointer;
|
|
264
238
|
}
|
|
265
239
|
|
|
266
|
-
.mop-
|
|
240
|
+
.mop-sidebar-primary a:hover,
|
|
241
|
+
.mop-nav-section a:hover,
|
|
242
|
+
.mop-nav-section button:hover {
|
|
267
243
|
color: var(--mop-cream);
|
|
268
|
-
background: rgba(254, 249, 225, .
|
|
244
|
+
background: rgba(254, 249, 225, .075);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.mop-sidebar-primary a.is-active,
|
|
248
|
+
.mop-nav-section a.is-active,
|
|
249
|
+
.mop-nav-section button.is-active {
|
|
250
|
+
color: var(--mop-cream);
|
|
251
|
+
border-color: rgba(254, 249, 225, .12);
|
|
252
|
+
background: rgba(254, 249, 225, .1);
|
|
253
|
+
box-shadow: 2px 2px 0 rgba(18, 38, 30, .22);
|
|
269
254
|
}
|
|
270
255
|
|
|
271
|
-
.mop-nav-section
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
256
|
+
.mop-nav-section { margin-bottom: 18px; }
|
|
257
|
+
.mop-nav-section > p {
|
|
258
|
+
margin: 0 11px 7px;
|
|
259
|
+
color: rgba(254, 249, 225, .46);
|
|
260
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
261
|
+
font-size: 10px;
|
|
262
|
+
font-weight: 750;
|
|
263
|
+
letter-spacing: .11em;
|
|
276
264
|
}
|
|
277
265
|
|
|
266
|
+
.mop-nav-section nav { display: grid; gap: 2px; }
|
|
267
|
+
|
|
278
268
|
.mop-nav-icon {
|
|
279
269
|
width: 21px;
|
|
280
270
|
text-align: center;
|
|
@@ -282,6 +272,32 @@ button {
|
|
|
282
272
|
font-size: 16px;
|
|
283
273
|
}
|
|
284
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
|
+
.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
|
+
|
|
285
301
|
.mop-sidebar-spacer { flex: 1; }
|
|
286
302
|
.mop-account-card {
|
|
287
303
|
width: 100%;
|
|
@@ -290,11 +306,12 @@ button {
|
|
|
290
306
|
gap: 9px;
|
|
291
307
|
padding: 10px 8px;
|
|
292
308
|
color: var(--mop-cream);
|
|
293
|
-
border: 1px solid
|
|
294
|
-
background:
|
|
309
|
+
border: 1px solid transparent;
|
|
310
|
+
background: transparent;
|
|
295
311
|
text-align: left;
|
|
296
312
|
cursor: pointer;
|
|
297
313
|
}
|
|
314
|
+
.mop-account-card:hover { border-color: rgba(254, 249, 225, .1); background: rgba(254, 249, 225, .07); }
|
|
298
315
|
|
|
299
316
|
.mop-account-avatar {
|
|
300
317
|
flex: 0 0 auto;
|
|
@@ -378,25 +395,16 @@ button {
|
|
|
378
395
|
flex-direction: column;
|
|
379
396
|
}
|
|
380
397
|
|
|
381
|
-
.mop-assistant-
|
|
382
|
-
min-height: 55px;
|
|
383
|
-
display: flex;
|
|
384
|
-
align-items: center;
|
|
385
|
-
justify-content: space-between;
|
|
386
|
-
gap: 16px;
|
|
387
|
-
padding: 9px 22px;
|
|
388
|
-
border-bottom: 1px solid rgba(45, 74, 62, .24);
|
|
389
|
-
background: rgba(254, 249, 225, .78);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
.mop-assistant-conversation { flex: 1; overflow-y: auto; padding: 0 28px; }
|
|
398
|
+
.mop-assistant-conversation { flex: 1; overflow-y: auto; padding: 0 clamp(18px, 5vw, 64px); }
|
|
393
399
|
.mop-assistant-welcome {
|
|
394
|
-
|
|
400
|
+
width: min(100%, 760px);
|
|
401
|
+
min-height: calc(100vh - 190px);
|
|
402
|
+
margin: 0 auto;
|
|
395
403
|
display: flex;
|
|
396
404
|
flex-direction: column;
|
|
397
405
|
align-items: center;
|
|
398
406
|
justify-content: center;
|
|
399
|
-
padding:
|
|
407
|
+
padding: 38px 0 110px;
|
|
400
408
|
text-align: center;
|
|
401
409
|
}
|
|
402
410
|
|
|
@@ -417,26 +425,6 @@ button {
|
|
|
417
425
|
}
|
|
418
426
|
|
|
419
427
|
|
|
420
|
-
.mop-settings-nav { padding: 9px; }
|
|
421
|
-
.mop-settings-nav button {
|
|
422
|
-
width: 100%;
|
|
423
|
-
display: flex;
|
|
424
|
-
align-items: center;
|
|
425
|
-
gap: 10px;
|
|
426
|
-
padding: 12px;
|
|
427
|
-
border: 1px solid transparent;
|
|
428
|
-
background: transparent;
|
|
429
|
-
color: var(--mop-green);
|
|
430
|
-
text-align: left;
|
|
431
|
-
cursor: pointer;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.mop-settings-nav button.is-active {
|
|
435
|
-
color: var(--mop-cream);
|
|
436
|
-
border-color: var(--mop-red);
|
|
437
|
-
background: var(--mop-red);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
428
|
.mop-settings-content { padding: clamp(18px, 3vw, 30px); }
|
|
441
429
|
.mop-settings-content th,
|
|
442
430
|
.mop-settings-content td {
|
|
@@ -472,11 +460,10 @@ button {
|
|
|
472
460
|
.mop-app-topbar { grid-template-columns: 66px minmax(0, 1fr); }
|
|
473
461
|
.mop-app-brand { justify-content: center; padding: 5px; }
|
|
474
462
|
.mop-app-brand img { width: 49px; height: 49px; }
|
|
475
|
-
.mop-app-brand span
|
|
463
|
+
.mop-app-brand span { display: none; }
|
|
476
464
|
.mop-app-topbar-main { padding: 0 10px; gap: 9px; }
|
|
477
465
|
.mop-menu-toggle { display: block; }
|
|
478
|
-
.mop-topbar-
|
|
479
|
-
.mop-topbar-meta > span:first-child { display: none; }
|
|
466
|
+
.mop-topbar-center { min-width: 0; flex: 1; padding: 8px 12px; font-size: 10px; }
|
|
480
467
|
.mop-app-sidebar {
|
|
481
468
|
position: fixed;
|
|
482
469
|
top: 62px;
|
|
@@ -498,72 +485,8 @@ button {
|
|
|
498
485
|
}
|
|
499
486
|
.mop-app-main { min-height: calc(100vh - 62px); }
|
|
500
487
|
.mop-assistant-page { min-height: calc(100vh - 62px); }
|
|
501
|
-
.mop-assistant-toolbar { align-items: flex-start; flex-direction: column; }
|
|
502
488
|
.mop-assistant-conversation { padding: 0 16px; }
|
|
503
489
|
.mop-assistant-composer-wrap { padding: 26px 12px 12px; }
|
|
504
490
|
.mop-settings-grid { grid-template-columns: 1fr; }
|
|
505
|
-
.mop-settings-nav { display: flex; gap: 7px; }
|
|
506
|
-
.mop-settings-nav button { justify-content: center; }
|
|
507
491
|
.mop-user-invite-form { grid-template-columns: 1fr !important; }
|
|
508
|
-
.mop-settings-sidebar {
|
|
509
|
-
position: fixed;
|
|
510
|
-
top: 62px;
|
|
511
|
-
left: 0;
|
|
512
|
-
bottom: 0;
|
|
513
|
-
width: min(82vw, 280px);
|
|
514
|
-
height: auto;
|
|
515
|
-
transform: translateX(-105%);
|
|
516
|
-
transition: transform 160ms steps(4, end);
|
|
517
|
-
display: flex !important;
|
|
518
|
-
flex-direction: column !important;
|
|
519
|
-
}
|
|
520
|
-
.mop-settings-sidebar.is-open {
|
|
521
|
-
transform: translateX(0);
|
|
522
|
-
}
|
|
523
492
|
}
|
|
524
|
-
|
|
525
|
-
.mop-settings-sidebar {
|
|
526
|
-
grid-area: sidebar;
|
|
527
|
-
position: sticky;
|
|
528
|
-
top: 70px;
|
|
529
|
-
height: calc(100vh - 70px);
|
|
530
|
-
z-index: 40;
|
|
531
|
-
display: flex;
|
|
532
|
-
flex-direction: column;
|
|
533
|
-
overflow-y: auto;
|
|
534
|
-
padding: 15px 9px 12px;
|
|
535
|
-
border-right: 2px solid rgba(45, 74, 62, .46);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
.mop-back-workspace-btn {
|
|
539
|
-
display: flex;
|
|
540
|
-
align-items: center;
|
|
541
|
-
justify-content: center;
|
|
542
|
-
gap: 8px;
|
|
543
|
-
width: 100%;
|
|
544
|
-
min-height: 40px;
|
|
545
|
-
margin-bottom: 9px;
|
|
546
|
-
padding: 9px 12px;
|
|
547
|
-
border: 1px solid var(--mop-red);
|
|
548
|
-
background: var(--mop-red);
|
|
549
|
-
color: var(--mop-cream);
|
|
550
|
-
font-family: "SFMono-Regular", Consolas, monospace;
|
|
551
|
-
font-size: 11px;
|
|
552
|
-
font-weight: 900;
|
|
553
|
-
text-decoration: none;
|
|
554
|
-
cursor: pointer;
|
|
555
|
-
transition: transform 80ms steps(2, end), box-shadow 80ms steps(2, end);
|
|
556
|
-
box-shadow: 2px 2px 0 rgba(45, 74, 62, .17);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
.mop-back-workspace-btn:hover {
|
|
560
|
-
transform: translate(-1px, -1px);
|
|
561
|
-
box-shadow: 3px 3px 0 rgba(45, 74, 62, .24);
|
|
562
|
-
color: var(--mop-cream);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
.mop-back-workspace-btn:active {
|
|
566
|
-
transform: translate(1px, 1px);
|
|
567
|
-
box-shadow: 0 0 0 rgba(45, 74, 62, 0);
|
|
568
|
-
}
|
|
569
|
-
|
|
@@ -12,13 +12,9 @@ export type AppViewer = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export type Project = { id: string; name: string; status: string };
|
|
15
|
-
export type ProviderState = { configured: boolean; provider?: string; model?: string | null };
|
|
16
15
|
|
|
17
16
|
interface MemoryCoreContextType {
|
|
18
|
-
selectedProject: string;
|
|
19
|
-
setSelectedProject: (id: string) => void;
|
|
20
17
|
projects: Project[];
|
|
21
|
-
provider: ProviderState;
|
|
22
18
|
settingsSection: "providers" | "users";
|
|
23
19
|
setSettingsSection: (section: "providers" | "users") => void;
|
|
24
20
|
}
|
|
@@ -27,15 +23,10 @@ const MemoryCoreContext = createContext<MemoryCoreContextType | undefined>(undef
|
|
|
27
23
|
|
|
28
24
|
export function useMemoryCore() {
|
|
29
25
|
const context = useContext(MemoryCoreContext);
|
|
30
|
-
if (!context)
|
|
31
|
-
throw new Error("useMemoryCore must be used within a MemoryCoreProvider");
|
|
32
|
-
}
|
|
26
|
+
if (!context) throw new Error("useMemoryCore must be used within a MemoryCoreProvider");
|
|
33
27
|
return context;
|
|
34
28
|
}
|
|
35
29
|
|
|
36
|
-
const selectStyle = { color: "#2d4a3e", border: "1px solid rgba(45,74,62,.42)", padding: "6px 8px", background: "#fffdf2" };
|
|
37
|
-
|
|
38
|
-
|
|
39
30
|
function pageTitle(pathname: string): string {
|
|
40
31
|
if (pathname === "/assistant") return "Assistant";
|
|
41
32
|
if (pathname === "/brain/graph") return "Knowledge Graph";
|
|
@@ -49,22 +40,17 @@ function pageTitle(pathname: string): string {
|
|
|
49
40
|
export function AppShell({ viewer, children }: { viewer: AppViewer; children: ReactNode }) {
|
|
50
41
|
const pathname = usePathname();
|
|
51
42
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
52
|
-
const isAdmin = viewer.role === "owner";
|
|
53
|
-
const title = pageTitle(pathname);
|
|
54
|
-
|
|
55
43
|
const [projects, setProjects] = useState<Project[]>([]);
|
|
56
|
-
const [provider, setProvider] = useState<ProviderState>({ configured: false });
|
|
57
|
-
const [selectedProject, setSelectedProject] = useState("");
|
|
58
44
|
const [settingsSection, setSettingsSection] = useState<"providers" | "users">("providers");
|
|
45
|
+
const isAdmin = viewer.role === "owner";
|
|
46
|
+
const isSettings = pathname.startsWith("/settings");
|
|
47
|
+
const title = pageTitle(pathname);
|
|
59
48
|
|
|
60
49
|
useEffect(() => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
setProjects(projectData.projects ?? []);
|
|
66
|
-
setProvider(providerData.config ?? { configured: false });
|
|
67
|
-
}).catch(() => {});
|
|
50
|
+
fetch("/api/projects")
|
|
51
|
+
.then((response) => response.json())
|
|
52
|
+
.then((data) => setProjects(data.projects ?? []))
|
|
53
|
+
.catch(() => {});
|
|
68
54
|
|
|
69
55
|
const requested = new URLSearchParams(window.location.search).get("section");
|
|
70
56
|
if (requested === "users") setSettingsSection("users");
|
|
@@ -75,21 +61,13 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
|
|
|
75
61
|
window.location.replace("/login");
|
|
76
62
|
}
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
setSettingsSection(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const isSettings = pathname.startsWith("/settings");
|
|
85
|
-
|
|
86
|
-
const nav = [
|
|
87
|
-
{ href: "/assistant", label: "Assistant", icon: "✦", active: pathname.startsWith("/assistant") || pathname.startsWith("/chat/") },
|
|
88
|
-
{ href: "/brain", label: "Brain", icon: "◉", active: pathname.startsWith("/brain") },
|
|
89
|
-
];
|
|
64
|
+
function selectSection(section: "providers" | "users") {
|
|
65
|
+
setSettingsSection(section);
|
|
66
|
+
window.history.replaceState(null, "", section === "providers" ? "/settings" : "/settings?section=users");
|
|
67
|
+
}
|
|
90
68
|
|
|
91
69
|
return (
|
|
92
|
-
<MemoryCoreContext.Provider value={{
|
|
70
|
+
<MemoryCoreContext.Provider value={{ projects, settingsSection, setSettingsSection }}>
|
|
93
71
|
<div className="mop-app-frame">
|
|
94
72
|
<header className="mop-app-topbar">
|
|
95
73
|
<a className="mop-app-brand" href="/assistant" aria-label="MOP-AGENT home">
|
|
@@ -106,96 +84,70 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
|
|
|
106
84
|
>
|
|
107
85
|
☰
|
|
108
86
|
</button>
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<span style={{ color: "rgba(45,74,62,.62)", marginLeft: 10, fontSize: 12 }}>
|
|
114
|
-
{provider.configured ? `${provider.provider}${provider.model ? ` · ${provider.model}` : ""}` : "offline demo"}
|
|
115
|
-
</span>
|
|
116
|
-
</div>
|
|
117
|
-
<label style={{ color: "#2d4a3e", fontSize: 12 }}>
|
|
118
|
-
MEMORY SCOPE
|
|
119
|
-
<select value={selectedProject} onChange={(e) => setSelectedProject(e.target.value)} style={selectStyle}>
|
|
120
|
-
<option value="">All memory</option>
|
|
121
|
-
{projects.map((project) => <option key={project.id} value={project.id}>{project.name}</option>)}
|
|
122
|
-
</select>
|
|
123
|
-
</label>
|
|
124
|
-
</div>
|
|
125
|
-
) : (
|
|
126
|
-
<>
|
|
127
|
-
<div className="mop-topbar-title">
|
|
128
|
-
<span className="mop-live-dot" />
|
|
129
|
-
<strong>{title}</strong>
|
|
130
|
-
</div>
|
|
131
|
-
<div className="mop-topbar-center">MOP MEMORYCORE</div>
|
|
132
|
-
<div className="mop-topbar-meta">
|
|
133
|
-
<span>{isAdmin ? "ADMIN" : "MEMBER"}</span>
|
|
134
|
-
<span className="mop-version">v0.1.10</span>
|
|
135
|
-
</div>
|
|
136
|
-
</>
|
|
137
|
-
)}
|
|
87
|
+
<div className="mop-topbar-center">
|
|
88
|
+
<span className="mop-live-dot" />
|
|
89
|
+
<strong>{title}</strong>
|
|
90
|
+
</div>
|
|
138
91
|
</div>
|
|
139
92
|
</header>
|
|
140
93
|
|
|
141
94
|
{menuOpen && <button className="mop-sidebar-scrim" aria-label="Close navigation" onClick={() => setMenuOpen(false)} />}
|
|
142
95
|
|
|
143
|
-
<aside className={
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<>
|
|
149
|
-
<button className={settingsSection === "providers" ? "is-active" : ""} onClick={() => { selectSection("providers"); setMenuOpen(false); }}>
|
|
150
|
-
<span>◇</span><strong>Providers</strong>
|
|
151
|
-
</button>
|
|
152
|
-
<button className={settingsSection === "users" ? "is-active" : ""} onClick={() => { selectSection("users"); setMenuOpen(false); }}>
|
|
153
|
-
<span>♙</span><strong>Users</strong>
|
|
154
|
-
</button>
|
|
155
|
-
</>
|
|
156
|
-
) : (
|
|
157
|
-
<>
|
|
158
|
-
<div className="mop-nav-section">
|
|
159
|
-
<p>WORKSPACE</p>
|
|
160
|
-
<nav>
|
|
161
|
-
{nav.map((item) => (
|
|
162
|
-
<a key={item.href} href={item.href} className={item.active ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
|
|
163
|
-
<span className="mop-nav-icon">{item.icon}</span>
|
|
164
|
-
<span>{item.label}</span>
|
|
165
|
-
</a>
|
|
166
|
-
))}
|
|
167
|
-
</nav>
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
{isAdmin && (
|
|
171
|
-
<div className="mop-nav-section">
|
|
172
|
-
<p>ADMIN</p>
|
|
173
|
-
<nav>
|
|
174
|
-
<a href="/settings" className={pathname.startsWith("/settings") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
|
|
175
|
-
<span className="mop-nav-icon">⚙</span>
|
|
176
|
-
<span>Settings</span>
|
|
177
|
-
</a>
|
|
178
|
-
</nav>
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
</>
|
|
182
|
-
)}
|
|
183
|
-
|
|
184
|
-
<div className="mop-sidebar-spacer" />
|
|
185
|
-
|
|
186
|
-
{isSettings && (
|
|
187
|
-
<a href="/assistant" className="mop-back-workspace-btn">
|
|
188
|
-
<span>← BACK TO WORKSPACE</span>
|
|
96
|
+
<aside className={`mop-app-sidebar${menuOpen ? " is-open" : ""}`}>
|
|
97
|
+
<nav className="mop-sidebar-primary" aria-label="Workspace">
|
|
98
|
+
<a href="/assistant" className={pathname.startsWith("/assistant") || pathname.startsWith("/chat/") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
|
|
99
|
+
<span className="mop-nav-icon">✎</span>
|
|
100
|
+
<span>New chat</span>
|
|
189
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>
|
|
124
|
+
<nav>
|
|
125
|
+
<a href="/settings" className={isSettings ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
|
|
126
|
+
<span className="mop-nav-icon">⚙</span>
|
|
127
|
+
<span>Settings</span>
|
|
128
|
+
</a>
|
|
129
|
+
{isSettings && (
|
|
130
|
+
<div className="mop-settings-subnav">
|
|
131
|
+
<button className={settingsSection === "providers" ? "is-active" : ""} onClick={() => { selectSection("providers"); setMenuOpen(false); }}>
|
|
132
|
+
<span>Providers</span>
|
|
133
|
+
</button>
|
|
134
|
+
<button className={settingsSection === "users" ? "is-active" : ""} onClick={() => { selectSection("users"); setMenuOpen(false); }}>
|
|
135
|
+
<span>Users</span>
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
</nav>
|
|
140
|
+
</div>
|
|
190
141
|
)}
|
|
191
142
|
|
|
143
|
+
<div className="mop-sidebar-spacer" />
|
|
192
144
|
<button className="mop-account-card" type="button" onClick={logout} title="Sign out">
|
|
193
145
|
<span className="mop-account-avatar">{viewer.name.slice(0, 1).toUpperCase()}</span>
|
|
194
146
|
<span className="mop-account-copy">
|
|
195
147
|
<strong>{viewer.name}</strong>
|
|
196
148
|
<small>{isAdmin ? "Administrator" : "Member"}</small>
|
|
197
149
|
</span>
|
|
198
|
-
<span aria-hidden="true"
|
|
150
|
+
<span aria-hidden="true">•••</span>
|
|
199
151
|
</button>
|
|
200
152
|
</aside>
|
|
201
153
|
|
|
@@ -204,4 +156,3 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
|
|
|
204
156
|
</MemoryCoreContext.Provider>
|
|
205
157
|
);
|
|
206
158
|
}
|
|
207
|
-
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mop-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
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.13",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"packages/*",
|
package/package.json
CHANGED