Windows Server ollama站点访问方案

<?php
// 设置请求的 URL - 使用 generate 接口进行对话
$url = 'http://192.168.3.249:11434/api/generate';

// 准备对话数据
$data = [
    'model' => 'deepseek-r1:14b',  // 替换为你实际安装的模型
    'prompt' => '你好,请介绍一下你自己',
    'stream' => false
];

// 初始化 cURL
$ch = curl_init();

// 设置 cURL 选项
curl_setopt_array($ch, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($data),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
    ],
    CURLOPT_TIMEOUT => 30
]);

// 执行请求并获取响应
$response = curl_exec($ch);

// 检查错误
if (curl_error($ch)) {
    echo '错误: ' . curl_error($ch);
} else {
    // 解析 JSON 响应
    $result = json_decode($response, true);
    
    // 输出 AI 的回复
    if (isset($result['response'])) {
        echo "AI 回复: " . $result['response'];
    } else {
        echo "未收到有效回复";
        print_r($result); // 打印完整响应用于调试
    }
}

// 关闭 cURL
curl_close($ch);
?>
// 创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();

// 配置请求
xhr.open('GET', 'http://localhost:11434/api/tags', true);

// 设置响应类型
xhr.responseType = 'json';

// 定义请求完成后的回调函数
xhr.onload = function() {
    if (xhr.status >= 200 && xhr.status < 300) {
        // 请求成功
        console.log('请求成功:', xhr.response);
    } else {
        // 请求失败
        console.error('请求失败,状态码:', xhr.status);
    }
};

// 定义错误处理
xhr.onerror = function() {
    console.error('请求发生错误');
};

