Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 11s
Build & Deploy / 🧪 QA (push) Successful in 1m18s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
CI - Lint, Typecheck & Test / quality-assurance (pull_request) Failing after 3m55s
135 lines
3.3 KiB
TypeScript
135 lines
3.3 KiB
TypeScript
import { QdrantClient } from '@qdrant/js-client-rest';
|
|
|
|
const isDockerContainer =
|
|
process.env.IS_DOCKER === 'true' || process.env.HOSTNAME?.includes('klz-app');
|
|
const qdrantUrl =
|
|
process.env.QDRANT_URL ||
|
|
(isDockerContainer ? 'http://klz-qdrant:6333' : 'http://localhost:6333');
|
|
const qdrantApiKey = process.env.QDRANT_API_KEY || '';
|
|
|
|
export const qdrant = new QdrantClient({
|
|
url: qdrantUrl,
|
|
apiKey: qdrantApiKey || undefined,
|
|
});
|
|
|
|
export const COLLECTION_NAME = 'klz_products';
|
|
export const VECTOR_SIZE = 1536; // OpenAI text-embedding-3-small
|
|
|
|
/**
|
|
* Ensure the collection exists in Qdrant.
|
|
*/
|
|
export async function ensureCollection() {
|
|
try {
|
|
const collections = await qdrant.getCollections();
|
|
const exists = collections.collections.some((c) => c.name === COLLECTION_NAME);
|
|
if (!exists) {
|
|
await qdrant.createCollection(COLLECTION_NAME, {
|
|
vectors: {
|
|
size: VECTOR_SIZE,
|
|
distance: 'Cosine',
|
|
},
|
|
});
|
|
console.log(`Successfully created Qdrant collection: ${COLLECTION_NAME}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error ensuring Qdrant collection:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate an embedding for a given text using OpenRouter (OpenAI embedding proxy)
|
|
*/
|
|
export async function generateEmbedding(text: string): Promise<number[]> {
|
|
const openRouterKey = process.env.OPENROUTER_API_KEY;
|
|
if (!openRouterKey) {
|
|
throw new Error('OPENROUTER_API_KEY is not set');
|
|
}
|
|
|
|
const response = await fetch('https://openrouter.ai/api/v1/embeddings', {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${openRouterKey}`,
|
|
'Content-Type': 'application/json',
|
|
'HTTP-Referer': process.env.NEXT_PUBLIC_BASE_URL || 'https://klz-cables.com',
|
|
'X-Title': 'KLZ Cables Search AI',
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'openai/text-embedding-3-small',
|
|
input: text,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
throw new Error(
|
|
`Failed to generate embedding: ${response.status} ${response.statusText} ${errorBody}`,
|
|
);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.data[0].embedding;
|
|
}
|
|
|
|
/**
|
|
* Upsert a product into Qdrant
|
|
*/
|
|
export async function upsertProductVector(
|
|
id: string | number,
|
|
text: string,
|
|
payload: Record<string, any>,
|
|
) {
|
|
try {
|
|
await ensureCollection();
|
|
const vector = await generateEmbedding(text);
|
|
|
|
await qdrant.upsert(COLLECTION_NAME, {
|
|
wait: true,
|
|
points: [
|
|
{
|
|
id: id,
|
|
vector,
|
|
payload,
|
|
},
|
|
],
|
|
});
|
|
} catch (error) {
|
|
console.error('Error writing to Qdrant:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a product from Qdrant
|
|
*/
|
|
export async function deleteProductVector(id: string | number) {
|
|
try {
|
|
await ensureCollection();
|
|
await qdrant.delete(COLLECTION_NAME, {
|
|
wait: true,
|
|
points: [id] as [string | number],
|
|
});
|
|
} catch (error) {
|
|
console.error('Error deleting from Qdrant:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search products in Qdrant
|
|
*/
|
|
export async function searchProducts(query: string, limit = 5) {
|
|
try {
|
|
await ensureCollection();
|
|
const vector = await generateEmbedding(query);
|
|
|
|
const results = await qdrant.search(COLLECTION_NAME, {
|
|
vector,
|
|
limit,
|
|
with_payload: true,
|
|
});
|
|
|
|
return results;
|
|
} catch (error) {
|
|
console.error('Error searching in Qdrant:', error);
|
|
return [];
|
|
}
|
|
}
|