Ollama部署本地大模型+可视化交互页面


ollama本地部署DeepSeek大模型

一、Ollama 安装

  1. 下载准备:访问 Ollama 官网下载 Ollama 安装程序。在安装前,请确保 C 盘预留足够的存储空间,以避免安装过程中出现空间不足的问题。

  1. 安装验证:安装完成后,打开命令提示符(CMD),输入 ollama 指令。若能看到如下提示信息,则表示 Ollama 安装成功。
C:\Users\23014>ollama
Usage:
  ollama [flags]
  ollama [command]

Available Commands:
  serve       Start ollama
  create      Create a model from a Modelfile
  show        Show information for a model
  run         Run a model
  stop        Stop a running model
  pull        Pull a model from a registry
  push        Push a model to a registry
  list        List models
  ps          List running models
  cp          Copy a model
  rm          Remove a model
  help        Help about any command

Flags:
  -h, --help      help for ollama
  -v, --version   Show version information

Use "ollama [command] --help" for more information about a command.

此信息为 Ollama 命令行工具的使用帮助,展示了该工具的基本用法和可用命令。

二、Ollama 命令行工具使用说明

1. 基础格式与作用

  • 格式ollama [flags]ollama [command]
  • 作用:通过命令行与 Ollama 进行交互,可实现模型的下载、运行、管理等一系列操作。

2. 核心命令详解

命令 功能 说明 示例
serve 启动 Ollama 服务 在运行模型之前,通常需要先启动该服务。服务默认在本地后台运行,支持模型的加载和交互。 ollama serve
create 从 Modelfile 创建自定义模型 Modelfile 是用于定义模型配置的文件,类似于 Dockerfile,可指定基础模型、参数、提示词模板等,用于微调或定制模型。 ollama create my-model -f Modelfile(从当前目录的 Modelfile 创建名为 my-model 的模型)
show 显示模型的详细信息 信息包括模型的参数、大小、描述、Modelfile 内容等,有助于用户全面了解模型配置。 ollama show llama3(查看 llama3 模型的信息)
run 运行一个模型并进入交互模式 这是最常用的命令之一,支持用户直接输入问题与模型进行对话,也可通过参数指定输入文件等。 ollama run llama3(启动 llama3 模型并进行交互)
stop 停止正在运行的模型 该命令可释放模型占用的内存资源,使用时需指定要停止的模型名称。 ollama stop llama3
pull 从模型仓库下载模型到本地 默认从 Ollama 官方仓库拉取模型,也支持从第三方仓库下载,使用时需指定模型的完整名称(如 username/model:tag)。 ollama pull mistral(下载 mistral 模型)
push 将本地模型推送到远程仓库 在推送前,需要先登录目标仓库(如 Ollama Hub),该命令常用于分享自定义模型。 ollama push my-username/my-model:latest
list 列出本地已下载的所有模型 会显示模型的名称、ID、大小、修改时间等信息,方便用户查看本地存储的模型情况。 ollama list
ps 列出当前正在运行的模型 类似于进程查看命令,会显示运行中的模型名称、PID、启动时间等信息。 ollama ps
cp 复制一个模型(重命名或创建副本) 可用于备份模型或创建不同名称的相同模型。 ollama cp llama3 my-llama3-copy(将 llama3 模型复制为 my-llama3-copy)
rm 删除本地模型 用于清理不再需要的本地模型,释放磁盘空间。 ollama rm old-model(删除名为 old-model 的模型)
help 查看某个命令的详细帮助 当用户对某个命令的具体用法不清楚时,可使用该命令获取详细信息。 ollama help run(查看 run 命令的具体用法)

3. 常用参数说明

参数 功能 示例
-h, --help 显示帮助信息 ollama -h(查看全局帮助)
-v, --version 显示 Ollama 的版本号 ollama -v

三、DeepSeek 模型下载与部署

1. 模型选择

打开 Ollama 的 Models 界面,从中找到 DeepSeek 模型。

可以看到有多种版本的 DeepSeek 模型可供选择,不同版本的区别在于模型的参数规模不同。一般来说,参数规模越大,对硬件的要求(如显存、硬盘等)就越高。本文以 1.5b 版本为例进行下载和部署,该版本模型仅需 4GB 显存的 GPU,CPU 方面,最低要求为 4 核处理器,同时需要 8GB 内存和 3GB 以上的存储空间。

