{"repo": "aragonaragon/aragon-write", "files": {"frontend/src/components/Settings.jsx": {"0963d89ba3a7b54153700b6c57c236a117b4e102": {"t": 1785746435.0123992, "n": 20036, "h": "01706b9a88338fc6", "c": "import { useEffect, useState } from \"react\";\nimport { X, RefreshCw, CheckCircle, AlertCircle, Power, Play, Eye, EyeOff, Cloud, HardDrive, Plug } from \"lucide-react\";\n\n// Built-in presets for popular OpenAI-compatible providers.\n// Users can edit Base URL/Model after applying, or fill them in manually.\nconst PROVIDER_PRESETS = {\n groq: {\n label: \"Groq\",\n baseUrl: \"https://api.groq.com/openai/v1\",\n model: \"llama-3.3-70b-versatile\",\n hint: \"مجاني وسريع — احصل على المفتاح من console.groq.com\",\n },\n openrouter: {\n label: \"OpenRouter\",\n baseUrl: \"https://openrouter.ai/api/v1\",\n model: \"google/gemini-2.0-flash-exp:free\",\n hint: \"موديلات كثيرة بسعر موحّد — openrouter.ai/keys\",\n },\n deepseek: {\n label: \"DeepSeek\",\n baseUrl: \"https://api.deepseek.com/v1\",\n model: \"deepseek-chat\",\n hint: \"رخيص وقوي بالعربي — platform.deepseek.com\",\n },\n};\n\nexport default function Settings({ settings, onUpdate, onClose, apiUrl, onOllamaStatusChange }) {\n const [models, setModels] = useState([]);\n const [loadingModels, setLoadingModels] = useState(false);\n const [modelError, setModelError] = useState(null);\n const [ollamaUrl, setOllamaUrl] = useState(settings.ollamaUrl);\n const [testStatus, setTestStatus] = useState(null);\n const [ollamaAction, setOllamaAction] = useState(null); // \"killing\" | \"starting\" | null\n\n // External provider state\n const [showKey, setShowKey] = useState(false);\n const [extModels, setExtModels] = useState([]);\n const [loadingExtModels, setLoadingExtModels] = useState(false);\n const [extTest, setExtTest] = useState(null); // { ok, error } | null\n const [testingExt, setTestingExt] = useState(false);\n\n const fetchModels = async () => {\n setLoadingModels(true);\n setModelError(null);\n try {\n const res = await fetch(`${apiUrl}/models`);\n const data = await res.json();\n if (data.models?.length > 0) {\n setModels(data.models);\n setTestStatus(\"success\");\n } else {\n setModels([]);\n setModelError(\"لم يتم العثور على موديلات. تأكد من تشغيل Ollama وتحميل موديل.\");\n setTestStatus(\"error\");\n }\n } catch {\n setModelError(\"تعذر الاتصال بـ Ollama. تأكد من تشغيله على المنفذ الصحيح.\");\n setTestStatus(\"error\");\n } finally {\n setLoadingModels(false);\n }\n };\n\n useEffect(() => { fetchModels(); }, []); // eslint-disable-line\n\n const saveOllamaUrl = () => {\n onUpdate({ ollamaUrl });\n fetchModels();\n };\n\n const killOllama = async () => {\n setOllamaAction(\"killing\");\n try {\n await fetch(`${apiUrl}/ollama/kill`, { method: \"POST\" });\n setTestStatus(null);\n setModels([]);\n onOllamaStatusChange?.(\"error\");\n } finally {\n setOllamaAction(null);\n }\n };\n\n const startOllama = async () => {\n setOllamaAction(\"starting\");\n try {\n await fetch(`${apiUrl}/ollama/start`, { method: \"POST\" });\n setTimeout(() => {\n fetchModels();\n onOllamaStatusChange?.(\"online\");\n }, 3000);\n } finally {\n setTimeout(() => setOllamaAction(null), 3200);\n }\n };\n\n // ── External provider helpers ──\n const applyPreset = (key) => {\n const preset = PROVIDER_PRESETS[key];\n if (!preset) return;\n onUpdate({\n apiBaseUrl: preset.baseUrl,\n apiModel: preset.model,\n });\n setExtTest(null);\n setExtModels([]);\n };\n\n const fetchExternalModels = async () => {\n if (!settings.apiBaseUrl) {\n setExtTest({ ok: false, error: \"أدخل Base URL أولاً\" });\n return;\n }\n setLoadingExtModels(true);\n setExtTest(null);\n try {\n const res = await fetch(`${apiUrl}/models/external`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ baseUrl: settings.apiBaseUrl, apiKey: settings.apiKey }),\n });\n const data = await res.json();\n if (res.ok && data.models?.length > 0) {\n setExtModels(data.models);\n } else {\n setExtModels([]);\n setExtTest({ ok: false, error: data.error || \"لم يتم العثور على موديلات\" });\n }\n } catch (err) {\n setExtTest({ ok: false, error: err.message || \"فشل جلب الموديلات\" });\n } finally {\n setLoadingExtModels(false);\n }\n };\n\n const testExternalConnection = async () => {\n if (!settings.apiBaseUrl) {\n setExtTest({ ok: false, error: \"أدخل Base URL أولاً\" });\n return;\n }\n setTestingExt(true);\n setExtTest(null);\n try {\n const res = await fetch(`${apiUrl}/health/provider`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ baseUrl: settings.apiBaseUrl, apiKey: settings.apiKey }),\n });\n const data = await res.json();\n setExtTest(data);\n } catch (err) {\n setExtTest({ ok: false, error: err.message || \"تعذّر الاتصال\" });\n } finally {\n setTestingExt(false);\n }\n };\n\n return (\n
e.target === e.currentTarget && onClose()}>\n
\n
\n

الإعدادات

\n \n
\n\n
\n\n {/* ── مصدر الذكاء الاصطناعي ── */}\n
\n
مصدر الذكاء الاصطناعي
\n\n
\n onUpdate({ provider: \"ollama\" })}\n >\n \n محلي (Ollama)\n \n onUpdate({ provider: \"openai_compat\" })}\n >\n \n API خارجي\n \n
\n\n {settings.provider === \"openai_compat\" && (\n <>\n
\n \n
\n {Object.entries(PROVIDER_PRESETS).map(([key, p]) => (\n applyPreset(key)}\n title={p.hint}\n >\n {p.label}\n \n ))}\n
\n اضغط لتعبئة Base URL والموديل الافتراضي تلقائياً\n
\n\n
\n \n onUpdate({ apiBaseUrl: e.target.value })}\n placeholder=\"https://api.groq.com/openai/v1\"\n dir=\"ltr\"\n />\n
\n\n
\n \n
\n onUpdate({ apiKey: e.target.value })}\n placeholder=\"sk-...\"\n dir=\"ltr\"\n autoComplete=\"off\"\n />\n setShowKey((v) => !v)}\n title={showKey ? \"إخفاء\" : \"إظهار\"}\n >\n {showKey ? : }\n \n
\n \n ⚠ المفتاح يُحفظ على جهازك في localStorage بدون تشفير. لا تشاركه ولا تستخدمه على جهاز مشترك.\n \n
\n\n
\n
\n \n \n \n \n
\n {extModels.length > 0 ? (\n onUpdate({ apiModel: e.target.value })}\n dir=\"ltr\"\n >\n \n {extModels.map((m) => )}\n \n ) : (\n onUpdate({ apiModel: e.target.value })}\n placeholder=\"llama-3.3-70b-versatile\"\n dir=\"ltr\"\n />\n )}\n
\n\n
\n \n \n {testingExt ? \"جاري الاختبار...\" : \"اختبار الاتصال\"}\n \n {extTest && (\n \n {extTest.ok ? : }\n {extTest.ok ? \"تم الاتصال بنجاح\" : extTest.error}\n
\n )}\n
\n\n \n ℹ ملاحظات: بياناتك المكتوبة (المستندات والمشاريع) تبقى محلية على جهازك.\n فقط طلبات المساعد ترسل إلى المزود الخارجي.
\n ⚠ التدقيق الإملائي يتعطّل تلقائياً في وضع API لتجنّب التكاليف.\n
\n \n )}\n
\n\n {/* ── Ollama ── */}\n
\n
إعدادات Ollama (الذكاء الاصطناعي)
\n\n
\n \n
\n setOllamaUrl(e.target.value)}\n placeholder=\"http://localhost:11434\"\n />\n \n
\n العنوان الافتراضي: http://localhost:11434\n
\n\n
\n
\n \n \n
\n\n {testStatus === \"success\" && (\n
\n Ollama يعمل — {models.length} موديل متاح\n
\n )}\n {testStatus === \"error\" && (\n
\n {modelError}\n
\n )}\n\n {models.length > 0 ? (\n \n ) : (\n onUpdate({ model: e.target.value })}\n placeholder=\"مثال: gemma4:e4b أو gemma2:9b\"\n />\n )}\n \n الموديلات الموصى بها للعربية: gemma4:e4b (خفيف وسريع)، gemma2:9b (متوازن)، gemma4:26b (الأفضل للأجهزة القوية)\n \n
\n\n {/* Kill / Start Ollama */}\n
\n \n
\n \n \n {ollamaAction === \"starting\" ? \"جاري التشغيل...\" : \"تشغيل Ollama\"}\n \n \n \n {ollamaAction === \"killing\" ? \"جاري الإيقاف...\" : \"إيقاف Ollama\"}\n \n
\n \n إيقاف Ollama يحرر الذاكرة (RAM) تماماً. شغّله مجدداً عند الحاجة للـ AI.\n \n
\n
\n\n {/* ── المظهر ── */}\n
\n
المظهر
\n
\n \n
\n \n \n \n
\n
\n
\n\n {/* ── الكتابة ── */}\n
\n
الكتابة
\n\n
\n \n onUpdate({ writingGoal: parseInt(e.target.value) || 0 })}\n placeholder=\"0 = بدون هدف\"\n min=\"0\"\n max=\"50000\"\n style={{ textAlign: \"right\", direction: \"rtl\" }}\n />\n يظهر شريط التقدم في أسفل الشاشة عند تحديد هدف.\n
\n\n
\n \n يُثبّت المؤشر في منتصف الشاشة أثناء الكتابة.\n
\n
\n\n {/* ── التدقيق الإملائي ── */}\n
\n
التدقيق الإملائي
\n
\n \n \n يستخدم Ollama لفحص الكلمات العربية. قد يستغرق وقتاً عند الكتابة.\n \n
\n
\n\n {/* ── عن التطبيق ── */}\n
\n
عن التطبيق
\n
\n
🔒 محلي 100% — بدون إنترنت، بدون سحابة، بدون تسجيل
\n
💾 المشاريع محفوظة في مجلد على قرصك الصلب
\n
🤖 يتكامل مع Ollama لتشغيل موديلات AI محلية
\n
📝 محرر نصوص WYSIWYG مع دعم كامل للعربية RTL
\n
\n
\n
\n\n
\n \n
\n \n \n );\n}\n"}, "be7ba8755149d59db2a7f05b0257f7d88e43307c": {"t": 1785764175.7287943, "n": 20036, "h": "01706b9a88338fc6", "c": "import { useEffect, useState } from \"react\";\nimport { X, RefreshCw, CheckCircle, AlertCircle, Power, Play, Eye, EyeOff, Cloud, HardDrive, Plug } from \"lucide-react\";\n\n// Built-in presets for popular OpenAI-compatible providers.\n// Users can edit Base URL/Model after applying, or fill them in manually.\nconst PROVIDER_PRESETS = {\n groq: {\n label: \"Groq\",\n baseUrl: \"https://api.groq.com/openai/v1\",\n model: \"llama-3.3-70b-versatile\",\n hint: \"مجاني وسريع — احصل على المفتاح من console.groq.com\",\n },\n openrouter: {\n label: \"OpenRouter\",\n baseUrl: \"https://openrouter.ai/api/v1\",\n model: \"google/gemini-2.0-flash-exp:free\",\n hint: \"موديلات كثيرة بسعر موحّد — openrouter.ai/keys\",\n },\n deepseek: {\n label: \"DeepSeek\",\n baseUrl: \"https://api.deepseek.com/v1\",\n model: \"deepseek-chat\",\n hint: \"رخيص وقوي بالعربي — platform.deepseek.com\",\n },\n};\n\nexport default function Settings({ settings, onUpdate, onClose, apiUrl, onOllamaStatusChange }) {\n const [models, setModels] = useState([]);\n const [loadingModels, setLoadingModels] = useState(false);\n const [modelError, setModelError] = useState(null);\n const [ollamaUrl, setOllamaUrl] = useState(settings.ollamaUrl);\n const [testStatus, setTestStatus] = useState(null);\n const [ollamaAction, setOllamaAction] = useState(null); // \"killing\" | \"starting\" | null\n\n // External provider state\n const [showKey, setShowKey] = useState(false);\n const [extModels, setExtModels] = useState([]);\n const [loadingExtModels, setLoadingExtModels] = useState(false);\n const [extTest, setExtTest] = useState(null); // { ok, error } | null\n const [testingExt, setTestingExt] = useState(false);\n\n const fetchModels = async () => {\n setLoadingModels(true);\n setModelError(null);\n try {\n const res = await fetch(`${apiUrl}/models`);\n const data = await res.json();\n if (data.models?.length > 0) {\n setModels(data.models);\n setTestStatus(\"success\");\n } else {\n setModels([]);\n setModelError(\"لم يتم العثور على موديلات. تأكد من تشغيل Ollama وتحميل موديل.\");\n setTestStatus(\"error\");\n }\n } catch {\n setModelError(\"تعذر الاتصال بـ Ollama. تأكد من تشغيله على المنفذ الصحيح.\");\n setTestStatus(\"error\");\n } finally {\n setLoadingModels(false);\n }\n };\n\n useEffect(() => { fetchModels(); }, []); // eslint-disable-line\n\n const saveOllamaUrl = () => {\n onUpdate({ ollamaUrl });\n fetchModels();\n };\n\n const killOllama = async () => {\n setOllamaAction(\"killing\");\n try {\n await fetch(`${apiUrl}/ollama/kill`, { method: \"POST\" });\n setTestStatus(null);\n setModels([]);\n onOllamaStatusChange?.(\"error\");\n } finally {\n setOllamaAction(null);\n }\n };\n\n const startOllama = async () => {\n setOllamaAction(\"starting\");\n try {\n await fetch(`${apiUrl}/ollama/start`, { method: \"POST\" });\n setTimeout(() => {\n fetchModels();\n onOllamaStatusChange?.(\"online\");\n }, 3000);\n } finally {\n setTimeout(() => setOllamaAction(null), 3200);\n }\n };\n\n // ── External provider helpers ──\n const applyPreset = (key) => {\n const preset = PROVIDER_PRESETS[key];\n if (!preset) return;\n onUpdate({\n apiBaseUrl: preset.baseUrl,\n apiModel: preset.model,\n });\n setExtTest(null);\n setExtModels([]);\n };\n\n const fetchExternalModels = async () => {\n if (!settings.apiBaseUrl) {\n setExtTest({ ok: false, error: \"أدخل Base URL أولاً\" });\n return;\n }\n setLoadingExtModels(true);\n setExtTest(null);\n try {\n const res = await fetch(`${apiUrl}/models/external`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ baseUrl: settings.apiBaseUrl, apiKey: settings.apiKey }),\n });\n const data = await res.json();\n if (res.ok && data.models?.length > 0) {\n setExtModels(data.models);\n } else {\n setExtModels([]);\n setExtTest({ ok: false, error: data.error || \"لم يتم العثور على موديلات\" });\n }\n } catch (err) {\n setExtTest({ ok: false, error: err.message || \"فشل جلب الموديلات\" });\n } finally {\n setLoadingExtModels(false);\n }\n };\n\n const testExternalConnection = async () => {\n if (!settings.apiBaseUrl) {\n setExtTest({ ok: false, error: \"أدخل Base URL أولاً\" });\n return;\n }\n setTestingExt(true);\n setExtTest(null);\n try {\n const res = await fetch(`${apiUrl}/health/provider`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ baseUrl: settings.apiBaseUrl, apiKey: settings.apiKey }),\n });\n const data = await res.json();\n setExtTest(data);\n } catch (err) {\n setExtTest({ ok: false, error: err.message || \"تعذّر الاتصال\" });\n } finally {\n setTestingExt(false);\n }\n };\n\n return (\n
e.target === e.currentTarget && onClose()}>\n
\n
\n

