Skip to content
This repository has been archived by the owner on Nov 6, 2023. It is now read-only.

Commit

Permalink
support input history
Browse files Browse the repository at this point in the history
  • Loading branch information
jlvihv committed Feb 20, 2023
1 parent f4cdbb8 commit 946e343
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
with:
tag_name: v${{ env.version }}
name: BingGPT v${{ env.version }}
name: v${{ env.version }}
body: See the assets to download this version and install.
prerelease: false
generate_release_notes: false
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ futures = "0.3.26"
gjson = "0.8.1"
rand = "0.8.5"
reqwest = "0.11.14"
rustyline = "10.1.1"
rustyline = { version = "10.1.1" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
tokio = { version = "1.25.0", features = ["full"] }
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ _BingGPT command line client, written in rust_

</div>

> Due to bing ai is being updated, the feature is temporarily unavailable
[中文说明](README_CN.md)

This project is a rust language implementation of [EdgeGPT](https://github.com/acheong08/EdgeGPT), all the hard stuff was done by the original project author `acheong08`, I just wrote it in rust, all credit goes to him, thanks for the hard work big guy!
Expand All @@ -23,6 +21,8 @@ This project is a rust language implementation of [EdgeGPT](https://github.com/a
cargo install binggpt
```

Or download the binary from the [release page](https://github.com/jlvihv/BingGPT/releases).

## Requirements

You must have a Microsoft account with access to BingGPT.
Expand Down
4 changes: 2 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ _BingGPT 命令行客户端,使用 rust 编写_

</div>

> 由于 Bing AI 正在更新,程序功能暂时不可用
这个项目是 [EdgeGPT](https://github.com/acheong08/EdgeGPT) 的 rust 语言实现,所有困难的事情都是原项目作者 `acheong08` 完成的,我仅仅是用 rust 写了一遍,所有的功劳都归功于他,感谢大佬的辛勤付出!

## 安装
Expand All @@ -21,6 +19,8 @@ _BingGPT 命令行客户端,使用 rust 编写_
cargo install binggpt
```

或者从 [release](https://github.com/jlvihv/BingGPT/releases) 页面下载预编译的二进制文件。

## 要求

你必须有一个可以访问 BingGPT 的微软账户。
Expand Down
98 changes: 77 additions & 21 deletions src/pkg/client.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
use super::core::chathub::ChatHub;
use anyhow::Result;
use crate::pkg::core::tools::get_path;
use anyhow::{bail, Result};
use colored::Colorize;
use rustyline::Editor;
use rustyline::{Cmd, Editor, KeyCode, KeyEvent, Modifiers};
use std::io::{stdout, Write};

const INPUT_HISTORY_PATH: &str = "~/.config/binggpt/input-history.txt";
const CONFIG_DIR: &str = "~/.config/binggpt";

pub struct Client {
chat_hub: ChatHub,
}

impl Client {
pub async fn new(cookie_path: &str) -> Result<Self> {
Self::init_config_dir()?;
let chat_hub = ChatHub::new(cookie_path).await?;
Ok(Self { chat_hub })
}

pub async fn run(&mut self) -> Result<()> {
loop {
let input = self.input();
self.ask(&input).await?;
self.get_answer().await?;
let input = self.input().await?;
if let Err(e) = self.ask(&input).await {
println!("send message failed: {}", e.to_string().red());
println!("You can use `:reset` to reset the conversation.");
};
if let Err(e) = self.get_answer().await {
println!("get answer failed: {}", e.to_string().red());
println!("You can use `:reset` to reset the conversation.");
};
}
}

Expand All @@ -30,7 +41,14 @@ impl Client {
println!("{}", "Bing:".blue());
let mut index = 0;
loop {
if let Some(suggesteds) = self.chat_hub.recv_suggesteds() {
let suggesteds = match self.chat_hub.recv_suggesteds() {
Ok(suggesteds) => suggesteds,
Err(e) => {
bail!(e)
}
};

if let Some(suggesteds) = suggesteds {
println!("\n{}", "Suggesteds:".purple());
for suggested in suggesteds {
println!(" {}", suggested);
Expand All @@ -53,52 +71,90 @@ impl Client {
Ok(())
}

pub fn input(&self) -> String {
// TODO: Plan to send messages with "Ctrl + Enter"

pub async fn input(&mut self) -> Result<String> {
println!("{}", "You:".cyan());

let mut rl = Editor::<()>::new().unwrap();
let _ = rl.load_history(&get_path(INPUT_HISTORY_PATH).unwrap_or_default());
rl.bind_sequence(KeyEvent(KeyCode::Enter, Modifiers::CTRL), Cmd::Newline);

let mut input = String::new();
let mut more_line_mode = false;
let mut multi_line_mode: bool = false;
loop {
loop {
let readline = rl.readline("");
let line = match readline {
Ok(line) => line,

Ok(line) => {
rl.add_history_entry(line.as_str());
line
}
// ctrl + c
Err(rustyline::error::ReadlineError::Interrupted) => {
self.chat_hub.close().await?;
std::process::exit(0);
}

Err(_) => "".to_string(),
};

match line.trim() {
"" => break,
":exit" | ":quit" | ":q" => {
self.chat_hub.close().await?;
std::process::exit(0)
}
":help" | ":h" => {
println!("twice Enter -> send message");
println!(":exit, :quit, :q -> exit");
println!(":reset -> reset conversation");
println!(":help, :h -> show help");
println!(":more -> enter multi-line mode");
println!(":end -> exit multi-line mode");
break;
}
":reset" => {
if (self.chat_hub.reset().await).is_ok() {
println!("{}", "Reset conversation success".green());
println!("{}", "You:".cyan());
} else {
println!("{}", "Reset conversation failed".red());
println!("{}", "You:".cyan());
};
break;
}
":more" => {
println!("{}", "You've entered multi-line mode. Enter ':end' to end the multi-line mode".green());
more_line_mode = true;
println!(
"{}",
"You've entered multi-line mode. Enter ':end' to exit multi-line mode"
.green()
);
multi_line_mode = true;
break;
}
":end" => {
more_line_mode = false;
multi_line_mode = false;
println!();
break;
}
":exit" | ":quit" | ":q" => std::process::exit(0),
_ => {}
}
input.push_str(&line)
input.push_str(&line);
}
let input = input.trim().to_string();
if input.is_empty() || more_line_mode {
let input = input.trim();
if input.is_empty() || multi_line_mode {
continue;
} else {
break;
}
}
input
let _ = rl.append_history(&get_path(INPUT_HISTORY_PATH).unwrap_or_default());
Ok(input)
}

fn init_config_dir() -> Result<()> {
let config_dir = get_path(CONFIG_DIR)?;
if !std::path::Path::new(&config_dir).exists() {
std::fs::create_dir_all(&config_dir)?;
}
Ok(())
}
}
43 changes: 23 additions & 20 deletions src/pkg/core/chathub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ impl ChatHub {
async fn create_websocket(&mut self) -> Result<()> {
let url = "wss://sydney.bing.com/sydney/ChatHub";
let (ws_stream, _) = tokio_tungstenite::connect_async(url).await?;
let (write, read) = ws_stream.split();
self.write = Some(read);
self.read = Some(write);
let (read, write) = ws_stream.split();
self.read = Some(read);
self.write = Some(write);
Ok(())
}

Expand All @@ -52,24 +52,26 @@ impl ChatHub {
}

pub async fn reset(&mut self) -> Result<()> {
self.close().await?;
self.conversation = Conversation::new(&self.cookie_path).await?;
// TODO: more research is needed here
// self.create_websocket().await?;
self.create_websocket().await?;
self.send_protocol().await?;
self.msg_cache = String::new();
Ok(())
}

pub async fn close(&mut self) -> Result<()> {
if self.read.is_some() {
self.read.as_mut().unwrap().close().await?;
}
Ok(())
}

pub async fn send_msg(&mut self, msg: &str) -> Result<()> {
let write = match self.read.as_mut() {
Some(write) => write,
None => {
self.reset().await?;
if self.read.as_mut().is_some() {
self.read.as_mut().unwrap()
} else {
bail!("Connection aborted, failed to reset");
}
bail!("Connection aborted, send message failed");
}
};
write
Expand All @@ -86,12 +88,7 @@ impl ChatHub {
let read = match self.write.as_mut() {
Some(read) => read,
None => {
self.reset().await?;
if self.write.as_mut().is_some() {
self.write.as_mut().unwrap()
} else {
bail!("Connection aborted, failed to reset");
}
bail!("Connection aborted, receive message failed");
}
};
self.msg_cache = read.next().await.unwrap()?.to_string();
Expand All @@ -118,13 +115,19 @@ impl ChatHub {
Ok(None)
}

pub fn recv_suggesteds(&mut self) -> Option<Vec<String>> {
pub fn recv_suggesteds(&mut self) -> Result<Option<Vec<String>>> {
let msg = self.msg_cache.clone();
if gjson::get(&msg, "type").i32() == 2 {
if gjson::get(&msg, "item.result.value").str() == "Throttled" {
let msg = gjson::get(&msg, "item.result.message").to_string();
bail!(format!("Error: Throttled: {msg}"))
}
let suggesteds = gjson::get(&msg, "item.messages.1.suggestedResponses.#.text");
self.msg_cache = String::new();
return Some(suggesteds.array().iter().map(|s| s.to_string()).collect());
return Ok(Some(
suggesteds.array().iter().map(|s| s.to_string()).collect(),
));
}
None
Ok(None)
}
}
13 changes: 3 additions & 10 deletions src/pkg/core/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use reqwest::header::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
use std::fs;

use super::tools::get_path;

#[derive(Debug, Clone)]
pub struct Client {
client: reqwest::Client,
Expand Down Expand Up @@ -94,16 +96,7 @@ pub struct Cookie {
}

fn get_cookie(cookie_path: &str) -> Result<String> {
let cookie_path = if cookie_path.starts_with('~') {
let home = std::env::var("HOME")
.unwrap_or_else(|_| std::env::var("USERPROFILE").unwrap_or_default());
if home.is_empty() {
bail!("Cannot find user home directory, please specify absolute path")
}
format!("{}{}", home, cookie_path.trim_start_matches('~'))
} else {
cookie_path.to_string()
};
let cookie_path = get_path(cookie_path)?;

let json_str = if let Ok(s) = fs::read_to_string(&cookie_path) {
s
Expand Down
1 change: 1 addition & 0 deletions src/pkg/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod chathub;
mod conversation;
mod http;
mod msg;
pub mod tools;
2 changes: 0 additions & 2 deletions src/pkg/core/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,9 @@ pub fn fill_msg(msg: &str, conversation: &Conversation) -> Result<String> {
arguments: vec![Argument {
source: "cib".to_string(),
options_sets: vec![
"nlu_direct_response_filter".to_string(),
"deepleo".to_string(),
"enable_debug_commands".to_string(),
"disable_emoji_spoken_text".to_string(),
"responsible_ai_policy_235".to_string(),
"enablemm".to_string(),
],
is_start_of_session: conversation.invocation_id == 0,
Expand Down
14 changes: 14 additions & 0 deletions src/pkg/core/tools.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use anyhow::{bail, Result};

pub fn get_path(path: &str) -> Result<String> {
if path.starts_with('~') {
let home = std::env::var("HOME")
.unwrap_or_else(|_| std::env::var("USERPROFILE").unwrap_or_default());
if home.is_empty() {
bail!("Cannot find user home directory, please specify absolute path")
}
Ok(format!("{}{}", home, path.trim_start_matches('~')))
} else {
Ok(path.to_string())
}
}

0 comments on commit 946e343

Please sign in to comment.