2. 下载与启动

在命令提示符(CMD)中输入以下安装指令:

ollama run deepseek-r1:1.5b

此指令既是安装指令,也是安装完成后启动模型的指令。运行该指令后,若出现 “success” 提示,则表示安装成功。

  • 安装过程示例截图:

出现success即表示安装成功。

3.模型交互

安装成功后,即可输入一些内容与模型进行交互,模型将对输入的问题进行回答。

  • 交互示例截图:

通过以上步骤,你可以在本地完成 Ollama 的安装,并成功下载和部署 DeepSeek 大模型,实现与模型的交互。

四、Ollama API

Ollama 本地服务的默认访问地址是http://localhost:11434,如果Ollama服务启动后访问这个url会提示Ollama is running的内容。

1. 列出本地模型

GET请求:

http://localhost:11434/api/tags

响应成功:

HTTP状态码:200,内容格式:JSON,响应示例:

{
    "models":[ {
        "name":"deepseek-r1:1.5b",
        "model":"deepseek-r1:1.5b",
        "modified_at":"2025-07-15T10:13:29.0236489+08:00",
        "size":1117322768,
        "digest":"e0979632db5a88d1a53884cb2a941772d10ff5d055aabaa6801c4e36f3a6c2d7",
        "details": {
            "parent_model": "", "format":"gguf", "family":"qwen2", "families":["qwen2"], "parameter_size":"1.8B", "quantization_level":"Q4_K_M"
        }
    }
    ]
}

2. 文本生成

单次文本生成,无上下文

POST请求:

http://localhost:11434/api/generate

请求格式:

{
  "model": "deepseek-r1:1.5b",      // 模型名称
  "prompt": "你好",       // 输入的提示词
  "stream": false,      // 是否启用流式响应(默认 false)
  "options": {              // 可选参数
    "temperature": 0.7,     // 温度参数
    "max_tokens": 100       // 最大 token 数
  }
}

响应成功:

HTTP状态码:200,内容格式:JSON,响应示例:

3. 聊天交互

支持多轮对话,模型会记录上下文

POST请求:

http://localhost:11434/api/chat

请求格式:

{
  "model": "deepseek-r1:1.5b",      // 模型名称
  "stream": False,      //流式响应
  "messages": [     // 消息列表
    {"role": "user", "content": "你好,我叫小明"},     // 用户的问题
    {"role": "assistant", "content": "你好小明!有什么可以帮你的吗?"},    // ai的回答
    {"role": "user", "content": "我刚才告诉你我叫什么了吗?"}        // 用户的问题
  ]
}

响应成功:

HTTP状态码:200,内容格式:JSON,响应示例:

{
    'model': 'deepseek-r1:1.5b',
    'created_at': '2025-07-31T09:14:27.6278501Z',
    'message': {
        'role': 'assistant', 
        'content': '<think>\n\n</think>\n\n您好,小明同学。您提到的名字是“小明”,这是一个常见的中文名字,没有特殊的含义或要求。如果您需要帮助,请告诉我具体的问题或者需求,我会尽力为您提供帮助。'
    },
    'done_reason': 'stop',
    'done': True,
    'total_duration': 626249000,
    'load_duration': 39893100,
    'prompt_eval_count': 30,
    'prompt_eval_duration': 3000000,
    'eval_count': 45,
    'eval_duration': 581000000
}

五、可视化聊天界面

Ollama 原生无可视化聊天界面,可通过第三方工具,如 Ollama Web UI、LlamaEdge、ChatUI 等工具。我们也可以通过调用Ollama的API自行创建聊天页面。

1. 流式响应

流式响应与普通响应的区别在于数据传输方式,普通响应时,服务器会完整生成响应数据并发送给客户端,而流式响应无需等待所有数据生成完毕,而是边生成数据边向客户端发送,数据会被分成多个 “”(chunk),每生成一块就立即发送一块,直到所有数据传输完成。例如:DeepSeek官网中的实时回复的打字效果(每生成一句就显示一句)。这种效果就是流式响应实现的。

处理ollama中的chat接口下的流式响应示例如下:

