Lee's Blog
  • 首页
  • 博客
  • 作品
  • 学习⌄
    • 成长路线
    • LeetCode
    • 练习
  • 关于
登录 / 注册
✦

保持联系

关注我的最新动态

GitHubbilibili
匿名问答箱粤ICP备2026008729号

© 2026 Lee's Blog

首页/博客/计划/还是忍不住
计划2026年5月22日21 分钟阅读

还是忍不住

AI日记软件 - 项目计划文档 项目概述 1.1 项目背景 做一个本地运行的AI日记软件,通过与AI对话的形式进行日记记录,最后自动总结归档。 核心特点: 本地运行,保护隐私 AI主动提问引导 多LLM支持 (OpenAI/Claude/Ollama) 双存储:SQLite查询…

X微博

AI日记软件 - 项目计划文档

1. 项目概述

1.1 项目背景

做一个本地运行的AI日记软件,通过与AI对话的形式进行日记记录,最后自动总结归档。

核心特点:

  • 本地运行,保护隐私
  • AI主动提问引导
  • 多LLM支持 (OpenAI/Claude/Ollama)
  • 双存储:SQLite查询 + txt归档

1.2 需求对齐

需求项 选择
技术栈 Python + CLI/TUI
LLM来源 多源支持 (OpenAI/Claude/Ollama)
存储方案 SQLite + txt (两者都要)
UI形式 命令行/TUI (textual)
核心流程 AI主动提问引导
附加功能 MVP优先,后续扩展

2. 技术选型

层级 技术选型 说明
TUI框架 textual v8+ 现代异步TUI,支持CSS布局
LLM调用 llm 库 多Provider统一接口 (OpenAI/Claude/Ollama)
数据存储 SQLite + txt SQLite查询 + txt归档
配置管理 JSON文件 API Key及参数配置

3. 架构设计

3.1 整体架构

┌─────────────────────────────────────────────────────────┐
│                      CLI/TUI 层                         │
│                  (textual / prompt_toolkit)             │
└─────────────────────┬───────────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────────┐
│                    业务逻辑层                            │
│  ┌─────────────┐  ┌─────────────┐  ┌───────────────┐  │
│  │ 会话管理器   │  │ Prompt引擎   │  │ 归档总结器    │  │
│  │ (Session)   │  │ (引导对话)   │  │ (summary)     │  │
│  └─────────────┘  └─────────────┘  └───────────────┘  │
└─────────────────────┬───────────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────────┐
│                    LLM 抽象层                            │
│  ┌─────────────┐  ┌─────────────┐  ┌───────────────┐  │
│  │ OpenAI适配  │  │ Claude适配  │  │ Ollama适配    │  │
│  │ (可复用llm) │  │ (可复用llm) │  │ (可复用llm)   │  │
│  └─────────────┘  └─────────────┘  └───────────────┘  │
└─────────────────────┬───────────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────────┐
│                    数据存储层                            │
│  ┌─────────────┐  ┌─────────────┐  ┌───────────────┐  │
│  │ SQLite      │  │ txt归档     │  │ config.json   │  │
│  │ (查询/统计) │  │ (备份/导出) │  │ (API配置)     │  │
│  └─────────────┘  └─────────────┘  └───────────────┘  │
└─────────────────────────────────────────────────────────┘

3.2 数据流设计

┌─────────────────────────────────────────────────────────────┐
│                      TUI 界面层                            │
│  DiaryApp.on_input_submit() → 显示消息                     │
└─────────────────────────┬───────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────┐
│                    会话管理层                               │
│  SessionManager.add_message() → 维护对话历史                │
└─────────────────────────┬───────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────┐
│                   Prompt 引导层                             │
│  PromptEngine.build_messages() → 构建设system + history    │
└─────────────────────────┬───────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────┐
│                     LLM 抽象层                              │
│  LLMManager.chat() → llm库统一接口                          │
│  (自动路由到 OpenAI / Claude / Ollama)                      │
└─────────────────────────┬───────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────┐
│                     存储层                                  │
│  SQLiteStorage (实时) + TxtArchiver (归档)                 │
└─────────────────────────────────────────────────────────────┘

4. 核心模块详细设计

4.1 LLM 抽象层 (src/llm/)

使用 llm 库作为底层,支持多Provider。

# src/llm/manager.py - LLM管理器
import llm

class LLMManager:
    """统一管理多Provider"""

    def __init__(self, config: dict):
        self.config = config
        self._model = None

    def get_model(self):
        """根据配置获取llm模型"""
        model_id = self.config.get('model', 'gpt-4')
        return llm.get_model(model_id)

    async def chat(self, messages: list[dict]) -> str:
        """统一聊天接口"""
        model = self.get_model()
        response = model.chat(messages)
        return response.text()

    async def stream_chat(self, messages: list[dict]):
        """流式聊天接口"""
        model = self.get_model()
        for chunk in model.chat(messages, stream=True):
            yield chunk.content

