better_prompt 0.2.0
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.cn.md +107 -0
- data/README.md +100 -0
- data/bin/bp +14 -0
- data/db/init.sql +83 -0
- data/lib/better_prompt/cli.rb +27 -0
- data/lib/better_prompt/components/feedback.rb +33 -0
- data/lib/better_prompt/components/main.rb +10 -0
- data/lib/better_prompt/components/model_call.rb +159 -0
- data/lib/better_prompt/components/prompt_list.rb +112 -0
- data/lib/better_prompt/components/response.rb +74 -0
- data/lib/better_prompt/components/root.rb +38 -0
- data/lib/better_prompt/components/show_prompt.rb +19 -0
- data/lib/better_prompt/components/sidebar.rb +15 -0
- data/lib/better_prompt/components/template_list.rb +90 -0
- data/lib/better_prompt/models.rb +31 -0
- data/lib/better_prompt/orm.rb +16 -0
- data/lib/better_prompt/version.rb +3 -0
- data/lib/better_prompt.rb +158 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1b0275e59070ddcb50314443ed257f616af962ab8e7e328ee78ff337ef106b37
|
4
|
+
data.tar.gz: 6e2a503dfa2399a1ddf25276647edf7a85422b723aeaf444c01832869d504e8b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e14e38212b0ac9b0840647f37acac1f61602e9a5152bce1dff23be3c2836bb658fb5a4c50434be1d1d9a22c9aaecfe0e16ab4f71467ebc35772996679c852117
|
7
|
+
data.tar.gz: 2614387d896abbfe25104ed50e38c2752fc363d3a876d47368c6f7ea4afb55bc5677f5f56a7a819df9721f61a52db453e83346a887d096648bc4965ec7e6a858
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 庄表伟
|
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.
|
data/README.cn.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# BetterPrompt: 命令行中的 AI 提示词管理利器
|
2
|
+
|
3
|
+
BetterPrompt 是一个功能强大的命令行工具,旨在帮助开发者和内容创作者高效地管理、测试和调用 AI 模型的提示词(Prompts)。
|
4
|
+
|
5
|
+
## 🚀 功能特性
|
6
|
+
|
7
|
+
* **提示词管理**: 轻松实现提示词的增、删、改、查。
|
8
|
+
* **模板支持**: 创建和管理提示词模板,提高复用性。
|
9
|
+
* **模型调用**: 直接在命令行中调用 AI 模型并查看结果。
|
10
|
+
* **本地存储**: 使用 SQLite 数据库在本地安全地存储您的提示词和历史记录。
|
11
|
+
* **灵活配置**: 支持自定义 AI 模型的 API 地址和密钥。
|
12
|
+
|
13
|
+
## 📦 安装
|
14
|
+
|
15
|
+
通过 RubyGems 安装 BetterPrompt:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
gem install better_prompt
|
19
|
+
```
|
20
|
+
|
21
|
+
## ⚙️ 配置
|
22
|
+
|
23
|
+
首次运行时,BetterPrompt 会引导您完成初始化。您也可以手动执行:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
bp --init
|
27
|
+
```
|
28
|
+
|
29
|
+
该命令会在您的用户主目录下创建 `.better_prompt` 文件夹,并包含 `config.yml` 配置文件和 `prompt.db` 数据库文件。
|
30
|
+
|
31
|
+
请修改 `config.yml` 文件,填入您的 AI 模型提供商的 `api_key` 和 `api_url`。
|
32
|
+
|
33
|
+
```yaml
|
34
|
+
# ~/.better_prompt/config.yml
|
35
|
+
api_key: "YOUR_API_KEY"
|
36
|
+
api_url: "https://api.openai.com/v1/chat/completions"
|
37
|
+
model: "gpt-3.5-turbo"
|
38
|
+
```
|
39
|
+
|
40
|
+
## 🛠️ 使用方法
|
41
|
+
|
42
|
+
BetterPrompt 提供了简洁的命令行接口来管理您的提示词。
|
43
|
+
|
44
|
+
### 主要命令
|
45
|
+
|
46
|
+
* **列出所有提示词**
|
47
|
+
```bash
|
48
|
+
bp --list
|
49
|
+
# or
|
50
|
+
bp -l
|
51
|
+
```
|
52
|
+
|
53
|
+
* **显示一个提示词的详细内容**
|
54
|
+
```bash
|
55
|
+
bp --show <ID>
|
56
|
+
# or
|
57
|
+
bp -s <ID>
|
58
|
+
```
|
59
|
+
|
60
|
+
* **添加一个新的提示词**
|
61
|
+
*BetterPrompt 会启动您默认的文本编辑器来编写提示词内容。*
|
62
|
+
```bash
|
63
|
+
bp --add
|
64
|
+
# or
|
65
|
+
bp -a
|
66
|
+
```
|
67
|
+
|
68
|
+
* **编辑一个已存在的提示词**
|
69
|
+
*BetterPrompt 会启动您默认的文本编辑器来修改提示词内容。*
|
70
|
+
```bash
|
71
|
+
bp --edit <ID>
|
72
|
+
# or
|
73
|
+
bp -e <ID>
|
74
|
+
```
|
75
|
+
|
76
|
+
* **删除一个提示词**
|
77
|
+
```bash
|
78
|
+
bp --delete <ID>
|
79
|
+
# or
|
80
|
+
bp -d <ID>
|
81
|
+
```
|
82
|
+
|
83
|
+
* **调用 AI 模型**
|
84
|
+
使用指定的提示词 ID 来调用在 `config.yml` 中配置的 AI 模型。
|
85
|
+
```bash
|
86
|
+
bp --call <ID>
|
87
|
+
```
|
88
|
+
|
89
|
+
* **提供反馈**
|
90
|
+
打开一个链接,为项目提供反馈。
|
91
|
+
```bash
|
92
|
+
bp --feedback
|
93
|
+
```
|
94
|
+
|
95
|
+
## 👨💻 开发
|
96
|
+
|
97
|
+
如果您想为 BetterPrompt 贡献代码,请遵循以下步骤:
|
98
|
+
|
99
|
+
1. **Fork** 本项目。
|
100
|
+
2. 创建您的功能分支 (`git checkout -b feature/AmazingFeature`)。
|
101
|
+
3. 提交您的更改 (`git commit -m 'Add some AmazingFeature'`)。
|
102
|
+
4. 将代码推送到分支 (`git push origin feature/AmazingFeature`)。
|
103
|
+
5. 开启一个 **Pull Request**。
|
104
|
+
|
105
|
+
## 📄 许可证
|
106
|
+
|
107
|
+
本项目采用 MIT 许可证。详情请见 `LICENSE` 文件。
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# Better Prompt
|
2
|
+
|
3
|
+
A Ruby Gem for tracking and analyzing LLM prompt interactions, designed to work with [smart_prompt](https://github.com/zhuangbiaowei/smart_prompt).
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Records complete prompt/response history including:
|
8
|
+
- Prompt content and length
|
9
|
+
- Response content, length and timing
|
10
|
+
- Model information (name, provider, size)
|
11
|
+
- Call parameters (temperature, max_tokens, etc.)
|
12
|
+
- Stream vs non-stream calls
|
13
|
+
- Stores user feedback on response quality:
|
14
|
+
- Star ratings (1-5)
|
15
|
+
- Detailed scores (accuracy, relevance, etc.)
|
16
|
+
- Free-form comments
|
17
|
+
- Organizes prompts with:
|
18
|
+
- Tags and categories
|
19
|
+
- Projects grouping
|
20
|
+
- SQLite backend for easy analysis
|
21
|
+
- Character interface for analyzing logs and comparing responses:
|
22
|
+
- Execute `better_prompt ./path/database.db` to launch
|
23
|
+
- View interaction logs and history
|
24
|
+
- Compare different model responses side-by-side
|
25
|
+
- Evaluate and rate response quality
|
26
|
+
- Built with [ruby_rich](https://github.com/zhuangbiaowei/ruby_rich) for rich terminal UI
|
27
|
+
|
28
|
+
## Database Schema
|
29
|
+
|
30
|
+
The database contains these main tables:
|
31
|
+
|
32
|
+
1. `users` - User accounts
|
33
|
+
2. `models` - LLM model information
|
34
|
+
3. `prompts` - Prompt content and metadata
|
35
|
+
4. `model_calls` - Individual API calls
|
36
|
+
5. `responses` - Model responses
|
37
|
+
6. `feedback` - User ratings and comments
|
38
|
+
7. `tags`/`prompt_tags` - Prompt categorization
|
39
|
+
8. `projects`/`project_prompts` - Prompt organization
|
40
|
+
|
41
|
+
See [db/init.sql](db/init.sql) for complete schema.
|
42
|
+
|
43
|
+
## Installation
|
44
|
+
|
45
|
+
Add to your Gemfile:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
gem 'better_prompt'
|
49
|
+
```
|
50
|
+
|
51
|
+
Then execute:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
bundle install
|
55
|
+
```
|
56
|
+
|
57
|
+
## Usage
|
58
|
+
|
59
|
+
Basic setup:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require 'better_prompt'
|
63
|
+
|
64
|
+
# Initialize with database path
|
65
|
+
BetterPrompt.setup(db_path: 'path/to/database.db')
|
66
|
+
|
67
|
+
# Record a prompt and response
|
68
|
+
BetterPrompt.record(
|
69
|
+
user_id: 1,
|
70
|
+
prompt: "Explain quantum computing",
|
71
|
+
response: "Quantum computing uses qubits...",
|
72
|
+
model_name: "gpt-4",
|
73
|
+
response_time_ms: 1250,
|
74
|
+
is_stream: false
|
75
|
+
)
|
76
|
+
|
77
|
+
# Add user feedback
|
78
|
+
BetterPrompt.add_feedback(
|
79
|
+
response_id: 123,
|
80
|
+
rating: 4,
|
81
|
+
comment: "Helpful but could be more detailed"
|
82
|
+
)
|
83
|
+
```
|
84
|
+
|
85
|
+
## Development
|
86
|
+
|
87
|
+
After checking out the repo:
|
88
|
+
|
89
|
+
```bash
|
90
|
+
bin/setup
|
91
|
+
bundle exec rake test
|
92
|
+
```
|
93
|
+
|
94
|
+
## Contributing
|
95
|
+
|
96
|
+
Bug reports and pull requests are welcome.
|
97
|
+
|
98
|
+
## License
|
99
|
+
|
100
|
+
The gem is available as open source under MIT License.
|
data/bin/bp
ADDED
data/db/init.sql
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
-- 创建模型表
|
2
|
+
CREATE TABLE models (
|
3
|
+
model_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
4
|
+
model_provider TEXT NOT NULL,
|
5
|
+
model_name TEXT NOT NULL,
|
6
|
+
model_version TEXT,
|
7
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
8
|
+
);
|
9
|
+
|
10
|
+
-- 创建提示词表
|
11
|
+
CREATE TABLE prompts (
|
12
|
+
prompt_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
13
|
+
role TEXT NOT NULL,
|
14
|
+
prompt_template_name TEXT NOT NULL,
|
15
|
+
prompt_title TEXT,
|
16
|
+
prompt_content TEXT NOT NULL,
|
17
|
+
prompt_length INTEGER NOT NULL,
|
18
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
19
|
+
);
|
20
|
+
|
21
|
+
-- 创建调用记录表
|
22
|
+
CREATE TABLE model_calls (
|
23
|
+
call_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
24
|
+
prompt_list TEXT NOT NULL,
|
25
|
+
model_id INTEGER,
|
26
|
+
is_streaming INTEGER NOT NULL, -- 在SQLite中使用0/1代替布尔值
|
27
|
+
temperature REAL,
|
28
|
+
max_tokens INTEGER,
|
29
|
+
top_p REAL,
|
30
|
+
top_k INTEGER,
|
31
|
+
additional_parameters TEXT, -- 使用JSON字符串存储
|
32
|
+
call_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
33
|
+
FOREIGN KEY (model_id) REFERENCES models(model_id)
|
34
|
+
);
|
35
|
+
|
36
|
+
-- 创建响应表
|
37
|
+
CREATE TABLE responses (
|
38
|
+
response_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
39
|
+
call_id INTEGER,
|
40
|
+
response_content TEXT NOT NULL,
|
41
|
+
response_length INTEGER NOT NULL,
|
42
|
+
response_time_ms INTEGER NOT NULL, -- 响应时间(毫秒)
|
43
|
+
is_streaming INTEGER NOT NULL, -- 在SQLite中使用0/1代替布尔值
|
44
|
+
token_count INTEGER, -- 令牌数量
|
45
|
+
cost REAL, -- 调用成本
|
46
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
47
|
+
FOREIGN KEY (call_id) REFERENCES model_calls(call_id)
|
48
|
+
);
|
49
|
+
|
50
|
+
-- 创建用户评价表
|
51
|
+
CREATE TABLE feedback (
|
52
|
+
feedback_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
53
|
+
response_id INTEGER,
|
54
|
+
rating INTEGER CHECK (rating BETWEEN 1 AND 5), -- 1-5星评价
|
55
|
+
feedback_comment TEXT,
|
56
|
+
accuracy_score INTEGER CHECK (accuracy_score BETWEEN 1 AND 10),
|
57
|
+
relevance_score INTEGER CHECK (relevance_score BETWEEN 1 AND 10),
|
58
|
+
creativity_score INTEGER CHECK (creativity_score BETWEEN 1 AND 10),
|
59
|
+
helpfulness_score INTEGER CHECK (helpfulness_score BETWEEN 1 AND 10),
|
60
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
61
|
+
FOREIGN KEY (response_id) REFERENCES responses(response_id)
|
62
|
+
);
|
63
|
+
|
64
|
+
-- 创建标签表,用于更好地分类提示词
|
65
|
+
CREATE TABLE tags (
|
66
|
+
tag_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
67
|
+
tag_name TEXT NOT NULL UNIQUE,
|
68
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
69
|
+
);
|
70
|
+
|
71
|
+
-- 创建提示词-标签关联表
|
72
|
+
CREATE TABLE prompt_tags (
|
73
|
+
prompt_id INTEGER,
|
74
|
+
tag_id INTEGER,
|
75
|
+
PRIMARY KEY (prompt_id, tag_id),
|
76
|
+
FOREIGN KEY (prompt_id) REFERENCES prompts(prompt_id),
|
77
|
+
FOREIGN KEY (tag_id) REFERENCES tags(tag_id)
|
78
|
+
);
|
79
|
+
|
80
|
+
-- 创建索引以提高查询性能
|
81
|
+
CREATE INDEX idx_responses_call_id ON responses(call_id);
|
82
|
+
CREATE INDEX idx_feedback_response_id ON feedback(response_id);
|
83
|
+
CREATE INDEX idx_prompt_tags_prompt_id ON prompt_tags(prompt_id);
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "ruby_rich"
|
2
|
+
|
3
|
+
module BetterPrompt
|
4
|
+
class CLI
|
5
|
+
class << self
|
6
|
+
def start(db_path)
|
7
|
+
@layout = Component::Root.build
|
8
|
+
@layout.split_row(
|
9
|
+
Component::Sidebar.build,
|
10
|
+
Component::Main.build
|
11
|
+
)
|
12
|
+
@layout["sidebar"].split_column(
|
13
|
+
Component::TemplateList.build,
|
14
|
+
Component::PromptList.build,
|
15
|
+
)
|
16
|
+
@layout["main"].split_column(
|
17
|
+
Component::ModelCall.build,
|
18
|
+
Component::Response.build
|
19
|
+
)
|
20
|
+
ORM.setup("sqlite://"+db_path)
|
21
|
+
RubyRich::Live.start(@layout, refresh_rate: 24) do |live|
|
22
|
+
live.listening = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module BetterPrompt
|
2
|
+
module Component
|
3
|
+
class Feedback
|
4
|
+
def self.build(live)
|
5
|
+
dialog = RubyRich::Dialog.new(title: "加载对话", content: "请问这个回答的内容打分,1~5 之间。", width: 80, height: 20, buttons: [:cancel])
|
6
|
+
dialog.live = live
|
7
|
+
register_event_listener(dialog)
|
8
|
+
return dialog
|
9
|
+
end
|
10
|
+
def self.register_event_listener(dialog)
|
11
|
+
dialog.key(:escape){|event, live|
|
12
|
+
live.layout.hide_dialog
|
13
|
+
}
|
14
|
+
dialog.key(:string){|event, live|
|
15
|
+
key_value = event[:value].to_i
|
16
|
+
if key_value >=1 && key_value <=5
|
17
|
+
if feedback=live.params[:feedback]
|
18
|
+
feedback.rating = key_value
|
19
|
+
feedback.save
|
20
|
+
else
|
21
|
+
feedback = ORM::Feedback.new(response_id: live.params[:response_id], rating: key_value)
|
22
|
+
feedback.save
|
23
|
+
live.params[:feedback] = feedback
|
24
|
+
end
|
25
|
+
response = live.find_panel("response")
|
26
|
+
response.title = "回答 (Ctrl+r/Ctrl+a/Ctrl+t) 评分: #{key_value}"
|
27
|
+
live.layout.hide_dialog
|
28
|
+
end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module BetterPrompt
|
2
|
+
module Component
|
3
|
+
class ModelCall
|
4
|
+
def self.build
|
5
|
+
@current_pos = 0
|
6
|
+
layout = RubyRich::Layout.new(name: "model_call", size: 14)
|
7
|
+
draw_view(layout)
|
8
|
+
register_event_listener(layout)
|
9
|
+
return layout
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.draw_view(layout)
|
13
|
+
layout.update_content(RubyRich::Panel.new(
|
14
|
+
"",
|
15
|
+
title: "模型调用 (Shift+3) 打分 (Ctrl+f)"
|
16
|
+
))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.update_model_call_list(live, prompt_id)
|
20
|
+
model_call = live.find_panel("model_call")
|
21
|
+
@call_list = ORM::ModelCall.where_all(Sequel.like(:prompt_list, "%,#{prompt_id}]"))
|
22
|
+
@page_count = (@call_list.size / 9.0).ceil
|
23
|
+
model_call.content = generate_content()
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.generate_content(pos=0, page_num=1)
|
27
|
+
i = 0
|
28
|
+
@current_page = page_num
|
29
|
+
start_pos = (page_num - 1) * 9
|
30
|
+
end_pos = start_pos + 9
|
31
|
+
call_list_str = ""
|
32
|
+
@last_pos = 0
|
33
|
+
@call_list.each do |call|
|
34
|
+
i += 1
|
35
|
+
model = ORM::Model[call.model_id]
|
36
|
+
row_str = "id: #{call.call_id}"
|
37
|
+
row_str = row_str + " "*(10-row_str.length) + "model:" + model.model_provider + "/" + model.model_name
|
38
|
+
if call.prompt_list.length < 42
|
39
|
+
row_str = row_str + " "*(60-row_str.length) + "prompts: "+ call.prompt_list
|
40
|
+
else
|
41
|
+
row_str = row_str + " "*(60-row_str.length) + "prompts: ..."+ call.prompt_list[-42..-1]
|
42
|
+
end
|
43
|
+
if i > start_pos && i <= end_pos
|
44
|
+
if i-start_pos==pos
|
45
|
+
call_list_str += RubyRich::AnsiCode.color(:blue)+ "#{i-start_pos} #{row_str}\n" + RubyRich::AnsiCode.reset
|
46
|
+
else
|
47
|
+
call_list_str += RubyRich::AnsiCode.color(:blue)+ "#{i-start_pos}" + RubyRich::AnsiCode.reset + " #{row_str}\n"
|
48
|
+
end
|
49
|
+
@last_pos += 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
@max_pos = i
|
53
|
+
if start_pos>0 or i > end_pos
|
54
|
+
call_list_str += " \n" * (11 - call_list_str.split("\n").size)
|
55
|
+
end
|
56
|
+
if start_pos>0
|
57
|
+
call_list_str += " " * 11 + "←" + " " * 2
|
58
|
+
else
|
59
|
+
call_list_str += " " * 14
|
60
|
+
end
|
61
|
+
if i > end_pos
|
62
|
+
call_list_str += " " * 2 + "→"
|
63
|
+
end
|
64
|
+
return call_list_str
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.try_parse(string)
|
68
|
+
return JSON.parse(string)
|
69
|
+
rescue JSON::ParserError
|
70
|
+
begin
|
71
|
+
return JSON.parse(string.gsub("=>",":").gsub("nil","\"nil\""))
|
72
|
+
rescue JSON::ParserError
|
73
|
+
return false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.show_call(model_call)
|
78
|
+
model_call.content = generate_content(@current_pos, @current_page)
|
79
|
+
call = @call_list[(@current_page - 1)*9+@current_pos-1]
|
80
|
+
response = ORM::Response.where(call_id: call.call_id).first
|
81
|
+
if response
|
82
|
+
response_content = response.response_content
|
83
|
+
if json = try_parse(response_content)
|
84
|
+
Response.set_content(json.dig("choices", 0, "message", "content"))
|
85
|
+
Response.set_reason_content(json.dig("choices", 0, "message", "reasoning_content"))
|
86
|
+
Response.set_tool_content(json.dig("choices",0,"message", "tool_calls"))
|
87
|
+
else
|
88
|
+
Response.set_content(response_content)
|
89
|
+
Response.set_reason_content("")
|
90
|
+
Response.set_tool_content("")
|
91
|
+
end
|
92
|
+
response_id = response.response_id
|
93
|
+
feedback = ORM::Feedback.where(response_id: response_id).first
|
94
|
+
else
|
95
|
+
Response.set_content("响应不存在")
|
96
|
+
Response.set_reason_content("响应不存在")
|
97
|
+
Response.set_tool_content("响应不存在")
|
98
|
+
end
|
99
|
+
Response.show_content(:content, feedback)
|
100
|
+
return feedback, response_id
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.register_event_listener(layout)
|
104
|
+
layout.key(:string) do |event, live|
|
105
|
+
model_call = live.find_panel("model_call")
|
106
|
+
if model_call.border_style == :green && @call_list
|
107
|
+
if event[:value].to_i >= 1 && event[:value].to_i <= @last_pos
|
108
|
+
@current_pos = event[:value].to_i
|
109
|
+
live.params[:feedback], live.params[:response_id] = show_call(model_call)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
layout.key(:right) do |event, live|
|
114
|
+
model_call = live.find_panel("model_call")
|
115
|
+
if model_call.border_style == :green && @call_list
|
116
|
+
if @current_page<@page_count
|
117
|
+
model_call.content = generate_content(0, @current_page+1)
|
118
|
+
@current_pos = 0
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
layout.key(:left) do |event, live|
|
123
|
+
model_call = live.find_panel("model_call")
|
124
|
+
if model_call.border_style == :green && @call_list
|
125
|
+
if @current_page>1
|
126
|
+
model_call.content = generate_content(0, @current_page-1)
|
127
|
+
@current_pos = 0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
layout.key(:up) do |event, live|
|
132
|
+
model_call = live.find_panel("model_call")
|
133
|
+
if model_call.border_style == :green && @call_list
|
134
|
+
if @current_pos > 1
|
135
|
+
@current_pos -= 1
|
136
|
+
show_call(model_call)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
layout.key(:down) do |event, live|
|
141
|
+
model_call = live.find_panel("model_call")
|
142
|
+
if model_call.border_style == :green && @call_list
|
143
|
+
if @current_pos < @max_pos
|
144
|
+
@current_pos += 1
|
145
|
+
show_call(model_call)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
layout.key(:ctrl_f) do |event, live|
|
150
|
+
model_call = live.find_panel("model_call")
|
151
|
+
if model_call.border_style == :green && @current_pos>0 && @call_list
|
152
|
+
dialog = Feedback.build(live)
|
153
|
+
live.layout.show_dialog(dialog)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module BetterPrompt
|
2
|
+
module Component
|
3
|
+
class PromptList
|
4
|
+
def self.build
|
5
|
+
layout = RubyRich::Layout.new(name: "prompt_list", ratio: 4)
|
6
|
+
draw_view(layout)
|
7
|
+
register_event_listener(layout)
|
8
|
+
return layout
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.draw_view(layout)
|
12
|
+
layout.update_content(RubyRich::Panel.new(
|
13
|
+
"",
|
14
|
+
title: "提示词列表 (Shift+2)"
|
15
|
+
))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.update_title(prompt)
|
19
|
+
result = BetterPrompt.engine.call_worker(:summarize, {text: prompt[:prompt_content]})
|
20
|
+
prompt[:prompt_title] = result.strip
|
21
|
+
prompt.save
|
22
|
+
return result.strip
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.update_prompt_list(live, template_name)
|
26
|
+
prompt_list = live.find_panel("prompt_list")
|
27
|
+
@prompt_list = BetterPrompt::ORM::Prompt.where(:prompt_template_name=>template_name).all
|
28
|
+
@page_count = (@prompt_list.size / 9.0).ceil
|
29
|
+
prompt_list.content = generate_content()
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.generate_content(pos=0, page_num=1)
|
33
|
+
i = 0
|
34
|
+
@current_page = page_num
|
35
|
+
start_pos = (page_num - 1) * 9
|
36
|
+
end_pos = start_pos + 9
|
37
|
+
prompt_list_str = ""
|
38
|
+
@prompt_list.each do |prompt|
|
39
|
+
i += 1
|
40
|
+
title = prompt.prompt_title
|
41
|
+
if title==nil
|
42
|
+
title = update_title(prompt)
|
43
|
+
end
|
44
|
+
title_width = title.chars.sum { |c| Unicode::DisplayWidth.of(c) }
|
45
|
+
if title_width>22
|
46
|
+
new_title = ""
|
47
|
+
title.chars.each do |c|
|
48
|
+
if (new_title+c).chars.sum { |c| Unicode::DisplayWidth.of(c) }>22
|
49
|
+
new_title += ".."
|
50
|
+
break
|
51
|
+
else
|
52
|
+
new_title += c
|
53
|
+
end
|
54
|
+
end
|
55
|
+
title = new_title
|
56
|
+
end
|
57
|
+
if i > start_pos && i <= end_pos
|
58
|
+
if i-start_pos==pos
|
59
|
+
prompt_list_str += RubyRich::AnsiCode.color(:blue)+ "#{i-start_pos} #{title}\n" + RubyRich::AnsiCode.reset
|
60
|
+
else
|
61
|
+
prompt_list_str += RubyRich::AnsiCode.color(:blue)+ "#{i-start_pos}" + RubyRich::AnsiCode.reset + " #{title}\n"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
if start_pos>0 or i > end_pos
|
66
|
+
prompt_list_str += " \n" * (25 - prompt_list_str.split("\n").size)
|
67
|
+
end
|
68
|
+
if start_pos>0
|
69
|
+
prompt_list_str += " " * 11 + "←" + " " * 2
|
70
|
+
else
|
71
|
+
prompt_list_str += " " * 14
|
72
|
+
end
|
73
|
+
if i > end_pos
|
74
|
+
prompt_list_str += " " * 2 + "→"
|
75
|
+
end
|
76
|
+
return prompt_list_str
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.register_event_listener(layout)
|
80
|
+
layout.key(:string) do |event, live|
|
81
|
+
prompt_list = live.find_panel("prompt_list")
|
82
|
+
if prompt_list.border_style == :green
|
83
|
+
if event[:value].to_i >= 1 && event[:value].to_i <= @prompt_list.count
|
84
|
+
prompt_list.content = generate_content(event[:value].to_i, @current_page)
|
85
|
+
prompt = @prompt_list[(@current_page - 1)*9+event[:value].to_i-1]
|
86
|
+
dialog = ShowPrompt.build(prompt.prompt_content, live)
|
87
|
+
live.layout.show_dialog(dialog)
|
88
|
+
Response.clean
|
89
|
+
ModelCall.update_model_call_list(live, prompt.prompt_id)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
layout.key(:right) do |event, live|
|
94
|
+
prompt_list = live.find_panel("prompt_list")
|
95
|
+
if prompt_list.border_style == :green
|
96
|
+
if @current_page<@page_count
|
97
|
+
prompt_list.content = generate_content(0, @current_page+1)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
layout.key(:left) do |event, live|
|
102
|
+
prompt_list = live.find_panel("prompt_list")
|
103
|
+
if prompt_list.border_style == :green
|
104
|
+
if @current_page>1
|
105
|
+
prompt_list.content = generate_content(0, @current_page-1)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module BetterPrompt
|
2
|
+
module Component
|
3
|
+
class Response
|
4
|
+
def self.build
|
5
|
+
layout = RubyRich::Layout.new(name: "response", ratio: 1)
|
6
|
+
draw_view(layout)
|
7
|
+
register_event_listener(layout)
|
8
|
+
return layout
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.draw_view(layout)
|
12
|
+
@panel = RubyRich::Panel.new(
|
13
|
+
"",
|
14
|
+
title: "回答 (Ctrl+r/Ctrl+a/Ctrl+t) 评分: 1~5"
|
15
|
+
)
|
16
|
+
layout.update_content(@panel)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.clean
|
20
|
+
@panel.content = ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.update_content(content)
|
24
|
+
if content.strip.empty?
|
25
|
+
@panel.content = "<Empty>"
|
26
|
+
else
|
27
|
+
@panel.content = ""
|
28
|
+
@panel.content = content
|
29
|
+
end
|
30
|
+
@panel.home
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.set_content(content)
|
34
|
+
@content = content
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.set_reason_content(content)
|
38
|
+
@reason_content = content
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.set_tool_content(content)
|
42
|
+
@tool_content = content
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.show_content(type, feedback)
|
46
|
+
rating = if feedback
|
47
|
+
feedback.rating
|
48
|
+
else
|
49
|
+
"1~5"
|
50
|
+
end
|
51
|
+
if type==:content
|
52
|
+
@panel.title = "回答 (Ctrl+r/Ctrl+a/Ctrl+t) 评分: #{rating}"
|
53
|
+
update_content(@content.to_s)
|
54
|
+
end
|
55
|
+
if type==:reason
|
56
|
+
@panel.title = "推理 (Ctrl+r/Ctrl+a/Ctrl+t) 评分: #{rating}"
|
57
|
+
update_content(@reason_content.to_s)
|
58
|
+
end
|
59
|
+
if type==:tool
|
60
|
+
@panel.title = "工具调用 (Ctrl+r/Ctrl+a/Ctrl+t) 评分: #{rating}"
|
61
|
+
update_content(@tool_content.to_s)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.register_event_listener(layout)
|
66
|
+
layout.key(:ctrl_r) { |event, live| show_content(:reason, live.params[:feedback])}
|
67
|
+
layout.key(:ctrl_a) { |event, live| show_content(:content, live.params[:feedback])}
|
68
|
+
layout.key(:ctrl_t) { |event, live| show_content(:tool, live.params[:feedback])}
|
69
|
+
layout.key(:page_up) { |event, live| @panel.page_up}
|
70
|
+
layout.key(:page_down) { |event, live| @panel.page_down}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module BetterPrompt
|
2
|
+
module Component
|
3
|
+
class Root
|
4
|
+
def self.build
|
5
|
+
layout = RubyRich::Layout.new
|
6
|
+
register_event_listener(layout)
|
7
|
+
return layout
|
8
|
+
end
|
9
|
+
def self.register_event_listener(layout)
|
10
|
+
layout.key(:string) do |event, live|
|
11
|
+
key = event[:value]
|
12
|
+
if key=="!"
|
13
|
+
template_list = live.find_panel("template_list")
|
14
|
+
template_list.border_style = :green
|
15
|
+
prompt_list = live.find_panel("prompt_list")
|
16
|
+
prompt_list.border_style = :white
|
17
|
+
model_call = live.find_panel("model_call")
|
18
|
+
model_call.border_style = :white
|
19
|
+
elsif key=="@"
|
20
|
+
template_list = live.find_panel("template_list")
|
21
|
+
template_list.border_style = :white
|
22
|
+
prompt_list = live.find_panel("prompt_list")
|
23
|
+
prompt_list.border_style = :green
|
24
|
+
model_call = live.find_panel("model_call")
|
25
|
+
model_call.border_style = :white
|
26
|
+
elsif key=="#"
|
27
|
+
template_list = live.find_panel("template_list")
|
28
|
+
template_list.border_style = :white
|
29
|
+
prompt_list = live.find_panel("prompt_list")
|
30
|
+
prompt_list.border_style = :white
|
31
|
+
model_call = live.find_panel("model_call")
|
32
|
+
model_call.border_style = :green
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module BetterPrompt
|
2
|
+
module Component
|
3
|
+
class ShowPrompt
|
4
|
+
def self.build(content, live)
|
5
|
+
width = 60
|
6
|
+
dialog = RubyRich::Dialog.new(title: "提示词内容", content: content, width: width, height: 20, buttons: [:ok])
|
7
|
+
dialog.live = live
|
8
|
+
register_event_listener(dialog)
|
9
|
+
return dialog
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.register_event_listener(dialog)
|
13
|
+
dialog.key(:enter){|event, live|
|
14
|
+
live.layout.hide_dialog
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BetterPrompt
|
2
|
+
module Component
|
3
|
+
class Sidebar
|
4
|
+
def self.build
|
5
|
+
layout = RubyRich::Layout.new(name: "sidebar", size: 30)
|
6
|
+
register_event_listener(layout)
|
7
|
+
return layout
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.register_event_listener(layout)
|
11
|
+
layout.key(:ctrl_c) { |event, live| live.stop }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module BetterPrompt
|
2
|
+
module Component
|
3
|
+
class TemplateList
|
4
|
+
def self.build
|
5
|
+
layout = RubyRich::Layout.new(name: "template_list", size: 14)
|
6
|
+
draw_view(layout)
|
7
|
+
register_event_listener(layout)
|
8
|
+
return layout
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.get_list_content
|
12
|
+
@template_list = ORM::Prompt
|
13
|
+
.where(prompt_template_name: 'NULL')
|
14
|
+
.invert
|
15
|
+
.select(:prompt_template_name)
|
16
|
+
.distinct
|
17
|
+
.map(&:prompt_template_name)
|
18
|
+
@page_count = (@template_list.size / 9.0).ceil
|
19
|
+
generate_content
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.generate_content(pos=0, page_num=1)
|
23
|
+
i = 0
|
24
|
+
@current_page = page_num
|
25
|
+
start_pos = (page_num - 1) * 9
|
26
|
+
end_pos = start_pos + 9
|
27
|
+
template_list_str = ""
|
28
|
+
@template_list.each do |template|
|
29
|
+
i += 1
|
30
|
+
if i > start_pos && i <= end_pos
|
31
|
+
if i-start_pos==pos
|
32
|
+
template_list_str += RubyRich::AnsiCode.color(:blue)+ "#{i-start_pos} #{template}\n" + RubyRich::AnsiCode.reset
|
33
|
+
else
|
34
|
+
template_list_str += RubyRich::AnsiCode.color(:blue)+ "#{i-start_pos}" + RubyRich::AnsiCode.reset + " #{template}\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
if start_pos>0 or i > end_pos
|
39
|
+
template_list_str += " \n" * (11 - template_list_str.split("\n").size)
|
40
|
+
end
|
41
|
+
if start_pos>0
|
42
|
+
template_list_str += " " * 11 + "←" + " " * 2
|
43
|
+
else
|
44
|
+
template_list_str += " " * 14
|
45
|
+
end
|
46
|
+
if i > end_pos
|
47
|
+
template_list_str += " " * 2 + "→"
|
48
|
+
end
|
49
|
+
return template_list_str
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.draw_view(layout)
|
53
|
+
layout.update_content(RubyRich::Panel.new(
|
54
|
+
get_list_content(),
|
55
|
+
title: "模板列表 (Shift+1)",
|
56
|
+
border_style: :green
|
57
|
+
))
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.register_event_listener(layout)
|
61
|
+
layout.key(:string) do |event, live|
|
62
|
+
template_list = live.find_panel("template_list")
|
63
|
+
if template_list.border_style == :green
|
64
|
+
if event[:value].to_i >= 1 && event[:value].to_i <= @template_list.count
|
65
|
+
template_list.content = generate_content(event[:value].to_i, @current_page)
|
66
|
+
template_name = @template_list[(@current_page - 1)*9+event[:value].to_i-1]
|
67
|
+
PromptList.update_prompt_list(live, template_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
layout.key(:right) do |event, live|
|
72
|
+
template_list = live.find_panel("template_list")
|
73
|
+
if template_list.border_style == :green
|
74
|
+
if @current_page<@page_count
|
75
|
+
template_list.content = generate_content(0, @current_page+1)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
layout.key(:left) do |event, live|
|
80
|
+
template_list = live.find_panel("template_list")
|
81
|
+
if template_list.border_style == :green
|
82
|
+
if @current_page>1
|
83
|
+
template_list.content = generate_content(0, @current_page-1)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
module BetterPrompt
|
3
|
+
module ORM
|
4
|
+
class Model < Sequel::Model(ORM.db[:models])
|
5
|
+
plugin :timestamps, update_on_create: true
|
6
|
+
end
|
7
|
+
|
8
|
+
class Prompt < Sequel::Model(ORM.db[:prompts])
|
9
|
+
plugin :timestamps, update_on_create: true
|
10
|
+
end
|
11
|
+
|
12
|
+
class ModelCall < Sequel::Model(ORM.db[:model_calls])
|
13
|
+
plugin :timestamps, update_on_create: true
|
14
|
+
end
|
15
|
+
|
16
|
+
class Response < Sequel::Model(ORM.db[:responses])
|
17
|
+
plugin :timestamps, update_on_create: true
|
18
|
+
end
|
19
|
+
|
20
|
+
class Feedback < Sequel::Model(ORM.db[:feedback])
|
21
|
+
plugin :timestamps, update_on_create: true
|
22
|
+
end
|
23
|
+
|
24
|
+
class Tag < Sequel::Model(ORM.db[:tags])
|
25
|
+
plugin :timestamps, update_on_create: true
|
26
|
+
end
|
27
|
+
|
28
|
+
class PromptTag < Sequel::Model(ORM.db[:prompt_tags])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
module BetterPrompt
|
3
|
+
module ORM
|
4
|
+
class << self
|
5
|
+
attr_reader :db
|
6
|
+
|
7
|
+
def setup(database_url)
|
8
|
+
raise "DATABASE_URL environment variable not set" unless database_url
|
9
|
+
@db = Sequel.connect(database_url)
|
10
|
+
@db.test_connection
|
11
|
+
rescue => e
|
12
|
+
raise "Failed to connect to database: #{e.message}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require "sqlite3"
|
2
|
+
require "fileutils"
|
3
|
+
require "time"
|
4
|
+
require "smart_prompt"
|
5
|
+
|
6
|
+
require_relative 'better_prompt/cli'
|
7
|
+
require_relative 'better_prompt/orm'
|
8
|
+
Dir["#{__dir__}/better_prompt/components/*.rb"].each { |f| require_relative f }
|
9
|
+
|
10
|
+
module BetterPrompt
|
11
|
+
class << self
|
12
|
+
def setup(db_path:)
|
13
|
+
BetterPrompt.logger = Logger.new("./log/better.log")
|
14
|
+
@db_path = db_path
|
15
|
+
# Return true if database already exists
|
16
|
+
if File.exist?(db_path)
|
17
|
+
ORM.setup("sqlite://"+db_path)
|
18
|
+
require_relative 'better_prompt/models'
|
19
|
+
return true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Ensure directory exists
|
23
|
+
FileUtils.mkdir_p(File.dirname(db_path))
|
24
|
+
|
25
|
+
# Initialize database
|
26
|
+
SQLite3::Database.new(db_path) do |db|
|
27
|
+
# Execute each statement from init.sql
|
28
|
+
File.read(File.join(__dir__, "../db/init.sql")).split(/;\s*\n/).each do |statement|
|
29
|
+
db.execute(statement.strip) unless statement.strip.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
ORM.setup("sqlite://"+db_path)
|
33
|
+
require_relative 'better_prompt/models'
|
34
|
+
true
|
35
|
+
rescue => e
|
36
|
+
raise "Failed to initialize database: #{e.message}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_model(provider, model_name)
|
40
|
+
model_version = if model_name.include?(':')
|
41
|
+
model_name.split(':', 2).last
|
42
|
+
else
|
43
|
+
'latest'
|
44
|
+
end
|
45
|
+
model_name = model_name.split(":")[0]
|
46
|
+
|
47
|
+
ORM::Model.find_or_create(
|
48
|
+
model_provider: provider,
|
49
|
+
model_name: model_name,
|
50
|
+
model_version: model_version
|
51
|
+
)
|
52
|
+
rescue => e
|
53
|
+
raise "Failed to add model: #{e.message}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_prompt(template_name, role, prompt_content)
|
57
|
+
prompt_length = prompt_content.length
|
58
|
+
|
59
|
+
ORM::Prompt.find_or_create(
|
60
|
+
prompt_template_name: template_name,
|
61
|
+
role: role,
|
62
|
+
prompt_content: prompt_content
|
63
|
+
) do |prompt|
|
64
|
+
prompt.prompt_length = prompt_length
|
65
|
+
end
|
66
|
+
rescue => e
|
67
|
+
raise "Failed to add prompt: #{e.message}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_model_call(provider, model_name, messages, streaming=false, temperature=0.7, max_tokens=0, top_p=0.0, top_k=0, params={})
|
71
|
+
model_version = if model_name.include?(':')
|
72
|
+
model_name.split(':', 2).last
|
73
|
+
else
|
74
|
+
'latest'
|
75
|
+
end
|
76
|
+
model_base_name = model_name.split(":")[0]
|
77
|
+
|
78
|
+
# 查找或创建model记录
|
79
|
+
model = ORM::Model.first(
|
80
|
+
model_provider: provider,
|
81
|
+
model_name: model_base_name,
|
82
|
+
model_version: model_version
|
83
|
+
)
|
84
|
+
raise "Model not found: #{provider}/#{model_name}" unless model
|
85
|
+
|
86
|
+
# 从messages构建prompt_list
|
87
|
+
prompt_list = messages.map do |msg|
|
88
|
+
role = msg[:role] || msg["role"]
|
89
|
+
content = msg[:content] || msg["content"]
|
90
|
+
if role == "assistant" || role == "tool"
|
91
|
+
add_prompt("NULL", role, content)
|
92
|
+
end
|
93
|
+
# 查找prompt_id
|
94
|
+
prompt = ORM::Prompt.first(role: role, prompt_content: content)
|
95
|
+
prompt&.prompt_id || 0
|
96
|
+
end.to_json
|
97
|
+
|
98
|
+
# 创建model_call记录
|
99
|
+
model_call = ORM::ModelCall.create(
|
100
|
+
prompt_list: prompt_list,
|
101
|
+
model_id: model.model_id,
|
102
|
+
is_streaming: streaming,
|
103
|
+
temperature: temperature,
|
104
|
+
max_tokens: max_tokens,
|
105
|
+
top_p: top_p,
|
106
|
+
top_k: top_k,
|
107
|
+
additional_parameters: params.to_json
|
108
|
+
)
|
109
|
+
|
110
|
+
model_call.call_id
|
111
|
+
rescue => e
|
112
|
+
raise "Failed to add model call: #{e.message}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_response(call_id, response_content, is_streaming)
|
116
|
+
response_content = response_content.to_s
|
117
|
+
response_length = response_content.length
|
118
|
+
current_time = Time.now
|
119
|
+
|
120
|
+
# 查找model_call记录
|
121
|
+
model_call = ORM::ModelCall[call_id]
|
122
|
+
raise "Model call not found: #{call_id}" unless model_call
|
123
|
+
|
124
|
+
# 计算响应时间(毫秒)
|
125
|
+
response_time_ms = ((current_time - model_call.call_timestamp) * 1000).to_i
|
126
|
+
|
127
|
+
# 创建response记录
|
128
|
+
response = ORM::Response.create(
|
129
|
+
call_id: call_id,
|
130
|
+
response_content: response_content,
|
131
|
+
response_length: response_length,
|
132
|
+
response_time_ms: response_time_ms,
|
133
|
+
is_streaming: is_streaming
|
134
|
+
)
|
135
|
+
|
136
|
+
response.response_id
|
137
|
+
rescue => e
|
138
|
+
raise "Failed to add response: #{e.message}"
|
139
|
+
end
|
140
|
+
def engine
|
141
|
+
if @engine == nil
|
142
|
+
file_path = "./config/config.yml"
|
143
|
+
file_path = "./config/llm_config.yml" unless File.exist?(file_path)
|
144
|
+
BetterPrompt.logger.info(file_path)
|
145
|
+
@engine = SmartPrompt::Engine.new(file_path)
|
146
|
+
end
|
147
|
+
return @engine
|
148
|
+
end
|
149
|
+
def logger=(logger)
|
150
|
+
@logger = logger
|
151
|
+
end
|
152
|
+
def logger
|
153
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
154
|
+
log.progname = "Better Prompt"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: better_prompt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- zhuang biaowei
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: ruby_rich
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 0.3.1
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.3.1
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: sqlite3
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.4'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.4'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: bundler
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rake
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '13.0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '13.0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rspec
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.0'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.0'
|
82
|
+
description: Provides enhanced command line prompt functionality with database storage
|
83
|
+
email:
|
84
|
+
- zbw@kaiyuanshe.org
|
85
|
+
executables:
|
86
|
+
- bp
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- LICENSE
|
91
|
+
- README.cn.md
|
92
|
+
- README.md
|
93
|
+
- bin/bp
|
94
|
+
- db/init.sql
|
95
|
+
- lib/better_prompt.rb
|
96
|
+
- lib/better_prompt/cli.rb
|
97
|
+
- lib/better_prompt/components/feedback.rb
|
98
|
+
- lib/better_prompt/components/main.rb
|
99
|
+
- lib/better_prompt/components/model_call.rb
|
100
|
+
- lib/better_prompt/components/prompt_list.rb
|
101
|
+
- lib/better_prompt/components/response.rb
|
102
|
+
- lib/better_prompt/components/root.rb
|
103
|
+
- lib/better_prompt/components/show_prompt.rb
|
104
|
+
- lib/better_prompt/components/sidebar.rb
|
105
|
+
- lib/better_prompt/components/template_list.rb
|
106
|
+
- lib/better_prompt/models.rb
|
107
|
+
- lib/better_prompt/orm.rb
|
108
|
+
- lib/better_prompt/version.rb
|
109
|
+
homepage: https://github.com/zhuangbiaowei/better_prompt
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 2.6.0
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubygems_version: 3.6.9
|
128
|
+
specification_version: 4
|
129
|
+
summary: A better command line prompt utility
|
130
|
+
test_files: []
|