def generate():
    url = 'http://localhost:11434/api/chat'
    model = 'deepseek-r1:1.5b'
    content = request.POST.get('message')
    payload = {
        "model": model,
        "messages": [
            {"role": "user", "content": content},
        ]
    }
    # 向chat接口发送请求
    response = requests.post(url, json=payload, stream=True)    # stream=True启用流式响应

    with response as r:
        # 遍历流式响应的每行数据
        for line in r.iter_lines():
            if line:
                # 将传输的字节流转换为字符串,再解析为字典
                data = json.loads(line.decode('utf-8'))
                # 筛选出content内容
                if 'message' in data and 'content' in data['message']:
                    content = data['message']['content']
                    # 转换为SSE格式
                    yield f"data: {json.dumps({'content': content})}\n\n"
        # 流结束标志
        yield "data: {\"end\": true}\n\n"

之所以要定义一个生成器generate是因为要配合Django中的一个用于流式传输数据的特殊响应类StreamingHttpResponse逐步向客户端发送响应内容。StreamingHttpResponse 接收一个迭代器(如生成器函数)作为参数,迭代器每次返回的内容会被逐步发送到客户端。

2. SSE格式

SSE(Server-Sent Events,服务器发送事件)是一种基于 HTTP 的服务器向客户端单向推送实时数据的技术格式,允许服务器主动向客户端发送信息,而无需客户端频繁请求,适用于实时通知、数据更新等场景。

SSE 的核心特点

  • 单向通信:数据仅从服务器流向客户端,客户端无法通过 SSE 向服务器发送数据(需搭配其他方式如 HTTP POST 实现双向通信)。
  • 基于 HTTP/HTTPS:使用常规的 HTTP 协议,无需额外协议(如 WebSocket),兼容性更好,可穿过大多数防火墙。
  • 文本格式:传输的数据以文本形式编码,通常为 UTF-8,支持自定义数据格式(如 JSON、纯文本等)。
  • 自动重连:客户端在连接断开时会自动尝试重连,服务器可通过retry字段指定重连时间(毫秒)。

SSE 的消息格式

SSE 的消息由一系列字段组成,每个字段以字段名 + 冒号 + 空格 + 值的格式表示,每行以\n(换行符)分隔,消息之间以两个换行符\n\n)分隔。常见字段包括:

  • data:消息的核心数据,可多行(每行前都需加data:)。

plaintext data: 这是第一行数据 data: 这是第二行数据

  • event:自定义事件类型,客户端可通过addEventListener监听特定事件(默认事件为message)。

plaintext event: update data: 新数据

  • id:消息的唯一标识,客户端会记录最后接收的id,重连时通过Last-Event-ID请求头告知服务器,便于服务器恢复数据传输。

plaintext id: 123 data: 带ID的消息

  • retry:指定客户端重连的间隔时间(毫秒),若服务器不指定,客户端使用默认值。

plaintext retry: 5000 data: 5秒后重连

3. 数据处理的核心

1.后端视图

作用:生成流式数据

  • 接收用户消息,向本地 Ollama 模型发送请求,获取 AI 的流式响应(Ollama 返回的是分块的增量数据)。
  • 将 Ollama 返回的原始流式数据包装成符合 SSE(Server-Sent Events)协议的格式(即 data: {内容}\n\n),以便前端能识别这是 “持续推送的流式数据”。
  • 通过StreamingHttpResponse将数据持续发送给前端,而不是等待所有数据生成后一次性返回。
