palacelite 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- palacelite-0.3.0/LICENSE +21 -0
- palacelite-0.3.0/PKG-INFO +140 -0
- palacelite-0.3.0/README.md +125 -0
- palacelite-0.3.0/palacelite/__init__.py +7 -0
- palacelite-0.3.0/palacelite/cli.py +251 -0
- palacelite-0.3.0/palacelite/core.py +227 -0
- palacelite-0.3.0/palacelite/models.py +145 -0
- palacelite-0.3.0/palacelite/retrieval.py +231 -0
- palacelite-0.3.0/palacelite/storage.py +519 -0
- palacelite-0.3.0/palacelite/utils.py +290 -0
- palacelite-0.3.0/palacelite.egg-info/PKG-INFO +140 -0
- palacelite-0.3.0/palacelite.egg-info/SOURCES.txt +16 -0
- palacelite-0.3.0/palacelite.egg-info/dependency_links.txt +1 -0
- palacelite-0.3.0/palacelite.egg-info/entry_points.txt +2 -0
- palacelite-0.3.0/palacelite.egg-info/requires.txt +4 -0
- palacelite-0.3.0/palacelite.egg-info/top_level.txt +1 -0
- palacelite-0.3.0/pyproject.toml +27 -0
- palacelite-0.3.0/setup.cfg +4 -0
palacelite-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fajknli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: palacelite
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: 轻量级 AI 记忆系统
|
|
5
|
+
Author: fajknli
|
|
6
|
+
Project-URL: Changelog, https://github.com/fajknli/palacelite/blob/main/CHANGELOG.md
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: chromadb>=1.5.0
|
|
11
|
+
Requires-Dist: sentence-transformers>=5.0.0
|
|
12
|
+
Requires-Dist: click>=8.0.0
|
|
13
|
+
Requires-Dist: rich>=13.0.0
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# PalaceLite
|
|
17
|
+
|
|
18
|
+
[](https://opensource.org/licenses/MIT)
|
|
19
|
+
[](https://www.python.org/downloads/)
|
|
20
|
+
[](https://github.com/fajknli/palacelite/releases)
|
|
21
|
+
|
|
22
|
+
轻量级 AI 记忆系统,灵感来自MemPalace。默认离线运行,数据完全本地化。
|
|
23
|
+
|
|
24
|
+
## 核心概念
|
|
25
|
+
|
|
26
|
+
采用三层结构组织记忆:**Wing**(项目或人)→ **Room**(具体话题)→ **Drawer**(记忆条目)。每条记忆存储原文和向量,支持混合检索(向量相似度 + 时间衰减 + 重要性加权)。
|
|
27
|
+
|
|
28
|
+
## 安装
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/fajknli/palacelite.git
|
|
32
|
+
cd palacelite
|
|
33
|
+
pip install -e .
|
|
34
|
+
pip install -r requirements.txt
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
首次使用需下载嵌入模型(约百MB,BAAI/bge-small-zh-v1.5),添加 `--online` 参数即可自动下载,之后可永久离线运行。
|
|
38
|
+
|
|
39
|
+
## 快速开始
|
|
40
|
+
|
|
41
|
+
### CLI
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# 添加记忆
|
|
45
|
+
palacelite add -w default -r chat -c "用户喜欢 PostgreSQL"
|
|
46
|
+
|
|
47
|
+
# 搜索
|
|
48
|
+
palacelite search -q "数据库"
|
|
49
|
+
|
|
50
|
+
# 列出活跃记忆
|
|
51
|
+
palacelite list
|
|
52
|
+
|
|
53
|
+
# 归档超过90天且重要性低于2的记忆
|
|
54
|
+
palacelite archive-old --days 90 --importance 2
|
|
55
|
+
|
|
56
|
+
# 导出已归档记忆(自动保存到 ~/.palacelite/exports/)
|
|
57
|
+
palacelite export-archived
|
|
58
|
+
|
|
59
|
+
# 永久删除已归档记忆
|
|
60
|
+
palacelite delete-archived
|
|
61
|
+
|
|
62
|
+
# 统计
|
|
63
|
+
palacelite stats
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Python API
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from palacelite import PalaceLite
|
|
70
|
+
|
|
71
|
+
p = PalaceLite() # 默认离线,offline=False 可联网下载模型
|
|
72
|
+
|
|
73
|
+
p.add_memory("用户喜欢 PostgreSQL", "default", "chat")
|
|
74
|
+
results = p.search("数据库")
|
|
75
|
+
context = p.build_context("数据库选型") # 构建 LLM 上下文
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
验证安装:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python -c "from palacelite import PalaceLite; print('OK')"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 带记忆对话
|
|
85
|
+
|
|
86
|
+
`examples/` 目录提供了完整的对话示例:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 离线模式(默认)
|
|
90
|
+
python examples/chat.py -m /path/to/model.gguf
|
|
91
|
+
|
|
92
|
+
# 联网模式(首次下载嵌入模型)
|
|
93
|
+
python examples/chat.py -m /path/to/model.gguf --online
|
|
94
|
+
|
|
95
|
+
# 指定 GPU 层数
|
|
96
|
+
python examples/chat.py -m /path/to/model.gguf -g 30
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
对话命令:`/help`、`/mem`、`/list`、`/stats`、`/clear`、`/quit`。
|
|
100
|
+
|
|
101
|
+
## 命令速查
|
|
102
|
+
|
|
103
|
+
| 命令 | 说明 |
|
|
104
|
+
|------|------|
|
|
105
|
+
| `add -w <wing> -r <room> -c <内容>` | 添加记忆 |
|
|
106
|
+
| `search -q <关键词>` | 搜索记忆 |
|
|
107
|
+
| `list [-w wing] [-r room]` | 列出活跃记忆 |
|
|
108
|
+
| `archive-old [--days 90] [--importance 2]` | 批量归档旧记忆 |
|
|
109
|
+
| `list-archived` | 列出已归档记忆 |
|
|
110
|
+
| `export-archived [-o 路径]` | 导出归档到 JSON |
|
|
111
|
+
| `delete-archived` | 永久删除归档记忆 |
|
|
112
|
+
| `stats` | 显示统计信息 |
|
|
113
|
+
| `wings` | 列出所有 Wing |
|
|
114
|
+
| `rooms <wing>` | 列出指定 Wing 下的 Room |
|
|
115
|
+
|
|
116
|
+
## 配置
|
|
117
|
+
|
|
118
|
+
| 环境变量 | 默认值 | 说明 |
|
|
119
|
+
|----------|--------|------|
|
|
120
|
+
| `PALACE_MODEL_PATH` | - | GGUF 模型文件路径 |
|
|
121
|
+
| `PALACE_MODEL_DIR` | `~/Public/ai/models` | 模型存放目录 |
|
|
122
|
+
| `GPU_LAYERS` | `20` | GPU 加速层数 |
|
|
123
|
+
|
|
124
|
+
工作目录默认为 `~/.palacelite`,可通过 `PalaceLite(workspace="/path")` 自定义。
|
|
125
|
+
|
|
126
|
+
## 数据维护
|
|
127
|
+
|
|
128
|
+
导出数据清洗(用于微调):
|
|
129
|
+
```bash
|
|
130
|
+
python scripts/clean_memories.py ~/backup.json ~/cleaned.json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
更换嵌入模型后重建向量库:
|
|
134
|
+
```bash
|
|
135
|
+
python scripts/rebuild_embeddings.py
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 许可证
|
|
139
|
+
|
|
140
|
+
MIT
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# PalaceLite
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://github.com/fajknli/palacelite/releases)
|
|
6
|
+
|
|
7
|
+
轻量级 AI 记忆系统,灵感来自MemPalace。默认离线运行,数据完全本地化。
|
|
8
|
+
|
|
9
|
+
## 核心概念
|
|
10
|
+
|
|
11
|
+
采用三层结构组织记忆:**Wing**(项目或人)→ **Room**(具体话题)→ **Drawer**(记忆条目)。每条记忆存储原文和向量,支持混合检索(向量相似度 + 时间衰减 + 重要性加权)。
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/fajknli/palacelite.git
|
|
17
|
+
cd palacelite
|
|
18
|
+
pip install -e .
|
|
19
|
+
pip install -r requirements.txt
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
首次使用需下载嵌入模型(约百MB,BAAI/bge-small-zh-v1.5),添加 `--online` 参数即可自动下载,之后可永久离线运行。
|
|
23
|
+
|
|
24
|
+
## 快速开始
|
|
25
|
+
|
|
26
|
+
### CLI
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 添加记忆
|
|
30
|
+
palacelite add -w default -r chat -c "用户喜欢 PostgreSQL"
|
|
31
|
+
|
|
32
|
+
# 搜索
|
|
33
|
+
palacelite search -q "数据库"
|
|
34
|
+
|
|
35
|
+
# 列出活跃记忆
|
|
36
|
+
palacelite list
|
|
37
|
+
|
|
38
|
+
# 归档超过90天且重要性低于2的记忆
|
|
39
|
+
palacelite archive-old --days 90 --importance 2
|
|
40
|
+
|
|
41
|
+
# 导出已归档记忆(自动保存到 ~/.palacelite/exports/)
|
|
42
|
+
palacelite export-archived
|
|
43
|
+
|
|
44
|
+
# 永久删除已归档记忆
|
|
45
|
+
palacelite delete-archived
|
|
46
|
+
|
|
47
|
+
# 统计
|
|
48
|
+
palacelite stats
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Python API
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from palacelite import PalaceLite
|
|
55
|
+
|
|
56
|
+
p = PalaceLite() # 默认离线,offline=False 可联网下载模型
|
|
57
|
+
|
|
58
|
+
p.add_memory("用户喜欢 PostgreSQL", "default", "chat")
|
|
59
|
+
results = p.search("数据库")
|
|
60
|
+
context = p.build_context("数据库选型") # 构建 LLM 上下文
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
验证安装:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python -c "from palacelite import PalaceLite; print('OK')"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 带记忆对话
|
|
70
|
+
|
|
71
|
+
`examples/` 目录提供了完整的对话示例:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 离线模式(默认)
|
|
75
|
+
python examples/chat.py -m /path/to/model.gguf
|
|
76
|
+
|
|
77
|
+
# 联网模式(首次下载嵌入模型)
|
|
78
|
+
python examples/chat.py -m /path/to/model.gguf --online
|
|
79
|
+
|
|
80
|
+
# 指定 GPU 层数
|
|
81
|
+
python examples/chat.py -m /path/to/model.gguf -g 30
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
对话命令:`/help`、`/mem`、`/list`、`/stats`、`/clear`、`/quit`。
|
|
85
|
+
|
|
86
|
+
## 命令速查
|
|
87
|
+
|
|
88
|
+
| 命令 | 说明 |
|
|
89
|
+
|------|------|
|
|
90
|
+
| `add -w <wing> -r <room> -c <内容>` | 添加记忆 |
|
|
91
|
+
| `search -q <关键词>` | 搜索记忆 |
|
|
92
|
+
| `list [-w wing] [-r room]` | 列出活跃记忆 |
|
|
93
|
+
| `archive-old [--days 90] [--importance 2]` | 批量归档旧记忆 |
|
|
94
|
+
| `list-archived` | 列出已归档记忆 |
|
|
95
|
+
| `export-archived [-o 路径]` | 导出归档到 JSON |
|
|
96
|
+
| `delete-archived` | 永久删除归档记忆 |
|
|
97
|
+
| `stats` | 显示统计信息 |
|
|
98
|
+
| `wings` | 列出所有 Wing |
|
|
99
|
+
| `rooms <wing>` | 列出指定 Wing 下的 Room |
|
|
100
|
+
|
|
101
|
+
## 配置
|
|
102
|
+
|
|
103
|
+
| 环境变量 | 默认值 | 说明 |
|
|
104
|
+
|----------|--------|------|
|
|
105
|
+
| `PALACE_MODEL_PATH` | - | GGUF 模型文件路径 |
|
|
106
|
+
| `PALACE_MODEL_DIR` | `~/Public/ai/models` | 模型存放目录 |
|
|
107
|
+
| `GPU_LAYERS` | `20` | GPU 加速层数 |
|
|
108
|
+
|
|
109
|
+
工作目录默认为 `~/.palacelite`,可通过 `PalaceLite(workspace="/path")` 自定义。
|
|
110
|
+
|
|
111
|
+
## 数据维护
|
|
112
|
+
|
|
113
|
+
导出数据清洗(用于微调):
|
|
114
|
+
```bash
|
|
115
|
+
python scripts/clean_memories.py ~/backup.json ~/cleaned.json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
更换嵌入模型后重建向量库:
|
|
119
|
+
```bash
|
|
120
|
+
python scripts/rebuild_embeddings.py
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 许可证
|
|
124
|
+
|
|
125
|
+
MIT
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
PalaceLite 命令行工具
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
palacelite add --wing <wing> --room <room> --content "记忆内容"
|
|
8
|
+
palacelite search --query "搜索内容"
|
|
9
|
+
palacelite list --wing <wing>
|
|
10
|
+
palacelite stats
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
from rich.table import Table
|
|
19
|
+
|
|
20
|
+
from .core import PalaceLite
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
|
26
|
+
def cli():
|
|
27
|
+
"""PalaceLite - 轻量级 AI 记忆系统"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@cli.command()
|
|
31
|
+
@click.option("--wing", "-w", required=True, help="Wing 名称(项目或人)")
|
|
32
|
+
@click.option("--room", "-r", required=True, help="Room 名称(话题)")
|
|
33
|
+
@click.option("--content", "-c", required=True, help="记忆内容")
|
|
34
|
+
@click.option("--tags", "-t", help="标签,逗号分隔")
|
|
35
|
+
@click.option("--importance", "-i", default=1, help="重要性(1-10)")
|
|
36
|
+
def add(wing, room, content, tags, importance):
|
|
37
|
+
"""添加记忆"""
|
|
38
|
+
p = PalaceLite()
|
|
39
|
+
|
|
40
|
+
# 确保 wing 存在
|
|
41
|
+
if not p.get_wing(wing):
|
|
42
|
+
p.add_wing(wing)
|
|
43
|
+
console.print(f"[yellow]自动创建 Wing: {wing}[/yellow]")
|
|
44
|
+
|
|
45
|
+
# 确保 room 存在
|
|
46
|
+
if not p.get_room(wing, room):
|
|
47
|
+
p.add_room(wing, room)
|
|
48
|
+
console.print(f"[yellow]自动创建 Room: {wing}/{room}[/yellow]")
|
|
49
|
+
|
|
50
|
+
tag_list = tags.split(",") if tags else []
|
|
51
|
+
drawer = p.add_memory(content, wing, room, tag_list, importance)
|
|
52
|
+
|
|
53
|
+
console.print(f"[green]✓[/green] 已添加记忆 (id: {drawer.id[:8]}...)")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@cli.command()
|
|
57
|
+
@click.option("--query", "-q", required=True, help="搜索内容")
|
|
58
|
+
@click.option("--wing", "-w", help="限定 Wing")
|
|
59
|
+
@click.option("--room", "-r", help="限定 Room(需与 --wing 一起使用)")
|
|
60
|
+
@click.option("--top", "-k", default=5, help="返回结果数量")
|
|
61
|
+
def search(query, wing, room, top):
|
|
62
|
+
"""搜索记忆"""
|
|
63
|
+
p = PalaceLite()
|
|
64
|
+
|
|
65
|
+
if wing and room:
|
|
66
|
+
results = p.search_by_room(query, wing, room, top)
|
|
67
|
+
title = f"在 {wing}/{room} 中搜索: {query}"
|
|
68
|
+
elif wing:
|
|
69
|
+
results = p.search_by_wing(query, wing, top)
|
|
70
|
+
title = f"在 {wing} 中搜索: {query}"
|
|
71
|
+
else:
|
|
72
|
+
results = p.search(query, top_k=top)
|
|
73
|
+
title = f"搜索: {query}"
|
|
74
|
+
|
|
75
|
+
if not results:
|
|
76
|
+
console.print("[yellow]未找到相关记忆[/yellow]")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
table = Table(title=title)
|
|
80
|
+
table.add_column("#", style="dim")
|
|
81
|
+
table.add_column("位置", style="cyan")
|
|
82
|
+
table.add_column("记忆内容", style="white")
|
|
83
|
+
table.add_column("得分", style="green")
|
|
84
|
+
|
|
85
|
+
for i, r in enumerate(results, 1):
|
|
86
|
+
location = f"{r.wing_name}/{r.room_name}"
|
|
87
|
+
content = r.drawer.content[:80] + ("..." if len(r.drawer.content) > 80 else "")
|
|
88
|
+
table.add_row(str(i), location, content, f"{r.score:.3f}")
|
|
89
|
+
|
|
90
|
+
console.print(table)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@cli.command()
|
|
94
|
+
@click.option("--wing", "-w", help="限定 Wing")
|
|
95
|
+
@click.option("--room", "-r", help="限定 Room(需与 --wing 一起使用)")
|
|
96
|
+
def list(wing, room):
|
|
97
|
+
"""列出记忆"""
|
|
98
|
+
p = PalaceLite()
|
|
99
|
+
|
|
100
|
+
if wing and room:
|
|
101
|
+
memories = p.list_memories(wing, room)
|
|
102
|
+
title = f"记忆列表: {wing}/{room}"
|
|
103
|
+
elif wing:
|
|
104
|
+
memories = p.list_memories(wing)
|
|
105
|
+
title = f"记忆列表: {wing}"
|
|
106
|
+
else:
|
|
107
|
+
memories = p.list_memories()
|
|
108
|
+
title = "所有记忆"
|
|
109
|
+
|
|
110
|
+
if not memories:
|
|
111
|
+
console.print("[yellow]没有记忆[/yellow]")
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
table = Table(title=title)
|
|
115
|
+
table.add_column("ID", style="dim")
|
|
116
|
+
table.add_column("位置", style="cyan")
|
|
117
|
+
table.add_column("记忆内容", style="white")
|
|
118
|
+
table.add_column("重要性", style="green")
|
|
119
|
+
|
|
120
|
+
for m in memories[:20]:
|
|
121
|
+
# 获取位置
|
|
122
|
+
room_obj = p.get_room_by_id(m.room_id)
|
|
123
|
+
if room_obj:
|
|
124
|
+
wing_obj = p.get_wing_by_id(room_obj.wing_id)
|
|
125
|
+
wing_name = wing_obj.name if wing_obj else "?"
|
|
126
|
+
room_name = room_obj.name
|
|
127
|
+
else:
|
|
128
|
+
wing_name = "?"
|
|
129
|
+
room_name = "?"
|
|
130
|
+
|
|
131
|
+
content = m.content[:60] + ("..." if len(m.content) > 60 else "")
|
|
132
|
+
table.add_row(m.id[:8], f"{wing_name}/{room_name}", content, str(m.importance))
|
|
133
|
+
|
|
134
|
+
console.print(table)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@cli.command()
|
|
138
|
+
def stats():
|
|
139
|
+
"""显示统计信息"""
|
|
140
|
+
p = PalaceLite()
|
|
141
|
+
stats = p.stats()
|
|
142
|
+
|
|
143
|
+
panel = Panel(
|
|
144
|
+
f"[bold]Wings:[/bold] {stats['wings']}\n"
|
|
145
|
+
f"[bold]Rooms:[/bold] {stats['rooms']}\n"
|
|
146
|
+
f"[bold]Drawers:[/bold] {stats['drawers']}\n"
|
|
147
|
+
f"[bold]总重要性:[/bold] {stats['total_importance']}\n"
|
|
148
|
+
f"[bold]数据库大小:[/bold] {stats['db_size_bytes'] / 1024:.1f} KB\n"
|
|
149
|
+
f"[bold]工作目录:[/bold] {stats['workspace']}",
|
|
150
|
+
title="PalaceLite 统计",
|
|
151
|
+
border_style="green",
|
|
152
|
+
)
|
|
153
|
+
console.print(panel)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@cli.command()
|
|
157
|
+
@click.option("--days", "-d", default=90, help="归档超过多少天的记忆")
|
|
158
|
+
@click.option("--importance", "-i", default=2, help="重要性低于此值的记忆")
|
|
159
|
+
def archive_old(days, importance):
|
|
160
|
+
"""归档旧记忆"""
|
|
161
|
+
p = PalaceLite()
|
|
162
|
+
count = p.archive_old_memories(min_importance=importance, days_old=days)
|
|
163
|
+
console.print(f"[green]✓ 已归档 {count} 条记忆[/green]")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@cli.command()
|
|
167
|
+
def list_archived():
|
|
168
|
+
"""列出已归档的记忆"""
|
|
169
|
+
p = PalaceLite()
|
|
170
|
+
memories = p.list_archived_memories()
|
|
171
|
+
|
|
172
|
+
if not memories:
|
|
173
|
+
console.print("[yellow]没有已归档的记忆[/yellow]")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
table = Table(title="已归档的记忆")
|
|
177
|
+
table.add_column("#", style="dim")
|
|
178
|
+
table.add_column("ID", style="dim")
|
|
179
|
+
table.add_column("内容", style="white")
|
|
180
|
+
|
|
181
|
+
for i, m in enumerate(memories[:20], 1):
|
|
182
|
+
content = m.content[:60] + ("..." if len(m.content) > 60 else "")
|
|
183
|
+
table.add_row(str(i), m.id[:8], content)
|
|
184
|
+
|
|
185
|
+
console.print(table)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@cli.command()
|
|
189
|
+
@click.option("--output", "-o", help="输出文件路径(可选,默认自动生成)")
|
|
190
|
+
def export_archived(output):
|
|
191
|
+
"""导出已归档的记忆到 JSON"""
|
|
192
|
+
p = PalaceLite()
|
|
193
|
+
count, path = p.export_archived_memories(output)
|
|
194
|
+
console.print(f"[green]已导出 {count} 条记忆到 {path}[/green]")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@cli.command()
|
|
198
|
+
@click.confirmation_option(prompt="确定要永久删除所有已归档的记忆吗?")
|
|
199
|
+
def delete_archived():
|
|
200
|
+
"""永久删除已归档的记忆"""
|
|
201
|
+
p = PalaceLite()
|
|
202
|
+
count = p.delete_archived_memories()
|
|
203
|
+
console.print(f"[green]✓ 已永久删除 {count} 条记忆[/green]")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@cli.command()
|
|
207
|
+
def wings():
|
|
208
|
+
"""列出所有 Wing"""
|
|
209
|
+
p = PalaceLite()
|
|
210
|
+
wings = p.list_wings()
|
|
211
|
+
|
|
212
|
+
if not wings:
|
|
213
|
+
console.print("[yellow]没有 Wing[/yellow]")
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
table = Table(title="Wings 列表")
|
|
217
|
+
table.add_column("名称", style="cyan")
|
|
218
|
+
table.add_column("类型", style="green")
|
|
219
|
+
table.add_column("创建时间", style="dim")
|
|
220
|
+
|
|
221
|
+
for w in wings:
|
|
222
|
+
created = datetime.fromtimestamp(w.created_at).strftime("%Y-%m-%d %H:%M")
|
|
223
|
+
table.add_row(w.name, w.type, created)
|
|
224
|
+
|
|
225
|
+
console.print(table)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@cli.command()
|
|
229
|
+
@click.argument("wing_name")
|
|
230
|
+
def rooms(wing_name):
|
|
231
|
+
"""列出指定 Wing 下的 Rooms"""
|
|
232
|
+
p = PalaceLite()
|
|
233
|
+
rooms = p.list_rooms(wing_name)
|
|
234
|
+
|
|
235
|
+
if not rooms:
|
|
236
|
+
console.print(f"[yellow]Wing '{wing_name}' 下没有 Room[/yellow]")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
table = Table(title=f"Rooms 列表: {wing_name}")
|
|
240
|
+
table.add_column("名称", style="cyan")
|
|
241
|
+
table.add_column("创建时间", style="dim")
|
|
242
|
+
|
|
243
|
+
for r in rooms:
|
|
244
|
+
created = datetime.fromtimestamp(r.created_at).strftime("%Y-%m-%d %H:%M")
|
|
245
|
+
table.add_row(r.name, created)
|
|
246
|
+
|
|
247
|
+
console.print(table)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
cli()
|