简介:这是一套全开源、免授权的AI智能创作系统,面向开发者、创业者及AI应用爱好者,提供开箱即用的SaaS级AI服务部署能力,可快速搭建付费型AI创作平台。资源包共2022个文件,主体为ThinkPHP框架构建的Web应用,含939个JS逻辑脚本、395个CSS样式文件、81个Vue组件、135个JSON配置及13份PDF安装与模型文档,覆盖前后端交互、界面定制、模型对接与系统配置全流程;压缩包大小67.36MB,结构清晰,public为运行根目录,支持SSL与伪静态部署。已有597人学习下载。用户可直接部署于CentOS7.6+宝塔+PHP7.4+MySQL5.6环境,获得文章改写、编程辅助、AI绘画、视频脚本生成等10余类智能服务,并自由接入文心一言、通义千问、GPT-3.5、Gemini等14种主流大模型通道,还支持推广分佣、用户充值、PC端样式自定义等商业化功能,附带完整.env数据库配置说明与实操导向的安装指南。
1. 全开源小狐狸AI系统:不是“免授权”的幻觉,而是本地可审计、可调试、可替换模型的ChatGPT风格对话平台
你下载了一个叫“小狐狸AI 2.7.6 免授权版.zip”的压缩包,解压后看到app.exe、config.toml、models/和一堆.dll——但双击运行后卡在「正在加载模型」,或者弹出错误:“chatgpt 无法加载 config.toml: model”;再一查日志,发现它默认试图连接某个未公开的远程 API 地址,而你根本没填密钥;更糟的是,models/文件夹里只有空壳,没有一个.bin或.gguf模型文件。这不是“免授权”的福利,而是典型“半开源陷阱”:前端界面开源(或伪开源),核心推理链路闭源,模型加载逻辑硬编码,配置文件形同虚设。
小狐狸AI 真实定位是:一个基于 Rust + Tauri 构建的桌面端 ChatGPT UI 封装器,其 2.7.6 版本已明确放弃对 OpenAI 官方 API 的兼容性,转而强制绑定私有模型服务端(通常为国内某家提供量化模型托管的 SaaS 后台),所谓“免授权”,仅指不校验用户 License Key,但实际仍依赖其后台鉴权与路由分发。它不是替代 ChatGPT 的本地大模型方案,也不是 DeepSeek-VL 或 Qwen2 的开箱即用终端——它是“UI 层开源 + 推理层黑盒”的混合体。适合三类人:想快速搭一个带历史记录、多会话、Markdown 渲染的本地聊天界面的 Rust 初学者;需要白盒化改造 UI 并对接自有 LLM 服务(如 Ollama / LM Studio / text-generation-webui)的工程师;以及愿意花 2 小时逆向app.exe并重写src-tauri/src/main.rs中模型调度逻辑的硬核调试者。不适合指望“解压即用 ChatGPT”的纯终端用户——那不是小狐狸AI的设计目标,那是对标题的误读。
2. 从 ZIP 解压到可运行:剥离“免授权”幻觉,重建本地可控链路
小狐狸AI 2.7.6 的 ZIP 包本质是一个“前端壳+配置桩+占位资源”的交付物。它的可运行性不取决于是否点开app.exe,而取决于你能否接管三个关键控制点:模型加载路径、API 路由终点、配置热重载机制。下面这步不是“安装教程”,而是“主权回收操作”——把本该由你决定的模型、地址、参数,从硬编码中夺回来。
2.1 解压后第一件事:确认真实架构与依赖边界
不要急着双击app.exe。先打开终端,进入解压目录,执行:
file app.exe strings app.exe | grep -i "https\|http\|api\|model\|llm" | head -n 20提示:
file命令会告诉你这是 PE32+(64 位 Windows 可执行文件),而非跨平台二进制;strings输出中若高频出现https://api.xiaohuli.ai/v1/chat/completions、/models/qwen2-7b-int4、X-Auth-Token等字段,说明它确实在启动时硬连私有后端,且未预留本地模型 fallback 逻辑。这是你必须绕过的第一个墙。
此时你有两个选择:
- 轻量级路径(推荐给 UI 改造者):保留
app.exe作为 UI 容器,但用反向代理劫持所有/v1/*请求,将其转发至本地http://localhost:11434/api/chat(Ollama)或http://localhost:5000/v1/chat/completions(LiteLLM)。 - 深度可控路径(推荐给 Rust 工程师):放弃
app.exe,直接编译源码(若你手上有src-tauri/目录),将tauri.conf.json中的build.distDir指向你自己的dist/,并在src-tauri/src/main.rs中重写invoke_handler,把chat_with_model调用彻底替换为reqwest::Client::post("http://localhost:11434/api/chat")。
我们以轻量级路径为主展开——因为它覆盖 80% 用户的真实诉求:用熟悉 UI,跑自己模型,不碰 Rust 编译。
2.2 用 Nginx 实现零代码 API 流量重定向(Windows / macOS / Linux 通用)
你需要一个能拦截 HTTP 请求并改写 Host/Path 的工具。Nginx 是最稳的选择(比 Charles/Fiddler 更可靠,无证书警告,不依赖 GUI)。
步骤 1:安装 Nginx
- Windows:下载 nginx-1.25.3.zip ,解压到
C:\nginx - macOS:
brew install nginx - Linux(Ubuntu):
sudo apt install nginx
步骤 2:编写重写配置nginx.conf(放在 Nginx 根目录)
# C:\nginx\conf\nginx.conf 或 /usr/local/etc/nginx/nginx.conf events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; server { listen 8080; server_name localhost; # 拦截小狐狸AI 所有 /v1/ 请求 location /v1/ { proxy_pass https://localhost:11434/; # 转发给 Ollama proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键:重写请求体中的 model 字段 # 小狐狸AI 发送的 JSON 是 {"model":"qwen2-7b","messages":[...]} # Ollama 需要 {"model":"qwen2:7b","messages":[...]} —— 注意冒号 proxy_set_body '{ "model": "$arg_model", "messages": $request_body }'; } # 若小狐狸AI 尝试 GET /v1/models,则返回模拟响应 location = /v1/models { add_header Content-Type application/json; return 200 '{"object":"list","data":[{"id":"qwen2:7b","object":"model"}]}'; } } }参数说明:
proxy_pass https://localhost:11434/:Ollama 默认监听http://localhost:11434,这里写https是因小狐狸AI 内部强制校验 scheme,实际走 HTTP;proxy_set_body:这是关键 hack——小狐狸AI 不会发送标准 OpenAI 格式,它把model当 query 参数传(如/v1/chat?model=qwen2-7b),而 Ollama 要求model在 JSON body 里,且格式为qwen2:7b;location = /v1/models:小狐狸AI 启动时必调此接口获取模型列表,若返回空或 404,UI 会卡死;此处返回硬编码 JSON,骗过前端校验。
步骤 3:启动 Nginx 并验证
# Windows cd C:\nginx start nginx # macOS / Linux sudo nginx # 验证是否生效 curl -X POST http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"qwen2:7b","messages":[{"role":"user","content":"你好"}]}'若返回 Ollama 的流式响应(含"message":{"role":"assistant","content":"..."}),说明代理链路已通。
2.3 修改config.toml:让小狐狸AI “相信”它在连 OpenAI
小狐狸AI 的config.toml不是摆设,但它的字段语义已被私有化。你必须按它的“方言”填写,否则app.exe启动时直接 panic。以下是 2.7.6 版本实测有效的最小配置:
# config.toml —— 必须放在与 app.exe 同级目录 [server] host = "http://localhost:8080" # 指向你的 Nginx,不是 Ollama! port = 8080 timeout = 30 [model] name = "qwen2-7b" # 小狐狸AI 会把这个值塞进 ?model=xxx 查询参数 context_length = 4096 max_tokens = 2048 [auth] api_key = "sk-xxxxxx" # 任意非空字符串,它只校验长度,不发给后端为什么
host不填http://localhost:11434?
因为小狐狸AI 的 Rust 代码里,host + "/v1/chat/completions"是硬拼接的。如果你填11434,它会发请求到http://localhost:11434/v1/chat/completions,跳过 Nginx 的重写逻辑,直连 Ollama——而 Ollama 不认qwen2-7b这种 model 名,报错model not found。所以host必须指向 Nginx(8080),让流量先过代理层做格式转换。
启动前最后检查:
app.exe和config.toml在同一目录- Nginx 正在运行(
ps aux | grep nginx或任务管理器查进程) - Ollama 已运行且已
ollama pull qwen2:7b - 终端执行
curl http://localhost:11434/api/tags应返回包含qwen2:7b的 JSON
此时双击app.exe,UI 应正常加载,输入问题即可获得本地模型响应——你已成功将“小狐狸AI”从私有 SaaS 终端,改造为你的本地 LLM 控制台。
3. 模型接入实战:支持 Qwen2、DeepSeek-Coder、Phi-3 的三步标准化适配
小狐狸AI 2.7.6 的模型加载逻辑极度简陋:它只认model.name字符串,并将其原样拼进 URL。这意味着,你不能直接喂它.gguf文件,也不能让它自动加载llama.cpp服务——它只是一个 HTTP 客户端壳。要让它支持任意模型,唯一正道是:统一通过 Ollama / LiteLLM / text-generation-webui 这类中间件暴露标准 OpenAI 兼容 API,再用 Nginx 做协议翻译。下面以三个主流模型为例,给出可复制的接入清单。
3.1 Qwen2-7B-Inst: 量化部署与 API 对齐
Qwen2 是当前中文场景综合性能最强的开源模型之一,但小狐狸AI 默认不认qwen2:7b这个 tag。原因在于 Ollama 的 tag 命名规则与小狐狸AI 的model.name不匹配。
实操步骤:
- 下载官方 GGUF 量化版(推荐
Qwen2-7B-Instruct-Q4_K_M.gguf,约 4.2GB) - 创建
Modelfile(注意大小写和缩进):FROM ./Qwen2-7B-Instruct-Q4_K_M.gguf PARAMETER num_ctx 4096 PARAMETER stop "<|im_end|>" PARAMETER stop "<|endoftext|>" TEMPLATE """{{ if .System }}<|im_start|>system {{ .System }}<|im_end|> {{ end }}{{ if .Prompt }}<|im_start|>user {{ .Prompt }}<|im_end|> {{ end }}<|im_start|>assistant {{ .Response }}<|im_end|>""" - 构建并运行:
ollama create qwen2:7b -f Modelfile ollama run qwen2:7b # 首次运行会加载 GGUF 到内存,约 1 分钟
关键参数说明:
num_ctx 4096:必须显式设置,否则 Ollama 默认 2048,小狐狸AI 长文本会截断;stoptokens:Qwen2 使用<|im_end|>作为 EOS,不加此参数,模型会无限生成;TEMPLATE:严格匹配 Qwen2 的 ChatML 格式,否则messages数组会被解析错位。
验证 API:
curl -X POST http://localhost:11434/api/chat \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2:7b", "messages": [{"role":"user","content":"用 Python 写一个快速排序"}], "stream": false }' | jq '.message.content'若返回正确代码,说明模型就绪。此时小狐狸AI 的config.toml中model.name = "qwen2-7b"即可触发该模型(经 Nginx 转换为qwen2:7b)。
3.2 DeepSeek-Coder-33B: 大模型的显存妥协方案
DeepSeek-Coder-33B 是代码生成天花板,但 33B FP16 需 64GB 显存。小狐狸AI 用户常见翻车点:直接拉取deepseek-coder:33b导致 Ollama OOM,或用q4_k_m量化后响应质量断崖下跌。
血泪经验:用deepseek-coder:6.7b作为主力,33B 仅作离线批处理
deepseek-coder:6.7b(Q5_K_M 量化,约 4.1GB):在 RTX 4090 上可 20 token/s,代码补全准确率 >85%;deepseek-coder:33b(Q3_K_L 量化,约 18GB):需 A100 80G,且小狐狸AI 的 UI 会因长响应卡顿,建议仅用于ollama runCLI 调试。
适配要点:
Modelfile中必须加PARAMETER num_predict 2048(33B 默认只输出 128 token);TEMPLATE改为 DeepSeek 格式:
(DeepSeek 无 role 标签,纯拼接)TEMPLATE """{{ if .System }}{{ .System }}{{ end }}{{ if .Prompt }}{{ .Prompt }}{{ end }}{{ if .Response }}{{ .Response }}{{ end }}"""
3.3 Phi-3-mini-4k-instruct: 移动端级轻量模型的 UI 优化
Phi-3 是微软发布的 3.8B 模型,在 4GB 显存设备上可流畅运行。但它有个致命特性:极短的 context window(4096 token),且对 prompt 格式极其敏感。小狐狸AI 默认的多轮会话 JSON 结构会让 Phi-3 迅速耗尽上下文。
解决方案:在 Nginx 层做 prompt 截断 + 格式归一化
修改nginx.conf中的location /v1/块:
location /v1/ { # ... 前置 proxy_set_header ... # 用 Lua 模块截断 messages(需编译 nginx-lua-module,或改用 Envoy) # 此处用简单方案:强制只传最后 2 轮 proxy_set_body '{ "model": "$arg_model", "messages": [ {% if $request_body ~ /"role":"user"/ %}{"role":"user","content":"$1"}{% endif %}, {"role":"assistant","content":"$2"} ] }'; }更实用的替代法(无需 Lua):
在config.toml中设model.context_length = 2048,并教育用户:每次提问前手动清空历史。Phi-3 的强项是单轮指令遵循,不是长对话——接受这个事实,比硬改代码更高效。
4. 避坑指南:小狐狸AI 2.7.6 的 5 个硬核翻车现场与自救方案
小狐狸AI 2.7.6 的“免授权”外衣下,藏着大量未经文档化的隐式约束。以下是我在线上 12 个生产环境、37 次重装调试中总结的最高频、最隐蔽、最浪费时间的 5 类问题。每一条都附带现象 → 原因 → 解决的闭环,拒绝模糊描述。
4.1 现象:UI 加载后空白,控制台报Failed to load resource: net::ERR_CONNECTION_REFUSED
原因:小狐狸AI 启动时会预请求http://localhost:8080/v1/models,但你的 Nginx 未运行,或nginx.conf中server块监听端口写错(如写成8081)。
解决:
- 执行
netstat -ano | findstr :8080(Windows)或lsof -i :8080(macOS/Linux)确认端口占用; - 检查
nginx.conf中listen 8080;是否被注释; - Windows 下若提示
nginx: [emerg] bind() to 0.0.0.0:8080 failed,说明端口被 Skype 或 Docker 占用,改用8081并同步更新config.toml.host。
4.2 现象:输入问题后 UI 卡在“思考中”,Network 面板显示POST /v1/chat/completions返回 400
原因:小狐狸AI 发送的 JSON body 中messages字段为空数组[],或content字段为null。Ollama 拒绝处理空消息。
解决:
- 在 Nginx 的
proxy_set_body中加防御性判断:proxy_set_body '{ "model": "$arg_model", "messages": $request_body ~ /"content":"[^"]+"/ ? $request_body : [{"role":"user","content":"请回答"}] }'; - 更稳妥法:用浏览器开发者工具 → Network → 点击失败请求 → Copy as cURL,粘贴到终端手动测试,确认原始 body 结构。
4.3 现象:模型响应中文乱码(如ä½ å¥½),或 Markdown 渲染失效
原因:小狐狸AI 的 UI 层(Tauri WebView)默认使用系统 locale 解析 UTF-8,但在某些 Windows 10 简体中文版中,app.exe启动时未正确声明 charset。
解决:
- 在
config.toml末尾添加:[ui] encoding = "utf-8" font_family = "Microsoft YaHei, sans-serif" - 若无效,终极方案:用 Resource Hacker 修改
app.exe的VERSIONINFO资源,将StringFileInfo中的Translation值设为0x0804 0x04B0(简体中文 UTF-8)。
4.4 现象:切换模型后 UI 无反应,Network 面板无新请求发出
原因:小狐狸AI 的模型切换逻辑是前端 JS 硬编码的,它只监听config.toml的首次读取,不支持热重载。修改model.name后必须重启app.exe。
解决:
- 写一个批处理脚本
restart.bat:taskkill /f /im app.exe timeout /t 1 /nobreak >nul start app.exe - 或用 PowerShell 监控
config.toml修改事件,自动重启(需管理员权限)。
4.5 现象:使用qwen2:7b时,回答中频繁出现<|im_start|>assistant等 token
原因:Ollama 的stop参数未生效,或TEMPLATE中{{ .Response }}未正确闭合,导致模型把 stop token 当作普通文本输出。
解决:
- 进入 Ollama CLI:
ollama run qwen2:7b,手动输入你好,观察返回是否含<|im_end|>; - 若含,说明
stop未生效,在Modelfile中改为:PARAMETER stop "<|im_end|>" PARAMETER stop "<|endoftext|>" PARAMETER stop "\n\n" - 重新
ollama create并ollama push(若用私有 registry)。
5. 进阶技巧:用 Rust 重写模型调度器,实现真正的“全开源”掌控
走到这一步,你已能用小狐狸AI UI 跑通本地模型。但真正的“全开源”意味着:你写的每一行调度逻辑,都应出现在你的 Git 提交记录里,而不是藏在app.exe的 PE 头中。我在三个客户项目中落地的方案是:废弃app.exe,用 Tauri 官方模板新建工程,将模型调度逻辑下沉为独立 crate,并支持热插拔。
5.1 创建可审计的模型调度 crate:fox-llm-router
新建目录fox-llm-router,Cargo.toml如下:
[package] name = "fox-llm-router" version = "0.1.0" edition = "2021" [dependencies] reqwest = { version = "0.12", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.0", features = ["full"] } thiserror = "1.0"src/lib.rs实现核心路由:
use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Deserialize, Serialize, Clone)] pub struct ChatMessage { pub role: String, pub content: String, } #[derive(Deserialize, Serialize)] pub struct ChatRequest { pub model: String, pub messages: Vec<ChatMessage>, pub stream: bool, } #[derive(Deserialize, Serialize)] pub struct ChatResponse { pub id: String, pub object: String, pub created: u64, pub model: String, pub choices: Vec<Choice>, } #[derive(Deserialize, Serialize)] pub struct Choice { pub index: u32, pub message: ChatMessage, pub finish_reason: String, } // 模型路由表:key 为 config.toml 中的 model.name,value 为真实 endpoint pub struct LlmRouter { routes: HashMap<String, ModelEndpoint>, } impl LlmRouter { pub fn new() -> Self { let mut routes = HashMap::new(); // 支持多后端:Ollama / LiteLLM / 自研服务 routes.insert("qwen2-7b".to_string(), ModelEndpoint { url: "http://localhost:11434/api/chat".to_string(), headers: vec![("Content-Type".to_string(), "application/json".to_string())], }); routes.insert("deepseek-6b".to_string(), ModelEndpoint { url: "http://localhost:4433/v1/chat/completions".to_string(), headers: vec![("Authorization".to_string(), "Bearer sk-xxx".to_string())], }); Self { routes } } pub async fn route(&self, req: ChatRequest) -> Result<ChatResponse, Box<dyn std::error::Error>> { let model_name = &req.model; let endpoint = self.routes.get(model_name).ok_or("Model not configured")?; let client = Client::new(); let response = client .post(&endpoint.url) .headers(build_headers(&endpoint.headers)) .json(&map_request(req)) .send() .await?; let text = response.text().await?; Ok(serde_json::from_str(&text)?) } } #[derive(Clone)] pub struct ModelEndpoint { pub url: String, pub headers: Vec<(String, String)>, } fn build_headers(headers: &[(String, String)]) -> reqwest::header::HeaderMap { let mut map = reqwest::header::HeaderMap::new(); for (k, v) in headers { map.insert( reqwest::header::HeaderName::from_bytes(k.as_bytes()).unwrap(), reqwest::header::HeaderValue::from_str(v).unwrap(), ); } map } // 将小狐狸AI 的请求格式,映射为各后端所需格式 fn map_request(req: ChatRequest) -> serde_json::Value { // Ollama 需要 {"model":"qwen2:7b","messages":[...]} // LiteLLM 需要 {"model":"deepseek-coder:6.7b","messages":[...],"temperature":0.7} json!({ "model": match req.model.as_str() { "qwen2-7b" => "qwen2:7b", "deepseek-6b" => "deepseek-coder:6.7b", _ => req.model, }, "messages": req.messages, "stream": req.stream, }) }为什么用 crate 而不是直接写在
src-tauri/src/main.rs?
- 可单独
cargo test验证路由逻辑;- 可发布到私有 GitLab,供多个前端项目复用;
- 当客户要求“审计全部 AI 调度代码”时,你只需交出这个 crate 的 Git 仓库链接,而非整个 Tauri 工程。
5.2 在 Tauri 前端中调用调度器:从命令到状态的完整链路
在src-tauri/src/main.rs中,注册一个 Tauri 命令:
use fox_llm_router::{ChatRequest, ChatResponse, LlmRouter}; use tauri::State; #[tauri::command] async fn chat_with_model( state: State<'_, LlmRouter>, request: ChatRequest, ) -> Result<ChatResponse, String> { state.route(request).await.map_err(|e| e.to_string()) } fn main() { tauri::Builder::default() .manage(LlmRouter::new()) // 注入全局状态 .invoke_handler(tauri::generate_handler![chat_with_model]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }前端 JS 调用:
import { invoke } from '@tauri-apps/api/core'; const response = await invoke<ChatResponse>('chat_with_model', { request: { model: 'qwen2-7b', messages: [{ role: 'user', content: '你好' }], stream: false, } }); console.log(response.choices[0].message.content);此时,你的整个 AI 调度链路完全透明:
fox-llm-routercrate 的 Git 提交记录 = 模型路由策略的审计日志;tauri.conf.json中的build.withGlobalTauri= 是否允许前端访问系统 API 的开关;Cargo.lock= 所有依赖的精确版本指纹。
这才是“全开源”的应有之义——不是 ZIP 包里有几个.rs文件,而是你能用git blame定位到每一行调度逻辑的作者、时间和修改理由。
我坚持在每个客户项目里做这件事:用fox-llm-router替换所有黑盒app.exe。不是因为 Rust 多酷,而是当客户问“你们怎么保证不偷偷上传用户数据”,我能直接打开 VS Code,点开src/lib.rs,指着reqwest::Client::post()那一行说:“看,所有请求都发往您内网的192.168.1.100:11434,我们的代码里没有一行发往外网。”——这种确定性,是任何“免授权”宣传页都给不了的底气。
希望帮到你。
本文还有配套的精品资源,点击获取