def stream_response(request):
    if request.method == 'POST':
        # 获取用户消息
        user_content = request.POST.get('message')
        # 创建会话和用户消息
        conversation_id = request.POST.get('conversation_id')
        if conversation_id:
            conversation = get_object_or_404(Conversation, id=conversation_id)
        else:
            conversation = Conversation.objects.create()
        Message.objects.create(
            conversation=conversation,
            content=user_content,
            sender='user'
        )
        # 定义一个生成器
        def generate():
            url = 'http://localhost:11434/api/chat'
            model = 'deepseek-r1:1.5b'
            content = request.POST.get('message')
            payload = {
                "model": model,
                "messages": [
                    {"role": "user", "content": content},
                ]
            }
            # 向chat接口发送请求
            response = requests.post(url, json=payload, stream=True)

            # 初始化AI回复内容
            ai_content = ''
            with response as r:
                for line in r.iter_lines():
                    if line:
                        # 将传输的字节流转换为字符串,再解析为字典
                        data = json.loads(line.decode('utf-8'))
                        # 筛选出content内容
                        if 'message' in data and 'content' in data['message']:
                            content_chunk = data['message']['content']
                            ai_content += content_chunk
                            # 转换为SSE格式
                            yield f"data: {json.dumps({'content': content_chunk})}\n\n"
                # 保存AI消息
                Message.objects.create(
                    conversation=conversation,
                    content=ai_content,
                    sender='ai'
                )
                # 流结束标志
                yield "data: {\"end\": true}\n\n"

        response = StreamingHttpResponse(generate(), content_type="text/event-stream; charset=utf-8")
        response['Cache-Control'] = 'no-cache'
        return response

    return HttpResponse("Method not allowed", status=405)

2. 前端JS

作用:消费流式数据

前端 JavaScript 需要处理流式响应的核心原因是:实时接收数据并更新用户界面

(1)接收并解析流式数据

后端发送的是符合 SSE 协议的字符串(如 data: {"content": "你好"}\n\n),前端需要:

  • 通过ReadableStream API 读取持续推送的二进制流数据。

  • 将二进制数据解码为文本(通过TextDecoder)。

  • 按 SSE 格式拆分数据块(按\n\n分割),提取data:字段后的 JSON 内容。

    // 前端解析流式数据的核心代码
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    function processStream({ done, value }) {
        if (done) return;
        const chunk = decoder.decode(value, { stream: true }); // 解码二进制流
        const lines = chunk.split('\n\n'); // SSE格式拆分
        lines.forEach(line => {
        if (line.startsWith('data:')) {
            const data = JSON.parse(line.substring(5).trim()); // 提取JSON内容
            // 处理数据...
            }
        });
        return reader.read().then(processStream); // 继续读取下一块
    }

(2)实时更新 UI,实现 “打字机效果”

流式响应的核心体验是 “AI 边思考边输出”,前端需要将每一个小数据块实时显示在聊天界面上:

  • 累加每个数据块的内容(fullResponse += data.content)。

  • 动态更新 DOM,将新增内容添加到 AI 消息框中。

  • 实时滚动聊天区域到底部,确保用户能看到最新内容。

    // 实时更新UI的核心代码
    if (data.content) {
        fullResponse += data.content;
        output.innerHTML = renderWithThinkContent(parsedContent); // 更新消息框内容
        chatMessages.scrollTop = chatMessages.scrollHeight; // 滚动到底部
    }

(3)处理流结束后的收尾工作

当后端发送完所有数据(返回 data: {"end": true}\n\n),前端需要:

  • 完成最终的内容渲染(如处理 Markdown 格式、代码高亮)。

  • 移除 “正在输入” 的加载动画(如打字指示器)。

    // 流结束后的处理
    if (done) {
    output.innerHTML = renderWithThinkContent(parsedContent); // 最终渲染
    hljs.highlightAll(); // 代码高亮
    }

4. 前端输出格式化

1. Markdown格式

使用markdown优化响应输出内容。安装django-markdownify依赖:

pip install django-markdownify

配置settings.py文件:

# 添加到INSTALLED_APPS
INSTALLED_APPS = [
    # ...默认应用
    'markdownify',
]


# 添加markdownify配置(用于渲染Markdown)
MARKDOWNIFY = {
    "default": {
        "WHITELIST_TAGS": [
            'a', 'abbr', 'acronym', 'b', 'blockquote', 'em', 'i',
            'li', 'ol', 'p', 'strong', 'ul', 'h1', 'h2', 'h3', 'h4',
            'h5', 'h6', 'pre', 'code', 'img'
        ],
        "WHITELIST_ATTRS": {
            'a': ['href', 'title'],
            'img': ['src', 'alt', 'title'],
        },
    }
}

对于模型思考部分可以通过响应<think></think>标签进行划分,并将思考内容切换样式显示。

2. 语法高亮

对于输出的代码块部分进行语法高亮,引入了 highlight.js 的核心库和样式文件:

