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.
Files changed (2) hide show
  1. package/index.js +414 -0
  2. 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
+ }