index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>小绿叶AI</title>
<style>
body{font-family:system-ui;margin:20px;}
#inputBox{width:100%;height:80px;padding:8px;box-sizing:border-box;}
#sendBtn{margin:8px 0;padding:6px 16px;}
#result{white-space:pre-wrap;border:1px solid #ccc;padding:10px;min-height:300px;margin-top:10px;}
</style>
</head>
<body>
<h3> 小绿叶AI Ollama </h3>
<textarea id="inputBox" placeholder="请输入问题,例如:2026年最新AI新闻"></textarea>
<br>
<button id="sendBtn">发送(开启联网搜索)</button>
<div id="result"></div>
<script>
let es = null;
const resultDom = document.getElementById('result');
const inputDom = document.getElementById('inputBox');
const sendBtn = document.getElementById('sendBtn');
sendBtn.onclick = ()=>{
const prompt = inputDom.value.trim();
if(!prompt) return;
resultDom.textContent = "";
if(es) es.close();
es = new EventSource(`stream.php?prompt=${encodeURIComponent(prompt)}`);
es.onmessage = (e)=>{
const data = JSON.parse(e.data);
if(data.type === 'chunk'){
resultDom.textContent += data.text;
}else if(data.type === 'end'){
es.close();
}
}
es.onerror = ()=>{
es.close();
resultDom.textContent += "\n\n【连接中断】";
}
}
</script>
</body>
</html>
stream.php
<?php
ob_implicit_flush(true);
ob_end_flush();
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
header("Connection: keep-alive");
header("Access-Control-Allow-Origin: *");
$prompt = trim($_GET['prompt'] ?? "");
if(empty($prompt)){
echo "data: " . json_encode(["type"=>"end"]) . "\n\n";
flush();
exit;
}
// ========== 天气意图识别 ==========
$isWeatherQuery = false;
$cityName = "重庆";
if(preg_match('/(天气|气温|温度|下雨|晴天|预报)/u',$prompt)){
$isWeatherQuery = true;
//简单提取城市,这里示例优先匹配重庆,你可以扩展正则提取任意城市
if(preg_match('/(北京|上海|广州|深圳|成都|重庆|天津|杭州)/u',$prompt,$m)){
$cityName = $m[1];
}
}
// ========== 调用中国天气网公开接口 ==========
$weatherData = null;
if($isWeatherQuery){
$weatherUrl = "http://wthrcdn.etouch.cn/weather_mini?city=".urlencode($cityName);
$ctxWeather = stream_context_create([
'http'=>[
'timeout'=>8,
'user_agent'=>'Mozilla/5.0'
]
]);
$raw = @file_get_contents($weatherUrl,false,$ctxWeather);
if($raw){
$json = json_decode($raw,true);
if($json && $json['status'] == 1){
$weatherData = $json['data'];
}
}
}
// 天气接口成功,直接输出结果
if($weatherData !== null){
$today = $weatherData['forecast'][0];
$text = sprintf(
"%s实时天气:%s,温度%s~%s℃,风向%s,风力%s。\n%s",
$cityName,
$today['type'],
$today['low'],
$today['high'],
$today['fengxiang'],
$today['fengli'],
$weatherData['ganmao']
);
echo "data: " . json_encode(["type"=>"chunk","text"=>$text]) . "\n\n";
echo "data: " . json_encode(["type"=>"end"]) . "\n\n";
flush();
exit;
}
// ========== 接口失败,回退原有searxng搜索逻辑 ==========
$searxUrl = "http://192.168.122.1:90/search?" . http_build_query([
"q" => $prompt,
"format" => "json",
"language" => "zh",
"time_range" => "day"
]);
$searchCtx = "";
$ctx = stream_context_create(['http'=>['timeout'=>10]]);
$searchRaw = @file_get_contents($searxUrl,false,$ctx);
if ($searchRaw !== false) {
$searchData = json_decode($searchRaw,true);
if(is_array($searchData['results']) && count($searchData['results'])>0){
$searchCtx .= "【参考网络搜索结果】\n";
foreach(array_slice($searchData['results'],0,3) as $item){
$title = $item['title'] ?? '';
$content = $item['content'] ?? '';
$searchCtx .= "标题:{$title}\n摘要:{$content}\n---\n";
}
} else {
$searchCtx = "【提示:未检索到网络相关资料】\n";
}
} else {
$searchCtx = "【警告:联网搜索失败】\n";
}
$systemPrompt = <<<SYS
你是检索增强问答助手。
规则:
1. 优先使用【参考网络搜索结果】中的内容回答用户问题。
2. 禁止输出“我无法联网”、“没有联网能力”这类语句。
3. 如果搜索到的资料有限,可基于已有摘要信息整理回答;不要编造虚假的实时气象数值。
SYS;
$userContent = $searchCtx . "\n用户问题:{$prompt}\n请根据上面参考信息回答问题。";
file_put_contents("debug_prompt.log", $userContent);
// ========== Ollama /api/chat 流式 ==========
$ollamaApi = "http://192.168.122.1:11434/api/chat";
$postBody = json_encode([
"model" => "deepseek-r1:1.5b",
"messages" => [
["role"=>"system","content"=>$systemPrompt],
["role"=>"user","content"=>$userContent]
],
"stream" => true,
"options" => [
"num_ctx" => 8192,
"temperature" => 0.3
]
]);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => $postBody,
'timeout' => 300
]
]);
$handle = @fopen($ollamaApi, 'r', false, $context);
if (!$handle) {
echo "data: ".json_encode(["type"=>"chunk","text"=>"❌无法连接Ollama"])."\n\n";
echo "data: ".json_encode(["type"=>"end"])."\n\n";
flush();
exit;
}
while (!feof($handle)) {
$line = fgets($handle);
$line = trim($line);
if (empty($line)) continue;
$obj = json_decode($line, true);
if (json_last_error() !== JSON_ERROR_NONE) continue;
if (isset($obj['message']['content'])) {
$msg = json_encode(["type"=>"chunk","text"=>$obj['message']['content']], JSON_UNESCAPED_UNICODE);
echo "data: {$msg}\n\n";
flush();
}
if (isset($obj['done']) && $obj['done'] === true) {
echo "data: ".json_encode(["type"=>"end"])."\n\n";
flush();
break;
}
}
fclose($handle);
?>
ai 部署
# nano docker.conf
server {
listen 80;
server_name docker.eisc.cn;
location / {
proxy_pass http://127.0.0.1:5000;
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_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 4 64k;
}
}
sudo mkdir -p /usr/local/lib/docker/cli-plugins
sudo curl -SL https://github.com/docker/compose/releases/download/v2.30.0/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
docker compose version
mkdir -p /datadisk/eisc/registry-proxy
cd /datadisk/eisc/registry-proxy
# nano docker-compose.yml
version: "3"
services:
registry:
image: registry:2
restart: always
ports:
- "127.0.0.1:5000:5000"
environment:
- REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io
volumes:
- ./data:/var/lib/registry
# docker compose up -d
内网配置
sudo snap install docker
sudo nano /var/snap/docker/current/config/daemon.json
{
"registry-mirrors": [
"http://docker.ddoss.cn"
],
"insecure-registries": [
"docker.ddoss.cn"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
# 如果是二进制docker文件启动,填写内容
# sudo nano /datadisk/eisc/server/docker27/daemon.json
{
"registry-mirrors": ["http://docker.ddoss.cn"],
"bip": "10.2.1.1/24",
"userland-proxy": true,
"userland-proxy-path": "/datadisk/eisc/server/docker27/docker-proxy",
"features": {
"buildkit": true
},
"data-root": "/datadisk/eisc/docker-data"
}
# sudo nano /etc/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target
[Service]
Type=notify
ExecStart=/datadisk/eisc/server/docker27/dockerd
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
[Install]
WantedBy=multi-user.target
sudo snap stop docker
sudo snap start docker
sudo docker info # 查看是否有 docker.eisc.cn
curl -i http://docker.eisc.cn/v2/ 测试联通性
sudo snap remove --purge docker # 卸载 docker
# docker 拉 deepseek
sudo docker pull ollama/ollama
mkdir -p /datadisk/eisc/docker-data/ollama
# 模型框架
docker run -d \
--name ollama \
-p 11434:11434 \
-v /datadisk/eisc/docker-data/ollama:/root/.ollama \
--restart unless-stopped \
ollama/ollama
# 网页版本接口
docker run -d \
--name searxng \
--restart always \
-p 8080:8080 \
searxng/searxng:latest
# 模型训练
docker exec -it ollama ollama pull deepseek-r1:1.5b
docker exec -it ollama bash
ollama pull qwen2.5:7b
# 使用千问
# 搜索引擎
docker run -d \
--name searxng \
-p 90:8080 \
-v searxng_data:/etc/searxng \
--restart always \
searxng/searxng:latest
docker exec -it searxng vi /etc/searxng/settings.yml
# 追加内容
search:
formats:
- html
- json
engines:
- name: duckduckgo
enabled: true
# 启动UI
docker run -d \
--name open-webui \
-p 92:8080 \
-e OLLAMA_BASE_URL=http://192.168.122.1:11434 \
-v open-webui:/app/backend/data \
--restart always \
ghcr.io/open-webui/open-webui:main
docker rmi ghcr.io/open-webui/open-webui:main # 删除残缺镜像 Powered by ddoss.cn 12.0
©2015 - 2026 ddoss
渝公网安备50011302222260号 |
渝ICP备2024035333号|小程序:渝ICP备2024035333号-2X |
博客简介 |
工程建设规划 |
【实验平台安全承诺书】
主机监控系统: 安全防火墙已开启检查cc攻击-下载文件完成后等待10s 恢复访问,检查连接数低于峰值恢复访问
您的IP:216.73.217.50,2026-08-31 07:53:26,Processed in 0.01008 second(s).