<!-- 引入 highlight.js 样式(使用 github 风格) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
<!-- 引入 highlight.js 核心库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>

然后使用js进行相关配置:

    document.addEventListener('DOMContentLoaded', function() {
        // 配置marked
        marked.setOptions({
            highlight: function(code, lang) {
                if (lang && hljs.getLanguage(lang)) {
                    return hljs.highlight(code, { language: lang }).value;
                }
                return hljs.highlightAuto(code).value;
            },
            breaks: true,
            gfm: true
        });

        // 初始化highlight.js
        hljs.highlightAll();

        // MathJax配置
        // 如果需要重新启用公式渲染,请添加相应的数学渲染库配置
    });

六、完整项目

项目地址:实现ollama本地部署的模型的可视化聊天页面

1. 项目结构

dschat/
├── .idea/                  # IDE配置文件(PyCharm)
├── .venv/                  # Python虚拟环境
├── chat_app/               # 主要的应用模块   ├── migrations/         # 数据库迁移文件   ├── __init__.py         # 应用初始化文件   ├── admin.py            # Django admin配置   ├── apps.py             # 应用配置   ├── models.py           # 数据模型定义   └── views.py            # 视图函数实现
├── dschat/                 # 项目主配置目录   ├── __init__.py         # 项目初始化文件   ├── asgi.py             # ASGI配置(用于异步支持)   ├── settings.py         # 项目设置配置   ├── urls.py             # URL路由配置   └── wsgi.py             # WSGI配置(用于部署)
├── templates/              # HTML模板目录   └── index.html          # 主聊天界面模板
├── db.sqlite3              # SQLite数据库文件
└── manage.py               # Django管理脚本

2. views.py

import json
import requests
from django.http import StreamingHttpResponse, HttpResponse, JsonResponse
from django.shortcuts import render, get_object_or_404
from django.views.decorators.csrf import csrf_exempt

from chat_app.models import Conversation, Message


def index(request):
    conversations = Conversation.objects.all()
    messages = Message.objects.all()
    for conversation in conversations:
        conversation.first_ai_message = conversation.messages.filter(sender='ai').first()
    return render(request, 'index.html', {'conversations': conversations, 'messages': messages})

@csrf_exempt
def create_conversation(request):
    if request.method == 'POST':
        conversation = Conversation.objects.create()
        # 创建一个默认的AI欢迎消息
        Message.objects.create(
            conversation=conversation,
            content="您好!我是AI助手,有什么可以帮助您的吗?",
            sender='ai'
        )
        return JsonResponse({'id': conversation.id, 'title': conversation.title})

@csrf_exempt
def stream_response(request):
    if request.method == 'POST':
        # 获取用户消息
        user_content = request.POST.get('message')
        # 创建会话和用户消息
        conversation_id = request.POST.get('conversation_id')
        if conversation_id:
            conversation = get_object_or_404(Conversation, id=conversation_id)
        else:
            conversation = Conversation.objects.create()
        Message.objects.create(
            conversation=conversation,
            content=user_content,
            sender='user'
        )
        # 定义一个生成器
        def generate():
            url = 'http://localhost:11434/api/chat'
            model = 'deepseek-r1:1.5b'
            content = request.POST.get('message')
            payload = {
                "model": model,
                "messages": [
                    {"role": "user", "content": content},
                ]
            }
            # 向chat接口发送请求
            response = requests.post(url, json=payload, stream=True)

            # 初始化AI回复内容
            ai_content = ''
            with response as r:
                for line in r.iter_lines():
                    if line:
                        # 将传输的字节流转换为字符串,再解析为字典
                        data = json.loads(line.decode('utf-8'))
                        # 筛选出content内容
                        if 'message' in data and 'content' in data['message']:
                            content_chunk = data['message']['content']
                            ai_content += content_chunk
                            # 转换为SSE格式
                            yield f"data: {json.dumps({'content': content_chunk})}\n\n"
                # 保存AI消息
                Message.objects.create(
                    conversation=conversation,
                    content=ai_content,
                    sender='ai'
                )
                # 流结束标志
                yield "data: {\"end\": true}\n\n"

        response = StreamingHttpResponse(generate(), content_type="text/event-stream; charset=utf-8")
        response['Cache-Control'] = 'no-cache'
        return response

    return HttpResponse("Method not allowed", status=405)

