parallelclaw 1.0.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.
- package/CHANGELOG.md +204 -0
- package/HELP.md +600 -0
- package/LICENSE +21 -0
- package/MULTI_MACHINE.md +152 -0
- package/README.md +417 -0
- package/README.ru.md +740 -0
- package/SYNC.md +844 -0
- package/bot/README.md +173 -0
- package/bot/config.js +66 -0
- package/bot/inbox.js +153 -0
- package/bot/index.js +294 -0
- package/bot/nexara.js +61 -0
- package/bot/poll.js +304 -0
- package/bot/search.js +155 -0
- package/bot/telegram.js +96 -0
- package/ingest.js +2712 -0
- package/lib/cli/index.js +1987 -0
- package/lib/config.js +220 -0
- package/lib/db-init.js +158 -0
- package/lib/hook/install.js +268 -0
- package/lib/import-telegram.js +158 -0
- package/lib/ingest-file.js +779 -0
- package/lib/notify-click-action.js +281 -0
- package/lib/openclaw-channel.js +643 -0
- package/lib/parse-cursor.js +172 -0
- package/lib/parse-obsidian.js +256 -0
- package/lib/parse-telegram-html.js +384 -0
- package/lib/parse.js +175 -0
- package/lib/render-markdown.js +0 -0
- package/lib/store-doc/canonicalize.js +116 -0
- package/lib/store-doc/detect.js +209 -0
- package/lib/store-doc/extract-title.js +162 -0
- package/lib/sync/auth.js +80 -0
- package/lib/sync/cert.js +144 -0
- package/lib/sync/cli.js +906 -0
- package/lib/sync/client.js +138 -0
- package/lib/sync/config.js +130 -0
- package/lib/sync/pair.js +145 -0
- package/lib/sync/pull.js +158 -0
- package/lib/sync/push.js +305 -0
- package/lib/sync/replicate.js +335 -0
- package/lib/sync/server.js +224 -0
- package/lib/sync/service.js +726 -0
- package/lib/tasks.js +215 -0
- package/lib/telegram-decisions.js +165 -0
- package/lib/telegram-discovery.js +373 -0
- package/lib/telegram-notify.js +272 -0
- package/lib/telegram-pending.js +200 -0
- package/lib/web/index.js +265 -0
- package/lib/web/routes/conversation.js +193 -0
- package/lib/web/routes/conversations.js +180 -0
- package/lib/web/routes/dashboard.js +175 -0
- package/lib/web/routes/pending.js +277 -0
- package/lib/web/routes/settings.js +226 -0
- package/lib/web/static/style.css +393 -0
- package/lib/web/templates.js +234 -0
- package/package.json +84 -0
- package/server.js +3816 -0
- package/skills/install-memex/README.md +109 -0
- package/skills/install-memex/SKILL.md +342 -0
- package/skills/install-memex/examples.md +294 -0
- package/skills/install-memex-claw/SKILL.md +423 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /settings — read-only configuration & status.
|
|
3
|
+
*
|
|
4
|
+
* Shows where the daemon is, what's running, what's installed.
|
|
5
|
+
* No write actions — those happen via CLI (`memex hook install`, etc.)
|
|
6
|
+
* to keep the web UI safe from accidental clicks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, statSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
|
|
13
|
+
import { renderPage, html, raw, esc, fmtBytes, fmtDateTime } from '../templates.js';
|
|
14
|
+
|
|
15
|
+
const HOME = homedir();
|
|
16
|
+
const MEMEX_DIR = process.env.MEMEX_DIR || join(HOME, '.memex');
|
|
17
|
+
const DB_PATH = join(MEMEX_DIR, 'data', 'memex.db');
|
|
18
|
+
const INBOX_DIR = join(MEMEX_DIR, 'inbox');
|
|
19
|
+
const PLIST_PATH = join(HOME, 'Library/LaunchAgents/com.parallelclaw.memex.sync.plist');
|
|
20
|
+
const SETTINGS_PATH = join(HOME, '.claude/settings.json');
|
|
21
|
+
|
|
22
|
+
function tryStat(p) {
|
|
23
|
+
try {
|
|
24
|
+
return statSync(p);
|
|
25
|
+
} catch (_) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function safeRead(p) {
|
|
31
|
+
try {
|
|
32
|
+
const { readFileSync } = require('node:fs');
|
|
33
|
+
return readFileSync(p, 'utf-8');
|
|
34
|
+
} catch (_) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sessionStartHookInstalled() {
|
|
40
|
+
// Soft check — look for "memex hook" inside ~/.claude/settings.json
|
|
41
|
+
try {
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
|
+
const { readFileSync } = require('node:fs');
|
|
44
|
+
const txt = readFileSync(SETTINGS_PATH, 'utf-8');
|
|
45
|
+
return /memex\s+hook/.test(txt) || /memex-mvp/.test(txt);
|
|
46
|
+
} catch (_) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function renderSettings(db, status) {
|
|
52
|
+
// ----- Daemon -----
|
|
53
|
+
const plistStat = tryStat(PLIST_PATH);
|
|
54
|
+
const dbStat = tryStat(DB_PATH);
|
|
55
|
+
const ingestLogStat = tryStat(join(MEMEX_DIR, 'data', 'ingest.log'));
|
|
56
|
+
|
|
57
|
+
// ----- Sources (count of conversations per source) -----
|
|
58
|
+
const sourceRows = db
|
|
59
|
+
.prepare(
|
|
60
|
+
`
|
|
61
|
+
SELECT source,
|
|
62
|
+
COUNT(*) AS conv_count,
|
|
63
|
+
SUM(message_count) AS msg_count
|
|
64
|
+
FROM conversations
|
|
65
|
+
WHERE archived_at IS NULL
|
|
66
|
+
GROUP BY source
|
|
67
|
+
ORDER BY msg_count DESC
|
|
68
|
+
`
|
|
69
|
+
)
|
|
70
|
+
.all();
|
|
71
|
+
|
|
72
|
+
// ----- Pending count -----
|
|
73
|
+
let pendingCount = 0;
|
|
74
|
+
try {
|
|
75
|
+
const { listPending } = await import('../../telegram-pending.js');
|
|
76
|
+
pendingCount = listPending().length;
|
|
77
|
+
} catch (_) {
|
|
78
|
+
/* optional */
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ----- Decisions count -----
|
|
82
|
+
let decisionStats = null;
|
|
83
|
+
try {
|
|
84
|
+
const { loadDecisions } = await import('../../telegram-decisions.js');
|
|
85
|
+
const d = loadDecisions();
|
|
86
|
+
decisionStats = {
|
|
87
|
+
mode: d.mode || 'pick',
|
|
88
|
+
allowed: d.allowed ? Object.keys(d.allowed).length : 0,
|
|
89
|
+
skipped: d.skipped ? Object.keys(d.skipped).length : 0,
|
|
90
|
+
blocked: d.blocked ? Object.keys(d.blocked).length : 0,
|
|
91
|
+
};
|
|
92
|
+
} catch (_) {
|
|
93
|
+
/* optional */
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const daemonState = status?.running
|
|
97
|
+
? html`<span class="status-pill ok">🟢 running</span>`
|
|
98
|
+
: status?.installed
|
|
99
|
+
? html`<span class="status-pill stale">🔴 installed but stopped</span>`
|
|
100
|
+
: html`<span class="status-pill">⚪ not installed</span>`;
|
|
101
|
+
|
|
102
|
+
const daemonCard = html`
|
|
103
|
+
<section class="card">
|
|
104
|
+
<div class="card-label">daemon</div>
|
|
105
|
+
<ul class="sources-list">
|
|
106
|
+
<li>
|
|
107
|
+
<span class="src-name">Status</span>
|
|
108
|
+
<span class="src-meta">${daemonState}</span>
|
|
109
|
+
</li>
|
|
110
|
+
<li>
|
|
111
|
+
<span class="src-name">LaunchAgent</span>
|
|
112
|
+
<span class="src-meta">${plistStat ? raw('<code>' + esc(PLIST_PATH) + '</code>') : 'not installed'}</span>
|
|
113
|
+
</li>
|
|
114
|
+
<li>
|
|
115
|
+
<span class="src-name">Last capture</span>
|
|
116
|
+
<span class="src-meta">${ingestLogStat ? fmtDateTime(Math.floor(ingestLogStat.mtimeMs / 1000)) : '—'}</span>
|
|
117
|
+
</li>
|
|
118
|
+
</ul>
|
|
119
|
+
<p class="conv-meta" style="margin-top:14px;">
|
|
120
|
+
Manage via CLI: <code>memex-sync install</code> · <code>memex-sync uninstall</code> · <code>launchctl unload …</code>
|
|
121
|
+
</p>
|
|
122
|
+
</section>
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
const dbCard = html`
|
|
126
|
+
<section class="card">
|
|
127
|
+
<div class="card-label">database</div>
|
|
128
|
+
<ul class="sources-list">
|
|
129
|
+
<li>
|
|
130
|
+
<span class="src-name">Path</span>
|
|
131
|
+
<span class="src-meta"><code>${esc(DB_PATH)}</code></span>
|
|
132
|
+
</li>
|
|
133
|
+
<li>
|
|
134
|
+
<span class="src-name">Size</span>
|
|
135
|
+
<span class="src-meta">${dbStat ? fmtBytes(dbStat.size) : '—'}</span>
|
|
136
|
+
</li>
|
|
137
|
+
<li>
|
|
138
|
+
<span class="src-name">Inbox</span>
|
|
139
|
+
<span class="src-meta"><code>${esc(INBOX_DIR)}</code> ${pendingCount > 0 ? raw(' · <strong>' + pendingCount + ' pending</strong>') : ''}</span>
|
|
140
|
+
</li>
|
|
141
|
+
</ul>
|
|
142
|
+
</section>
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
const sourcesCard = html`
|
|
146
|
+
<section class="card">
|
|
147
|
+
<div class="card-label">sources captured</div>
|
|
148
|
+
<ul class="sources-list">
|
|
149
|
+
${sourceRows.length === 0
|
|
150
|
+
? html`<li><span class="src-meta">No sources captured yet.</span></li>`
|
|
151
|
+
: sourceRows.map(
|
|
152
|
+
(s) => html`
|
|
153
|
+
<li>
|
|
154
|
+
<span class="src-name">${s.source}</span>
|
|
155
|
+
<span class="src-meta">${s.conv_count} chats · ${s.msg_count} msgs</span>
|
|
156
|
+
</li>
|
|
157
|
+
`
|
|
158
|
+
)}
|
|
159
|
+
</ul>
|
|
160
|
+
</section>
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const hookCard = html`
|
|
164
|
+
<section class="card">
|
|
165
|
+
<div class="card-label">hooks</div>
|
|
166
|
+
<ul class="sources-list">
|
|
167
|
+
<li>
|
|
168
|
+
<span class="src-name">SessionStart (Claude Code)</span>
|
|
169
|
+
<span class="src-meta">${sessionStartHookInstalled()
|
|
170
|
+
? raw('<span class="status-pill ok">✓ installed</span>')
|
|
171
|
+
: raw('<span class="status-pill">— not installed</span>')}</span>
|
|
172
|
+
</li>
|
|
173
|
+
</ul>
|
|
174
|
+
<p class="conv-meta" style="margin-top:14px;">
|
|
175
|
+
Install: <code>memex hook install</code> · Uninstall: <code>memex hook uninstall</code>
|
|
176
|
+
</p>
|
|
177
|
+
</section>
|
|
178
|
+
`;
|
|
179
|
+
|
|
180
|
+
const decisionsCard = decisionStats
|
|
181
|
+
? html`
|
|
182
|
+
<section class="card">
|
|
183
|
+
<div class="card-label">telegram decisions</div>
|
|
184
|
+
<ul class="sources-list">
|
|
185
|
+
<li>
|
|
186
|
+
<span class="src-name">Mode</span>
|
|
187
|
+
<span class="src-meta">${decisionStats.mode}</span>
|
|
188
|
+
</li>
|
|
189
|
+
<li>
|
|
190
|
+
<span class="src-name">Allowed</span>
|
|
191
|
+
<span class="src-meta">${decisionStats.allowed}</span>
|
|
192
|
+
</li>
|
|
193
|
+
<li>
|
|
194
|
+
<span class="src-name">Skipped</span>
|
|
195
|
+
<span class="src-meta">${decisionStats.skipped}</span>
|
|
196
|
+
</li>
|
|
197
|
+
<li>
|
|
198
|
+
<span class="src-name">Blocked patterns</span>
|
|
199
|
+
<span class="src-meta">${decisionStats.blocked}</span>
|
|
200
|
+
</li>
|
|
201
|
+
</ul>
|
|
202
|
+
<p class="conv-meta" style="margin-top:14px;">
|
|
203
|
+
Manage via CLI: <code>memex telegram allow|skip|block|unblock|mode</code>
|
|
204
|
+
</p>
|
|
205
|
+
</section>
|
|
206
|
+
`
|
|
207
|
+
: null;
|
|
208
|
+
|
|
209
|
+
const body = html`
|
|
210
|
+
<div class="callout">
|
|
211
|
+
Read-only view. Destructive operations (uninstall, remove data) live in the CLI to prevent accidental clicks.
|
|
212
|
+
</div>
|
|
213
|
+
${daemonCard}
|
|
214
|
+
${dbCard}
|
|
215
|
+
${sourcesCard}
|
|
216
|
+
${hookCard}
|
|
217
|
+
${decisionsCard}
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
return renderPage({
|
|
221
|
+
title: 'Settings',
|
|
222
|
+
active: 'settings',
|
|
223
|
+
body,
|
|
224
|
+
status,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/* memex web dashboard — brand-aligned with memex.parallelclaw.ai */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #0f1115;
|
|
5
|
+
--bg-soft: #151922;
|
|
6
|
+
--bg-card: rgba(255,255,255,0.05);
|
|
7
|
+
--text: #f5f7fb;
|
|
8
|
+
--text-soft: #dbe4f3;
|
|
9
|
+
--text-muted: #a6b0c3;
|
|
10
|
+
--accent: #6ee7b7;
|
|
11
|
+
--accent-light: #a7f3d0;
|
|
12
|
+
--accent-2: #60a5fa;
|
|
13
|
+
--accent-bg: rgba(110, 231, 183, 0.12);
|
|
14
|
+
--green: #6ee7b7;
|
|
15
|
+
--red-soft: #f87171;
|
|
16
|
+
--amber: #fbbf24;
|
|
17
|
+
--border: rgba(255,255,255,0.10);
|
|
18
|
+
--shadow: 0 20px 60px rgba(0,0,0,0.35);
|
|
19
|
+
--radius: 16px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
23
|
+
html { scroll-behavior: smooth; }
|
|
24
|
+
body {
|
|
25
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
26
|
+
background:
|
|
27
|
+
radial-gradient(circle at 10% 10%, rgba(110,231,183,0.10), transparent 30%),
|
|
28
|
+
radial-gradient(circle at 90% 20%, rgba(96,165,250,0.10), transparent 25%),
|
|
29
|
+
linear-gradient(180deg, #0d1016 0%, #11151c 100%);
|
|
30
|
+
background-attachment: fixed;
|
|
31
|
+
color: var(--text);
|
|
32
|
+
line-height: 1.55;
|
|
33
|
+
font-size: 15px;
|
|
34
|
+
-webkit-font-smoothing: antialiased;
|
|
35
|
+
}
|
|
36
|
+
code, pre, kbd { font-family: 'JetBrains Mono', 'SF Mono', Menlo, monospace; }
|
|
37
|
+
a { color: var(--accent-light); text-decoration: none; }
|
|
38
|
+
a:hover { text-decoration: underline; }
|
|
39
|
+
|
|
40
|
+
/* ===== Top nav ===== */
|
|
41
|
+
.topbar {
|
|
42
|
+
position: sticky; top: 0; z-index: 50;
|
|
43
|
+
background: rgba(15, 17, 21, 0.72);
|
|
44
|
+
backdrop-filter: saturate(180%) blur(14px);
|
|
45
|
+
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
46
|
+
}
|
|
47
|
+
.topbar-inner {
|
|
48
|
+
max-width: 1120px; margin: 0 auto;
|
|
49
|
+
padding: 14px 24px;
|
|
50
|
+
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
|
51
|
+
}
|
|
52
|
+
.brand {
|
|
53
|
+
display: flex; align-items: center; gap: 10px;
|
|
54
|
+
color: var(--text);
|
|
55
|
+
text-decoration: none;
|
|
56
|
+
}
|
|
57
|
+
.brand-svg { width: 32px; height: auto; display: block; flex-shrink: 0; }
|
|
58
|
+
.brand-wordmark {
|
|
59
|
+
font-weight: 800; font-size: 17px;
|
|
60
|
+
letter-spacing: -0.04em;
|
|
61
|
+
color: var(--text);
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
}
|
|
64
|
+
.brand-wordmark .accent { color: var(--accent); }
|
|
65
|
+
.nav-links {
|
|
66
|
+
display: flex; align-items: center; gap: 4px;
|
|
67
|
+
}
|
|
68
|
+
.nav-link {
|
|
69
|
+
color: var(--text-muted);
|
|
70
|
+
padding: 6px 12px;
|
|
71
|
+
border-radius: 8px;
|
|
72
|
+
font-size: 13px;
|
|
73
|
+
font-weight: 500;
|
|
74
|
+
transition: all 0.15s;
|
|
75
|
+
}
|
|
76
|
+
.nav-link:hover { color: var(--text); background: var(--bg-card); text-decoration: none; }
|
|
77
|
+
.nav-link.active { color: var(--accent); background: var(--accent-bg); }
|
|
78
|
+
|
|
79
|
+
/* ===== Page header ===== */
|
|
80
|
+
.page-header {
|
|
81
|
+
max-width: 1120px; margin: 0 auto;
|
|
82
|
+
padding: 32px 24px 16px;
|
|
83
|
+
}
|
|
84
|
+
.page-header-inner {
|
|
85
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
|
|
86
|
+
flex-wrap: wrap;
|
|
87
|
+
}
|
|
88
|
+
.page-title {
|
|
89
|
+
font-size: clamp(24px, 4vw, 32px);
|
|
90
|
+
font-weight: 800; letter-spacing: -0.025em;
|
|
91
|
+
}
|
|
92
|
+
.status-pill {
|
|
93
|
+
font-size: 12px;
|
|
94
|
+
padding: 4px 12px;
|
|
95
|
+
border-radius: 999px;
|
|
96
|
+
background: var(--bg-card);
|
|
97
|
+
border: 1px solid var(--border);
|
|
98
|
+
color: var(--text-soft);
|
|
99
|
+
}
|
|
100
|
+
.status-pill.ok { color: var(--accent); border-color: rgba(110,231,183,0.30); }
|
|
101
|
+
.status-pill.stale { color: var(--red-soft); border-color: rgba(248,113,113,0.30); }
|
|
102
|
+
|
|
103
|
+
/* ===== Main content ===== */
|
|
104
|
+
.main {
|
|
105
|
+
max-width: 1120px; margin: 0 auto;
|
|
106
|
+
padding: 16px 24px 48px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ===== Cards ===== */
|
|
110
|
+
.card {
|
|
111
|
+
background: var(--bg-card);
|
|
112
|
+
border: 1px solid var(--border);
|
|
113
|
+
border-radius: var(--radius);
|
|
114
|
+
padding: 22px 24px;
|
|
115
|
+
margin-bottom: 20px;
|
|
116
|
+
}
|
|
117
|
+
.card-label {
|
|
118
|
+
font-size: 12px;
|
|
119
|
+
color: var(--text-muted);
|
|
120
|
+
text-transform: uppercase;
|
|
121
|
+
letter-spacing: 0.08em;
|
|
122
|
+
margin-bottom: 14px;
|
|
123
|
+
padding-bottom: 10px;
|
|
124
|
+
border-bottom: 1px dashed var(--border);
|
|
125
|
+
}
|
|
126
|
+
.card-body { color: var(--text); }
|
|
127
|
+
.card-body p { margin-bottom: 10px; line-height: 1.6; }
|
|
128
|
+
.card-body p:last-child { margin-bottom: 0; }
|
|
129
|
+
|
|
130
|
+
/* ===== Stat grid ===== */
|
|
131
|
+
.stat-grid {
|
|
132
|
+
display: grid;
|
|
133
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
134
|
+
gap: 16px;
|
|
135
|
+
margin-bottom: 24px;
|
|
136
|
+
}
|
|
137
|
+
.stat {
|
|
138
|
+
background: var(--bg-card);
|
|
139
|
+
border: 1px solid var(--border);
|
|
140
|
+
border-radius: var(--radius);
|
|
141
|
+
padding: 18px 22px;
|
|
142
|
+
}
|
|
143
|
+
.stat-value {
|
|
144
|
+
font-size: 28px;
|
|
145
|
+
font-weight: 800;
|
|
146
|
+
letter-spacing: -0.02em;
|
|
147
|
+
color: var(--accent-light);
|
|
148
|
+
line-height: 1.1;
|
|
149
|
+
margin-bottom: 4px;
|
|
150
|
+
}
|
|
151
|
+
.stat-label {
|
|
152
|
+
font-size: 12px;
|
|
153
|
+
color: var(--text-muted);
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
letter-spacing: 0.06em;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* ===== Sources list ===== */
|
|
159
|
+
.sources-list { list-style: none; }
|
|
160
|
+
.sources-list li {
|
|
161
|
+
display: flex; align-items: baseline; gap: 12px;
|
|
162
|
+
padding: 8px 0;
|
|
163
|
+
border-bottom: 1px dashed var(--border);
|
|
164
|
+
font-size: 14px;
|
|
165
|
+
}
|
|
166
|
+
.sources-list li:last-child { border-bottom: none; }
|
|
167
|
+
.sources-list .src-name { font-weight: 600; color: var(--text); min-width: 140px; }
|
|
168
|
+
.sources-list .src-meta { color: var(--text-soft); }
|
|
169
|
+
.sources-list .src-spacer { flex: 1; }
|
|
170
|
+
|
|
171
|
+
/* ===== Conversation list ===== */
|
|
172
|
+
.conv-list { display: flex; flex-direction: column; gap: 8px; }
|
|
173
|
+
.conv-item {
|
|
174
|
+
display: block;
|
|
175
|
+
padding: 14px 18px;
|
|
176
|
+
background: var(--bg-card);
|
|
177
|
+
border: 1px solid var(--border);
|
|
178
|
+
border-radius: 12px;
|
|
179
|
+
text-decoration: none;
|
|
180
|
+
color: var(--text);
|
|
181
|
+
transition: border-color 0.15s, background 0.15s;
|
|
182
|
+
}
|
|
183
|
+
.conv-item:hover {
|
|
184
|
+
background: rgba(255,255,255,0.07);
|
|
185
|
+
border-color: rgba(110,231,183,0.30);
|
|
186
|
+
text-decoration: none;
|
|
187
|
+
}
|
|
188
|
+
.conv-item-top {
|
|
189
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
|
|
190
|
+
margin-bottom: 4px;
|
|
191
|
+
}
|
|
192
|
+
.conv-title { font-weight: 600; font-size: 15px; }
|
|
193
|
+
.conv-count { color: var(--text-muted); font-size: 12px; flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
|
194
|
+
.conv-meta { color: var(--text-muted); font-size: 12px; }
|
|
195
|
+
.conv-source-tag {
|
|
196
|
+
display: inline-block;
|
|
197
|
+
padding: 1px 8px;
|
|
198
|
+
border-radius: 999px;
|
|
199
|
+
background: var(--bg-soft);
|
|
200
|
+
color: var(--accent-light);
|
|
201
|
+
font-size: 11px;
|
|
202
|
+
font-weight: 600;
|
|
203
|
+
text-transform: lowercase;
|
|
204
|
+
letter-spacing: 0.02em;
|
|
205
|
+
margin-right: 6px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* ===== Search bar ===== */
|
|
209
|
+
.search-bar {
|
|
210
|
+
display: flex; gap: 12px; flex-wrap: wrap;
|
|
211
|
+
margin-bottom: 20px;
|
|
212
|
+
align-items: center;
|
|
213
|
+
}
|
|
214
|
+
.search-input {
|
|
215
|
+
flex: 1; min-width: 240px;
|
|
216
|
+
padding: 10px 14px;
|
|
217
|
+
background: var(--bg-card);
|
|
218
|
+
border: 1px solid var(--border);
|
|
219
|
+
border-radius: 10px;
|
|
220
|
+
color: var(--text);
|
|
221
|
+
font: inherit; font-size: 14px;
|
|
222
|
+
}
|
|
223
|
+
.search-input:focus {
|
|
224
|
+
outline: none;
|
|
225
|
+
border-color: var(--accent);
|
|
226
|
+
}
|
|
227
|
+
.search-meta { color: var(--text-muted); font-size: 13px; }
|
|
228
|
+
|
|
229
|
+
/* ===== Chat-bubble transcript (for /c/:id) ===== */
|
|
230
|
+
.transcript {
|
|
231
|
+
max-width: 880px; margin: 0 auto;
|
|
232
|
+
display: flex; flex-direction: column; gap: 12px;
|
|
233
|
+
}
|
|
234
|
+
.transcript-day {
|
|
235
|
+
text-align: center;
|
|
236
|
+
font-size: 11px;
|
|
237
|
+
color: var(--text-muted);
|
|
238
|
+
text-transform: uppercase;
|
|
239
|
+
letter-spacing: 0.1em;
|
|
240
|
+
padding: 12px 0;
|
|
241
|
+
position: relative;
|
|
242
|
+
}
|
|
243
|
+
.transcript-day::before, .transcript-day::after {
|
|
244
|
+
content: '';
|
|
245
|
+
position: absolute; top: 50%;
|
|
246
|
+
width: calc(50% - 60px);
|
|
247
|
+
border-top: 1px solid var(--border);
|
|
248
|
+
}
|
|
249
|
+
.transcript-day::before { left: 0; }
|
|
250
|
+
.transcript-day::after { right: 0; }
|
|
251
|
+
|
|
252
|
+
.chat-bubble {
|
|
253
|
+
padding: 12px 16px;
|
|
254
|
+
border-radius: 12px;
|
|
255
|
+
max-width: 80%;
|
|
256
|
+
font-size: 14px;
|
|
257
|
+
line-height: 1.55;
|
|
258
|
+
word-wrap: break-word;
|
|
259
|
+
overflow-wrap: break-word;
|
|
260
|
+
}
|
|
261
|
+
.chat-bubble.user {
|
|
262
|
+
background: var(--bg-soft);
|
|
263
|
+
margin-left: auto;
|
|
264
|
+
border-right: 3px solid var(--accent-light);
|
|
265
|
+
}
|
|
266
|
+
.chat-bubble.ai {
|
|
267
|
+
background: rgba(110,231,183,0.06);
|
|
268
|
+
border-left: 3px solid var(--accent);
|
|
269
|
+
}
|
|
270
|
+
.chat-who {
|
|
271
|
+
display: block;
|
|
272
|
+
font-size: 10.5px;
|
|
273
|
+
font-weight: 700;
|
|
274
|
+
color: var(--text-muted);
|
|
275
|
+
text-transform: uppercase;
|
|
276
|
+
letter-spacing: 0.07em;
|
|
277
|
+
margin-bottom: 6px;
|
|
278
|
+
}
|
|
279
|
+
.chat-bubble p { margin: 0 0 6px; color: var(--text); white-space: pre-wrap; }
|
|
280
|
+
.chat-bubble p:last-child { margin-bottom: 0; }
|
|
281
|
+
.chat-bubble code {
|
|
282
|
+
background: rgba(255,255,255,0.06);
|
|
283
|
+
padding: 1px 5px;
|
|
284
|
+
border-radius: 3px;
|
|
285
|
+
font-family: 'JetBrains Mono', monospace;
|
|
286
|
+
font-size: 12.5px;
|
|
287
|
+
}
|
|
288
|
+
.chat-bubble pre {
|
|
289
|
+
background: var(--bg-soft);
|
|
290
|
+
padding: 12px;
|
|
291
|
+
border-radius: 6px;
|
|
292
|
+
overflow-x: auto;
|
|
293
|
+
margin: 8px 0;
|
|
294
|
+
font-size: 12.5px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* Search highlight inside transcript */
|
|
298
|
+
.chat-bubble mark {
|
|
299
|
+
background: var(--accent-bg);
|
|
300
|
+
color: var(--accent-light);
|
|
301
|
+
padding: 0 2px;
|
|
302
|
+
border-radius: 2px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* ===== Pending list ===== */
|
|
306
|
+
.pending-list { display: flex; flex-direction: column; gap: 12px; }
|
|
307
|
+
.pending-item {
|
|
308
|
+
background: var(--bg-card);
|
|
309
|
+
border: 1px solid var(--border);
|
|
310
|
+
border-radius: 12px;
|
|
311
|
+
padding: 18px 20px;
|
|
312
|
+
}
|
|
313
|
+
.pending-item-top {
|
|
314
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
|
|
315
|
+
margin-bottom: 6px;
|
|
316
|
+
}
|
|
317
|
+
.pending-title { font-weight: 600; font-size: 15px; color: var(--text); }
|
|
318
|
+
.pending-count { color: var(--text-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
319
|
+
.pending-meta { color: var(--text-soft); font-size: 13px; margin-bottom: 12px; }
|
|
320
|
+
.pending-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
321
|
+
|
|
322
|
+
.btn {
|
|
323
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
324
|
+
padding: 8px 14px;
|
|
325
|
+
border-radius: 8px;
|
|
326
|
+
font: inherit; font-size: 13px; font-weight: 600;
|
|
327
|
+
border: 1px solid var(--border);
|
|
328
|
+
background: var(--bg-card);
|
|
329
|
+
color: var(--text-soft);
|
|
330
|
+
cursor: pointer;
|
|
331
|
+
transition: all 0.15s;
|
|
332
|
+
text-decoration: none;
|
|
333
|
+
}
|
|
334
|
+
.btn:hover { background: rgba(255,255,255,0.08); border-color: rgba(255,255,255,0.20); text-decoration: none; }
|
|
335
|
+
.btn-primary {
|
|
336
|
+
background: var(--accent);
|
|
337
|
+
color: var(--bg);
|
|
338
|
+
border-color: var(--accent);
|
|
339
|
+
}
|
|
340
|
+
.btn-primary:hover { background: var(--accent-light); border-color: var(--accent-light); }
|
|
341
|
+
.btn-danger {
|
|
342
|
+
color: var(--red-soft);
|
|
343
|
+
border-color: rgba(248,113,113,0.30);
|
|
344
|
+
}
|
|
345
|
+
.btn-danger:hover { background: rgba(248,113,113,0.10); }
|
|
346
|
+
|
|
347
|
+
/* ===== Forms & inputs ===== */
|
|
348
|
+
.callout {
|
|
349
|
+
background: var(--accent-bg);
|
|
350
|
+
border-left: 3px solid var(--accent);
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
padding: 14px 18px;
|
|
353
|
+
margin-bottom: 20px;
|
|
354
|
+
font-size: 14px;
|
|
355
|
+
color: var(--text-soft);
|
|
356
|
+
}
|
|
357
|
+
.callout strong { color: var(--accent-light); }
|
|
358
|
+
|
|
359
|
+
/* ===== Empty states ===== */
|
|
360
|
+
.empty {
|
|
361
|
+
text-align: center;
|
|
362
|
+
padding: 60px 24px;
|
|
363
|
+
color: var(--text-muted);
|
|
364
|
+
}
|
|
365
|
+
.empty h3 {
|
|
366
|
+
font-size: 18px;
|
|
367
|
+
font-weight: 600;
|
|
368
|
+
color: var(--text-soft);
|
|
369
|
+
margin-bottom: 8px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* ===== Footer ===== */
|
|
373
|
+
.footer {
|
|
374
|
+
max-width: 1120px; margin: 40px auto 0;
|
|
375
|
+
padding: 24px;
|
|
376
|
+
font-size: 12px;
|
|
377
|
+
color: var(--text-muted);
|
|
378
|
+
text-align: center;
|
|
379
|
+
}
|
|
380
|
+
.footer a { color: var(--text-muted); }
|
|
381
|
+
.footer a:hover { color: var(--accent-light); }
|
|
382
|
+
.footer span { margin: 0 4px; }
|
|
383
|
+
|
|
384
|
+
/* ===== Responsive ===== */
|
|
385
|
+
@media (max-width: 640px) {
|
|
386
|
+
.topbar-inner { padding: 12px 16px; }
|
|
387
|
+
.page-header { padding: 24px 16px 12px; }
|
|
388
|
+
.main { padding: 12px 16px 32px; }
|
|
389
|
+
.nav-links { gap: 0; }
|
|
390
|
+
.nav-link { padding: 6px 8px; font-size: 12px; }
|
|
391
|
+
.conv-item-top { flex-direction: column; align-items: flex-start; }
|
|
392
|
+
.stat-value { font-size: 22px; }
|
|
393
|
+
}
|