الإعدادات

\n \n
\n\n
\n\n {/* ── مصدر الذكاء الاصطناعي ── */}\n
\n
مصدر الذكاء الاصطناعي
\n\n
\n onUpdate({ provider: \"ollama\" })}\n >\n \n محلي (Ollama)\n \n onUpdate({ provider: \"openai_compat\" })}\n >\n \n API خارجي\n \n
\n\n {settings.provider === \"openai_compat\" && (\n <>\n
\n \n
\n {Object.entries(PROVIDER_PRESETS).map(([key, p]) => (\n applyPreset(key)}\n title={p.hint}\n >\n {p.label}\n \n ))}\n
\n اضغط لتعبئة Base URL والموديل الافتراضي تلقائياً\n
\n\n
\n \n onUpdate({ apiBaseUrl: e.target.value })}\n placeholder=\"https://api.groq.com/openai/v1\"\n dir=\"ltr\"\n />\n
\n\n
\n \n
\n onUpdate({ apiKey: e.target.value })}\n placeholder=\"sk-...\"\n dir=\"ltr\"\n autoComplete=\"off\"\n />\n setShowKey((v) => !v)}\n title={showKey ? \"إخفاء\" : \"إظهار\"}\n >\n {showKey ? : }\n \n
\n \n ⚠ المفتاح يُحفظ على جهازك في localStorage بدون تشفير. لا تشاركه ولا تستخدمه على جهاز مشترك.\n \n
\n\n
\n
\n \n \n \n \n
\n {extModels.length > 0 ? (\n onUpdate({ apiModel: e.target.value })}\n dir=\"ltr\"\n >\n \n {extModels.map((m) => )}\n \n ) : (\n onUpdate({ apiModel: e.target.value })}\n placeholder=\"llama-3.3-70b-versatile\"\n dir=\"ltr\"\n />\n )}\n
\n\n
\n \n \n {testingExt ? \"جاري الاختبار...\" : \"اختبار الاتصال\"}\n \n {extTest && (\n \n {extTest.ok ? : }\n {extTest.ok ? \"تم الاتصال بنجاح\" : extTest.error}\n
\n )}\n
\n\n \n ℹ ملاحظات: بياناتك المكتوبة (المستندات والمشاريع) تبقى محلية على جهازك.\n فقط طلبات المساعد ترسل إلى المزود الخارجي.
\n ⚠ التدقيق الإملائي يتعطّل تلقائياً في وضع API لتجنّب التكاليف.\n
\n \n )}\n
\n\n {/* ── Ollama ── */}\n
\n
إعدادات Ollama (الذكاء الاصطناعي)
\n\n
\n \n
\n setOllamaUrl(e.target.value)}\n placeholder=\"http://localhost:11434\"\n />\n \n
\n العنوان الافتراضي: http://localhost:11434\n
\n\n
\n
\n \n \n
\n\n {testStatus === \"success\" && (\n
\n Ollama يعمل — {models.length} موديل متاح\n
\n )}\n {testStatus === \"error\" && (\n
\n {modelError}\n
\n )}\n\n {models.length > 0 ? (\n \n ) : (\n onUpdate({ model: e.target.value })}\n placeholder=\"مثال: gemma4:e4b أو gemma2:9b\"\n />\n )}\n \n الموديلات الموصى بها للعربية: gemma4:e4b (خفيف وسريع)، gemma2:9b (متوازن)، gemma4:26b (الأفضل للأجهزة القوية)\n \n
\n\n {/* Kill / Start Ollama */}\n
\n \n
\n \n \n {ollamaAction === \"starting\" ? \"جاري التشغيل...\" : \"تشغيل Ollama\"}\n \n \n \n {ollamaAction === \"killing\" ? \"جاري الإيقاف...\" : \"إيقاف Ollama\"}\n \n
\n \n إيقاف Ollama يحرر الذاكرة (RAM) تماماً. شغّله مجدداً عند الحاجة للـ AI.\n \n
\n
\n\n {/* ── المظهر ── */}\n
\n
المظهر
\n
\n \n
\n \n \n \n
\n
\n
\n\n {/* ── الكتابة ── */}\n
\n
الكتابة
\n\n
\n \n onUpdate({ writingGoal: parseInt(e.target.value) || 0 })}\n placeholder=\"0 = بدون هدف\"\n min=\"0\"\n max=\"50000\"\n style={{ textAlign: \"right\", direction: \"rtl\" }}\n />\n يظهر شريط التقدم في أسفل الشاشة عند تحديد هدف.\n
\n\n
\n \n يُثبّت المؤشر في منتصف الشاشة أثناء الكتابة.\n
\n
\n\n {/* ── التدقيق الإملائي ── */}\n
\n
التدقيق الإملائي
\n
\n \n \n يستخدم Ollama لفحص الكلمات العربية. قد يستغرق وقتاً عند الكتابة.\n \n
\n
\n\n {/* ── عن التطبيق ── */}\n
\n
عن التطبيق
\n
\n
🔒 محلي 100% — بدون إنترنت، بدون سحابة، بدون تسجيل
\n
💾 المشاريع محفوظة في مجلد على قرصك الصلب
\n
🤖 يتكامل مع Ollama لتشغيل موديلات AI محلية
\n
📝 محرر نصوص WYSIWYG مع دعم كامل للعربية RTL
\n
\n
\n
\n\n
\n \n
\n \n \n );\n}\n"}, "35d00dcad922fd1b49f4c42b48b7912b18bd3868": {"t": 1785764178.1948612, "n": 10365, "h": "9cdefad5737822f1", "c": "import { useEffect, useState } from \"react\";\nimport { X, RefreshCw, CheckCircle, AlertCircle, Power, Play } from \"lucide-react\";\n\nexport default function Settings({ settings, onUpdate, onClose, apiUrl, onOllamaStatusChange }) {\n const [models, setModels] = useState([]);\n const [loadingModels, setLoadingModels] = useState(false);\n const [modelError, setModelError] = useState(null);\n const [ollamaUrl, setOllamaUrl] = useState(settings.ollamaUrl);\n const [testStatus, setTestStatus] = useState(null);\n const [ollamaAction, setOllamaAction] = useState(null); // \"killing\" | \"starting\" | null\n\n const fetchModels = async () => {\n setLoadingModels(true);\n setModelError(null);\n try {\n const res = await fetch(`${apiUrl}/models`);\n const data = await res.json();\n if (data.models?.length > 0) {\n setModels(data.models);\n setTestStatus(\"success\");\n } else {\n setModels([]);\n setModelError(\"لم يتم العثور على موديلات. تأكد من تشغيل Ollama وتحميل موديل.\");\n setTestStatus(\"error\");\n }\n } catch {\n setModelError(\"تعذر الاتصال بـ Ollama. تأكد من تشغيله على المنفذ الصحيح.\");\n setTestStatus(\"error\");\n } finally {\n setLoadingModels(false);\n }\n };\n\n useEffect(() => { fetchModels(); }, []); // eslint-disable-line\n\n const saveOllamaUrl = () => {\n onUpdate({ ollamaUrl });\n fetchModels();\n };\n\n const killOllama = async () => {\n setOllamaAction(\"killing\");\n try {\n await fetch(`${apiUrl}/ollama/kill`, { method: \"POST\" });\n setTestStatus(null);\n setModels([]);\n onOllamaStatusChange?.(\"error\");\n } finally {\n setOllamaAction(null);\n }\n };\n\n const startOllama = async () => {\n setOllamaAction(\"starting\");\n try {\n await fetch(`${apiUrl}/ollama/start`, { method: \"POST\" });\n setTimeout(() => {\n fetchModels();\n onOllamaStatusChange?.(\"online\");\n }, 3000);\n } finally {\n setTimeout(() => setOllamaAction(null), 3200);\n }\n };\n\n return (\n
e.target === e.currentTarget && onClose()}>\n
\n
\n

