From cc0b6b6017248bdc6252d87bbe6b40fba2fb0724 Mon Sep 17 00:00:00 2001 From: ht Date: Thu, 27 Aug 2026 09:44:34 +0300 Subject: [PATCH] =?UTF-8?q?feat(admin):=20AI=20model=20y=C3=B6netimi=20pan?= =?UTF-8?q?eli=20(fallback=20zinciri=20se=C3=A7imi)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 6 model kataloğu: GLM 5.3, DeepSeek V4 Flash, Gemini 3.7 Flash, Grok 4.6, GPT-5.6 Sol, Claude Opus 5 - Varsayılan fallback zinciri: GLM (birincil) → DeepSeek → Gemini - Admin panelden sıralama, birincil seçme, ekleme/çıkarma; localStorage'da saklanır - AI analizi aktif modeli kullanır --- src/App.jsx | 2 + src/components/TopNavigation.jsx | 3 +- src/components/tools/AiModelSettings.jsx | 171 +++++++++++++++++++++++ src/context/AppContext.jsx | 37 ++++- 4 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 src/components/tools/AiModelSettings.jsx 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,