Skip to content

Commit a3b4679

Browse files
committed
added sort by property feature
1 parent 4bfc3cf commit a3b4679

File tree

9 files changed

+545
-25
lines changed

9 files changed

+545
-25
lines changed
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
You are a real estate agent for virtual properties in a futuristic city metaverse.
2+
Your job is to examine the recent conversation messages and extract key information about the user's property search request.
3+
4+
From the recent messages given below, analyze the most recent relevant message to generate:
5+
1. A natural language search query
6+
2. Any ordering preferences (largest, smallest, cheapest, etc.)
7+
3. Whether the user is specifically looking for properties that are for sale
8+
9+
Output your analysis as a JSON object with the following fields:
10+
- searchQuery: A single line of text representing the search query
11+
- orderByParameter: One of [largest, smallest, cheapest, mostExpensive, tallest, shortest, closestToOcean, closestToBay, none]
12+
- salesOnly: true if the user is specifically looking for properties that are for sale/available to buy
13+
14+
IMPORTANT: Use the most recent user message to generate the search query and ordering preferences.
15+
16+
Recent Messages:
17+
{{recentMessages}}
18+
19+
#EXAMPLES of valid responses:
20+
For "Show me the largest plots in Space Mind":
21+
{
22+
"searchQuery": "Which Space Mind properties are largest?",
23+
"orderByParameter": "largest",
24+
"salesOnly": false
25+
}
26+
27+
For "What's available to buy in Flashing Lights?":
28+
{
29+
"searchQuery": "Which Flashing Lights properties are for sale?",
30+
"orderByParameter": "none",
31+
"salesOnly": true
32+
}
33+
34+
For "Find the cheapest property near the ocean":
35+
{
36+
"searchQuery": "Which properties are close to the ocean?",
37+
"orderByParameter": "cheapest",
38+
"salesOnly": true
39+
}
40+
For "Which Space Mind property is closest to the Ocean?":
41+
{
42+
"searchQuery": "Which Space Mind property is closest to the Ocean?",
43+
"orderByParameter": "closestToOcean",
44+
"salesOnly": false
45+
}
46+
For "Which SM property is closest to the Ocean?":
47+
{
48+
"searchQuery": "Which Space Mind property is closest to the Ocean?",
49+
"orderByParameter": "closestToOcean",
50+
"salesOnly": false
51+
}
52+
For "Which properties in Tranquility Gardens are at least Macro in size?":
53+
{
54+
"searchQuery": "Which properties in Tranquility Gardens are at least Macro in size?",
55+
"orderByParameter": "largest",
56+
"salesOnly": false
57+
}
58+
For "Which buildings in Nexus are industrial are for sale?":
59+
{
60+
"searchQuery": "Which buildings in Nexus are industrial?",
61+
"orderByParameter": "none",
62+
"salesOnly": true
63+
}
64+
For "Which plots in TG/LM are at least Micro in size?":
65+
{
66+
"searchQuery": "Which plots in TG/LM are at least Micro in size?",
67+
"orderByParameter": "largest",
68+
"salesOnly": false
69+
}
70+
71+
#GUIDELINES for Analysis:
72+
73+
1. Order Parameter Detection:
74+
- "largest", "biggest" -> largest
75+
- "smallest", "tiniest" -> smallest
76+
- "cheapest", "lowest price" -> cheapest
77+
- "most expensive", "highest price" -> mostExpensive
78+
- "tallest", "highest" -> tallest
79+
- "shortest", "lowest" -> shortest
80+
- "closest to ocean", "nearest to ocean" -> closestToOcean
81+
- "closest to bay", "nearest to bay" -> closestToBay
82+
- If no ordering mentioned -> none
83+
84+
2. Sales Only Detection (true if):
85+
- User mentions "buy", "purchase", "for sale", "available", "price", "cost"
86+
- User asks about "listings" or "market"
87+
- User specifically asks about NFT prices or availability
88+
89+
#For reference, here are COMMON ABBREVIATIONS, NEIGHBORHOODS, AND ZONING TYPES:
90+
**1. Property Attributes**
91+
- `Nexus`
92+
- `Flashing Lights` (FL)
93+
- `Space Mind` (SM)
94+
- `North Star` (NS)
95+
- `District ZERO` (DZ)
96+
- `Tranquility Gardens` (TG)
97+
- `Little Meow` (LM)
98+
- `Haven Heights` (HH)
99+
- **Zoning Types**: `Legendary, Mixed Use, Industrial, Residential, Commercial`
100+
- **Plot Sizes**: `Nano, Micro, Macro, Mid, Mega, Mammoth, Giga`
101+
- **Building Types**: `Lowrise, Highrise, Tall, Megatall`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { config } from './config/index.js';
2+
import { PostgresDatabaseAdapter } from '@ai16z/adapter-postgres';
3+
import { LandDatabaseAdapter } from '@ai16z/plugin-unreal';
4+
import { PostgresLandDataProvider } from '@ai16z/plugin-unreal';
5+
import { PropertySearchManager } from '@ai16z/plugin-unreal';
6+
import { TestRuntime } from './runtime/TestRuntime.js';
7+
import { PlotSize, LandPlotMemory, NFTPrice, DistanceCategory } from '@ai16z/plugin-unreal';
8+
9+
async function main() {
10+
const userId = 'test-user-1';
11+
let searchManager: PropertySearchManager | undefined;
12+
let postgresAdapter: PostgresDatabaseAdapter | undefined;
13+
let runtime: TestRuntime | undefined;
14+
15+
try {
16+
// Initialize components
17+
postgresAdapter = new PostgresDatabaseAdapter(config.database);
18+
await postgresAdapter.init(); // Make sure to initialize the adapter
19+
20+
const landDbAdapter = new LandDatabaseAdapter(postgresAdapter);
21+
const landDataProvider = new PostgresLandDataProvider(landDbAdapter);
22+
runtime = new TestRuntime(postgresAdapter);
23+
searchManager = new PropertySearchManager(runtime);
24+
25+
console.log('Initializing search session...');
26+
await searchManager.initializeNewSearchSession(userId);
27+
28+
const searchMetadata = {
29+
searchText: "Looking for space mind properties",
30+
metadata: {
31+
neighborhoods: ["Space Mind"],
32+
plotSizes: [],
33+
buildingTypes: [],
34+
zoningTypes: [],
35+
}
36+
};
37+
38+
// create
39+
console.log('Executing search with metadata:', searchMetadata);
40+
const enrichedResults = await searchManager.executeSearchV2(searchMetadata);
41+
42+
if (!enrichedResults || enrichedResults.length === 0) {
43+
console.log('No property results found');
44+
return;
45+
}
46+
47+
// Display results
48+
console.log(`\nFound ${enrichedResults.length} total properties:`);
49+
50+
const matchedResults = enrichedResults.filter(result => result.content.metadata.nftData?.price);
51+
console.log(`${matchedResults.length} properties have NFT price matches:\n`);
52+
53+
matchedResults.forEach((result, index) => {
54+
console.log(`Result ${index + 1}:`);
55+
console.log('Property:', result.content.metadata.name);
56+
console.log('NFT Match Found:');
57+
console.log('- Price:', result.content.metadata.nftData?.price || 'N/A');
58+
console.log('- Token ID:', result.content.metadata.tokenId);
59+
console.log('- Last Updated:', result.content.metadata.nftData?.lastUpdated || 'N/A');
60+
console.log();
61+
});
62+
63+
} catch (error) {
64+
console.error('Error:', error instanceof Error ? error.message : error);
65+
} finally {
66+
try {
67+
/* if (searchManager) {
68+
await searchManager.cleanup?.();
69+
}
70+
if (runtime) {
71+
await runtime.cleanup?.();
72+
}
73+
if (postgresAdapter) {
74+
await postgresAdapter.close();
75+
} */
76+
process.exit(0);
77+
} catch (cleanupError) {
78+
console.error('Error during cleanup:', cleanupError);
79+
process.exit(1);
80+
}
81+
}
82+
}
83+
84+
main().catch(console.error);

