diff --git a/src/App.jsx b/src/App.jsx
index 6ebadc6..e913411 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -25,6 +25,7 @@ import { DocumentVault } from './components/tools/DocumentVault';
import { AnalyticsDashboard } from './components/tools/AnalyticsDashboard';
import { ApiWebhooks } from './components/tools/ApiWebhooks';
import { PwaSettings } from './components/tools/PwaSettings';
+import { AiModelSettings } from './components/tools/AiModelSettings';
const MainContent = () => {
const { activeTab } = useApp();
@@ -57,6 +58,7 @@ const MainContent = () => {
{activeTab === 'analitik_dashboard' && }
{activeTab === 'api_webhooks' && }
{activeTab === 'pwa_settings' && }
+ {activeTab === 'ai_ayarlari' && }
{/* Global Modals */}
diff --git a/src/components/TopNavigation.jsx b/src/components/TopNavigation.jsx
index 1f289be..16e0482 100644
--- a/src/components/TopNavigation.jsx
+++ b/src/components/TopNavigation.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import { useApp } from '../context/AppContext';
-import { Newspaper, Trophy, CalendarDays, FileCheck2, Calculator, TrendingUp, LayoutGrid, Kanban, Radar, FolderKanban, BarChart3, Webhook, Smartphone } from 'lucide-react';
+import { Newspaper, Trophy, CalendarDays, FileCheck2, Calculator, TrendingUp, LayoutGrid, Kanban, Radar, FolderKanban, BarChart3, Webhook, Smartphone, Cpu } from 'lucide-react';
export const TopNavigation = () => {
const { activeTab, setActiveTab } = useApp();
@@ -18,6 +18,7 @@ export const TopNavigation = () => {
{ id: 'dokuman_deposu', label: 'Doküman Deposu', icon: FolderKanban },
{ id: 'analitik_dashboard', label: 'Analitik Dashboard', icon: BarChart3 },
{ id: 'api_webhooks', label: 'API & Webhooks', icon: Webhook },
+ { id: 'ai_ayarlari', label: 'AI Model Ayarları', icon: Cpu, badge: 'Admin' },
{ id: 'pwa_settings', label: 'Mobil PWA', icon: Smartphone }
];
diff --git a/src/components/tools/AiModelSettings.jsx b/src/components/tools/AiModelSettings.jsx
new file mode 100644
index 0000000..7c6e214
--- /dev/null
+++ b/src/components/tools/AiModelSettings.jsx
@@ -0,0 +1,171 @@
+import React, { useState } from 'react';
+import { useApp } from '../../context/AppContext';
+import { Cpu, Check, ArrowUp, ArrowDown, X, Plus, Sparkles, Save, Shield } from 'lucide-react';
+import confetti from 'canvas-confetti';
+
+const RENK = {
+ blue: 'bg-blue-50 text-blue-700 border-blue-200',
+ indigo: 'bg-indigo-50 text-indigo-700 border-indigo-200',
+ amber: 'bg-amber-50 text-amber-700 border-amber-200',
+ slate: 'bg-slate-100 text-slate-700 border-slate-200',
+ emerald: 'bg-emerald-50 text-emerald-700 border-emerald-200',
+ orange: 'bg-orange-50 text-orange-700 border-orange-200'
+};
+
+const SIRA_ETIKET = ['Birincil (Primary)', '2. Yedek (Fallback)', '3. Yedek (Fallback)', '4. Yedek', '5. Yedek', '6. Yedek'];
+
+export const AiModelSettings = () => {
+ const { AI_MODEL_CATALOG, aiModelConfig, setAiModelConfig, getModelById } = useApp();
+ const [saved, setSaved] = useState(false);
+
+ const chain = aiModelConfig.chain;
+ const available = AI_MODEL_CATALOG.filter(m => !chain.includes(m.id));
+
+ const update = (newChain) => {
+ setAiModelConfig({ ...aiModelConfig, chain: newChain });
+ setSaved(false);
+ };
+
+ const move = (idx, dir) => {
+ const j = idx + dir;
+ if (j < 0 || j >= chain.length) return;
+ const next = [...chain];
+ [next[idx], next[j]] = [next[j], next[idx]];
+ update(next);
+ };
+
+ const setPrimary = (idx) => {
+ if (idx === 0) return;
+ const next = [...chain];
+ const [item] = next.splice(idx, 1);
+ next.unshift(item);
+ update(next);
+ };
+
+ const remove = (idx) => {
+ if (chain.length <= 1) return; // en az bir model kalmalı
+ update(chain.filter((_, i) => i !== idx));
+ };
+
+ const add = (id) => update([...chain, id]);
+
+ const handleSave = () => {
+ // aiModelConfig zaten localStorage'a otomatik yazılıyor; buton kullanıcı geri bildirimi için
+ setSaved(true);
+ confetti({ particleCount: 60, spread: 70 });
+ setTimeout(() => setSaved(false), 3500);
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
Yapay Zeka Model Yönetimi
+
Şartname analizi ve AI özelliklerinde kullanılacak modeli ve yedek (fallback) sırasını belirleyin.
+
+
+
+
+
+ {/* Aktif zincir */}
+
+
+
+
+ Aktif Model Sırası (Fallback Zinciri)
+
+
Birincil model yanıt vermezse sıradaki yedeğe otomatik geçilir.
+
+
+
+
+ {chain.map((id, idx) => {
+ const m = getModelById(id);
+ if (!m) return null;
+ return (
+
+
+
+
{idx + 1}
+
+
+
+
+
+ {m.label}
+ {m.provider}
+ {idx === 0 && (
+
+ BİRİNCİL
+
+ )}
+
+
{m.alias} → {m.model}
+
{SIRA_ETIKET[idx] || `${idx + 1}. sırada`}
+
+
+
+ {idx !== 0 && (
+
+ )}
+
+
+
+ );
+ })}
+
+
+
+ {/* Eklenebilir modeller */}
+ {available.length > 0 && (
+
+
+
Kullanılabilir Diğer Modeller
+
Zincire eklemek için bir modele tıklayın.
+
+
+ {available.map(m => (
+
+ ))}
+
+
+ )}
+
+
+ Seçimler tarayıcınızda saklanır ve tüm AI özelliklerinde (şartname analizi, fiyat/risk değerlendirmesi) kullanılır.
+
+
+ );
+};
diff --git a/src/context/AppContext.jsx b/src/context/AppContext.jsx
index 830de8d..2e453ed 100644
--- a/src/context/AppContext.jsx
+++ b/src/context/AppContext.jsx
@@ -128,12 +128,42 @@ export const AppProvider = ({ children }) => {
{ id: 'key-1', name: 'ERP Entegrasyon Anahtarı', key: 'ih_live_8f92a19c3b88412e', createdAt: '2026-04-10', lastUsed: 'Bugün 12:45' }
]);
+ // ---------------------------------------------------------------------------
+ // AI Model Yönetimi (admin panelden seçilebilir + fallback zinciri)
+ // ---------------------------------------------------------------------------
+ const AI_MODEL_CATALOG = [
+ { id: 'glm', label: 'GLM 5.3', provider: 'Z.ai', alias: '~z-ai/glm-latest', model: 'glm-5.3', renk: 'blue' },
+ { id: 'deepseek', label: 'DeepSeek V4 Flash', provider: 'DeepSeek', alias: '~deepseek/deepseek-v4-flash-latest', model: 'deepseek-v4-flash-0731', renk: 'indigo' },
+ { id: 'gemini', label: 'Gemini 3.7 Flash', provider: 'Google', alias: '~google/gemini-flash-latest', model: 'gemini-3.7-flash', renk: 'amber' },
+ { id: 'grok', label: 'Grok 4.6', provider: 'xAI', alias: '~x-ai/grok-latest', model: 'grok-4.6', renk: 'slate' },
+ { id: 'gpt', label: 'GPT-5.6 Sol', provider: 'OpenAI', alias: '~openai/gpt-latest', model: 'gpt-5.6-sol', renk: 'emerald' },
+ { id: 'claude', label: 'Claude Opus 5', provider: 'Anthropic', alias: '~anthropic/claude-opus-latest', model: 'claude-opus-5', renk: 'orange' }
+ ];
+
+ // Varsayılan fallback zinciri: GLM (birincil) → DeepSeek → Gemini
+ const [aiModelConfig, setAiModelConfig] = useState(() => {
+ const saved = localStorage.getItem('ihaleist_ai_model_config');
+ if (saved) {
+ try { return JSON.parse(saved); } catch { /* yoksay */ }
+ }
+ return { chain: ['glm', 'deepseek', 'gemini'] };
+ });
+
+ useEffect(() => {
+ localStorage.setItem('ihaleist_ai_model_config', JSON.stringify(aiModelConfig));
+ }, [aiModelConfig]);
+
+ const getModelById = (id) => AI_MODEL_CATALOG.find(m => m.id === id);
+ const activeAiModel = getModelById(aiModelConfig.chain[0]) || AI_MODEL_CATALOG[0];
+
const analyzeSpecPdfWithGemini = (fileName = 'Teknik_Sartname.pdf') => {
return new Promise((resolve) => {
setTimeout(() => {
const result = {
fileName,
- model: 'Gemini 3.6 Flash',
+ model: `${activeAiModel.label} (${activeAiModel.provider})`,
+ modelId: activeAiModel.model,
+ fallbackChain: aiModelConfig.chain.map(id => getModelById(id)?.model).filter(Boolean),
analyzedAt: new Date().toLocaleString('tr-TR'),
summary: 'İşbu teknik şartname, idarenin ihtiyacı olan medikal sarf malzemeleri ve cihaz parçalarının teknik özellikleri, garanti şartları, teslimat vadeleri ve muayene kabul kriterlerini düzenlemektedir.',
qualificationCriteria: [
@@ -348,6 +378,11 @@ export const AppProvider = ({ children }) => {
setWebhooksConfig,
apiKeys,
setApiKeys,
+ AI_MODEL_CATALOG,
+ aiModelConfig,
+ setAiModelConfig,
+ getModelById,
+ activeAiModel,
userProfile,
notifications,
toggleTrackTender,