关键点:

  • llm 库已实现多Provider支持,通过 llm.get_model('模型名') 自动路由
  • 支持通过环境变量或 llm keys set 管理API Key
  • Ollama通过 llm-ollama 插件支持

4.2 会话管理层 (src/session/)

# src/session/manager.py
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
import uuid

@dataclass
class DiarySession:
    """日记会话"""
    id: str
    created_at: datetime
    messages: list[dict]  # [{"role": "user/assistant", "content": "..."}]
    summary: Optional[str] = None  # 归档总结
    status: str = "active"  # active / archived
class SessionManager:
    """会话管理器"""

    def __init__(self, storage):
        self.storage = storage
        self.current_session: Optional[DiarySession] = None

    def create_session(self) -> DiarySession:
        """创建新会话"""
        session = DiarySession(
            id=str(uuid.uuid4()),
            created_at=datetime.now(),
            messages=[]
        )
        self.storage.save_session(session)
        self.current_session = session
        return session

    def add_message(self, role: str, content: str):
        """添加消息到当前会话"""
        self.current_session.messages.append({
            "role": role,
            "content": content,
            "timestamp": datetime.now().isoformat()
        })
        self.storage.update_session(self.current_session)

    def archive_session(self, summary: str):
        """归档会话,生成总结"""
        self.current_session.summary = summary
        self.current_session.status = "archived"
        self.storage.update_session(self.current_session)
        self.storage.export_to_txt(self.current_session)

4.3 Prompt引导引擎 (src/prompt/)

# src/prompt/engine.py
from typing import Optional
import json
from pathlib import Path

class PromptEngine:
    """AI引导Prompt引擎"""

    def __init__(self, prompts_dir: str = "prompts"):
        self.prompts_dir = Path(prompts_dir)
        self.prompts = self._load_prompts()

    def _load_prompts(self) -> dict:
        """加载prompts配置"""
        default_prompt = {
            "system": "你是一个温暖的朋友,擅长通过提问帮助用户记录日记。请用友好的语气,主动引导用户分享今天的经历、感受和想法。每次对话后,根据用户的回答提出一个深入的追问。",
            "archive": "请为这段日记生成一个简洁的总结,包括:1)今天的主要事件;2)用户的情绪变化;3)重要的收获或感悟。不超过100字。"
        }
        prompt_file = self.prompts_dir / "prompts.json"
        if prompt_file.exists():
            with open(prompt_file) as f:
                return json.load(f)
        return default_prompt

    def get_system_prompt(self) -> str:
        """获取系统引导语"""
        return self.prompts.get("system", "")

    def get_archive_prompt(self) -> str:
        """获取归档总结Prompt"""
        return self.prompts.get("archive", "")

    def build_messages(self, user_input: str, history: list[dict]) -> list[dict]:
        """构建完整的messages数组"""
        messages = [
            {"role": "system", "content": self.get_system_prompt()}
        ]
        messages.extend(history)
        messages.append({"role": "user", "content": user_input})
        return messages

4.4 存储层 (src/storage/)

SQLite存储

# src/storage/sqlite.py
import sqlite3
from pathlib import Path
from datetime import datetime
from typing import Optional
import json

