@cc-soul/openclaw 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.
@@ -0,0 +1,236 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>cc-soul Knowledge Hub</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: -apple-system, 'SF Pro', 'Helvetica Neue', sans-serif; background: #0a0a0f; color: #e0e0e0; min-height: 100vh; }
10
+
11
+ .header { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); padding: 24px 32px; border-bottom: 1px solid #2a2a4a; }
12
+ .header h1 { font-size: 24px; font-weight: 600; color: #fff; }
13
+ .header h1 span { color: #7c6bf0; }
14
+ .header .subtitle { color: #888; font-size: 14px; margin-top: 4px; }
15
+
16
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; padding: 24px 32px; }
17
+ .stat-card { background: #12121f; border: 1px solid #2a2a4a; border-radius: 12px; padding: 20px; }
18
+ .stat-card .label { color: #888; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; }
19
+ .stat-card .value { font-size: 32px; font-weight: 700; color: #fff; margin-top: 8px; }
20
+ .stat-card .value.purple { color: #7c6bf0; }
21
+ .stat-card .value.green { color: #4ade80; }
22
+ .stat-card .value.blue { color: #60a5fa; }
23
+ .stat-card .value.orange { color: #fb923c; }
24
+
25
+ .section { padding: 0 32px 24px; }
26
+ .section h2 { font-size: 18px; font-weight: 600; margin-bottom: 16px; color: #ccc; }
27
+
28
+ table { width: 100%; border-collapse: collapse; background: #12121f; border-radius: 12px; overflow: hidden; border: 1px solid #2a2a4a; }
29
+ th { text-align: left; padding: 12px 16px; background: #1a1a2e; color: #888; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; font-weight: 500; }
30
+ td { padding: 12px 16px; border-top: 1px solid #1e1e35; font-size: 14px; max-width: 500px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
31
+ tr:hover td { background: #16162a; }
32
+
33
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
34
+ .badge.fact { background: #1e3a5f; color: #60a5fa; }
35
+ .badge.discovery { background: #1e3a2e; color: #4ade80; }
36
+ .badge.consolidated { background: #3a2e1e; color: #fb923c; }
37
+ .badge.high { background: #1e3a2e; color: #4ade80; }
38
+ .badge.medium { background: #3a3a1e; color: #fbbf24; }
39
+ .badge.low { background: #3a1e1e; color: #f87171; }
40
+
41
+ .toolbar { padding: 16px 32px; display: flex; gap: 12px; align-items: center; }
42
+ .toolbar input { background: #12121f; border: 1px solid #2a2a4a; border-radius: 8px; padding: 8px 14px; color: #e0e0e0; font-size: 14px; width: 300px; outline: none; }
43
+ .toolbar input:focus { border-color: #7c6bf0; }
44
+ .toolbar button { background: #7c6bf0; color: #fff; border: none; border-radius: 8px; padding: 8px 16px; font-size: 14px; cursor: pointer; font-weight: 500; }
45
+ .toolbar button:hover { background: #6c5ce7; }
46
+ .toolbar button.danger { background: #e74c3c; }
47
+ .toolbar button.danger:hover { background: #c0392b; }
48
+
49
+ .empty { text-align: center; padding: 40px; color: #555; }
50
+ .refresh-hint { color: #555; font-size: 12px; margin-left: auto; }
51
+
52
+ .tabs { display: flex; gap: 0; padding: 0 32px; }
53
+ .tab { padding: 10px 20px; background: #12121f; border: 1px solid #2a2a4a; border-bottom: none; cursor: pointer; color: #888; font-size: 14px; border-radius: 8px 8px 0 0; }
54
+ .tab.active { background: #1a1a2e; color: #7c6bf0; border-color: #7c6bf0; border-bottom: 1px solid #1a1a2e; }
55
+
56
+ .tab-content { display: none; }
57
+ .tab-content.active { display: block; }
58
+
59
+ .instance-card { background: #12121f; border: 1px solid #2a2a4a; border-radius: 12px; padding: 16px 20px; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; }
60
+ .instance-name { font-weight: 600; font-size: 16px; }
61
+ .instance-meta { color: #888; font-size: 13px; margin-top: 4px; }
62
+ .instance-stats { text-align: right; }
63
+ .instance-stats span { display: block; font-size: 13px; color: #888; }
64
+ .instance-stats .num { color: #7c6bf0; font-weight: 600; }
65
+ </style>
66
+ </head>
67
+ <body>
68
+
69
+ <div class="header">
70
+ <h1>🧠 <span>cc-soul</span> Knowledge Hub</h1>
71
+ <div class="subtitle" id="subtitle">loading...</div>
72
+ </div>
73
+
74
+ <div class="stats-grid" id="stats-grid"></div>
75
+
76
+ <div class="tabs">
77
+ <div class="tab active" onclick="switchTab('memories')">知识库</div>
78
+ <div class="tab" onclick="switchTab('instances')">实例</div>
79
+ </div>
80
+
81
+ <div id="tab-memories" class="tab-content active">
82
+ <div class="toolbar">
83
+ <input type="text" id="search" placeholder="搜索知识..." oninput="filterMemories()">
84
+ <button onclick="refreshData()">刷新</button>
85
+ <button class="danger" onclick="resetKey()">重设 API Key</button>
86
+ <span class="refresh-hint" id="last-refresh"></span>
87
+ </div>
88
+ <div class="section">
89
+ <table>
90
+ <thead>
91
+ <tr>
92
+ <th>类型</th>
93
+ <th>内容</th>
94
+ <th>来源</th>
95
+ <th>信任</th>
96
+ <th>时间</th>
97
+ <th>举报</th>
98
+ </tr>
99
+ </thead>
100
+ <tbody id="memories-body"></tbody>
101
+ </table>
102
+ <div class="empty" id="memories-empty" style="display:none">暂无知识数据</div>
103
+ </div>
104
+ </div>
105
+
106
+ <div id="tab-instances" class="tab-content">
107
+ <div class="toolbar">
108
+ <button onclick="refreshData()">刷新</button>
109
+ </div>
110
+ <div class="section" id="instances-list"></div>
111
+ </div>
112
+
113
+ <script>
114
+ const HUB_URL = location.origin
115
+ const API_KEY = localStorage.getItem('hub_api_key') || prompt('输入你的 API Key (csk-...)') || ''
116
+ if (API_KEY) localStorage.setItem('hub_api_key', API_KEY)
117
+
118
+ let allMemories = []
119
+
120
+ async function fetchJSON(path, opts = {}) {
121
+ const headers = { 'Authorization': `Bearer ${API_KEY}`, ...opts.headers }
122
+ const resp = await fetch(HUB_URL + path, { ...opts, headers })
123
+ const data = await resp.json()
124
+ if (data.error === 'invalid api key') {
125
+ const newKey = prompt('API Key 无效,请重新输入 (csk-...)')
126
+ if (newKey) {
127
+ localStorage.setItem('hub_api_key', newKey)
128
+ location.reload()
129
+ }
130
+ }
131
+ return data
132
+ }
133
+
134
+ async function refreshData() {
135
+ // Stats (no auth needed)
136
+ const stats = await fetchJSON('/federation/stats')
137
+ document.getElementById('subtitle').textContent =
138
+ `${stats.instances} 个实例 · ${stats.totalMemories} 条知识 · ${stats.dbSize}`
139
+
140
+ document.getElementById('stats-grid').innerHTML = `
141
+ <div class="stat-card"><div class="label">总知识</div><div class="value purple">${stats.totalMemories}</div></div>
142
+ <div class="stat-card"><div class="label">实例数</div><div class="value green">${stats.instances}</div></div>
143
+ <div class="stat-card"><div class="label">数据库</div><div class="value blue">${stats.dbSize}</div></div>
144
+ <div class="stat-card"><div class="label">状态</div><div class="value green">在线</div></div>
145
+ `
146
+
147
+ // Memories
148
+ const memData = await fetchJSON('/federation/download?since=0&instance=__dashboard__')
149
+ allMemories = memData.memories || []
150
+ renderMemories(allMemories)
151
+
152
+ // Instances
153
+ const instanceList = stats.instanceList || []
154
+ renderInstances(instanceList)
155
+
156
+ document.getElementById('last-refresh').textContent = '更新于 ' + new Date().toLocaleTimeString('zh-CN')
157
+ }
158
+
159
+ function renderMemories(memories) {
160
+ const tbody = document.getElementById('memories-body')
161
+ if (memories.length === 0) {
162
+ tbody.innerHTML = ''
163
+ document.getElementById('memories-empty').style.display = 'block'
164
+ return
165
+ }
166
+ document.getElementById('memories-empty').style.display = 'none'
167
+
168
+ tbody.innerHTML = memories.map(m => {
169
+ const scope = m.scope || 'fact'
170
+ const trust = m.source_quality >= 7 ? 'high' : m.source_quality >= 5 ? 'medium' : 'low'
171
+ const trustLabel = trust === 'high' ? '高' : trust === 'medium' ? '中' : '低'
172
+ const time = new Date(m.received_at || m.timestamp).toLocaleString('zh-CN', {month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'})
173
+ const reports = m.reports || 0
174
+ return `<tr>
175
+ <td><span class="badge ${scope}">${scope}</span></td>
176
+ <td title="${escapeHtml(m.content)}">${escapeHtml(m.content).slice(0, 80)}</td>
177
+ <td>${m.source_instance || '-'}</td>
178
+ <td><span class="badge ${trust}">${trustLabel} ${(m.source_quality||0).toFixed(1)}</span></td>
179
+ <td>${time}</td>
180
+ <td>${reports > 0 ? '⚠️ ' + reports : '-'}</td>
181
+ </tr>`
182
+ }).join('')
183
+ }
184
+
185
+ function renderInstances(instances) {
186
+ const container = document.getElementById('instances-list')
187
+ if (instances.length === 0) {
188
+ container.innerHTML = '<div class="empty">暂无实例</div>'
189
+ return
190
+ }
191
+ container.innerHTML = instances.map(inst => `
192
+ <div class="instance-card">
193
+ <div>
194
+ <div class="instance-name">${escapeHtml(inst.instance_name || 'unnamed')}</div>
195
+ <div class="instance-meta">上传 ${inst.uploads} 次 · 下载 ${inst.downloads} 次</div>
196
+ </div>
197
+ <div class="instance-stats">
198
+ <span>上传 <span class="num">${inst.uploads}</span></span>
199
+ <span>下载 <span class="num">${inst.downloads}</span></span>
200
+ </div>
201
+ </div>
202
+ `).join('')
203
+ }
204
+
205
+ function filterMemories() {
206
+ const q = document.getElementById('search').value.toLowerCase()
207
+ if (!q) return renderMemories(allMemories)
208
+ const filtered = allMemories.filter(m => m.content.toLowerCase().includes(q))
209
+ renderMemories(filtered)
210
+ }
211
+
212
+ function switchTab(tab) {
213
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'))
214
+ document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'))
215
+ document.querySelector(`.tab-content#tab-${tab}`).classList.add('active')
216
+ event.target.classList.add('active')
217
+ }
218
+
219
+ function resetKey() {
220
+ const newKey = prompt('输入新的 API Key (csk-...)')
221
+ if (newKey) {
222
+ localStorage.setItem('hub_api_key', newKey)
223
+ location.reload()
224
+ }
225
+ }
226
+
227
+ function escapeHtml(str) {
228
+ return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')
229
+ }
230
+
231
+ // Auto refresh
232
+ refreshData()
233
+ setInterval(refreshData, 60000) // every minute
234
+ </script>
235
+ </body>
236
+ </html>
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "cc-soul-hub",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "tsx server.ts",
7
+ "dev": "tsx watch server.ts"
8
+ },
9
+ "dependencies": {
10
+ "better-sqlite3": "^11.0.0"
11
+ },
12
+ "devDependencies": {
13
+ "tsx": "^4.0.0",
14
+ "@types/better-sqlite3": "^7.6.0"
15
+ }
16
+ }