Skip to content

Commit

Permalink
Adds web server example
Browse files Browse the repository at this point in the history
This example enables browser chat with context history using local DeepSeek-R1 1.5B model.
  • Loading branch information
guynich committed Feb 26, 2025
1 parent b63a20c commit 4a43af3
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 2 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ installation.
- [Chat script](#chat-script)
- [Installation](#installation)
- [Run](#run)
- [Web server](#web-server)
- [References](#references)
- [Next steps](#next-steps)

Expand Down Expand Up @@ -247,6 +248,9 @@ rm -f .ollama/history

# Examples

This section contains several different examples showing the usage of a local
version of DeepSeek-R1 model.

## Temperature (experimental)

DeepSeek documentation recommends changing
Expand Down Expand Up @@ -337,7 +341,7 @@ trade-off for this CPU.
This section describes a chat example with several stored prompts using Ollama's
Python API. It is more convenient for testing than using the `ollama run`
command.
command. Runs on Terminal command line.

### Installation

Expand Down Expand Up @@ -386,6 +390,28 @@ The model generates reasoning and answers with context. The rate of the model
is printed in tokens per second including the session average rate. See
[chat folder README](/chat/README.md#result) for more information.

## Web server

This example provides a local HTML page for user input with the DeepSeek-R1 1.5B
model.

Run the web server.
```console
cd
source ./venv_ollama/bin/activate
python3 deepseek_opi5plus/browser/server.py
```

Navigate to `http://127.0.0.1:5000` in a browser for the chat session. Tested
with Chromium browser on Ubuntu 22.04 on OrangePi 5, and in Safari browser on
MacOS.

<img src="/images/chat_browser.png" alt="Web browser interface"/>

The browser web page is updated after the model has finished generating text.
Context history is preserved during the session.

# References

* Ollama.
Expand All @@ -407,4 +433,5 @@ is printed in tokens per second including the session average rate. See
* [x] Try larger size DeepSeek-R1 "7B" model (4.7GiB download) on the OrangePi 5 Plus.
* [x] Try [OrangePi 3B](http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-3B.html) single board computer (~50 USD retail with 4GB RAM) with microSD card.
* [x] Add Ollama Modefile with temperature.
* [x] Adds local web server using DeepSeek-R1 1.5B model for browser chat session.
* [ ] Update chat/README for OrangePi 5 run with `"seed": 42` in chat script.
7 changes: 7 additions & 0 deletions browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Chat session web server using Ollama's Python API and DeepSeek-R1 1.5B model.

Instructions are provided in a [top level README section](/README.md#web-server).

A browser chat session example.

<img src="../images/chat_browser_example.png" alt="Web browser example"/>
95 changes: 95 additions & 0 deletions browser/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""A simple web-based chat application using Flask and Ollama.
This application allows users to interact with a locally running DeepSeek model
"""

import markdown
from flask import Flask, render_template, request
from ollama import chat as ollama_chat

VERBOSE = False

app = Flask(__name__)

messages = []


def get_chat_response(user_input: str) -> str:
"""Get response from locally running Ollama model."""
try:
messages.append({"role": "user", "content": user_input})
content = ""
for part in ollama_chat(
model="deepseek-r1:1.5b",
messages=messages,
options={
"seed": 42,
"temperature": 0.6,
},
stream=True,
):
chunk = part["message"]["content"]
content += chunk
if VERBOSE:
print(chunk, end="", flush=True)
print()
messages.append({"role": "assistant", "content": content})

# Process the content to handle <think> blocks and markdown in HTML.

# First, protect <think> blocks by replacing them temporarily
think_blocks = []
import re

def save_think_block(match):
think_blocks.append(match.group(0))
return f"THINK_BLOCK_{len(think_blocks) - 1}"

# Save think blocks and replace with placeholders
content_with_placeholders = re.sub(
r"<think>[\s\S]*?</think>", save_think_block, content
)

# Convert markdown to HTML
processed_content = markdown.markdown(
content_with_placeholders, extensions=["fenced_code"]
)

# Restore think blocks
for i, block in enumerate(think_blocks):
processed_content = processed_content.replace(
f"<p>THINK_BLOCK_{i}</p>", block
)
if VERBOSE:
print("Processed content:")
print(processed_content)
return processed_content
except Exception as e:
print(f"Error getting response from Ollama: {e}")
return "Sorry, please try again."


@app.route("/", methods=["GET", "POST"])
def index() -> str:
"""Handle main page requests."""
if request.method != "POST":
return render_template("index.html", user_input="", bot_response="")

submitted_input = request.form.get("user_input", "")
chat_response = get_chat_response(submitted_input)
return render_template(
"index.html",
last_question=submitted_input,
bot_response=chat_response,
)


@app.route("/chat", methods=["POST"])
def chat():
"""Handle chat API requests."""
submitted_input = request.form.get("user_input", "")
chat_response = get_chat_response(submitted_input)
return {"response": chat_response, "last_question": submitted_input}


if __name__ == "__main__":
app.run(debug=True)
133 changes: 133 additions & 0 deletions browser/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Local chat using DeepSeek-R1 and Ollama</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f0f0f0; margin: 0; padding: 0;}
.container { width: 50%; margin: 0 auto; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); }
h1 { text-align: center; }
.chat-box { margin-top: 0; border-top: 2px solid #ddd; padding-top: 20px;}
.chat-box .user-message, .chat-box .bot-message { padding: 10px; margin: 10px 0; border-radius: 5px;}
.user-message { background-color: #e1f5fe; text-align: right;}
.bot-message { background-color: #f1f1f1; white-space: pre-wrap; padding: 10px; margin: 10px 0; border-radius: 5px; }
.bot-message think {
color: #666;
font-style: italic;
border-left: 3px solid #57b368;
padding-left: 10px;
margin: 5px 0;
display: block;
background-color: #f8f9fa; /* Lighter background to distinguish thinking steps */
white-space: pre-wrap; /* Preserve line breaks */
word-wrap: break-word; /* Break long words if needed */
overflow-wrap: break-word; /* Modern browsers */
}
.bot-message pre {
background-color: #f8f9fa;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
.bot-message code {
font-family: 'Courier New', Courier, monospace;
background-color: #f8f9fa;
padding: 2px 4px;
border-radius: 3px;
}
.bot-message p {
margin: 0 0 1em 0;
}
.bot-message ul, .bot-message ol {
margin-left: 20px;
}
form { margin-top: 20px; margin-bottom: 20px; }
input[type="text"] { width: 80%; padding: 10px; border-radius: 5px; border: 1px solid #ddd;}
button { padding: 10px 20px; border: none; background-color: #007BFF; color: white; border-radius: 5px; cursor: pointer;}
#thinking {
display: none;
color: #666;
font-style: italic;
padding: 10px;
margin: 10px 0;
background-color: #f8f9fa;
border-left: 3px solid #57b368;
}
.dot-flashing {
display: inline-block;
position: relative;
width: 4px;
height: 4px;
background-color: #666;
border-radius: 50%;
animation: dot-flashing 1s infinite linear alternate;
margin-left: 8px;
}
@keyframes dot-flashing {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 0; }
}
</style>
</head>
<body>
<div class="container">
<h1>Reasoning with DeepSeek-R1</h1>
<form id="chat-form" method="POST">
<input type="text"
name="user_input"
placeholder="Ask me something that needs reasoning..."
value=""
required>
<button type="submit">Send</button>
</form>
<div id="thinking">
Thinking<span class="dot-flashing"></span>
</div>
<div class="chat-box">
{% if last_question %}
<div class="user-message">{{ last_question }}</div>
<div class="bot-message">{{ bot_response|safe }}</div>
{% endif %}
</div>
</div>

<script>
document.getElementById('chat-form').addEventListener('submit', async function(e) {
e.preventDefault();
const form = e.target;
const input = form.querySelector('input');
const thinking = document.getElementById('thinking');
const chatBox = document.querySelector('.chat-box');

// Show thinking indicator
thinking.style.display = 'block';

try {
const formData = new FormData(form);
const response = await fetch('/chat', {
method: 'POST',
body: formData
});
const data = await response.json();

// Add new messages to chat box
chatBox.innerHTML = `
<div class="user-message">${data.last_question}</div>
<div class="bot-message">${data.response}</div>
${chatBox.innerHTML}
`;

// Clear input
input.value = '';
} catch (error) {
console.error('Error:', error);
} finally {
// Hide thinking indicator
thinking.style.display = 'none';
}
});
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion chat/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Chat session example using Ollama's Python API and DeepSeek-R1 1.5B model.
Chat session client using Ollama's Python API and DeepSeek-R1 1.5B model.

Reasoning prompts with context are stored in `PROMPTS` list in the `main.py` script.
These were selected from some recommendations by OpenAI's ChatGPT.
Expand Down
Binary file added images/chat_browser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/chat_browser_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
Flask
markdown
ollama

0 comments on commit 4a43af3

Please sign in to comment.