class SQLiteStorage:
    """SQLite存储"""

    def __init__(self, db_path: str = "diary.db"):
        self.db_path = db_path
        self._init_db()

    def _init_db(self):
        """初始化数据库表"""
        conn = sqlite3.connect(self.db_path)
        conn.execute("""
            CREATE TABLE IF NOT EXISTS sessions (
                id TEXT PRIMARY KEY,
                created_at TEXT NOT NULL,
                messages TEXT NOT NULL,
                summary TEXT,
                status TEXT DEFAULT 'active'
            )
        """)
        conn.commit()
        conn.close()

    def save_session(self, session):
        conn = sqlite3.connect(self.db_path)
        conn.execute("""
            INSERT INTO sessions (id, created_at, messages, status)
            VALUES (?, ?, ?, ?)
        """, (
            session.id,
            session.created_at.isoformat(),
            json.dumps(session.messages),
            session.status
        ))
        conn.commit()
        conn.close()

    def update_session(self, session):
        conn = sqlite3.connect(self.db_path)
        conn.execute("""
            UPDATE sessions
            SET messages = ?, summary = ?, status = ?
            WHERE id = ?
        """, (
            json.dumps(session.messages),
            session.summary,
            session.status,
            session.id
        ))
        conn.commit()
        conn.close()

    def list_sessions(self, limit: int = 10) -> list[dict]:
        """列出最近会话"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.execute("""
            SELECT id, created_at, summary, status
            FROM sessions
            ORDER BY created_at DESC
            LIMIT ?
        """, (limit,))
        rows = cursor.fetchall()
        conn.close()
        return [{"id": r[0], "created_at": r[1], "summary": r[2], "status": r[3]}
                for r in rows]

Txt归档

# src/storage/txt.py
from pathlib import Path
from datetime import datetime

class TxtArchiver:
    """txt归档器"""

    def __init__(self, diary_dir: str = "diary"):
        self.diary_dir = Path(diary_dir)
        self.diary_dir.mkdir(exist_ok=True)

    def export_session(self, session) -> Path:
        """导出会话到txt文件"""
        date_str = session.created_at.strftime("%Y-%m-%d")
        file_path = self.diary_dir / f"{date_str}.txt"

        content = self._build_content(session)

        if file_path.exists():
            with open(file_path, "a", encoding="utf-8") as f:
                f.write("\n\n" + "="*30 + "\n\n")
                f.write(content)
        else:
            with open(file_path, "w", encoding="utf-8") as f:
                f.write(content)

        return file_path

    def _build_content(self, session) -> str:
        """构建txt内容"""
        lines = [f"# 日记 - {session.created_at.strftime('%Y年%m月%d日 %H:%M')}\n"]

        for msg in session.messages:
            role = "我" if msg["role"] == "user" else "AI"
            lines.append(f"**{role}**: {msg['content']}")

        if session.summary:
            lines.append(f"\n---\n**总结**: {session.summary}")

        return "\n\n".join(lines)

4.5 TUI界面层 (src/ui/)

# src/ui/app.py
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Static, Input, Button
from textual.containers import Container, VerticalScroll
from textual.binding import Binding

class DiaryApp(App):
    """AI日记主应用"""

    CSS = """
    Screen {
        background: $surface;
    }
    #chat-container {
        height: 80%;
    }
    .message {
        padding: 1;
        margin: 1 0;
    }
    .user-message {
        color: $text;
        background: $accent-darken-1;
    }
    .ai-message {
        color: $text;
        background: $primary-darken-1;
    }
    #input-area {
        height: 3;
        border-top: solid primary;
    }
    """

    BINDINGS = [
        Binding("ctrl+q", "quit", "退出", priority=True),
        Binding("ctrl+n", "new_session", "新建日记"),
        Binding("ctrl+s", "archive_session", "归档"),
    ]

    def __init__(self, session_manager, llm_manager):
        super().__init__()
        self.session_manager = session_manager
        self.llm_manager = llm_manager
        self.conversation_history = []

    def compose(self) -> ComposeResult:
        yield Header()
        with Container(id="chat-container"):
            yield VerticalScroll(Static(id="messages"))
        with Container(id="input-area"):
            yield Input(placeholder="写下今天的故事...", id="user-input")
            yield Button("发送", id="send-btn", variant="primary")
        yield Footer()

    async def on_mount(self) -> None:
        """应用启动时创建新会话并获取AI引导"""
        session = self.session_manager.create_session()
        initial_prompt = "你好!今天过得怎么样?有什么想分享的吗?"
        await self._add_ai_message(initial_prompt)

    async def on_input_submit(self, event: Input.Submit) -> None:
        """处理用户提交"""
        user_input = event.value
        if not user_input.strip():
            return

        await self._add_user_message(user_input)
        self.session_manager.add_message("user", user_input)

        self.query_one("#user-input", Input).value = ""
        await self._get_ai_response(user_input)

    async def _add_user_message(self, content: str):
        """添加用户消息到界面"""
        messages = self.query_one("#messages", Static)
        messages.update(messages.renderable + f"\n[我]: {content}")

    async def _add_ai_message(self, content: str):
        """添加AI消息到界面"""
        messages = self.query_one("#messages", Static)
        messages.update(messages.renderable + f"\n[AI]: {content}")

    async def _get_ai_response(self, user_input: str):
        """获取AI响应"""
        from src.prompt.engine import PromptEngine
        prompt_engine = PromptEngine()
        messages = prompt_engine.build_messages(user_input, self.conversation_history)

        response_text = ""
        async for chunk in self.llm_manager.stream_chat(messages):
            response_text += chunk

        self.session_manager.add_message("assistant", response_text)
        self.conversation_history.append({"role": "user", "content": user_input})
        self.conversation_history.append({"role": "assistant", "content": response_text})

    def action_new_session(self):
        """新建会话"""
        self.conversation_history = []
        self.session_manager.create_session()

    def action_archive_session(self):
        """归档当前会话"""
        pass

    def action_quit(self):
        """退出应用"""
        self.exit()

5. 数据结构

5.1 数据库Schema

-- sessions 表
CREATE TABLE sessions (
    id TEXT PRIMARY KEY,           -- UUID
    created_at TEXT NOT NULL,      -- ISO时间
    messages TEXT NOT NULL,        -- JSON: [{"role":"user","content":"...","timestamp":"..."}]
    summary TEXT,                  -- 归档总结
    status TEXT DEFAULT 'active',  -- active / archived
    tags TEXT                      -- JSON: ["工作", "情感"] (未来扩展)
);

-- 索引
CREATE INDEX idx_sessions_created ON sessions(created_at);
CREATE INDEX idx_sessions_status ON sessions(status);

5.2 配置文件格式

// config.json
{
  "llm": {
    "provider": "openai",
    "model": "gpt-4o-mini",
    "temperature": 0.7,
    "max_tokens": 2048
  },
  "storage": {
    "db_path": "diary.db",
    "diary_dir": "diary"
  },
  "prompts": {
    "custom": true,
    "file": "prompts/prompts.json"
  },
  "ui": {
    "theme": "nord",
    "show_timestamp": true
  }
}
// prompts/prompts.json
{
  "system": "你是一个温暖的朋友,擅长通过提问帮助用户记录日记...",
  "archive": "请为这段日记生成一个简洁的总结..."
}

6. 目录结构

ai-diary/
├── main.py                    # 入口,CLI命令
├── config.json                # 主配置
├── requirements.txt           # 依赖
├── prompts/
│   └── prompts.json          # 可配置的引导prompt
├── src/
│   ├── __init__.py
│   ├── llm/
│   │   ├── __init__.py
│   │   └── manager.py         # LLM统一接口
│   ├── session/
│   │   ├── __init__.py
│   │   └── manager.py         # 会话管理
│   ├── prompt/
│   │   ├── __init__.py
│   │   └── engine.py          # Prompt引导引擎
│   ├── storage/
│   │   ├── __init__.py
│   │   ├── sqlite.py          # SQLite操作
│   │   └── txt.py             # txt归档
│   └── ui/
│       ├── __init__.py
│       ├── app.py             # TUI主应用
│       └── widgets.py         # 自定义组件
├── diary/                     # 归档txt
│   ├── 2026-05-20.txt
│   └── ...
└── diary.db                   # SQLite数据库

7. 关键实现要点

模块 关键点 注意事项
LLM调用 使用 llm 库的 model.chat() 接口 流式输出需用 stream=True
异步处理 Textual基于asyncio,需使用 async/await LLM调用必须是异步的
消息历史 每次构建messages时需包含完整history 考虑token限制,可截断
归档总结 会话结束时调用LLM生成总结 需要单独的一次LLM调用
配置管理 API Key用 llm keys set 管理 不直接存明文

8. 相关项目参考

项目 特点 借鉴点
simonw/llm CLI工具,多LLM支持 LLM抽象层、多Provider
local-diary-llm 本地LLM日记,PyQt6 Prompt可配置、本地隐私
reflective-journal-cli-app CLI日记,情绪追踪 会话管理

9. 待确认问题

  1. 每日Prompt策略:固定引导语 vs 随日期变化
  2. 归档触发:手动触发 vs 自动归档
  3. 多会话支持:是否需要同时多个日记会话
← 上一篇微信朋友圈卖的都来了下一篇 →重构文档

相关文章

  • 关于项目管理平台约 1 分钟
  • 职业规划软件构想约 4 分钟

评论

需要登录账号才能发表评论。

  • 加载中…

本页内容

  • AI日记软件 - 项目计划文档
  • 1. 项目概述
  • 1.1 项目背景
  • 1.2 需求对齐
  • 2. 技术选型
  • 3. 架构设计
  • 3.1 整体架构
  • 3.2 数据流设计
  • 4. 核心模块详细设计
  • 4.1 LLM 抽象层 (`src/llm/`)
  • src/llm/manager.py - LLM管理器
  • 4.2 会话管理层 (`src/session/`)
  • src/session/manager.py
  • 4.3 Prompt引导引擎 (`src/prompt/`)
  • src/prompt/engine.py
  • 4.4 存储层 (`src/storage/`)
  • src/storage/sqlite.py
  • src/storage/txt.py
  • 4.5 TUI界面层 (`src/ui/`)
  • src/ui/app.py
  • 5. 数据结构
  • 5.1 数据库Schema
  • 5.2 配置文件格式
  • 6. 目录结构
  • 7. 关键实现要点
  • 8. 相关项目参考
  • 9. 待确认问题