feat: konu bazlı bildirim + gerçek site tarama
1) Bildirim artık KONU odaklı (il değil)
- Kullanıcı hangi konularda ihale alabildiğini seçer; varsayılan tüm Türkiye
- İl/alım türü/favori kurum 'daha da daralt' altında isteğe bağlı
- notify-store: konular kolonu + otomatik şema geçişi
- notifier: konu başlıkları ihale başlığı/sektör/branş/kalemlerde aranır
2) Gerçek firma sitesi tarama (webScraperService mock'unun yerine)
- server/site-scan.mjs: siteyi GERÇEKTEN çeker, ana sayfa + ürün/hizmet/
hakkımızda iç sayfalarını tarar, HTML'i metne çevirir, AI ile firmanın
tedarik edebildiği ihale konularını çıkarır ('uydurma' yasaklı prompt)
- /api/company/scan (auth'lu)
- ai.mjs: systemOverride desteği (farklı görevler için ayrı sistem promptu)
- Arayüz: 'Sitenizi tarayıp konuları otomatik önerelim' → tıklayarak ekleme
Not: eski webScraperService.js sabit anahtar-kelime sözlüğüydü; domain adında
'tanriverdi' geçince tıbbi ürün listesi uyduruyordu.
This commit is contained in:
+14
-13
@@ -118,7 +118,7 @@ function buildUserPrompt({ text, fileName, tender }) {
|
||||
}
|
||||
|
||||
// --- OpenRouter (OpenAI uyumlu) ---
|
||||
async function callOpenRouter(model, key, userPrompt, signal) {
|
||||
async function callOpenRouter(model, key, userPrompt, signal, sistem = SYSTEM_PROMPT) {
|
||||
const res = await fetch(OPENROUTER_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -131,7 +131,7 @@ async function callOpenRouter(model, key, userPrompt, signal) {
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [
|
||||
{ role: 'system', content: SYSTEM_PROMPT },
|
||||
{ role: 'system', content: sistem },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
temperature: 0.2,
|
||||
@@ -146,14 +146,14 @@ async function callOpenRouter(model, key, userPrompt, signal) {
|
||||
return data?.choices?.[0]?.message?.content || '';
|
||||
}
|
||||
|
||||
async function callOpenAiStyle(p, key, model, userPrompt, signal) {
|
||||
async function callOpenAiStyle(p, key, model, userPrompt, signal, sistem = SYSTEM_PROMPT) {
|
||||
const res = await fetch(p.url, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [
|
||||
{ role: 'system', content: SYSTEM_PROMPT },
|
||||
{ role: 'system', content: sistem },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
temperature: 0.2,
|
||||
@@ -166,13 +166,13 @@ async function callOpenAiStyle(p, key, model, userPrompt, signal) {
|
||||
return data?.choices?.[0]?.message?.content || '';
|
||||
}
|
||||
|
||||
async function callGemini(p, key, model, userPrompt, signal) {
|
||||
async function callGemini(p, key, model, userPrompt, signal, sistem = SYSTEM_PROMPT) {
|
||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
systemInstruction: { parts: [{ text: SYSTEM_PROMPT }] },
|
||||
systemInstruction: { parts: [{ text: sistem }] },
|
||||
contents: [{ role: 'user', parts: [{ text: userPrompt }] }],
|
||||
generationConfig: { temperature: 0.2, maxOutputTokens: 4000, responseMimeType: 'application/json' },
|
||||
}),
|
||||
@@ -183,14 +183,14 @@ async function callGemini(p, key, model, userPrompt, signal) {
|
||||
return data?.candidates?.[0]?.content?.parts?.map((x) => x.text).join('') || '';
|
||||
}
|
||||
|
||||
async function callAnthropic(p, key, model, userPrompt, signal) {
|
||||
async function callAnthropic(p, key, model, userPrompt, signal, sistem = SYSTEM_PROMPT) {
|
||||
const res = await fetch(p.url, {
|
||||
method: 'POST',
|
||||
headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01', 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
max_tokens: 4000,
|
||||
system: SYSTEM_PROMPT,
|
||||
system: sistem,
|
||||
messages: [{ role: 'user', content: userPrompt }],
|
||||
}),
|
||||
signal,
|
||||
@@ -212,10 +212,11 @@ function extractJson(raw) {
|
||||
}
|
||||
|
||||
/** Zinciri sırayla dener; ilk başarılı sonucu döndürür. */
|
||||
export async function analyzeSpec({ text, fileName, tender, chain }) {
|
||||
export async function analyzeSpec({ text, fileName, tender, chain, systemOverride }) {
|
||||
const order = (Array.isArray(chain) && chain.length ? chain : ['glmflash', 'gemini', 'deepseek', 'grok', 'gpt', 'claude'])
|
||||
.filter((id) => PROVIDERS[id]);
|
||||
const userPrompt = buildUserPrompt({ text, fileName, tender });
|
||||
const sistem = systemOverride || SYSTEM_PROMPT;
|
||||
const attempts = [];
|
||||
|
||||
for (const id of order) {
|
||||
@@ -230,13 +231,13 @@ export async function analyzeSpec({ text, fileName, tender, chain }) {
|
||||
try {
|
||||
let raw;
|
||||
if (route.yol === 'openrouter') {
|
||||
raw = await callOpenRouter(route.model, route.key, userPrompt, ac.signal);
|
||||
raw = await callOpenRouter(route.model, route.key, userPrompt, ac.signal, sistem);
|
||||
} else if (p.style === 'gemini') {
|
||||
raw = await callGemini(p, route.key, route.model, userPrompt, ac.signal);
|
||||
raw = await callGemini(p, route.key, route.model, userPrompt, ac.signal, sistem);
|
||||
} else if (p.style === 'anthropic') {
|
||||
raw = await callAnthropic(p, route.key, route.model, userPrompt, ac.signal);
|
||||
raw = await callAnthropic(p, route.key, route.model, userPrompt, ac.signal, sistem);
|
||||
} else {
|
||||
raw = await callOpenAiStyle(p, route.key, route.model, userPrompt, ac.signal);
|
||||
raw = await callOpenAiStyle(p, route.key, route.model, userPrompt, ac.signal, sistem);
|
||||
}
|
||||
const result = extractJson(raw);
|
||||
attempts.push({ id, ok: true, yol: route.yol });
|
||||
|
||||
Reference in New Issue
Block a user