Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
13 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "0furkancolak/fur-chat", "files": {"server/src/config.rs": {"37ab503aa25e293a670df0a963f78ce4d9403160": {"t": 1785745878.8566713, "n": 2140, "h": "7b66073ecaa486cd", "c": "use std::env;\n\n#[derive(Clone, Debug)]\npub struct Config {\n pub host: String,\n pub port: u16,\n pub database_url: String,\n pub deepseek_api_key: String,\n pub deepseek_base_url: String,\n pub deepseek_model: String,\n pub available_models: Vec<String>,\n pub session_secret: String,\n pub cookie_secure: bool,\n pub client_origin: String,\n /// When set, serve built Astro static files (production single-process deploy).\n pub serve_client_dist: Option<String>,\n}\n\nimpl Config {\n pub fn from_env() -> Result<Self, String> {\n Ok(Self {\n host: env::var(\"HOST\").unwrap_or_else(|_| \"127.0.0.1\".into()),\n port: env::var(\"PORT\")\n .unwrap_or_else(|_| \"8080\".into())\n .parse()\n .map_err(|_| \"PORT must be a number\")?,\n database_url: env::var(\"DATABASE_URL\")\n .unwrap_or_else(|_| \"sqlite:data/app.db?mode=rwc\".into()),\n deepseek_api_key: env::var(\"DEEPSEEK_API_KEY\")\n .map_err(|_| \"DEEPSEEK_API_KEY is required\")?,\n deepseek_base_url: env::var(\"DEEPSEEK_BASE_URL\")\n .unwrap_or_else(|_| \"https://api.deepseek.com\".into()),\n deepseek_model: env::var(\"DEEPSEEK_MODEL\")\n .unwrap_or_else(|_| \"deepseek-chat\".into()),\n available_models: env::var(\"DEEPSEEK_MODELS\")\n .unwrap_or_else(|_| \"deepseek-chat,deepseek-reasoner\".into())\n .split(',')\n .map(|s| s.trim().to_string())\n .filter(|s| !s.is_empty())\n .collect(),\n session_secret: env::var(\"SESSION_SECRET\")\n .map_err(|_| \"SESSION_SECRET is required\")?,\n cookie_secure: env::var(\"COOKIE_SECURE\")\n .map(|v| v == \"true\" || v == \"1\")\n .unwrap_or(false),\n client_origin: env::var(\"CLIENT_ORIGIN\")\n .or_else(|_| env::var(\"FRONTEND_ORIGIN\"))\n .unwrap_or_else(|_| \"http://localhost:4321\".into()),\n serve_client_dist: env::var(\"SERVE_CLIENT_DIST\").ok().filter(|s| !s.is_empty()),\n })\n }\n}\n"}, "48f1f4dcb0066a377528fd5d4b4eb401f93ac0ab": {"t": 1785757484.9178479, "n": 2140, "h": "7b66073ecaa486cd", "c": "use std::env;\n\n#[derive(Clone, Debug)]\npub struct Config {\n pub host: String,\n pub port: u16,\n pub database_url: String,\n pub deepseek_api_key: String,\n pub deepseek_base_url: String,\n pub deepseek_model: String,\n pub available_models: Vec<String>,\n pub session_secret: String,\n pub cookie_secure: bool,\n pub client_origin: String,\n /// When set, serve built Astro static files (production single-process deploy).\n pub serve_client_dist: Option<String>,\n}\n\nimpl Config {\n pub fn from_env() -> Result<Self, String> {\n Ok(Self {\n host: env::var(\"HOST\").unwrap_or_else(|_| \"127.0.0.1\".into()),\n port: env::var(\"PORT\")\n .unwrap_or_else(|_| \"8080\".into())\n .parse()\n .map_err(|_| \"PORT must be a number\")?,\n database_url: env::var(\"DATABASE_URL\")\n .unwrap_or_else(|_| \"sqlite:data/app.db?mode=rwc\".into()),\n deepseek_api_key: env::var(\"DEEPSEEK_API_KEY\")\n .map_err(|_| \"DEEPSEEK_API_KEY is required\")?,\n deepseek_base_url: env::var(\"DEEPSEEK_BASE_URL\")\n .unwrap_or_else(|_| \"https://api.deepseek.com\".into()),\n deepseek_model: env::var(\"DEEPSEEK_MODEL\")\n .unwrap_or_else(|_| \"deepseek-chat\".into()),\n available_models: env::var(\"DEEPSEEK_MODELS\")\n .unwrap_or_else(|_| \"deepseek-chat,deepseek-reasoner\".into())\n .split(',')\n .map(|s| s.trim().to_string())\n .filter(|s| !s.is_empty())\n .collect(),\n session_secret: env::var(\"SESSION_SECRET\")\n .map_err(|_| \"SESSION_SECRET is required\")?,\n cookie_secure: env::var(\"COOKIE_SECURE\")\n .map(|v| v == \"true\" || v == \"1\")\n .unwrap_or(false),\n client_origin: env::var(\"CLIENT_ORIGIN\")\n .or_else(|_| env::var(\"FRONTEND_ORIGIN\"))\n .unwrap_or_else(|_| \"http://localhost:4321\".into()),\n serve_client_dist: env::var(\"SERVE_CLIENT_DIST\").ok().filter(|s| !s.is_empty()),\n })\n }\n}\n"}}, "server/.env.example": {"37ab503aa25e293a670df0a963f78ce4d9403160": {"t": 1785747139.5637527, "n": 1698, "h": "dafec208784db2b1", "c": "# ==============================================================================\n# Fur Chat — Server environment\n# ==============================================================================\n# Copy this file to `.env` in the same directory and fill in required values.\n# Never commit `.env` to version control.\n# ==============================================================================\n\n# --- HTTP server ----------------------------------------------------------------\nHOST=127.0.0.1\nPORT=8080\n\n# --- Client app (CORS + cookie scope) -------------------------------------------\n# Development (Astro dev server):\nCLIENT_ORIGIN=http://localhost:4321\n# Production (same host as server when using make start):\n# CLIENT_ORIGIN=http://127.0.0.1:8080\n\n# --- Production static client (optional) --------------------------------------\n# Path to `client/dist` — server serves UI + API on one port.\n# SERVE_CLIENT_DIST=../client/dist\n\n# --- Database -------------------------------------------------------------------\n# SQLite file is created under server/data/ when missing.\nDATABASE_URL=sqlite:data/app.db?mode=rwc\n\n# --- Authentication / sessions --------------------------------------------------\n# Use a long random string (32+ characters) in production.\nSESSION_SECRET=change-me-to-a-long-random-string\n# true = Secure cookie flag (requires HTTPS in production)\nCOOKIE_SECURE=false\n\n# --- DeepSeek LLM ---------------------------------------------------------------\n# API key: https://platform.deepseek.com/\nDEEPSEEK_API_KEY=\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nDEEPSEEK_MODEL=deepseek-chat\n# Comma-separated list for client model picker\nDEEPSEEK_MODELS=deepseek-chat,deepseek-reasoner\n"}, "48f1f4dcb0066a377528fd5d4b4eb401f93ac0ab": {"t": 1785757484.7118914, "n": 1697, "h": "5096c52fbd544518", "c": "# ==============================================================================\n# Fur Chat — Sunucu ortam değişkenleri\n# ==============================================================================\n# Bu dosyayı aynı dizinde `.env` olarak kopyalayın ve zorunlu alanları doldurun.\n# `.env` dosyasını asla sürüm kontrolüne eklemeyin.\n# ==============================================================================\n\n# --- HTTP sunucu --------------------------------------------------------------\nHOST=127.0.0.1\nPORT=8080\n\n# --- İstemci uygulaması (CORS + çerez kapsamı) ----------------------------------\n# Geliştirme (Astro dev sunucusu):\nCLIENT_ORIGIN=http://localhost:4321\n# Üretim (make start ile tek port):\n# CLIENT_ORIGIN=http://127.0.0.1:8080\n\n# --- Statik client (isteğe bağlı, üretim) --------------------------------------\n# `client/dist` yolu — sunucu arayüz + API’yi tek portta sunar.\n# SERVE_CLIENT_DIST=../client/dist\n\n# --- Veritabanı ---------------------------------------------------------------\n# SQLite dosyası yoksa server/data/ altında oluşturulur.\nDATABASE_URL=sqlite:data/app.db?mode=rwc\n\n# --- Kimlik doğrulama / oturum ------------------------------------------------\n# Üretimde uzun rastgele bir dize kullanın (32+ karakter).\nSESSION_SECRET=change-me-to-a-long-random-string\n# true = Secure çerez bayrağı (üretimde HTTPS gerekir)\nCOOKIE_SECURE=false\n\n# --- DeepSeek LLM -------------------------------------------------------------\n# API anahtarı: https://platform.deepseek.com/\nDEEPSEEK_API_KEY=\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nDEEPSEEK_MODEL=deepseek-chat\n# İstemci model seçici için virgülle ayrılmış liste\nDEEPSEEK_MODELS=deepseek-chat,deepseek-reasoner\n"}}, "server/src/deepseek.rs": {"37ab503aa25e293a670df0a963f78ce4d9403160": {"t": 1786067493.071253, "n": 4634, "h": "d4277fce099d5019", "c": "use std::pin::Pin;\n\nuse futures::{Stream, StreamExt};\nuse reqwest::Client;\nuse serde::{Deserialize, Serialize};\nuse serde_json::Value;\n\nuse crate::config::Config;\nuse crate::error::{AppError, AppResult};\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct ChatMessage {\n pub role: String,\n pub content: String,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\npub struct TokenUsage {\n pub prompt_tokens: i64,\n pub completion_tokens: i64,\n pub total_tokens: i64,\n}\n\n#[derive(Debug, Clone)]\npub struct StreamChunk {\n pub delta: String,\n pub usage: Option<TokenUsage>,\n}\n\npub async fn stream_chat_completion(\n config: &Config,\n model: &str,\n messages: Vec<ChatMessage>,\n) -> AppResult<Pin<Box<dyn Stream<Item = AppResult<StreamChunk>> + Send>>> {\n let client = Client::new();\n let url = format!(\n \"{}/v1/chat/completions\",\n config.deepseek_base_url.trim_end_matches('/')\n );\n\n let body = serde_json::json!({\n \"model\": model,\n \"messages\": messages,\n \"stream\": true,\n \"stream_options\": { \"include_usage\": true }\n });\n\n let response = client\n .post(&url)\n .bearer_auth(&config.deepseek_api_key)\n .json(&body)\n .send()\n .await?;\n\n if !response.status().is_success() {\n let status = response.status();\n let text = response.text().await.unwrap_or_default();\n return Err(AppError::Internal(format!(\n \"DeepSeek error {status}: {text}\"\n )));\n }\n\n let byte_stream = response.bytes_stream();\n let stream = async_stream::stream! {\n let mut buffer = String::new();\n futures::pin_mut!(byte_stream);\n while let Some(chunk) = byte_stream.next().await {\n let chunk = chunk.map_err(AppError::Reqwest)?;\n buffer.push_str(&String::from_utf8_lossy(&chunk));\n while let Some(pos) = buffer.find(\"\\n\\n\") {\n let frame = buffer[..pos].to_string();\n buffer.drain(..pos + 2);\n if let Some(parsed) = parse_sse_frame(&(frame + \"\\n\\n\")) {\n yield Ok(parsed);\n }\n }\n }\n if !buffer.trim().is_empty() {\n if let Some(parsed) = parse_sse_frame(&buffer) {\n yield Ok(parsed);\n }\n }\n };\n\n Ok(Box::pin(stream))\n}\n\npub async fn complete_chat(\n config: &Config,\n model: &str,\n messages: Vec<ChatMessage>,\n) -> AppResult<(String, TokenUsage)> {\n let client = Client::new();\n let url = format!(\n \"{}/v1/chat/completions\",\n config.deepseek_base_url.trim_end_matches('/')\n );\n\n let body = serde_json::json!({\n \"model\": model,\n \"messages\": messages,\n \"stream\": false\n });\n\n let response = client\n .post(&url)\n .bearer_auth(&config.deepseek_api_key)\n .json(&body)\n .send()\n .await?;\n\n if !response.status().is_success() {\n let status = response.status();\n let text = response.text().await.unwrap_or_default();\n return Err(AppError::Internal(format!(\n \"DeepSeek error {status}: {text}\"\n )));\n }\n\n let value: Value = response.json().await?;\n let content = value[\"choices\"][0][\"message\"][\"content\"]\n .as_str()\n .unwrap_or(\"\")\n .trim()\n .to_string();\n let usage = parse_usage(&value[\"usage\"]).unwrap_or_default();\n Ok((content, usage))\n}\n\nfn parse_sse_frame(frame: &str) -> Option<StreamChunk> {\n for line in frame.lines() {\n let line = line.trim();\n if let Some(data) = line.strip_prefix(\"data: \") {\n if data == \"[DONE]\" {\n return Some(StreamChunk {\n delta: String::new(),\n usage: None,\n });\n }\n if let Ok(value) = serde_json::from_str::<Value>(data) {\n let delta = value[\"choices\"][0][\"delta\"][\"content\"]\n .as_str()\n .unwrap_or(\"\")\n .to_string();\n let usage = parse_usage(&value[\"usage\"]);\n return Some(StreamChunk {\n delta,\n usage,\n });\n }\n }\n }\n None\n}\n\nfn parse_usage(value: &Value) -> Option<TokenUsage> {\n if value.is_null() {\n return None;\n }\n Some(TokenUsage {\n prompt_tokens: value[\"prompt_tokens\"].as_i64().unwrap_or(0),\n completion_tokens: value[\"completion_tokens\"].as_i64().unwrap_or(0),\n total_tokens: value[\"total_tokens\"].as_i64().unwrap_or(0),\n })\n}\n"}}}}