الإعدادات

\n \n
\n\n
\n\n {/* ── Ollama ── */}\n
\n
إعدادات Ollama (الذكاء الاصطناعي)
\n\n
\n \n
\n setOllamaUrl(e.target.value)}\n placeholder=\"http://localhost:11434\"\n />\n \n
\n العنوان الافتراضي: http://localhost:11434\n
\n\n
\n
\n \n \n
\n\n {testStatus === \"success\" && (\n
\n Ollama يعمل — {models.length} موديل متاح\n
\n )}\n {testStatus === \"error\" && (\n
\n {modelError}\n
\n )}\n\n {models.length > 0 ? (\n \n ) : (\n onUpdate({ model: e.target.value })}\n placeholder=\"مثال: gemma4:e4b أو gemma2:9b\"\n />\n )}\n \n الموديلات الموصى بها للعربية: gemma4:e4b (خفيف وسريع)، gemma2:9b (متوازن)، gemma4:26b (الأفضل للأجهزة القوية)\n \n
\n\n {/* Kill / Start Ollama */}\n
\n \n
\n \n \n {ollamaAction === \"starting\" ? \"جاري التشغيل...\" : \"تشغيل Ollama\"}\n \n \n \n {ollamaAction === \"killing\" ? \"جاري الإيقاف...\" : \"إيقاف Ollama\"}\n \n
\n \n إيقاف Ollama يحرر الذاكرة (RAM) تماماً. شغّله مجدداً عند الحاجة للـ AI.\n \n
\n
\n\n {/* ── المظهر ── */}\n
\n
المظهر
\n
\n \n
\n \n \n \n
\n
\n
\n\n {/* ── الكتابة ── */}\n
\n
الكتابة
\n\n
\n \n onUpdate({ writingGoal: parseInt(e.target.value) || 0 })}\n placeholder=\"0 = بدون هدف\"\n min=\"0\"\n max=\"50000\"\n style={{ textAlign: \"right\", direction: \"rtl\" }}\n />\n يظهر شريط التقدم في أسفل الشاشة عند تحديد هدف.\n
\n\n
\n \n يُثبّت المؤشر في منتصف الشاشة أثناء الكتابة.\n
\n
\n\n {/* ── التدقيق الإملائي ── */}\n
\n
التدقيق الإملائي
\n
\n \n \n يستخدم Ollama لفحص الكلمات العربية. قد يستغرق وقتاً عند الكتابة.\n \n
\n
\n\n {/* ── عن التطبيق ── */}\n
\n
عن التطبيق
\n
\n
🔒 محلي 100% — بدون إنترنت، بدون سحابة، بدون تسجيل
\n
💾 المشاريع محفوظة في مجلد على قرصك الصلب
\n
🤖 يتكامل مع Ollama لتشغيل موديلات AI محلية
\n
📝 محرر نصوص WYSIWYG مع دعم كامل للعربية RTL
\n
\n
\n
\n\n
\n \n
\n
\n
\n );\n}\n"}, "a60a61b0d628f985eb13396bd3b188e1984e36f2": {"t": 1785764179.736863, "n": 10365, "h": "6b56f45bd616dad6", "c": "import { useEffect, useState } from \"react\";\nimport { X, RefreshCw, CheckCircle, AlertCircle, Power, Play } from \"lucide-react\";\n\nexport default function Settings({ settings, onUpdate, onClose, apiUrl, onOllamaStatusChange }) {\n const [models, setModels] = useState([]);\n const [loadingModels, setLoadingModels] = useState(false);\n const [modelError, setModelError] = useState(null);\n const [ollamaUrl, setOllamaUrl] = useState(settings.ollamaUrl);\n const [testStatus, setTestStatus] = useState(null);\n const [ollamaAction, setOllamaAction] = useState(null); // \"killing\" | \"starting\" | null\n\n const fetchModels = async () => {\n setLoadingModels(true);\n setModelError(null);\n try {\n const res = await fetch(`${apiUrl}/models`);\n const data = await res.json();\n if (data.models?.length > 0) {\n setModels(data.models);\n setTestStatus(\"success\");\n } else {\n setModels([]);\n setModelError(\"لم يتم العثور على موديلات. تأكد من تشغيل Ollama وتحميل موديل.\");\n setTestStatus(\"error\");\n }\n } catch {\n setModelError(\"تعذر الاتصال بـ Ollama. تأكد من تشغيله على المنفذ الصحيح.\");\n setTestStatus(\"error\");\n } finally {\n setLoadingModels(false);\n }\n };\n\n useEffect(() => { fetchModels(); }, []); // eslint-disable-line\n\n const saveOllamaUrl = () => {\n onUpdate({ ollamaUrl });\n fetchModels();\n };\n\n const killOllama = async () => {\n setOllamaAction(\"killing\");\n try {\n await fetch(`${apiUrl}/ollama/kill`, { method: \"POST\" });\n setTestStatus(null);\n setModels([]);\n onOllamaStatusChange?.(\"error\");\n } finally {\n setOllamaAction(null);\n }\n };\n\n const startOllama = async () => {\n setOllamaAction(\"starting\");\n try {\n await fetch(`${apiUrl}/ollama/start`, { method: \"POST\" });\n setTimeout(() => {\n fetchModels();\n onOllamaStatusChange?.(\"online\");\n }, 3000);\n } finally {\n setTimeout(() => setOllamaAction(null), 3200);\n }\n };\n\n return (\n
e.target === e.currentTarget && onClose()}>\n
\n
\n

