holosphere 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/index.js +414 -0
- package/package.json +19 -0
package/index.js
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import 'dotenv/config'
|
|
2
|
+
import h3 from 'h3-js';
|
|
3
|
+
import OpenAI from 'openai';
|
|
4
|
+
import Gun from 'gun'
|
|
5
|
+
import Ajv2019 from 'ajv/dist/2019.js'
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
|
|
8
|
+
class HoloSphere {
|
|
9
|
+
constructor(appname) {
|
|
10
|
+
this.validator = new Ajv2019({ allErrors: false, strict: false });
|
|
11
|
+
this.gun = Gun({
|
|
12
|
+
peers: ['https://59.src.eco/gun'],
|
|
13
|
+
axe: false,
|
|
14
|
+
// uuid: (content) => { // generate a unique id for each node
|
|
15
|
+
// console.log('uuid', content);
|
|
16
|
+
// return content;}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
this.gun = gun.get(appname)
|
|
20
|
+
this.openai = new OpenAI({
|
|
21
|
+
apiKey: process.env.OPENAI,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
//this.bot.command('sethex', async (ctx) => { this.setHex(ctx) }) TODO: MOVE HERE FROM SETTINGS
|
|
25
|
+
|
|
26
|
+
this.bot.command('resethex', async (ctx) => {
|
|
27
|
+
let chatID = ctx.message.chat.id;
|
|
28
|
+
let hex = (await this.db.get('settings', chatID)).hex
|
|
29
|
+
this.delete(hex, ctx.message.text.split(' ')[1])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
this.bot.command('get', async (ctx) => {
|
|
33
|
+
const chatID = ctx.message.chat.id;
|
|
34
|
+
const lense = ctx.message.text.split(' ')[1];
|
|
35
|
+
if (!lense) {
|
|
36
|
+
return ctx.reply('Please specify a tag.');
|
|
37
|
+
}
|
|
38
|
+
let hex = (await this.db.get('settings', chatID)).hex
|
|
39
|
+
//let hex = settings.hex
|
|
40
|
+
console.log('hex', hex)
|
|
41
|
+
|
|
42
|
+
let data = await this.get(ctx, hex, lense)
|
|
43
|
+
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
this.bot.command('gethex', async (ctx) => {
|
|
47
|
+
let settings = await this.db.get('settings', chatID)
|
|
48
|
+
let id = settings.hex ? settings.hex : 'Hex not set, use /sethex'
|
|
49
|
+
ctx.reply(id)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
this.bot.command('compute', async (ctx) => {
|
|
53
|
+
const chatID = ctx.message.chat.id;
|
|
54
|
+
let operation = ctx.message.text.split(' ')[1];
|
|
55
|
+
if (operation != 'sum') {
|
|
56
|
+
ctx.reply('Operation not implemented')
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
let lense = ctx.message.text.split(' ')[2]
|
|
60
|
+
if (!lense) {
|
|
61
|
+
ctx.reply('Please specify a lense where to perform the operation ')
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
let hex = (await this.db.get('settings', chatID)).hex
|
|
65
|
+
await this.compute(hex, lense, operation)
|
|
66
|
+
|
|
67
|
+
// let parentInfo = await this.getCellInfo(parent)
|
|
68
|
+
// parentInfo.wisdom[id] = report
|
|
69
|
+
// //update summary
|
|
70
|
+
// let summary = await this.summarize(Object.values(parentInfo.wisdom).join('\n'))
|
|
71
|
+
// parentInfo.summary = summary
|
|
72
|
+
|
|
73
|
+
// await this.db.put('cell', parentInfo)
|
|
74
|
+
// return parentInfo
|
|
75
|
+
|
|
76
|
+
// let content = await this.db.getAll(hex+'/tags')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
this.bot.command('cast', async (ctx) => {
|
|
83
|
+
if (!ctx.message.reply_to_message) {
|
|
84
|
+
return ctx.reply('Please reply to a message you want to tag.');
|
|
85
|
+
}
|
|
86
|
+
const tags = ctx.message.text.split(' ').slice(1);
|
|
87
|
+
if (tags.length === 0) {
|
|
88
|
+
return ctx.reply('Please provide at least one tag.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const messageID = ctx.message.reply_to_message.message_id;
|
|
92
|
+
const chatID = ctx.message.chat.id;
|
|
93
|
+
const messageContent = ctx.message.reply_to_message.text;
|
|
94
|
+
let settings = await this.db.get('settings', chatID)
|
|
95
|
+
let id = settings.hex ? settings.hex : 'Hex not set, use /sethex'
|
|
96
|
+
//create root node for the item
|
|
97
|
+
let node = await this.gun.get(chatID + '/' + messageID).put({ id: chatID + '/' + messageID, content: messageContent })
|
|
98
|
+
for (let tag of tags) {
|
|
99
|
+
await this.gun.get(id).get(tag).set(node)
|
|
100
|
+
this.upcast(id, tag, node)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
this.bot.command('publish', async (ctx) => {
|
|
105
|
+
console.log(ctx.message)
|
|
106
|
+
if (!ctx.message.reply_to_message) {
|
|
107
|
+
return ctx.reply('Please reply to a message you want to tag.');
|
|
108
|
+
}
|
|
109
|
+
const tags = ctx.message.text.split(' ').slice(1);
|
|
110
|
+
if (tags.length === 0) {
|
|
111
|
+
return ctx.reply('Please provide at least one tag.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const messageID = ctx.message.reply_to_message.message_id;
|
|
115
|
+
const chatID = ctx.message.chat.id;
|
|
116
|
+
const messageContent = ctx.message.reply_to_message.text;
|
|
117
|
+
let settings = await this.db.get('settings', chatID)
|
|
118
|
+
let hex = settings.hex
|
|
119
|
+
|
|
120
|
+
for (let tag of tags) {
|
|
121
|
+
let node = await this.gun.get(chatID + '/' + messageID).put({ id: chatID + '/' + messageID, content: messageContent })
|
|
122
|
+
await this.put(hex, tag, node)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
ctx.reply('Tag published.');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async setSchema(lense, schema) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
this.db.gun.get(lense).get('schema').put(JSON.stringify(schema), ack => {
|
|
133
|
+
if (ack.err) {
|
|
134
|
+
resolve(new Error('Failed to add schema: ' + ack.err));
|
|
135
|
+
} else {
|
|
136
|
+
console.log('Schema added successfully under tag:', lense);
|
|
137
|
+
resolve(ack);
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async setHex(ctx) {
|
|
144
|
+
const chatID = ctx.message.chat.id;
|
|
145
|
+
const hex = ctx.message.text.split(' ')[1];
|
|
146
|
+
this.db.gun.get(hex).set('chats').put(chatID)
|
|
147
|
+
this.db.gun.get('settings').get(chatID).put(hex)
|
|
148
|
+
return hex
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async getHexContent(ctx) {
|
|
152
|
+
const chatID = ctx.message.chat.id;
|
|
153
|
+
let settings = await this.getSettings(chatID)
|
|
154
|
+
let hex = settings.hex
|
|
155
|
+
let content = await this.db.getAll(hex + '/tags')
|
|
156
|
+
//console.log(content)
|
|
157
|
+
return content ? content[0].id : 'not found'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async delete(id, tag) {
|
|
161
|
+
await this.gun.get(id).get(tag).put(null)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async put(hex, lense, content) {
|
|
165
|
+
// Retrieve the schema for the lense
|
|
166
|
+
let schemaData;
|
|
167
|
+
try {
|
|
168
|
+
schemaData = await new Promise((resolve, reject) => {
|
|
169
|
+
this.gun.get(lense).get('schema').once(data => {
|
|
170
|
+
if (data) {
|
|
171
|
+
resolve(JSON.parse(data));
|
|
172
|
+
} else {
|
|
173
|
+
reject(new Error('No schema data found'));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(`Error fetching schema for "${lense}": ${error}`);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Validate the content against the schema
|
|
183
|
+
const valid = this.validator.validate(schemaData, content);
|
|
184
|
+
if (!valid) {
|
|
185
|
+
console.error('Invalid content:', this.validator.errors);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Create a node for the content
|
|
190
|
+
const payload = JSON.stringify(content);
|
|
191
|
+
|
|
192
|
+
let noderef;
|
|
193
|
+
|
|
194
|
+
if (content.id) { //use the user-defined id. Important to be able to send updates using put
|
|
195
|
+
noderef = this.gun.get(lense).get(content.id).put(payload)
|
|
196
|
+
this.gun.get(hex.toString()).get(lense).get(content.id).put(payload)
|
|
197
|
+
} else { // create a content-addressable reference like IPFS. Note: no updates possible using put
|
|
198
|
+
const hash = createHash("sha256").update(payload).digest("hex");
|
|
199
|
+
noderef = this.gun.get(lense).get(hash).put(payload)
|
|
200
|
+
this.gun.get(hex.toString()).get(lense).get(hash).put(payload)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// return new Promise((resolve, reject) => {
|
|
204
|
+
// this.gun.get(hex.toString()).get(lense).set(noderef, ack => {
|
|
205
|
+
// if (ack.err) {
|
|
206
|
+
// reject(new Error('Failed to add content: ' + ack.err));
|
|
207
|
+
// } else {
|
|
208
|
+
// console.log('Content added successfully under tag:', lense);
|
|
209
|
+
// resolve(ack);
|
|
210
|
+
// }
|
|
211
|
+
// });
|
|
212
|
+
// });
|
|
213
|
+
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async get(hex, lense) {
|
|
217
|
+
// Wrap the GunDB operation in a promise
|
|
218
|
+
//retrieve lense schema
|
|
219
|
+
const schemaData = JSON.parse(await new Promise((resolve, reject) => {
|
|
220
|
+
this.gun.get(lense).get('schema').once(data => {
|
|
221
|
+
if (data) {
|
|
222
|
+
resolve(data);
|
|
223
|
+
} else {
|
|
224
|
+
resolve(null);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
})
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if (!schemaData) {
|
|
231
|
+
console.log('The schema for "' + lense + '" is not defined');
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return new Promise(async (resolve, reject) => {
|
|
236
|
+
let output = []
|
|
237
|
+
let counter = 0
|
|
238
|
+
this.gun.get(hex.toString()).get(lense).once((data, key) => {
|
|
239
|
+
if (data) {
|
|
240
|
+
const maplenght = Object.keys(data).length - 1
|
|
241
|
+
this.gun.get(hex.toString()).get(lense).map().once(async (itemdata, key) => {
|
|
242
|
+
counter += 1
|
|
243
|
+
if (itemdata) {
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
// if (itemdata._["#"]) {
|
|
247
|
+
// // If the data is a reference, fetch the actual content
|
|
248
|
+
// itemdata = await this.gun.get(itemdata._['#']).then();
|
|
249
|
+
// console.log("Data :",itemdata)
|
|
250
|
+
// }
|
|
251
|
+
var parsed = {}
|
|
252
|
+
try {
|
|
253
|
+
parsed = JSON.parse(itemdata);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
console.log('Invalid JSON:', itemdata);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let valid = this.validator.validate(schemaData, parsed);
|
|
259
|
+
if (!valid || parsed == null || parsed == undefined) {
|
|
260
|
+
console.log('Removing Invalid content:', this.validator.errors);
|
|
261
|
+
this.gun.get(hex).get(lense).get(key).put(null);
|
|
262
|
+
|
|
263
|
+
} else {
|
|
264
|
+
output.push(parsed);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (counter == maplenght) {
|
|
269
|
+
resolve(output);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
} else resolve(output)
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Operations
|
|
280
|
+
|
|
281
|
+
async compute(hex, lense, operation) {
|
|
282
|
+
|
|
283
|
+
let res = h3.getResolution(hex);
|
|
284
|
+
if (res < 1 || res > 15) return;
|
|
285
|
+
console.log(res)
|
|
286
|
+
let parent = h3.cellToParent(hex, res - 1);
|
|
287
|
+
let siblings = h3.cellToChildren(parent, res);
|
|
288
|
+
console.log(hex, parent, siblings, res)
|
|
289
|
+
|
|
290
|
+
let content = [];
|
|
291
|
+
let promises = [];
|
|
292
|
+
|
|
293
|
+
for (let i = 0; i < siblings.length; i++) {
|
|
294
|
+
promises.push(new Promise((resolve) => {
|
|
295
|
+
let timeout = setTimeout(() => {
|
|
296
|
+
console.log(`Timeout for sibling ${i}`);
|
|
297
|
+
resolve(); // Resolve the promise to prevent it from hanging
|
|
298
|
+
}, 1000); // Timeout of 5 seconds
|
|
299
|
+
|
|
300
|
+
this.db.gun.get(siblings[i]).get(lense).map().once((data, key) => {
|
|
301
|
+
clearTimeout(timeout); // Clear the timeout if data is received
|
|
302
|
+
if (data) {
|
|
303
|
+
content.push(data.content);
|
|
304
|
+
}
|
|
305
|
+
resolve(); // Resolve after processing data
|
|
306
|
+
});
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
await Promise.all(promises);
|
|
311
|
+
console.log('Content:', content);
|
|
312
|
+
let computed = await this.summarize(content.join('\n'))
|
|
313
|
+
console.log('Computed:', computed)
|
|
314
|
+
let node = await this.gun.get(parent + '_summary').put({ id: parent + '_summary', content: computed })
|
|
315
|
+
|
|
316
|
+
this.put(parent, lense, node);
|
|
317
|
+
this.compute(parent, lense, operation)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async clearlense(hex, lense) {
|
|
321
|
+
let entities = {};
|
|
322
|
+
|
|
323
|
+
// Get list out of Gun
|
|
324
|
+
this.gun.get(hex).get(lense).map().once((data, key) => {
|
|
325
|
+
//entities = data;
|
|
326
|
+
//const id = Object.keys(entities)[0] // since this would be in object form, you can manipulate it as you would like.
|
|
327
|
+
this.gun.get(hex).get(lense).put({ [key]: null })
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
async summarize(history) {
|
|
333
|
+
//const run = await this.openai.beta.threads.runs.retrieve(thread.id,run.id)
|
|
334
|
+
const assistant = await this.openai.beta.assistants.retrieve("asst_qhk79F8wV9BDNuwfOI80TqzC")
|
|
335
|
+
const thread = await this.openai.beta.threads.create()
|
|
336
|
+
const message = await this.openai.beta.threads.messages.create(thread.id, {
|
|
337
|
+
role: "user",
|
|
338
|
+
content: history
|
|
339
|
+
})
|
|
340
|
+
const run = await this.openai.beta.threads.runs.create(thread.id, {
|
|
341
|
+
assistant_id: assistant.id //,
|
|
342
|
+
//instructions: "What is the meaning of life?",
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
let runStatus = await this.openai.beta.threads.runs.retrieve(
|
|
346
|
+
thread.id,
|
|
347
|
+
run.id
|
|
348
|
+
);
|
|
349
|
+
// Polling mechanism to see if runStatus is completed
|
|
350
|
+
// This should be made more robust.
|
|
351
|
+
while (runStatus.status !== "completed") {
|
|
352
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
353
|
+
runStatus = await this.openai.beta.threads.runs.retrieve(thread.id, run.id);
|
|
354
|
+
}
|
|
355
|
+
// Get the latest messages from the thread
|
|
356
|
+
const messages = await this.openai.beta.threads.messages.list(thread.id)
|
|
357
|
+
const summary = messages.data[0].content[0].text.value.replace(/\`\`\`json\n/, '').replace(/\`\`\`/, '').trim()
|
|
358
|
+
return summary
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async upcast(hex, lense, content) {
|
|
362
|
+
let res = h3.getResolution(hex)
|
|
363
|
+
if (res == 0)
|
|
364
|
+
return content
|
|
365
|
+
|
|
366
|
+
console.log('Upcasting ', hex, lense, content)
|
|
367
|
+
let parent = h3.cellToParent(hex, res - 1)
|
|
368
|
+
await this.put(parent, lense, content)
|
|
369
|
+
return this.upcast(parent, lense, content)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// send information upwards, triggers the parent to update its summary
|
|
373
|
+
async updateParent(id, report) {
|
|
374
|
+
let cellinfo = await this.getCellInfo(id)
|
|
375
|
+
let res = h3.getResolution(id)
|
|
376
|
+
let parent = h3.cellToParent(id, res - 1)
|
|
377
|
+
let parentInfo = await this.getCellInfo(parent)
|
|
378
|
+
parentInfo.wisdom[id] = report
|
|
379
|
+
//update summary
|
|
380
|
+
let summary = await this.summarize(Object.values(parentInfo.wisdom).join('\n'))
|
|
381
|
+
parentInfo.summary = summary
|
|
382
|
+
|
|
383
|
+
await this.db.put('cell', parentInfo)
|
|
384
|
+
return parentInfo
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
async getHex(lat, lng, resolution) {
|
|
389
|
+
return h3.latLngToCell(lat, lng, resolution);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// returns the list of all the containing hexagons at xall scales
|
|
393
|
+
getScalespace(lat, lng) {
|
|
394
|
+
let list = []
|
|
395
|
+
let cell = h3.latLngToCell(lat, lng, 14);
|
|
396
|
+
list.push(cell)
|
|
397
|
+
for (let i = 13; i >= 0; i--) {
|
|
398
|
+
list.push(h3.cellToParent(cell, i))
|
|
399
|
+
}
|
|
400
|
+
return list
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// returns the list of all the containing hexagons at xall scales
|
|
404
|
+
getHexScalespace(hex) {
|
|
405
|
+
let list = []
|
|
406
|
+
let res = h3.getResolution(hex)
|
|
407
|
+
for (let i = res; i >= 0; i--) {
|
|
408
|
+
list.push(h3.cellToParent(hex, i))
|
|
409
|
+
}
|
|
410
|
+
return list
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export default HoloSphere;
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "holosphere",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Holonic Geospatial Communication Infrastructure based on h3.js and gun.js",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"author": "Roberto Valenti",
|
|
11
|
+
"license": "GPL-3.0-or-later",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"ajv": "^8.12.0",
|
|
14
|
+
"dotenv": "^16.4.5",
|
|
15
|
+
"gun": "^0.2020.1239",
|
|
16
|
+
"h3-js": "^4.1.0",
|
|
17
|
+
"openai": "^4.29.2"
|
|
18
|
+
}
|
|
19
|
+
}
|