holosphere 1.1.19 → 2.0.0-alpha0
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/.env.example +36 -0
- package/.eslintrc.json +16 -0
- package/.prettierrc.json +7 -0
- package/README.md +476 -531
- package/bin/holosphere-activitypub.js +158 -0
- package/cleanup-test-data.js +204 -0
- package/examples/demo.html +1333 -0
- package/examples/example-bot.js +197 -0
- package/package.json +47 -87
- package/scripts/check-bundle-size.js +54 -0
- package/scripts/check-quest-ids.js +77 -0
- package/scripts/import-holons.js +578 -0
- package/scripts/publish-to-relay.js +101 -0
- package/scripts/read-example.js +186 -0
- package/scripts/relay-diagnostic.js +59 -0
- package/scripts/relay-example.js +179 -0
- package/scripts/resync-to-relay.js +245 -0
- package/scripts/revert-import.js +196 -0
- package/scripts/test-hybrid-mode.js +108 -0
- package/scripts/test-local-storage.js +63 -0
- package/scripts/test-nostr-direct.js +55 -0
- package/scripts/test-read-data.js +45 -0
- package/scripts/test-write-read.js +63 -0
- package/scripts/verify-import.js +95 -0
- package/scripts/verify-relay-data.js +139 -0
- package/src/ai/aggregation.js +319 -0
- package/src/ai/breakdown.js +511 -0
- package/src/ai/classifier.js +217 -0
- package/src/ai/council.js +228 -0
- package/src/ai/embeddings.js +279 -0
- package/src/ai/federation-ai.js +324 -0
- package/src/ai/h3-ai.js +955 -0
- package/src/ai/index.js +112 -0
- package/src/ai/json-ops.js +225 -0
- package/src/ai/llm-service.js +205 -0
- package/src/ai/nl-query.js +223 -0
- package/src/ai/relationships.js +353 -0
- package/src/ai/schema-extractor.js +218 -0
- package/src/ai/spatial.js +293 -0
- package/src/ai/tts.js +194 -0
- package/src/content/social-protocols.js +168 -0
- package/src/core/holosphere.js +273 -0
- package/src/crypto/secp256k1.js +259 -0
- package/src/federation/discovery.js +334 -0
- package/src/federation/hologram.js +1042 -0
- package/src/federation/registry.js +386 -0
- package/src/hierarchical/upcast.js +110 -0
- package/src/index.js +2669 -0
- package/src/schema/validator.js +91 -0
- package/src/spatial/h3-operations.js +110 -0
- package/src/storage/backend-factory.js +125 -0
- package/src/storage/backend-interface.js +142 -0
- package/src/storage/backends/activitypub/server.js +653 -0
- package/src/storage/backends/activitypub-backend.js +272 -0
- package/src/storage/backends/gundb-backend.js +233 -0
- package/src/storage/backends/nostr-backend.js +136 -0
- package/src/storage/filesystem-storage-browser.js +41 -0
- package/src/storage/filesystem-storage.js +138 -0
- package/src/storage/global-tables.js +81 -0
- package/src/storage/gun-async.js +281 -0
- package/src/storage/gun-wrapper.js +221 -0
- package/src/storage/indexeddb-storage.js +122 -0
- package/src/storage/key-storage-simple.js +76 -0
- package/src/storage/key-storage.js +136 -0
- package/src/storage/memory-storage.js +59 -0
- package/src/storage/migration.js +338 -0
- package/src/storage/nostr-async.js +811 -0
- package/src/storage/nostr-client.js +939 -0
- package/src/storage/nostr-wrapper.js +211 -0
- package/src/storage/outbox-queue.js +208 -0
- package/src/storage/persistent-storage.js +109 -0
- package/src/storage/sync-service.js +164 -0
- package/src/subscriptions/manager.js +142 -0
- package/test-ai-real-api.js +202 -0
- package/tests/unit/ai/aggregation.test.js +295 -0
- package/tests/unit/ai/breakdown.test.js +446 -0
- package/tests/unit/ai/classifier.test.js +294 -0
- package/tests/unit/ai/council.test.js +262 -0
- package/tests/unit/ai/embeddings.test.js +384 -0
- package/tests/unit/ai/federation-ai.test.js +344 -0
- package/tests/unit/ai/h3-ai.test.js +458 -0
- package/tests/unit/ai/index.test.js +304 -0
- package/tests/unit/ai/json-ops.test.js +307 -0
- package/tests/unit/ai/llm-service.test.js +390 -0
- package/tests/unit/ai/nl-query.test.js +383 -0
- package/tests/unit/ai/relationships.test.js +311 -0
- package/tests/unit/ai/schema-extractor.test.js +384 -0
- package/tests/unit/ai/spatial.test.js +279 -0
- package/tests/unit/ai/tts.test.js +279 -0
- package/tests/unit/content.test.js +332 -0
- package/tests/unit/contract/core.test.js +88 -0
- package/tests/unit/contract/crypto.test.js +198 -0
- package/tests/unit/contract/data.test.js +223 -0
- package/tests/unit/contract/federation.test.js +181 -0
- package/tests/unit/contract/hierarchical.test.js +113 -0
- package/tests/unit/contract/schema.test.js +114 -0
- package/tests/unit/contract/social.test.js +217 -0
- package/tests/unit/contract/spatial.test.js +110 -0
- package/tests/unit/contract/subscriptions.test.js +128 -0
- package/tests/unit/contract/utils.test.js +159 -0
- package/tests/unit/core.test.js +152 -0
- package/tests/unit/crypto.test.js +328 -0
- package/tests/unit/federation.test.js +234 -0
- package/tests/unit/gun-async.test.js +252 -0
- package/tests/unit/hierarchical.test.js +399 -0
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
- package/tests/unit/integration/scenario-02-federation.test.js +76 -0
- package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
- package/tests/unit/integration/scenario-04-validation.test.js +129 -0
- package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
- package/tests/unit/integration/scenario-06-social.test.js +135 -0
- package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
- package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
- package/tests/unit/performance/benchmark.test.js +85 -0
- package/tests/unit/schema.test.js +213 -0
- package/tests/unit/spatial.test.js +158 -0
- package/tests/unit/storage.test.js +195 -0
- package/tests/unit/subscriptions.test.js +328 -0
- package/tests/unit/test-data-permanence-debug.js +197 -0
- package/tests/unit/test-data-permanence.js +340 -0
- package/tests/unit/test-key-persistence-fixed.js +148 -0
- package/tests/unit/test-key-persistence.js +172 -0
- package/tests/unit/test-relay-permanence.js +376 -0
- package/tests/unit/test-second-node.js +95 -0
- package/tests/unit/test-simple-write.js +89 -0
- package/vite.config.js +49 -0
- package/vitest.config.js +20 -0
- package/FEDERATION.md +0 -213
- package/compute.js +0 -298
- package/content.js +0 -1022
- package/federation.js +0 -1234
- package/global.js +0 -736
- package/hexlib.js +0 -335
- package/hologram.js +0 -183
- package/holosphere-bundle.esm.js +0 -34549
- package/holosphere-bundle.js +0 -34580
- package/holosphere-bundle.min.js +0 -49
- package/holosphere.d.ts +0 -604
- package/holosphere.js +0 -739
- package/node.js +0 -246
- package/schema.js +0 -139
- package/utils.js +0 -302
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Extractor - Extract structured data from text using JSON schemas
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Schema Extractor class
|
|
7
|
+
*/
|
|
8
|
+
export class SchemaExtractor {
|
|
9
|
+
/**
|
|
10
|
+
* @param {LLMService} llmService - LLM service instance
|
|
11
|
+
*/
|
|
12
|
+
constructor(llmService) {
|
|
13
|
+
this.llm = llmService;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract data from text based on JSON schema
|
|
18
|
+
* @param {string} text - Text to extract from
|
|
19
|
+
* @param {Object} schema - JSON schema defining expected structure
|
|
20
|
+
* @param {Object} options - Extraction options
|
|
21
|
+
* @returns {Promise<Object>} Extracted data conforming to schema
|
|
22
|
+
*/
|
|
23
|
+
async extractToSchema(text, schema, options = {}) {
|
|
24
|
+
const schemaDescription = this._describeSchema(schema);
|
|
25
|
+
|
|
26
|
+
const systemPrompt = `You are a data extraction expert. Extract structured information from the provided text according to this JSON schema:
|
|
27
|
+
|
|
28
|
+
${JSON.stringify(schema, null, 2)}
|
|
29
|
+
|
|
30
|
+
Schema field descriptions:
|
|
31
|
+
${schemaDescription}
|
|
32
|
+
|
|
33
|
+
Rules:
|
|
34
|
+
1. Extract ONLY information present in the text
|
|
35
|
+
2. Use null for fields where information is not available
|
|
36
|
+
3. Follow the exact schema structure and field types
|
|
37
|
+
4. For arrays, include all relevant items found
|
|
38
|
+
5. Return ONLY valid JSON matching the schema`;
|
|
39
|
+
|
|
40
|
+
const response = await this.llm.getJSON(systemPrompt, text, {
|
|
41
|
+
temperature: 0.2,
|
|
42
|
+
maxTokens: options.maxTokens || 2000
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return response;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate a human-readable description of the schema
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
_describeSchema(schema, path = '') {
|
|
53
|
+
const descriptions = [];
|
|
54
|
+
|
|
55
|
+
if (schema.description) {
|
|
56
|
+
descriptions.push(`${path || 'root'}: ${schema.description}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (schema.properties) {
|
|
60
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
61
|
+
const fieldPath = path ? `${path}.${key}` : key;
|
|
62
|
+
const type = prop.type || 'any';
|
|
63
|
+
const required = schema.required?.includes(key) ? ' (required)' : '';
|
|
64
|
+
const desc = prop.description || '';
|
|
65
|
+
|
|
66
|
+
descriptions.push(`- ${fieldPath} [${type}]${required}: ${desc}`);
|
|
67
|
+
|
|
68
|
+
if (prop.type === 'object' && prop.properties) {
|
|
69
|
+
descriptions.push(this._describeSchema(prop, fieldPath));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (prop.type === 'array' && prop.items) {
|
|
73
|
+
if (prop.items.properties) {
|
|
74
|
+
descriptions.push(this._describeSchema(prop.items, `${fieldPath}[]`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return descriptions.join('\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract multiple items from text
|
|
85
|
+
* @param {string} text - Text containing multiple items
|
|
86
|
+
* @param {Object} itemSchema - Schema for each item
|
|
87
|
+
* @param {Object} options - Options
|
|
88
|
+
* @returns {Promise<Object[]>} Array of extracted items
|
|
89
|
+
*/
|
|
90
|
+
async extractMultiple(text, itemSchema, options = {}) {
|
|
91
|
+
const systemPrompt = `You are a data extraction expert. Extract ALL items matching this schema from the text:
|
|
92
|
+
|
|
93
|
+
${JSON.stringify(itemSchema, null, 2)}
|
|
94
|
+
|
|
95
|
+
Return a JSON array of all items found. If no items found, return [].
|
|
96
|
+
Return ONLY valid JSON array.`;
|
|
97
|
+
|
|
98
|
+
const response = await this.llm.getJSON(systemPrompt, text, {
|
|
99
|
+
temperature: 0.2,
|
|
100
|
+
maxTokens: options.maxTokens || 3000
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return Array.isArray(response) ? response : [response];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Fill missing fields in existing data
|
|
108
|
+
* @param {Object} data - Existing data with some fields
|
|
109
|
+
* @param {string} text - Text to extract missing data from
|
|
110
|
+
* @param {Object} schema - Full schema
|
|
111
|
+
* @returns {Promise<Object>} Complete data
|
|
112
|
+
*/
|
|
113
|
+
async fillMissing(data, text, schema) {
|
|
114
|
+
const missingFields = this._findMissingFields(data, schema);
|
|
115
|
+
|
|
116
|
+
if (missingFields.length === 0) {
|
|
117
|
+
return data;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const systemPrompt = `You have existing data:
|
|
121
|
+
${JSON.stringify(data, null, 2)}
|
|
122
|
+
|
|
123
|
+
Extract ONLY the following missing fields from the text:
|
|
124
|
+
${missingFields.join(', ')}
|
|
125
|
+
|
|
126
|
+
Based on this schema:
|
|
127
|
+
${JSON.stringify(schema, null, 2)}
|
|
128
|
+
|
|
129
|
+
Return JSON with ONLY the missing fields that you can extract. Do not modify existing fields.`;
|
|
130
|
+
|
|
131
|
+
const extracted = await this.llm.getJSON(systemPrompt, text, { temperature: 0.2 });
|
|
132
|
+
|
|
133
|
+
return { ...data, ...extracted };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Find fields missing from data based on schema
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
_findMissingFields(data, schema, path = '') {
|
|
141
|
+
const missing = [];
|
|
142
|
+
|
|
143
|
+
if (schema.properties) {
|
|
144
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
145
|
+
const fieldPath = path ? `${path}.${key}` : key;
|
|
146
|
+
const value = data[key];
|
|
147
|
+
|
|
148
|
+
if (value === undefined || value === null) {
|
|
149
|
+
missing.push(fieldPath);
|
|
150
|
+
} else if (prop.type === 'object' && prop.properties) {
|
|
151
|
+
missing.push(...this._findMissingFields(value, prop, fieldPath));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return missing;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Validate extracted data against schema
|
|
161
|
+
* @param {Object} data - Extracted data
|
|
162
|
+
* @param {Object} schema - JSON schema
|
|
163
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
164
|
+
*/
|
|
165
|
+
validateExtraction(data, schema) {
|
|
166
|
+
const errors = [];
|
|
167
|
+
|
|
168
|
+
if (schema.required) {
|
|
169
|
+
for (const field of schema.required) {
|
|
170
|
+
if (data[field] === undefined || data[field] === null) {
|
|
171
|
+
errors.push(`Missing required field: ${field}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (schema.properties) {
|
|
177
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
178
|
+
const value = data[key];
|
|
179
|
+
|
|
180
|
+
if (value !== undefined && value !== null) {
|
|
181
|
+
const typeValid = this._validateType(value, prop.type);
|
|
182
|
+
if (!typeValid) {
|
|
183
|
+
errors.push(`Invalid type for ${key}: expected ${prop.type}, got ${typeof value}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
valid: errors.length === 0,
|
|
191
|
+
errors
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Validate value type
|
|
197
|
+
* @private
|
|
198
|
+
*/
|
|
199
|
+
_validateType(value, type) {
|
|
200
|
+
switch (type) {
|
|
201
|
+
case 'string':
|
|
202
|
+
return typeof value === 'string';
|
|
203
|
+
case 'number':
|
|
204
|
+
case 'integer':
|
|
205
|
+
return typeof value === 'number';
|
|
206
|
+
case 'boolean':
|
|
207
|
+
return typeof value === 'boolean';
|
|
208
|
+
case 'array':
|
|
209
|
+
return Array.isArray(value);
|
|
210
|
+
case 'object':
|
|
211
|
+
return typeof value === 'object' && !Array.isArray(value);
|
|
212
|
+
default:
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export default SchemaExtractor;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spatial Analysis - AI-powered analysis of data patterns across geographic regions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Spatial Analysis class
|
|
7
|
+
*/
|
|
8
|
+
export class SpatialAnalysis {
|
|
9
|
+
/**
|
|
10
|
+
* @param {LLMService} llmService - LLM service instance
|
|
11
|
+
* @param {Object} holosphere - HoloSphere instance
|
|
12
|
+
*/
|
|
13
|
+
constructor(llmService, holosphere = null) {
|
|
14
|
+
this.llm = llmService;
|
|
15
|
+
this.holosphere = holosphere;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Set HoloSphere instance
|
|
20
|
+
* @param {Object} holosphere - HoloSphere instance
|
|
21
|
+
*/
|
|
22
|
+
setHoloSphere(holosphere) {
|
|
23
|
+
this.holosphere = holosphere;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Analyze data patterns in a region
|
|
28
|
+
* @param {string} holon - Holon identifier
|
|
29
|
+
* @param {string} lens - Lens to analyze (optional, all if null)
|
|
30
|
+
* @param {string} aspect - Analysis aspect (activity, trends, distribution, etc.)
|
|
31
|
+
* @returns {Promise<Object>} Analysis results
|
|
32
|
+
*/
|
|
33
|
+
async analyzeRegion(holon, lens = null, aspect = 'general') {
|
|
34
|
+
if (!this.holosphere) {
|
|
35
|
+
throw new Error('HoloSphere instance required');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get data from holon
|
|
39
|
+
let data = [];
|
|
40
|
+
if (lens) {
|
|
41
|
+
data = await this.holosphere.getAll(holon, lens);
|
|
42
|
+
} else {
|
|
43
|
+
// Get from all known lenses - would need lens list
|
|
44
|
+
data = await this.holosphere.getAll(holon, 'default');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const dataStr = JSON.stringify(data.slice(0, 50), null, 2); // Sample for analysis
|
|
48
|
+
|
|
49
|
+
const systemPrompt = `You are a geospatial data analyst. Analyze the following data from a geographic region.
|
|
50
|
+
|
|
51
|
+
Region (holon): ${holon}
|
|
52
|
+
Lens: ${lens || 'all'}
|
|
53
|
+
Analysis aspect: ${aspect}
|
|
54
|
+
Total items: ${data.length}
|
|
55
|
+
|
|
56
|
+
Provide insights about:
|
|
57
|
+
1. Key patterns and trends
|
|
58
|
+
2. Notable clusters or concentrations
|
|
59
|
+
3. Anomalies or outliers
|
|
60
|
+
4. Recommendations for the region
|
|
61
|
+
|
|
62
|
+
Return JSON:
|
|
63
|
+
{
|
|
64
|
+
"summary": "brief overview",
|
|
65
|
+
"patterns": ["pattern1", "pattern2"],
|
|
66
|
+
"insights": ["insight1", "insight2"],
|
|
67
|
+
"metrics": {"metric1": value},
|
|
68
|
+
"recommendations": ["rec1", "rec2"],
|
|
69
|
+
"confidence": 0.0-1.0
|
|
70
|
+
}`;
|
|
71
|
+
|
|
72
|
+
const analysis = await this.llm.getJSON(systemPrompt, dataStr, { temperature: 0.3 });
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
holon,
|
|
76
|
+
lens,
|
|
77
|
+
aspect,
|
|
78
|
+
dataCount: data.length,
|
|
79
|
+
analysis,
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Compare data between two regions
|
|
86
|
+
* @param {string} holon1 - First holon
|
|
87
|
+
* @param {string} holon2 - Second holon
|
|
88
|
+
* @param {string} lens - Lens to compare
|
|
89
|
+
* @returns {Promise<Object>} Comparison results
|
|
90
|
+
*/
|
|
91
|
+
async compareRegions(holon1, holon2, lens) {
|
|
92
|
+
if (!this.holosphere) {
|
|
93
|
+
throw new Error('HoloSphere instance required');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data1 = await this.holosphere.getAll(holon1, lens);
|
|
97
|
+
const data2 = await this.holosphere.getAll(holon2, lens);
|
|
98
|
+
|
|
99
|
+
const systemPrompt = `You are a comparative analyst. Compare data between two geographic regions.
|
|
100
|
+
|
|
101
|
+
Region 1 (${holon1}): ${data1.length} items
|
|
102
|
+
Region 2 (${holon2}): ${data2.length} items
|
|
103
|
+
Lens: ${lens}
|
|
104
|
+
|
|
105
|
+
Analyze and compare:
|
|
106
|
+
1. Similarities between regions
|
|
107
|
+
2. Key differences
|
|
108
|
+
3. Relative strengths of each region
|
|
109
|
+
4. Opportunities for collaboration
|
|
110
|
+
|
|
111
|
+
Return JSON:
|
|
112
|
+
{
|
|
113
|
+
"summary": "brief comparison",
|
|
114
|
+
"similarities": ["sim1", "sim2"],
|
|
115
|
+
"differences": ["diff1", "diff2"],
|
|
116
|
+
"region1_strengths": ["strength1"],
|
|
117
|
+
"region2_strengths": ["strength1"],
|
|
118
|
+
"collaboration_opportunities": ["opp1"],
|
|
119
|
+
"metrics_comparison": {"metric": {"region1": val, "region2": val}}
|
|
120
|
+
}`;
|
|
121
|
+
|
|
122
|
+
const userMessage = `Region 1 data sample:
|
|
123
|
+
${JSON.stringify(data1.slice(0, 25), null, 2)}
|
|
124
|
+
|
|
125
|
+
Region 2 data sample:
|
|
126
|
+
${JSON.stringify(data2.slice(0, 25), null, 2)}`;
|
|
127
|
+
|
|
128
|
+
const comparison = await this.llm.getJSON(systemPrompt, userMessage, { temperature: 0.3 });
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
regions: [holon1, holon2],
|
|
132
|
+
lens,
|
|
133
|
+
dataCounts: { [holon1]: data1.length, [holon2]: data2.length },
|
|
134
|
+
comparison,
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Identify spatial trends over time
|
|
141
|
+
* @param {string} holon - Holon identifier
|
|
142
|
+
* @param {string} lens - Lens to analyze
|
|
143
|
+
* @param {Object} timeRange - Time range {start, end}
|
|
144
|
+
* @returns {Promise<Object>} Trend analysis
|
|
145
|
+
*/
|
|
146
|
+
async spatialTrends(holon, lens, timeRange = {}) {
|
|
147
|
+
if (!this.holosphere) {
|
|
148
|
+
throw new Error('HoloSphere instance required');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const data = await this.holosphere.getAll(holon, lens);
|
|
152
|
+
|
|
153
|
+
// Filter by time if timestamps available
|
|
154
|
+
let filteredData = data;
|
|
155
|
+
if (timeRange.start || timeRange.end) {
|
|
156
|
+
filteredData = data.filter(item => {
|
|
157
|
+
const ts = item.timestamp || item.created_at || item.date;
|
|
158
|
+
if (!ts) return true;
|
|
159
|
+
|
|
160
|
+
const itemTime = new Date(ts).getTime();
|
|
161
|
+
if (timeRange.start && itemTime < new Date(timeRange.start).getTime()) return false;
|
|
162
|
+
if (timeRange.end && itemTime > new Date(timeRange.end).getTime()) return false;
|
|
163
|
+
return true;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const systemPrompt = `You are a trend analyst. Analyze temporal patterns in geographic data.
|
|
168
|
+
|
|
169
|
+
Region: ${holon}
|
|
170
|
+
Lens: ${lens}
|
|
171
|
+
Time range: ${JSON.stringify(timeRange)}
|
|
172
|
+
Items analyzed: ${filteredData.length}
|
|
173
|
+
|
|
174
|
+
Identify:
|
|
175
|
+
1. Growth/decline trends
|
|
176
|
+
2. Seasonal patterns
|
|
177
|
+
3. Emerging themes
|
|
178
|
+
4. Predictions
|
|
179
|
+
|
|
180
|
+
Return JSON:
|
|
181
|
+
{
|
|
182
|
+
"trends": [{"trend": "description", "direction": "up|down|stable", "strength": 0.0-1.0}],
|
|
183
|
+
"patterns": ["pattern1"],
|
|
184
|
+
"emerging_themes": ["theme1"],
|
|
185
|
+
"predictions": ["pred1"],
|
|
186
|
+
"summary": "overview"
|
|
187
|
+
}`;
|
|
188
|
+
|
|
189
|
+
const trends = await this.llm.getJSON(
|
|
190
|
+
systemPrompt,
|
|
191
|
+
JSON.stringify(filteredData.slice(0, 50), null, 2),
|
|
192
|
+
{ temperature: 0.3 }
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
holon,
|
|
197
|
+
lens,
|
|
198
|
+
timeRange,
|
|
199
|
+
dataCount: filteredData.length,
|
|
200
|
+
trends,
|
|
201
|
+
timestamp: Date.now(),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Find hotspots in hierarchical data
|
|
207
|
+
* @param {string} holon - Starting holon
|
|
208
|
+
* @param {string} lens - Lens to analyze
|
|
209
|
+
* @param {string} metric - Metric to identify hotspots by
|
|
210
|
+
* @returns {Promise<Object>} Hotspot analysis
|
|
211
|
+
*/
|
|
212
|
+
async findHotspots(holon, lens, metric = 'count') {
|
|
213
|
+
if (!this.holosphere) {
|
|
214
|
+
throw new Error('HoloSphere instance required');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Get data from holon and children
|
|
218
|
+
const data = await this.holosphere.getAll(holon, lens);
|
|
219
|
+
|
|
220
|
+
const systemPrompt = `You are a spatial analyst. Identify hotspots (areas of high concentration/activity) in geographic data.
|
|
221
|
+
|
|
222
|
+
Region: ${holon}
|
|
223
|
+
Lens: ${lens}
|
|
224
|
+
Metric: ${metric}
|
|
225
|
+
Data points: ${data.length}
|
|
226
|
+
|
|
227
|
+
Identify:
|
|
228
|
+
1. High-activity areas
|
|
229
|
+
2. Emerging areas
|
|
230
|
+
3. Underserved areas
|
|
231
|
+
4. Recommended focus areas
|
|
232
|
+
|
|
233
|
+
Return JSON:
|
|
234
|
+
{
|
|
235
|
+
"hotspots": [{"location": "desc", "intensity": 0.0-1.0, "type": "high_activity|emerging|underserved"}],
|
|
236
|
+
"concentration_patterns": ["pattern1"],
|
|
237
|
+
"recommendations": ["rec1"],
|
|
238
|
+
"summary": "overview"
|
|
239
|
+
}`;
|
|
240
|
+
|
|
241
|
+
return this.llm.getJSON(
|
|
242
|
+
systemPrompt,
|
|
243
|
+
JSON.stringify(data.slice(0, 50), null, 2),
|
|
244
|
+
{ temperature: 0.3 }
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generate spatial report for a region
|
|
250
|
+
* @param {string} holon - Holon identifier
|
|
251
|
+
* @param {Object} options - Report options
|
|
252
|
+
* @returns {Promise<string>} Formatted report
|
|
253
|
+
*/
|
|
254
|
+
async generateReport(holon, options = {}) {
|
|
255
|
+
if (!this.holosphere) {
|
|
256
|
+
throw new Error('HoloSphere instance required');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const lenses = options.lenses || ['default'];
|
|
260
|
+
const allData = {};
|
|
261
|
+
|
|
262
|
+
for (const lens of lenses) {
|
|
263
|
+
allData[lens] = await this.holosphere.getAll(holon, lens);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const systemPrompt = `You are a regional analyst. Generate a comprehensive report for this geographic region.
|
|
267
|
+
|
|
268
|
+
Region: ${holon}
|
|
269
|
+
Data by lens: ${Object.entries(allData).map(([l, d]) => `${l}: ${d.length} items`).join(', ')}
|
|
270
|
+
|
|
271
|
+
Generate a professional report including:
|
|
272
|
+
1. Executive Summary
|
|
273
|
+
2. Key Statistics
|
|
274
|
+
3. Notable Patterns
|
|
275
|
+
4. Opportunities
|
|
276
|
+
5. Recommendations
|
|
277
|
+
|
|
278
|
+
Format as markdown.`;
|
|
279
|
+
|
|
280
|
+
const dataSample = {};
|
|
281
|
+
for (const [lens, data] of Object.entries(allData)) {
|
|
282
|
+
dataSample[lens] = data.slice(0, 20);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return this.llm.sendMessage(
|
|
286
|
+
systemPrompt,
|
|
287
|
+
JSON.stringify(dataSample, null, 2),
|
|
288
|
+
{ temperature: 0.5, maxTokens: 2000 }
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export default SpatialAnalysis;
|
package/src/ai/tts.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Speech - OpenAI TTS integration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Available voices for TTS
|
|
7
|
+
*/
|
|
8
|
+
export const VOICES = {
|
|
9
|
+
ALLOY: 'alloy',
|
|
10
|
+
ECHO: 'echo',
|
|
11
|
+
FABLE: 'fable',
|
|
12
|
+
ONYX: 'onyx',
|
|
13
|
+
NOVA: 'nova',
|
|
14
|
+
SHIMMER: 'shimmer',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Available TTS models
|
|
19
|
+
*/
|
|
20
|
+
export const MODELS = {
|
|
21
|
+
TTS_1: 'tts-1',
|
|
22
|
+
TTS_1_HD: 'tts-1-hd',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* TTS class for text-to-speech conversion
|
|
27
|
+
*/
|
|
28
|
+
export class TTS {
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} openai - OpenAI client instance
|
|
31
|
+
*/
|
|
32
|
+
constructor(openai) {
|
|
33
|
+
this.openai = openai;
|
|
34
|
+
this.defaultVoice = VOICES.ECHO;
|
|
35
|
+
this.defaultModel = MODELS.TTS_1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert text to speech
|
|
40
|
+
* @param {string} text - Text to convert
|
|
41
|
+
* @param {Object} options - TTS options
|
|
42
|
+
* @returns {Promise<Buffer>} Audio buffer (MP3)
|
|
43
|
+
*/
|
|
44
|
+
async speak(text, options = {}) {
|
|
45
|
+
const {
|
|
46
|
+
voice = this.defaultVoice,
|
|
47
|
+
model = this.defaultModel,
|
|
48
|
+
speed = 1.0,
|
|
49
|
+
responseFormat = 'mp3',
|
|
50
|
+
} = options;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const response = await this.openai.audio.speech.create({
|
|
54
|
+
model,
|
|
55
|
+
voice,
|
|
56
|
+
input: text,
|
|
57
|
+
speed,
|
|
58
|
+
response_format: responseFormat,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Convert response to Buffer
|
|
62
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
63
|
+
return Buffer.from(arrayBuffer);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`TTS failed: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Convert text to speech and return as base64
|
|
71
|
+
* @param {string} text - Text to convert
|
|
72
|
+
* @param {Object} options - TTS options
|
|
73
|
+
* @returns {Promise<string>} Base64 encoded audio
|
|
74
|
+
*/
|
|
75
|
+
async speakBase64(text, options = {}) {
|
|
76
|
+
const buffer = await this.speak(text, options);
|
|
77
|
+
return buffer.toString('base64');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convert text to speech and return as data URL
|
|
82
|
+
* @param {string} text - Text to convert
|
|
83
|
+
* @param {Object} options - TTS options
|
|
84
|
+
* @returns {Promise<string>} Data URL for audio
|
|
85
|
+
*/
|
|
86
|
+
async speakDataUrl(text, options = {}) {
|
|
87
|
+
const format = options.responseFormat || 'mp3';
|
|
88
|
+
const mimeType = format === 'mp3' ? 'audio/mpeg' : `audio/${format}`;
|
|
89
|
+
const base64 = await this.speakBase64(text, options);
|
|
90
|
+
return `data:${mimeType};base64,${base64}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Set default voice
|
|
95
|
+
* @param {string} voice - Voice name
|
|
96
|
+
*/
|
|
97
|
+
setDefaultVoice(voice) {
|
|
98
|
+
if (!Object.values(VOICES).includes(voice)) {
|
|
99
|
+
throw new Error(`Invalid voice: ${voice}. Use one of: ${Object.values(VOICES).join(', ')}`);
|
|
100
|
+
}
|
|
101
|
+
this.defaultVoice = voice;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Set default model
|
|
106
|
+
* @param {string} model - Model name
|
|
107
|
+
*/
|
|
108
|
+
setDefaultModel(model) {
|
|
109
|
+
if (!Object.values(MODELS).includes(model)) {
|
|
110
|
+
throw new Error(`Invalid model: ${model}. Use one of: ${Object.values(MODELS).join(', ')}`);
|
|
111
|
+
}
|
|
112
|
+
this.defaultModel = model;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get available voices
|
|
117
|
+
* @returns {string[]} Available voice names
|
|
118
|
+
*/
|
|
119
|
+
static getVoices() {
|
|
120
|
+
return Object.values(VOICES);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get available models
|
|
125
|
+
* @returns {string[]} Available model names
|
|
126
|
+
*/
|
|
127
|
+
static getModels() {
|
|
128
|
+
return Object.values(MODELS);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Estimate audio duration (approximate)
|
|
133
|
+
* @param {string} text - Text to estimate
|
|
134
|
+
* @param {number} speed - Speech speed (default: 1.0)
|
|
135
|
+
* @returns {number} Estimated duration in seconds
|
|
136
|
+
*/
|
|
137
|
+
static estimateDuration(text, speed = 1.0) {
|
|
138
|
+
// Average speaking rate: ~150 words per minute
|
|
139
|
+
const words = text.split(/\s+/).length;
|
|
140
|
+
const minutes = words / 150;
|
|
141
|
+
return (minutes * 60) / speed;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Split text into chunks for long content
|
|
146
|
+
* @param {string} text - Long text
|
|
147
|
+
* @param {number} maxChars - Max characters per chunk (default: 4000)
|
|
148
|
+
* @returns {string[]} Text chunks
|
|
149
|
+
*/
|
|
150
|
+
static splitText(text, maxChars = 4000) {
|
|
151
|
+
if (text.length <= maxChars) {
|
|
152
|
+
return [text];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const chunks = [];
|
|
156
|
+
const sentences = text.split(/(?<=[.!?])\s+/);
|
|
157
|
+
let currentChunk = '';
|
|
158
|
+
|
|
159
|
+
for (const sentence of sentences) {
|
|
160
|
+
if ((currentChunk + sentence).length > maxChars) {
|
|
161
|
+
if (currentChunk) {
|
|
162
|
+
chunks.push(currentChunk.trim());
|
|
163
|
+
}
|
|
164
|
+
currentChunk = sentence;
|
|
165
|
+
} else {
|
|
166
|
+
currentChunk += (currentChunk ? ' ' : '') + sentence;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (currentChunk) {
|
|
171
|
+
chunks.push(currentChunk.trim());
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return chunks;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Convert long text to speech (handles chunking)
|
|
179
|
+
* @param {string} text - Long text
|
|
180
|
+
* @param {Object} options - TTS options
|
|
181
|
+
* @returns {Promise<Buffer[]>} Array of audio buffers
|
|
182
|
+
*/
|
|
183
|
+
async speakLong(text, options = {}) {
|
|
184
|
+
const chunks = TTS.splitText(text, options.maxChars || 4000);
|
|
185
|
+
|
|
186
|
+
const buffers = await Promise.all(
|
|
187
|
+
chunks.map(chunk => this.speak(chunk, options))
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return buffers;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export default TTS;
|