@csrf_exempt
def delete_all_conversations(request):
    if request.method == 'POST':
        try:
            # 删除所有会话
            Conversation.objects.all().delete()
            return JsonResponse({'success': True, 'message': '所有会话已删除'})
        except Exception as e:
            return JsonResponse({'success': False, 'message': str(e)}, status=500)
    return HttpResponse("Method not allowed", status=405)

@csrf_exempt
def get_conversation_messages(request, conversation_id):
    if request.method == 'GET':
        try:
            conversation = Conversation.objects.get(id=conversation_id)
            messages = conversation.messages.all().values('id', 'content', 'sender', 'time')
            return JsonResponse({'messages': list(messages)})
        except Conversation.DoesNotExist:
            return JsonResponse({'error': '会话不存在'}, status=404)
    return HttpResponse("Method not allowed", status=405)

3. urls.py

from django.contrib import admin
from django.urls import path
from chat_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.index, name='index'),
    path('stream_response/', views.stream_response, name='stream_response'),
    path('create_conversation/', views.create_conversation, name='create_conversation'),
    path('get_conversation_messages/<int:conversation_id>/', views.get_conversation_messages, name='get_conversation_messages'),
    path('delete_all_conversations/', views.delete_all_conversations, name='delete_all_conversations'),
]

4. settings.py

"""
Django settings for dschat project.

Generated by 'django-admin startproject' using Django 5.2.4.

For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
import os
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-0+8-&3kej+%3c=3+93q+0=%t*8-7o@bux93^5*6230_@y8*x)@'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'chat_app',
    'markdownify',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'dschat.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates']
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'dschat.wsgi.application'


# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/

LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


MARKDOWNIFY = {
    "default": {
        "WHITELIST_TAGS": [
            'a', 'abbr', 'acronym', 'b', 'blockquote', 'em', 'i',
            'li', 'ol', 'p', 'strong', 'ul', 'h1', 'h2', 'h3', 'h4',
            'h5', 'h6', 'pre', 'code', 'img'
        ],
        "WHITELIST_ATTRS": {
            'a': ['href', 'title'],
            'img': ['src', 'alt', 'title'],
        },
    }
}

5. models.py

from django.db import models

# Create your models here.
class Conversation(models.Model):
    """会话模型,代表一次完整的聊天会话"""
    title = models.CharField(max_length=200, default="新会话")  # 会话标题
    created_at = models.DateTimeField(auto_now_add=True)  # 创建时间
    updated_at = models.DateTimeField(auto_now=True)  # 更新时间

    class Meta:
        ordering = ['-updated_at']  # 按更新时间倒序排列,最近的会话在前面
        verbose_name = "会话"
        verbose_name_plural = "会话"

    def __str__(self):
        return self.title or f"会话 {self.id}"

    def title_user_message(self):
        """从会话中用户发送的第一条消息更新标题"""
        # 查找当前会话中用户发送的第一条消息
        first_user_msg = self.messages.filter(sender='user').order_by('time').first()

        if first_user_msg:
            # 截取前30个字符作为标题,超过则加省略号
            self.title = first_user_msg.content[:30] + ("..." if len(first_user_msg.content) > 30 else "")
            self.save(update_fields=['title'])  # 只更新title字段,提高效率


class Message(models.Model):
    """消息模型,代表会话中的一条消息"""
    SENDER_CHOICES = (
        ('user', '用户'),
        ('ai', 'AI'),
    )

    conversation = models.ForeignKey(
        Conversation,
        on_delete=models.CASCADE,
        related_name="messages"
    )

    content = models.TextField()  # 消息内容
    sender = models.CharField(max_length=10, choices=SENDER_CHOICES)  # 发送者
    time = models.DateTimeField(auto_now_add=True)  # 发送时间


    class Meta:
        ordering = ['time']  # 按时间顺序排列消息
        verbose_name = "消息"
        verbose_name_plural = "消息"

    def __str__(self):
        return f"{self.get_sender_display()}: {self.content[:20]}"

6. index.html

见下方源码链接

7. 项目源码

https://github.com/bird-six/dschat 效果展示:

0 条评论

发表评论

暂无评论,欢迎发表您的观点!