packages/plugin-unreal/src/adapters/PostgresLandDataProvider.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { LandDatabaseAdapter } from '../database/land_database_adapter';
22
import { ILandDataProvider } from '../interfaces/ILandDataProvider';
3-
import { LandPlotMemory, LandSearchParams } from '../types';
3+
import { LandPlotMemory, LandSearchParams, OrderByParameter } from '../types';
44
import { UUID } from '@ai16z/eliza';
55

66
export class PostgresLandDataProvider implements ILandDataProvider {
@@ -26,4 +26,8 @@ export class PostgresLandDataProvider implements ILandDataProvider {
2626
async searchLandByMetadata(params: LandSearchParams): Promise<LandPlotMemory[]> {
2727
return await this.dbAdapter.searchLandByMetadata(params);
2828
}
29+
30+
async searchLandByMetadataV2(params: LandSearchParams, orderBy?: OrderByParameter): Promise<LandPlotMemory[]> {
31+
return await this.dbAdapter.searchLandByMetadataV2(params, orderBy);
32+
}
2933
}

packages/plugin-unreal/src/database/land_database_adapter.ts

+161-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
LAND_TABLE,
88
LAND_ROOM_ID,
99
LAND_AGENT_ID,
10-
DEFAULT_MATCH_THRESHOLD
10+
DEFAULT_MATCH_THRESHOLD,
11+
OrderByParameter
1112
} from "../types";
1213