الإعدادات

\n \n
\n\n
\n\n {/* ── Ollama ── */}\n
\n
إعدادات Ollama (الذكاء الاصطناعي)
\n\n
\n \n
\n setOllamaUrl(e.target.value)}\n placeholder=\"http://localhost:11434\"\n />\n \n
\n العنوان الافتراضي: http://localhost:11434\n
\n\n
\n
\n \n \n
\n\n {testStatus === \"success\" && (\n
\n Ollama يعمل — {models.length} موديل متاح\n
\n )}\n {testStatus === \"error\" && (\n
\n {modelError}\n
\n )}\n\n {models.length > 0 ? (\n \n ) : (\n onUpdate({ model: e.target.value })}\n placeholder=\"مثال: gemma4:e4b أو gemma2:9b\"\n />\n )}\n \n الموديلات الموصى بها للعربية: gemma4:e4b (خفيف وسريع)، gemma2:9b (متوازن)، gemma4:26b (الأفضل للأجهزة القوية)\n \n
\n\n {/* Kill / Start Ollama */}\n
\n \n
\n \n \n {ollamaAction === \"starting\" ? \"جاري التشغيل...\" : \"تشغيل Ollama\"}\n \n \n \n {ollamaAction === \"killing\" ? \"جاري الإيقاف...\" : \"إيقاف Ollama\"}\n \n
\n \n إيقاف Ollama يحرر الذاكرة (RAM) تماماً. شغّله مجدداً عند الحاجة للـ AI.\n \n
\n
\n\n {/* ── المظهر ── */}\n
\n
المظهر
\n
\n \n
\n \n \n \n
\n
\n
\n\n {/* ── الكتابة ── */}\n
\n
الكتابة
\n\n
\n \n onUpdate({ writingGoal: parseInt(e.target.value) || 0 })}\n placeholder=\"0 = بدون هدف\"\n min=\"0\"\n max=\"50000\"\n style={{ textAlign: \"right\", direction: \"rtl\" }}\n />\n يظهر شريط التقدم في أسفل الشاشة عند تحديد هدف.\n
\n\n
\n \n يُثبّت المؤشر في منتصف الشاشة أثناء الكتابة.\n
\n
\n\n {/* ── التدقيق الإملائي ── */}\n
\n
التدقيق الإملائي
\n
\n \n \n يستخدم Ollama لفحص الكلمات العربية. قد يستغرق وقتاً عند الكتابة.\n \n
\n
\n\n {/* ── عن التطبيق ── */}\n
\n
عن التطبيق
\n
\n
🔒 محلي 100% — بدون إنترنت، بدون سحابة، بدون تسجيل
\n
💾 الروايات محفوظة في مجلد على قرصك الصلب
\n
🤖 يتكامل مع Ollama لتشغيل موديلات AI محلية
\n
📝 محرر نصوص WYSIWYG مع دعم كامل للعربية RTL
\n
\n
\n
\n\n
\n \n
\n
\n
\n );\n}\n"}, "9fff018884a4e518f15fb228adf19becfceaefc3": {"t": 1785764182.235458, "n": 10317, "h": "625811302a11138d", "c": "import { useEffect, useState } from \"react\";\nimport { X, RefreshCw, CheckCircle, AlertCircle, Power, Play } from \"lucide-react\";\n\nexport default function Settings({ settings, onUpdate, onClose, apiUrl, onOllamaStatusChange }) {\n const [models, setModels] = useState([]);\n const [loadingModels, setLoadingModels] = useState(false);\n const [modelError, setModelError] = useState(null);\n const [ollamaUrl, setOllamaUrl] = useState(settings.ollamaUrl);\n const [testStatus, setTestStatus] = useState(null);\n const [ollamaAction, setOllamaAction] = useState(null); // \"killing\" | \"starting\" | null\n\n const fetchModels = async () => {\n setLoadingModels(true);\n setModelError(null);\n try {\n const res = await fetch(`${apiUrl}/models`);\n const data = await res.json();\n if (data.models?.length > 0) {\n setModels(data.models);\n setTestStatus(\"success\");\n } else {\n setModels([]);\n setModelError(\"لم يتم العثور على موديلات. تأكد من تشغيل Ollama وتحميل موديل.\");\n setTestStatus(\"error\");\n }\n } catch {\n setModelError(\"تعذر الاتصال بـ Ollama. تأكد من تشغيله على المنفذ الصحيح.\");\n setTestStatus(\"error\");\n } finally {\n setLoadingModels(false);\n }\n };\n\n useEffect(() => { fetchModels(); }, []); // eslint-disable-line\n\n const saveOllamaUrl = () => {\n onUpdate({ ollamaUrl });\n fetchModels();\n };\n\n const killOllama = async () => {\n setOllamaAction(\"killing\");\n try {\n await fetch(`${apiUrl}/ollama/kill`, { method: \"POST\" });\n setTestStatus(null);\n setModels([]);\n onOllamaStatusChange?.(\"error\");\n } finally {\n setOllamaAction(null);\n }\n };\n\n const startOllama = async () => {\n setOllamaAction(\"starting\");\n try {\n await fetch(`${apiUrl}/ollama/start`, { method: \"POST\" });\n setTimeout(() => {\n fetchModels();\n onOllamaStatusChange?.(\"online\");\n }, 3000);\n } finally {\n setTimeout(() => setOllamaAction(null), 3200);\n }\n };\n\n return (\n
e.target === e.currentTarget && onClose()}>\n
\n
\n

