SQL(MySQL用 schema.sql)
CREATE DATABASE IF NOT EXISTS deepseeks
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE deepseeks;
-- シリアル番号管理テーブル(常に1行だけを保持)
CREATE TABLE IF NOT EXISTS serials (
id INT PRIMARY KEY,
current_serial INT NOT NULL
);
-- 初期値(0001スタートなど任意)
INSERT INTO serials (id, current_serial)
VALUES (1, 1)
ON DUPLICATE KEY UPDATE current_serial = current_serial;
-- トークンログテーブル
CREATE TABLE IF NOT EXISTS logs (
id INT AUTO_INCREMENT PRIMARY KEY,
serial_int INT NOT NULL,
serial_str VARCHAR(4) NOT NULL,
title VARCHAR(255) NOT NULL,
prompt_tokens INT NOT NULL,
completion_tokens INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Python: src/db/connection.py(MySQL接続)
import pymysql
DB_CONFIG = {
"host": "localhost",
"user": "deepseeks",
"password": "deepseeks0000",
"database": "deepseeks",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
"autocommit": True,
}
def get_connection():
return pymysql.connect(**DB_CONFIG)
Python: src/services/serial_service.py(シリアル採番)
from db.connection import get_connection
def get_and_increment_serial():
"""
serials.current_serial を取得し、その後 +1 して更新。
戻り値は取得時点の値(例: 1 → '0001')。
"""
conn = get_connection()
try:
with conn.cursor() as cur:
# 排他制御(シンプルに FOR UPDATE)
cur.execute("SELECT current_serial FROM serials WHERE id = 1 FOR UPDATE")
row = cur.fetchone()
if not row:
raise RuntimeError("serials テーブルが初期化されていません。")
current_serial = row["current_serial"]
next_serial = current_serial + 1
cur.execute(
"UPDATE serials SET current_serial = %s WHERE id = 1",
(next_serial,),
)
conn.commit()
return current_serial
finally:
conn.close()
def format_serial(serial_int: int) -> str:
"""
4桁ゼロ埋め文字列に変換(1 → '0001')
"""
return f"{serial_int:04d}"
Python: src/services/log_service.py(トークンログ記録)
from db.connection import get_connection
def insert_log(serial_int: int, serial_str: str, title: str,
prompt_tokens: int, completion_tokens: int):
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO logs (serial_int, serial_str, title,
prompt_tokens, completion_tokens)
VALUES (%s, %s, %s, %s, %s)
""",
(serial_int, serial_str, title, prompt_tokens, completion_tokens),
)
conn.commit()
finally:
conn.close()
Python: src/services/file_service.py(履歴ファイル保存)
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parents[2] # deepseeks/ を想定
PROMPTS_DIR = BASE_DIR / "src" / "prompts"
RESULTS_DIR = BASE_DIR / "src" / "results"
PROMPTS_DIR.mkdir(parents=True, exist_ok=True)
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
def sanitize_title(title: str) -> str:
"""
ファイル名に使えない文字を簡易除去/置換。
必要に応じて強化。
"""
bad_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|']
sanitized = title
for ch in bad_chars:
sanitized = sanitized.replace(ch, "_")
return sanitized.strip() or "untitled"
def build_prompt_filename(serial_str: str, title: str) -> Path:
safe_title = sanitize_title(title)
filename = f"{serial_str}_prompt_{safe_title}.txt"
return PROMPTS_DIR / filename
def build_result_filename(serial_str: str, title: str) -> Path:
safe_title = sanitize_title(title)
filename = f"{serial_str}_result_{safe_title}.txt"
return RESULTS_DIR / filename
def save_text(path: Path, content: str):
path.write_text(content, encoding="utf-8")
Python: src/services/deepseek_service.py(DeepSeek呼び出しラッパ・ダミー)
※ 実際の DeepSeek V4 API 仕様に合わせて書き換えてください。ここではダミー実装。
import os
import requests # 実際のAPIに合わせて利用(不要なら削除)
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY", "")
DEEPSEEK_API_URL = "https://api.deepseek.com/v4/chat/completions" # 仮URL
def call_deepseek(prompt: str):
"""
DeepSeek V4 を呼び出す想定のラッパ。
戻り値: (reply_text, prompt_tokens, completion_tokens)
"""
# TODO: 実際のAPI仕様に合わせて実装
# ここではダミー応答とダミートークン数を返す
dummy_reply = f"[DEEPSEEK REPLY]\n{prompt[:100]}..."
dummy_prompt_tokens = max(1, len(prompt) // 4)
dummy_completion_tokens = max(1, len(dummy_reply) // 4)
return dummy_reply, dummy_prompt_tokens, dummy_completion_tokens
Python: src/flask_server.py(Flaskアプリ本体)
from flask import Flask, render_template, request
from services.serial_service import get_and_increment_serial, format_serial
from services.file_service import (
build_prompt_filename,
build_result_filename,
save_text,
)
from services.log_service import insert_log
from services.deepseek_service import call_deepseek
app = Flask(
__name__,
template_folder="templates",
static_folder="static_deepseeks",
)
@app.route("/", methods=["GET", "POST"])
def index():
reply_text = None
prompt_text = None
title = None
serial_str = None
if request.method == "POST":
title = request.form.get("title", "").strip()
prompt_text = request.form.get("prompt", "").strip()
if title and prompt_text:
# シリアル取得&インクリメント
serial_int = get_and_increment_serial()
serial_str = format_serial(serial_int)
# DeepSeek呼び出し
reply_text, prompt_tokens, completion_tokens = call_deepseek(prompt_text)
# ファイル保存
prompt_path = build_prompt_filename(serial_str, title)
result_path = build_result_filename(serial_str, title)
save_text(prompt_path, prompt_text)
save_text(result_path, reply_text)
# トークンログ保存
insert_log(
serial_int=serial_int,
serial_str=serial_str,
title=title,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
)
return render_template(
"page.html",
reply_text=reply_text,
prompt_text=prompt_text,
title_value=title,
serial_str=serial_str,
)
if __name__ == "__main__":
app.run(debug=True)
HTML: src/templates/base.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}DeepSeeks Wrapper{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<header class="header">
<h1>DeepSeek V4 Wrapper</h1>
</header>
<main class="main">
{% block content %}{% endblock %}
</main>
<footer class="footer">
<small>© deepseeks wrapper</small>
</footer>
</body>
</html>
HTML: src/templates/page.html
{% extends "base.html" %}
{% block title %}
DeepSeeks Wrapper
{% endblock %}
{% block content %}
<section class="form-section">
<form method="post">
<div class="form-group">
<label for="title">Title(page.htmlのtitleとしても使用)</label>
<input type="text" id="title" name="title"
value="{{ title_value or '' }}" required>
</div>
<div class="form-group">
<label for="prompt">Prompt</label>
<textarea id="prompt" name="prompt" rows="10" required>{{ prompt_text or '' }}</textarea>
</div>
<div class="form-group">
<button type="submit">Send to DeepSeek</button>
</div>
</form>
</section>
{% if reply_text %}
<section class="result-section">
<h2>Result</h2>
{% if serial_str %}
<p class="serial-info">Serial: {{ serial_str }}</p>
{% endif %}
<pre class="result-pre">{{ reply_text }}</pre>
</section>
{% endif %}
{% endblock %}
CSS: src/static_deepseeks/style.css
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background-color: #f5f5f7;
color: #222;
}
.header {
background-color: #111827;
color: #f9fafb;
padding: 1rem 2rem;
}
.header h1 {
margin: 0;
font-size: 1.4rem;
}
.main {
max-width: 960px;
margin: 2rem auto;
padding: 0 1rem;
}
.form-section,
.result-section {
background-color: #ffffff;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
margin-bottom: 1.5rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 0.4rem;
}
.form-group input[type="text"],
.form-group textarea {
width: 100%;
box-sizing: border-box;
padding: 0.5rem 0.6rem;
border-radius: 4px;
border: 1px solid #d1d5db;
font-size: 0.95rem;
}
.form-group textarea {
resize: vertical;
}
.form-group button {
padding: 0.5rem 1.2rem;
border-radius: 4px;
border: none;
background-color: #2563eb;
color: #f9fafb;
font-size: 0.95rem;
cursor: pointer;
}
.form-group button:hover {
background-color: #1d4ed8;
}
.result-pre {
white-space: pre-wrap;
background-color: #f3f4f6;
padding: 1rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.9rem;
}
.serial-info {
font-size: 0.85rem;
color: #6b7280;
margin-bottom: 0.5rem;
}
.footer {
text-align: center;
padding: 1rem 0;
color: #6b7280;
font-size: 0.8rem;
}
必要に応じて:
DEEPSEEK_API_URLとcall_deepseek内の実装を実際の DeepSeek V4 API 仕様に差し替え。
ここまでで、指定フォルダ構成・ファイル命名規則・MySQLログ・シリアル採番が一通りつながる形になっています。