1314
const LAND_MEMORY_TYPE = 'land_plot';
@@ -52,7 +53,6 @@ export class LandDatabaseAdapter {
5253
async removeAllLandMemories(roomId: UUID): Promise<void> {
5354
await this.dbAdapter.removeAllMemories(roomId, LAND_MEMORY_TYPE, LAND_TABLE);
5455
} */
55-
5656
async searchLandByMetadata(params: LandSearchParams): Promise<LandPlotMemory[]> {
5757
let sql = `
5858
SELECT * FROM ${LAND_TABLE}
@@ -166,6 +166,165 @@ export class LandDatabaseAdapter {
166166
}
167167
}
168168

169+
async searchLandByMetadataV2(params: LandSearchParams, orderBy?: OrderByParameter): Promise<LandPlotMemory[]> {
170+
let sql = `
171+
SELECT * FROM ${LAND_TABLE}
172+
WHERE type = $1
173+
AND content IS NOT NULL
174+
`;
175+
const values: any[] = [LAND_MEMORY_TYPE];
176+
let paramCount = 1;
177+
178+
// Add names condition
179+
if (params.names?.length) {
180+
paramCount++;
181+
sql += ` AND content->'metadata'->>'name' = ANY($${paramCount}::text[])`;
182+
values.push(params.names);
183+
}
184+
185+
if (params.neighborhoods?.length) {
186+
paramCount++;
187+
sql += ` AND content->'metadata'->>'neighborhood' = ANY($${paramCount}::text[])`;
188+
values.push(params.neighborhoods);
189+
}
190+
191+
if (params.zoningTypes?.length) {
192+
paramCount++;
193+
sql += ` AND content->'metadata'->>'zoning' = ANY($${paramCount}::text[])`;
194+
values.push(params.zoningTypes);
195+
}
196+
197+
if (params.plotSizes?.length) {
198+
paramCount++;
199+
sql += ` AND content->'metadata'->>'plotSize' = ANY($${paramCount}::text[])`;
200+
values.push(params.plotSizes);
201+
}
202+
203+
if (params.buildingTypes?.length) {
204+
paramCount++;
205+
sql += ` AND content->'metadata'->>'buildingType' = ANY($${paramCount}::text[])`;
206+
values.push(params.buildingTypes);
207+
}
208+
209+
if (params.distances?.ocean) {
210+
if (params.distances.ocean.maxMeters) {
211+
paramCount++;
212+
sql += ` AND (content->'metadata'->'distances'->'ocean'->>'meters')::int <= $${paramCount}`;
213+
values.push(params.distances.ocean.maxMeters);
214+
}
215+
if (params.distances.ocean.category) {
216+
paramCount++;
217+
sql += ` AND content->'metadata'->'distances'->'ocean'->>'category' = $${paramCount}`;
218+
values.push(params.distances.ocean.category);
219+
}
220+
}
221+
222+
if (params.distances?.bay) {
223+
if (params.distances.bay.maxMeters) {
224+
paramCount++;
225+
sql += ` AND (content->'metadata'->'distances'->'bay'->>'meters')::int <= $${paramCount}`;
226+
values.push(params.distances.bay.maxMeters);
227+
}
228+
if (params.distances.bay.category) {
229+
paramCount++;
230+
sql += ` AND content->'metadata'->'distances'->'bay'->>'category' = $${paramCount}`;
231+
values.push(params.distances.bay.category);
232+
}
233+
}
234+
235+
if (params.building?.floors) {
236+
if (params.building.floors.min) {
237+
paramCount++;
238+
sql += ` AND (content->'metadata'->'building'->'floors'->>'min')::int >= $${paramCount}`;
239+
values.push(params.building.floors.min);
240+
}
241+
if (params.building.floors.max) {
242+
paramCount++;
243+
sql += ` AND (content->'metadata'->'building'->'floors'->>'max')::int <= $${paramCount}`;
244+
values.push(params.building.floors.max);
245+
}
246+
}
247+
248+
if (params.building?.height) {
249+
if (params.building.height.min) {
250+
paramCount++;
251+
sql += ` AND (content->'metadata'->'building'->'height'->>'min')::int >= $${paramCount}`;
252+
values.push(params.building.height.min);
253+
}
254+
if (params.building.height.max) {
255+
paramCount++;
256+
sql += ` AND (content->'metadata'->'building'->'height'->>'max')::int <= $${paramCount}`;
257+
values.push(params.building.height.max);
258+
}
259+
}
260+
261+
if (params.rarity?.rankRange) {
262+
if (params.rarity.rankRange.min) {
263+
paramCount++;
264+
sql += ` AND (content->'metadata'->>'rank')::int >= $${paramCount}`;
265+
values.push(params.rarity.rankRange.min);
266+
}
267+
if (params.rarity.rankRange.max) {
268+
paramCount++;
269+
sql += ` AND (content->'metadata'->>'rank')::int <= $${paramCount}`;
270+
values.push(params.rarity.rankRange.max);
271+
}
272+
}
273+
274+
// Add tokenId search condition
275+
if (params.tokenId) {
276+
paramCount++;
277+
sql += ` AND content->'metadata'->>'tokenId' = $${paramCount}`;
278+
values.push(params.tokenId);
279+
}
280+
281+
// Add ORDER BY clause based on orderBy parameter
282+
if (orderBy) {
283+
console.log("ORDER BY", orderBy);
284+
switch (orderBy) {
285+
case OrderByParameter.Largest:
286+
sql += ` ORDER BY (content->'metadata'->'plotArea')::float DESC NULLS LAST`;
287+
break;
288+
case OrderByParameter.Smallest:
289+
sql += ` ORDER BY (content->'metadata'->'plotArea')::float ASC NULLS LAST`;
290+
break;
291+
case OrderByParameter.Cheapest:
292+
sql += ` ORDER BY (content->'metadata'->'nftData'->'price')::float ASC NULLS LAST`;
293+
break;
294+
case OrderByParameter.MostExpensive:
295+
sql += ` ORDER BY (content->'metadata'->'nftData'->'price')::float DESC NULLS LAST`;
296+
break;
297+
case OrderByParameter.Tallest:
298+
sql += ` ORDER BY (content->'metadata'->'building'->'height'->>'max')::int DESC NULLS LAST`;
299+
break;
300+
case OrderByParameter.Shortest:
301+
sql += ` ORDER BY (content->'metadata'->'building'->'height'->>'max')::int ASC NULLS LAST`;
302+
break;
303+
case OrderByParameter.ClosestToOcean:
304+
sql += ` ORDER BY (content->'metadata'->'distances'->'ocean'->>'meters')::int ASC`;
305+
break;
306+
case OrderByParameter.ClosestToBay:
307+
sql += ` ORDER BY (content->'metadata'->'distances'->'bay'->>'meters')::int ASC`;
308+
break;
309+
}
310+
}
311+
312+
sql += ` LIMIT 100`; // Add a reasonable limit
313+
314+
try {
315+
const { rows } = await (this.dbAdapter as PostgresDatabaseAdapter).query(sql, values);
316+
return rows.map(row => ({
317+
...row,
318+
content: typeof row.content === 'string' ? JSON.parse(row.content) : row.content
319+
}));
320+
} catch (error) {
321+
elizaLogger.error('Error in searchLandByMetadataV2:', {
322+
error: error instanceof Error ? error.message : String(error),
323+
params
324+
});
325+
throw error;
326+
}
327+
}
169328

170329
async getPropertiesByRarityRange(
171330
minRank: number,

0 commit comments

Comments
 (0)