الإعدادات

\n \n
\n\n
\n\n {/* ── Ollama ── */}\n
\n
إعدادات Ollama (الذكاء الاصطناعي)
\n\n
\n \n
\n setOllamaUrl(e.target.value)}\n placeholder=\"http://localhost:11434\"\n />\n \n
\n العنوان الافتراضي: http://localhost:11434\n
\n\n
\n
\n \n \n
\n\n {testStatus === \"success\" && (\n
\n Ollama يعمل — {models.length} موديل متاح\n
\n )}\n {testStatus === \"error\" && (\n
\n {modelError}\n
\n )}\n\n {models.length > 0 ? (\n \n ) : (\n onUpdate({ model: e.target.value })}\n placeholder=\"مثال: qwen2.5:7b أو llama3.1:8b\"\n />\n )}\n \n الموديلات الموصى بها للعربية: qwen2.5:7b، Llama 3.1، Gemma2\n \n
\n\n {/* Kill / Start Ollama */}\n
\n \n
\n \n \n {ollamaAction === \"starting\" ? \"جاري التشغيل...\" : \"تشغيل Ollama\"}\n \n \n \n {ollamaAction === \"killing\" ? \"جاري الإيقاف...\" : \"إيقاف Ollama\"}\n \n
\n \n إيقاف Ollama يحرر الذاكرة (RAM) تماماً. شغّله مجدداً عند الحاجة للـ AI.\n \n
\n
\n\n {/* ── المظهر ── */}\n
\n
المظهر
\n
\n \n
\n \n \n \n
\n
\n
\n\n {/* ── الكتابة ── */}\n
\n
الكتابة
\n\n
\n \n onUpdate({ writingGoal: parseInt(e.target.value) || 0 })}\n placeholder=\"0 = بدون هدف\"\n min=\"0\"\n max=\"50000\"\n style={{ textAlign: \"right\", direction: \"rtl\" }}\n />\n يظهر شريط التقدم في أسفل الشاشة عند تحديد هدف.\n
\n\n
\n \n يُثبّت المؤشر في منتصف الشاشة أثناء الكتابة.\n
\n
\n\n {/* ── التدقيق الإملائي ── */}\n
\n
التدقيق الإملائي
\n
\n \n \n يستخدم Ollama لفحص الكلمات العربية. قد يستغرق وقتاً عند الكتابة.\n \n
\n
\n\n {/* ── عن التطبيق ── */}\n
\n
عن التطبيق
\n
\n
🔒 محلي 100% — بدون إنترنت، بدون سحابة، بدون تسجيل
\n
💾 الروايات محفوظة في مجلد على قرصك الصلب
\n
🤖 يتكامل مع Ollama لتشغيل موديلات AI محلية
\n
📝 محرر نصوص WYSIWYG مع دعم كامل للعربية RTL
\n
\n
\n
\n\n
\n \n
\n
\n
\n );\n}\n"}, "654b98a215cead9b30ae7557b0f5c8d679d9ad39": {"t": 1785764183.583239, "n": 8934, "h": "7a779a48b9cb61e1", "c": "import { useEffect, useState } from \"react\";\nimport { X, RefreshCw, CheckCircle, AlertCircle } from \"lucide-react\";\n\nexport default function Settings({ settings, onUpdate, onClose, apiUrl }) {\n const [models, setModels] = useState([]);\n const [loadingModels, setLoadingModels] = useState(false);\n const [modelError, setModelError] = useState(null);\n const [ollamaUrl, setOllamaUrl] = useState(settings.ollamaUrl);\n const [testStatus, setTestStatus] = useState(null);\n\n const fetchModels = async (url = ollamaUrl) => {\n setLoadingModels(true);\n setModelError(null);\n try {\n const res = await fetch(`${apiUrl}/models`);\n const data = await res.json();\n if (data.models?.length > 0) {\n setModels(data.models);\n setTestStatus(\"success\");\n } else {\n setModels([]);\n setModelError(\"لم يتم العثور على موديلات. تأكد من تشغيل Ollama وتحميل موديل.\");\n setTestStatus(\"error\");\n }\n } catch {\n setModelError(\"تعذر الاتصال بـ Ollama. تأكد من تشغيله على المنفذ الصحيح.\");\n setTestStatus(\"error\");\n } finally {\n setLoadingModels(false);\n }\n };\n\n useEffect(() => {\n fetchModels();\n }, []); // eslint-disable-line\n\n const saveOllamaUrl = () => {\n onUpdate({ ollamaUrl });\n fetchModels(ollamaUrl);\n };\n\n return (\n
e.target === e.currentTarget && onClose()}>\n
\n
\n