// 发送请求
xhr.send();
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ollama大模型测试工具</title>
    <style>
        :root {
            --primary-color: #4a6fa5;
            --secondary-color: #6b8cbc;
            --success-color: #28a745;
            --danger-color: #dc3545;
            --warning-color: #ffc107;
            --light-color: #f8f9fa;
            --dark-color: #343a40;
            --border-radius: 8px;
            --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background-color: #f5f7fa;
            color: #333;
            line-height: 1.6;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        
        header {
            text-align: center;
            margin-bottom: 30px;
            padding: 20px;
            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
            color: white;
            border-radius: var(--border-radius);
            box-shadow: var(--box-shadow);
        }
        
        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
        }
        
        .subtitle {
            font-size: 1.2rem;
            opacity: 0.9;
        }
        
        .card {
            background: white;
            border-radius: var(--border-radius);
            box-shadow: var(--box-shadow);
            padding: 25px;
            margin-bottom: 25px;
        }
        
        .card-title {
            font-size: 1.5rem;
            margin-bottom: 20px;
            color: var(--primary-color);
            border-bottom: 2px solid var(--light-color);
            padding-bottom: 10px;
        }
        
        .form-group {
            margin-bottom: 20px;
        }
        
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
        }
        
        input, select, textarea {
            width: 100%;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: var(--border-radius);
            font-size: 1rem;
            transition: border 0.3s;
        }
        
        input:focus, select:focus, textarea:focus {
            border-color: var(--primary-color);
            outline: none;
            box-shadow: 0 0 0 2px rgba(74, 111, 165, 0.2);
        }
        
        .btn {
            display: inline-block;
            background-color: var(--primary-color);
            color: white;
            padding: 12px 24px;
            border: none;
            border-radius: var(--border-radius);
            cursor: pointer;
            font-size: 1rem;
            font-weight: 600;
            transition: all 0.3s;
        }
        
        .btn:hover {
            background-color: var(--secondary-color);
            transform: translateY(-2px);
        }
        
        .btn-success {
            background-color: var(--success-color);
        }
        
        .btn-success:hover {
            background-color: #218838;
        }
        
        .btn-danger {
            background-color: var(--danger-color);
        }
        
        .btn-danger:hover {
            background-color: #c82333;
        }
        
        .btn-warning {
            background-color: var(--warning-color);
            color: var(--dark-color);
        }
        
        .btn-warning:hover {
            background-color: #e0a800;
        }
        
        .btn-group {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }
        
        .flex-container {
            display: flex;
            gap: 25px;
            flex-wrap: wrap;
        }
        
        .flex-item {
            flex: 1;
            min-width: 300px;
        }
        
        .test-area {
            display: flex;
            flex-direction: column;
            height: 400px;
        }
        
        .chat-container {
            flex: 1;
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: var(--border-radius);
            padding: 15px;
            margin-bottom: 15px;
            background-color: #f9f9f9;
        }
        
        .message {
            margin-bottom: 15px;
            padding: 12px;
            border-radius: var(--border-radius);
            max-width: 80%;
        }
        
        .user-message {
            background-color: var(--primary-color);
            color: white;
            margin-left: auto;
        }
        
        .ai-message {
            background-color: #e9ecef;
            color: var(--dark-color);
        }
        
        .input-group {
            display: flex;
            gap: 10px;
        }
        
        .input-group input {
            flex: 1;
        }
        
        .status {
            margin-top: 15px;
            padding: 10px;
            border-radius: var(--border-radius);
            text-align: center;
            font-weight: 600;
        }
        
        .status-success {
            background-color: rgba(40, 167, 69, 0.1);
            color: var(--success-color);
            border: 1px solid var(--success-color);
        }
        
        .status-error {
            background-color: rgba(220, 53, 69, 0.1);
            color: var(--danger-color);
            border: 1px solid var(--danger-color);
        }
        
        .status-loading {
            background-color: rgba(74, 111, 165, 0.1);
            color: var(--primary-color);
            border: 1px solid var(--primary-color);
        }
        
        .hidden {
            display: none;
        }
        
        .params-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 15px;
        }
        
        .info-box {
            background-color: rgba(74, 111, 165, 0.1);
            border-left: 4px solid var(--primary-color);
            padding: 15px;
            margin-bottom: 20px;
            border-radius: 4px;
        }
        
        .info-box h3 {
            margin-top: 0;
            color: var(--primary-color);
        }
        
        .info-box ul {
            padding-left: 20px;
        }
        
        .info-box li {
            margin-bottom: 8px;
        }
        
        @media (max-width: 768px) {
            .flex-container {
                flex-direction: column;
            }
            
            .params-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>Ollama大模型测试工具</h1>
            <p class="subtitle">专为自部署Ollama设计的API测试页面</p>
        </header>
        
        <div class="info-box">
            <h3>Ollama使用说明</h3>
            <ul>
                <li><strong>Base URL格式</strong>: http://localhost:11434 或 http://您的服务器IP:11434</li>
                <li><strong>Ollama默认端口</strong>: 11434</li>
                <li><strong>模型名称</strong>: 使用 <code>ollama list</code> 命令查看已安装的模型</li>
                <li><strong>常见模型</strong>: llama2, codellama, mistral, gemma, qwen 等</li>
                <li><strong>无需API Key</strong>: Ollama本地部署通常不需要API密钥</li>
            </ul>
        </div>
        
        <div class="flex-container">
            <div class="flex-item">
                <div class="card">
                    <h2 class="card-title">Ollama配置</h2>
                    <div class="form-group">
                        <label for="base-url">Ollama服务地址 (Base URL)</label>
                        <input type="text" id="base-url" placeholder="http://localhost:11434" value="http://localhost:11434">
                        <small>格式: http://IP地址或域名:端口 (默认端口11434)</small>
                    </div>
                    
                    <div class="form-group">
                        <label for="model-name">模型名称</label>
                        <input type="text" id="model-name" placeholder="例如: llama2, mistral, codellama" value="llama2">
                        <small>使用 <code>ollama list</code> 查看已安装的模型</small>
                    </div>
                    
                    <div class="btn-group">
                        <button id="save-config" class="btn">保存配置</button>
                        <button id="test-connection" class="btn btn-warning">测试连接</button>
                        <button id="reset-config" class="btn btn-danger">重置</button>
                    </div>
                </div>
                
                <div class="card">
                    <h2 class="card-title">模型参数</h2>
                    <div class="params-grid">
                        <div class="form-group">
                            <label for="temperature">Temperature</label>
                            <input type="number" id="temperature" min="0" max="2" step="0.1" value="0.7">
                            <small>控制随机性 (0-2)</small>
                        </div>
                        
                        <div class="form-group">
                            <label for="top-k">Top K</label>
                            <input type="number" id="top-k" min="1" max="100" value="40">
                            <small>限制候选token数量</small>
                        </div>
                        
                        <div class="form-group">
                            <label for="top-p">Top P</label>
                            <input type="number" id="top-p" min="0" max="1" step="0.05" value="0.9">
                            <small>核采样 (0-1)</small>
                        </div>
                        
                        <div class="form-group">
                            <label for="seed">Seed</label>
                            <input type="number" id="seed" min="0" value="">
                            <small>随机种子 (留空为随机)</small>
                        </div>
                    </div>
                </div>
                
                <div class="card">
                    <h2 class="card-title">可用模型列表</h2>
                    <div class="form-group">
                        <label for="model-list">已安装模型</label>
                        <select id="model-list">
                            <option value="">-- 点击刷新模型列表 --</option>
                        </select>
                    </div>
                    <button id="refresh-models" class="btn">刷新模型列表</button>
                </div>
            </div>
            
            <div class="flex-item">
                <div class="card">
                    <h2 class="card-title">对话测试</h2>
                    <div class="test-area">
                        <div class="chat-container" id="chat-container">
                            <div class="message ai-message">
                                您好!我是Ollama AI助手。请配置您的Ollama服务地址和模型,然后开始与我对话。
                            </div>
                        </div>
                        
                        <div class="input-group">
                            <input type="text" id="user-input" placeholder="输入您的问题...">
                            <button id="send-btn" class="btn btn-success">发送</button>
                        </div>
                    </div>
                    
                    <div id="status" class="status hidden"></div>
                </div>
                
                <div class="card">
                    <h2 class="card-title">测试结果</h2>
                    <div class="form-group">
                        <label for="response-time">响应时间</label>
                        <input type="text" id="response-time" readonly>
                    </div>
                    
                    <div class="form-group">
                        <label for="tokens-used">Tokens使用量</label>
                        <input type="text" id="tokens-used" readonly>
                    </div>
                    
                    <div class="form-group">
                        <label for="raw-response">原始响应</label>
                        <textarea id="raw-response" rows="5" readonly></textarea>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            // 获取DOM元素
            const baseUrlInput = document.getElementById('base-url');
            const modelNameInput = document.getElementById('model-name');
            const saveConfigBtn = document.getElementById('save-config');
            const resetConfigBtn = document.getElementById('reset-config');
            const testConnectionBtn = document.getElementById('test-connection');
            const userInput = document.getElementById('user-input');
            const sendBtn = document.getElementById('send-btn');
            const chatContainer = document.getElementById('chat-container');
            const statusDiv = document.getElementById('status');
            const responseTimeInput = document.getElementById('response-time');
            const tokensUsedInput = document.getElementById('tokens-used');
            const rawResponseInput = document.getElementById('raw-response');
            const modelListSelect = document.getElementById('model-list');
            const refreshModelsBtn = document.getElementById('refresh-models');
            
            // 从本地存储加载配置
            loadConfig();
            
            // 保存配置
            saveConfigBtn.addEventListener('click', function() {
                const config = {
                    baseUrl: baseUrlInput.value,
                    modelName: modelNameInput.value
                };
                
                localStorage.setItem('ollamaTestConfig', JSON.stringify(config));
                showStatus('配置已保存!', 'success');
            });
            
            // 重置配置
            resetConfigBtn.addEventListener('click', function() {
                baseUrlInput.value = 'http://localhost:11434';
                modelNameInput.value = 'llama2';
                localStorage.removeItem('ollamaTestConfig');
                showStatus('配置已重置!', 'success');
            });
            
            // 测试连接
            testConnectionBtn.addEventListener('click', testConnection);
            
            // 刷新模型列表
            refreshModelsBtn.addEventListener('click', refreshModelList);
            
            // 发送消息
            sendBtn.addEventListener('click', sendMessage);
            userInput.addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                    sendMessage();
                }
            });
            
            // 选择模型
            modelListSelect.addEventListener('change', function() {
                if (this.value) {
                    modelNameInput.value = this.value;
                }
            });
            
            function testConnection() {
                const baseUrl = baseUrlInput.value;
                if (!baseUrl) {
                    showStatus('请输入Ollama服务地址!', 'error');
                    return;
                }
                
                showStatus('正在测试连接...', 'loading');
                
                fetch(`${baseUrl}/api/tags`)
                    .then(response => {
                        if (!response.ok) {
                            throw new Error(`HTTP错误: ${response.status}`);
                        }
                        return response.json();
                    })
                    .then(data => {
                        showStatus('连接成功!Ollama服务运行正常。', 'success');
                        console.log('Ollama连接测试响应:', data);
                    })
                    .catch(error => {
                        console.error('连接测试失败:', error);
                        showStatus(`连接失败: ${error.message}`, 'error');
                    });
            }
            
            function refreshModelList() {
                const baseUrl = baseUrlInput.value;
                if (!baseUrl) {
                    showStatus('请输入Ollama服务地址!', 'error');
                    return;
                }
                
                showStatus('正在获取模型列表...', 'loading');
                
                fetch(`${baseUrl}/api/tags`)
                    .then(response => {
                        if (!response.ok) {
                            throw new Error(`HTTP错误: ${response.status}`);
                        }
                        return response.json();
                    })
                    .then(data => {
                        modelListSelect.innerHTML = '<option value="">-- 选择模型 --</option>';
                        
                        if (data.models && data.models.length > 0) {
                            data.models.forEach(model => {
                                const option = document.createElement('option');
                                option.value = model.name;
                                option.textContent = model.name;
                                modelListSelect.appendChild(option);
                            });
                            showStatus(`成功加载 ${data.models.length} 个模型`, 'success');
                        } else {
                            showStatus('未找到任何模型,请先使用 ollama pull 命令下载模型', 'error');
                        }
                    })
                    .catch(error => {
                        console.error('获取模型列表失败:', error);
                        showStatus(`获取模型列表失败: ${error.message}`, 'error');
                    });
            }
            
            function sendMessage() {
                const message = userInput.value.trim();
                if (!message) return;
                
                // 获取配置
                const config = JSON.parse(localStorage.getItem('ollamaTestConfig') || '{}');
                const baseUrl = config.baseUrl || baseUrlInput.value;
                const modelName = config.modelName || modelNameInput.value;
                
                if (!baseUrl || !modelName) {
                    showStatus('请先配置Ollama服务地址和模型名称!', 'error');
                    return;
                }
                
                // 添加用户消息到聊天界面
                addMessage(message, 'user');
                userInput.value = '';
                
                // 显示加载状态
                showStatus('正在请求Ollama服务...', 'loading');
                
                // 获取参数
                const temperature = parseFloat(document.getElementById('temperature').value);
                const topK = parseInt(document.getElementById('top-k').value);
                const topP = parseFloat(document.getElementById('top-p').value);
                const seed = document.getElementById('seed').value ? parseInt(document.getElementById('seed').value) : undefined;
                
                // 记录开始时间
                const startTime = Date.now();
                
                // 构建Ollama请求数据
                const requestData = {
                    model: modelName,
                    prompt: message,
                    stream: false,
                    options: {
                        temperature: temperature,
                        top_k: topK,
                        top_p: topP
                    }
                };
                
                // 添加seed参数(如果设置了)
                if (seed) {
                    requestData.options.seed = seed;
                }
                
                // 发送请求到Ollama
                fetch(`${baseUrl}/api/generate`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(requestData)
                })
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`HTTP错误: ${response.status}`);
                    }
                    return response.json();
                })
                .then(data => {
                    // 计算响应时间
                    const responseTime = Date.now() - startTime;
                    
                    // 显示响应时间
                    responseTimeInput.value = `${responseTime} ms`;
                    
                    // 显示Tokens使用量
                    if (data.eval_count !== undefined) {
                        tokensUsedInput.value = `上下文: ${data.prompt_eval_count || 'N/A'}, 生成: ${data.eval_count}, 总计: ${(data.prompt_eval_count || 0) + data.eval_count}`;
                    }
                    
                    // 显示原始响应
                    rawResponseInput.value = JSON.stringify(data, null, 2);
                    
                    // 添加AI回复到聊天界面
                    if (data.response) {
                        addMessage(data.response, 'ai');
                        showStatus('请求成功!', 'success');
                    } else {
                        throw new Error('响应格式不正确,未找到response字段');
                    }
                })
                .catch(error => {
                    console.error('请求失败:', error);
                    addMessage(`抱歉,请求失败: ${error.message}`, 'ai');
                    showStatus(`请求失败: ${error.message}`, 'error');
                });
            }
            
            function addMessage(content, sender) {
                const messageDiv = document.createElement('div');
                messageDiv.classList.add('message');
                messageDiv.classList.add(sender === 'user' ? 'user-message' : 'ai-message');
                messageDiv.textContent = content;
                
                chatContainer.appendChild(messageDiv);
                chatContainer.scrollTop = chatContainer.scrollHeight;
            }
            
            function showStatus(message, type) {
                statusDiv.textContent = message;
                statusDiv.className = 'status';
                
                if (type === 'success') {
                    statusDiv.classList.add('status-success');
                } else if (type === 'error') {
                    statusDiv.classList.add('status-error');
                } else if (type === 'loading') {
                    statusDiv.classList.add('status-loading');
                }
                
                statusDiv.classList.remove('hidden');
                
                // 3秒后自动隐藏成功/错误状态
                if (type !== 'loading') {
                    setTimeout(() => {
                        statusDiv.classList.add('hidden');
                    }, 3000);
                }
            }
            
            function loadConfig() {
                const config = JSON.parse(localStorage.getItem('ollamaTestConfig') || '{}');
                baseUrlInput.value = config.baseUrl || 'http://localhost:11434';
                modelNameInput.value = config.modelName || 'llama2';
            }
        });
    </script>
</body>
</html>

第1步:设置环境变量(让Ollama监听所有接口)

在Windows Server上以管理员身份打开PowerShell,依次执行:

# 1. 设置环境变量
[Environment]::SetEnvironmentVariable("OLLAMA_HOST", "0.0.0.0:11434", "Machine")

# 2. 立即应用到当前会话
$env:OLLAMA_HOST="0.0.0.0:11434"

# 3. 重启Ollama服务
Restart-Service ollama

第2步:配置防火墙(放行端口)

继续在同一个PowerShell窗口中执行:

# 添加防火墙规则
New-NetFirewallRule -DisplayName "Ollama" -Direction Inbound -Protocol TCP -LocalPort 11434 -Action Allow

第3步:验证配置

# 检查端口监听状态
netstat -ano | findstr :11434

应该看到:

TCP    0.0.0.0:11434    0.0.0.0:0    LISTENING

参考

  1. https://blog.csdn.net/weixin_46759000/article/details/142175930