الإعدادات

\n \n
\n\n
\n {/* Ollama Settings */}\n
\n
إعدادات Ollama (الذكاء الاصطناعي)
\n\n
\n \n
\n setOllamaUrl(e.target.value)}\n placeholder=\"http://localhost:11434\"\n />\n \n
\n \n العنوان الافتراضي: http://localhost:11434\n \n
\n\n
\n
\n \n fetchModels()}\n title=\"تحديث قائمة الموديلات\"\n disabled={loadingModels}\n >\n \n \n
\n\n {testStatus === \"success\" && (\n
\n \n Ollama يعمل — {models.length} موديل متاح\n
\n )}\n {testStatus === \"error\" && (\n
\n \n {modelError}\n
\n )}\n\n {models.length > 0 ? (\n onUpdate({ model: e.target.value })}\n >\n {models.map((m) => (\n \n ))}\n \n ) : (\n onUpdate({ model: e.target.value })}\n placeholder=\"مثال: qwen2.5:7b أو llama3.1:8b\"\n />\n )}\n \n الموديلات الموصى بها للعربية: qwen2.5:7b، Llama 3.1، Gemma2\n \n
\n
\n\n {/* Appearance */}\n
\n
المظهر
\n\n
\n \n
\n onUpdate({ theme: \"light\" })}\n >\n
\n فاتح\n \n onUpdate({ theme: \"dark\" })}\n >\n
\n داكن\n \n onUpdate({ theme: \"sepia\" })}\n >\n
\n عاجي\n \n
\n
\n
\n\n {/* Writing */}\n
\n
الكتابة
\n\n
\n \n onUpdate({ writingGoal: parseInt(e.target.value) || 0 })}\n placeholder=\"0 = بدون هدف\"\n min=\"0\"\n max=\"50000\"\n style={{ textAlign: \"right\", direction: \"rtl\" }}\n />\n \n يظهر شريط التقدم في أسفل الشاشة عند تحديد هدف.\n \n
\n\n
\n \n \n يُثبّت المؤشر في منتصف الشاشة أثناء الكتابة — أريح للجلسات الطويلة.\n \n
\n
\n\n {/* Spellcheck */}\n
\n
التدقيق الإملائي
\n
\n \n \n يستخدم Ollama لفحص الكلمات العربية. قد يستغرق وقتاً عند الكتابة.\n \n
\n
\n\n {/* About */}\n
\n
عن التطبيق
\n
\n
🔒 محلي 100% — بدون إنترنت، بدون سحابة، بدون تسجيل
\n
💾 الملفات محفوظة في متصفحك (localStorage)
\n
🤖 يتكامل مع Ollama لتشغيل موديلات AI محلية
\n
📝 محرر نصوص WYSIWYG مع دعم كامل للعربية RTL
\n
\n
\n
\n\n
\n \n
\n
\n
\n );\n}\n"}, "35abf5177fb84adc3a86a2aa760e2b62585ee324": {"t": 1785764184.920802, "n": 7150, "h": "dfdc4afeffc258f8", "c": "import { useEffect, useState } from \"react\";\nimport { X, RefreshCw, CheckCircle, AlertCircle } from \"lucide-react\";\n\nexport default function Settings({ settings, onUpdate, onClose, apiUrl }) {\n const [models, setModels] = useState([]);\n const [loadingModels, setLoadingModels] = useState(false);\n const [modelError, setModelError] = useState(null);\n const [ollamaUrl, setOllamaUrl] = useState(settings.ollamaUrl);\n const [testStatus, setTestStatus] = useState(null);\n\n const fetchModels = async (url = ollamaUrl) => {\n setLoadingModels(true);\n setModelError(null);\n try {\n const res = await fetch(`${apiUrl}/models`);\n const data = await res.json();\n if (data.models?.length > 0) {\n setModels(data.models);\n setTestStatus(\"success\");\n } else {\n setModels([]);\n setModelError(\"لم يتم العثور على موديلات. تأكد من تشغيل Ollama وتحميل موديل.\");\n setTestStatus(\"error\");\n }\n } catch {\n setModelError(\"تعذر الاتصال بـ Ollama. تأكد من تشغيله على المنفذ الصحيح.\");\n setTestStatus(\"error\");\n } finally {\n setLoadingModels(false);\n }\n };\n\n useEffect(() => {\n fetchModels();\n }, []); // eslint-disable-line\n\n const saveOllamaUrl = () => {\n onUpdate({ ollamaUrl });\n fetchModels(ollamaUrl);\n };\n\n return (\n
e.target === e.currentTarget && onClose()}>\n
\n
\n

الإعدادات

\n \n
\n\n
\n {/* Ollama Settings */}\n
\n
إعدادات Ollama (الذكاء الاصطناعي)
\n\n
\n \n
\n setOllamaUrl(e.target.value)}\n placeholder=\"http://localhost:11434\"\n />\n \n
\n \n العنوان الافتراضي: http://localhost:11434\n \n
\n\n
\n
\n \n fetchModels()}\n title=\"تحديث قائمة الموديلات\"\n disabled={loadingModels}\n >\n \n \n
\n\n {testStatus === \"success\" && (\n
\n \n Ollama يعمل — {models.length} موديل متاح\n
\n )}\n {testStatus === \"error\" && (\n
\n \n {modelError}\n
\n )}\n\n {models.length > 0 ? (\n onUpdate({ model: e.target.value })}\n >\n {models.map((m) => (\n \n ))}\n \n ) : (\n onUpdate({ model: e.target.value })}\n placeholder=\"مثال: qwen2.5:7b أو llama3.1:8b\"\n />\n )}\n \n الموديلات الموصى بها للعربية: qwen2.5:7b، Llama 3.1، Gemma2\n \n
\n
\n\n {/* Appearance */}\n
\n
المظهر
\n\n
\n \n
\n onUpdate({ theme: \"light\" })}\n >\n
\n فاتح\n \n onUpdate({ theme: \"dark\" })}\n >\n
\n داكن\n \n
\n
\n
\n\n {/* Spellcheck */}\n
\n
التدقيق الإملائي
\n
\n \n \n يستخدم Ollama لفحص الكلمات العربية. قد يستغرق وقتاً عند الكتابة.\n \n
\n
\n\n {/* About */}\n
\n
عن التطبيق
\n
\n
🔒 محلي 100% — بدون إنترنت، بدون سحابة، بدون تسجيل
\n
💾 الملفات محفوظة في متصفحك (localStorage)
\n
🤖 يتكامل مع Ollama لتشغيل موديلات AI محلية
\n
📝 محرر نصوص WYSIWYG مع دعم كامل للعربية RTL
\n
\n
\n
\n\n
\n \n
\n
\n
\n );\n}\n"}}}}