Skip to content

Commit 00d86f9

Browse files
authored
Merge pull request #8 from Netdex/gpt5-support
GPT-5 support
2 parents 3fee63c + d289951 commit 00d86f9

File tree

8 files changed

+153
-85
lines changed

8 files changed

+153
-85
lines changed

niinii/src/app.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ impl App {
165165
}
166166
}
167167

168-
fn transition(&mut self, ui: &Ui, state: State) {
168+
fn transition(&mut self, _ui: &Ui, state: State) {
169169
self.state = state;
170170
}
171171

@@ -213,14 +213,11 @@ impl App {
213213
}
214214
}
215215

216-
match &self.state {
217-
State::Completed => {
218-
if let Some(request_gloss_text) = self.request_gloss_text.clone() {
219-
self.request_gloss_text = None;
220-
self.request_parse(ui, &request_gloss_text);
221-
}
216+
if let State::Completed = &self.state {
217+
if let Some(request_gloss_text) = self.request_gloss_text.clone() {
218+
self.request_gloss_text = None;
219+
self.request_parse(ui, &request_gloss_text);
222220
}
223-
_ => (),
224221
};
225222

226223
if self.settings.watch_clipboard {

niinii/src/settings.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub struct ChatSettings {
3636
pub presence_penalty: Option<f32>,
3737
pub connection_timeout: u64,
3838
pub timeout: u64,
39+
pub stream: bool,
3940
}
4041
impl Default for ChatSettings {
4142
fn default() -> Self {
@@ -51,6 +52,7 @@ impl Default for ChatSettings {
5152
presence_penalty: None,
5253
connection_timeout: 3000,
5354
timeout: 10000,
55+
stream: true,
5456
}
5557
}
5658
}

niinii/src/translator/chat.rs

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl Translator for ChatTranslator {
5353
let chatgpt = &settings.chat;
5454

5555
let permit = self.semaphore.clone().acquire_owned().await.unwrap();
56-
let mut exchange = {
56+
let exchange = {
5757
let mut chat = self.chat.lock().await;
5858
chat.start_exchange(
5959
Message {
@@ -74,47 +74,54 @@ impl Translator for ChatTranslator {
7474
messages: exchange.prompt(),
7575
temperature: chatgpt.temperature,
7676
top_p: chatgpt.top_p,
77-
max_tokens: chatgpt.max_tokens,
77+
max_completion_tokens: chatgpt.max_tokens,
7878
presence_penalty: chatgpt.presence_penalty,
7979
..Default::default()
8080
};
8181

8282
let exchange = Arc::new(Mutex::new(exchange));
83-
let mut stream = self.client.stream(chat_request).await?;
8483
let token = CancellationToken::new();
85-
let chat = &self.chat;
86-
tokio::spawn(
87-
enclose! { (chat, token, exchange, chatgpt.max_context_tokens => max_context_tokens) async move {
88-
// Hold permit: We are not allowed to begin another translation
89-
// request until this one is complete.
90-
let _permit = permit;
91-
loop {
92-
tokio::select! {
93-
msg = stream.next() => match msg {
94-
Some(Ok(completion)) => {
95-
let mut exchange = exchange.lock().await;
96-
let message = &completion.choices.first().unwrap().delta;
97-
exchange.append(message)
84+
if chatgpt.stream {
85+
let mut stream = self.client.stream(chat_request).await?;
86+
let chat = &self.chat;
87+
tokio::spawn(
88+
enclose! { (chat, token, exchange, chatgpt.max_context_tokens => max_context_tokens) async move {
89+
// Hold permit: We are not allowed to begin another translation
90+
// request until this one is complete.
91+
let _permit = permit;
92+
loop {
93+
tokio::select! {
94+
msg = stream.next() => match msg {
95+
Some(Ok(completion)) => {
96+
let mut exchange = exchange.lock().await;
97+
let message = &completion.choices.first().unwrap().delta;
98+
exchange.append_partial(message)
99+
},
100+
Some(Err(err)) => {
101+
tracing::error!(%err, "stream");
102+
break
103+
},
104+
None => {
105+
let mut chat = chat.lock().await;
106+
let mut exchange = exchange.lock().await;
107+
chat.commit(&mut exchange);
108+
chat.enforce_context_limit(max_context_tokens);
109+
break
110+
}
98111
},
99-
Some(Err(err)) => {
100-
tracing::error!(%err, "stream");
101-
break
102-
},
103-
None => {
104-
let mut chat = chat.lock().await;
105-
let mut exchange = exchange.lock().await;
106-
chat.commit(&mut exchange);
107-
chat.enforce_context_limit(max_context_tokens);
112+
_ = token.cancelled() => {
108113
break
109114
}
110-
},
111-
_ = token.cancelled() => {
112-
break
113115
}
114116
}
115-
}
116-
}.instrument(tracing::Span::current())},
117-
);
117+
}.instrument(tracing::Span::current())},
118+
);
119+
} else {
120+
let response = self.client.chat(chat_request).await?;
121+
let mut e = exchange.lock().await;
122+
e.set_complete(response.choices.first().unwrap().message.clone());
123+
self.chat.lock().await.commit(&mut e);
124+
}
118125

119126
Ok(Box::new(ChatTranslation {
120127
model: chatgpt.model,

niinii/src/view/translator/chat.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use openai_chat::chat::{Model, Role};
44
use crate::{
55
settings::Settings,
66
translator::chat::{ChatTranslation, ChatTranslator},
7-
view::mixins::drag_handle,
7+
view::mixins::{drag_handle, help_marker},
88
};
99

1010
use crate::view::{
@@ -30,7 +30,10 @@ impl View for ViewChatTranslator<'_> {
3030
}
3131
});
3232
if ui.collapsing_header("Tuning", TreeNodeFlags::DEFAULT_OPEN) {
33-
if let Some(_token) = ui.begin_table("##", 2) {
33+
if let Some(_token) = ui.begin_table("##", 3) {
34+
ui.table_next_column();
35+
ui.set_next_item_width(ui.current_font_size() * -8.0);
36+
combo_enum(ui, "Model", &mut chatgpt.model);
3437
ui.table_next_column();
3538
ui.set_next_item_width(ui.current_font_size() * -8.0);
3639
ui.input_scalar("Max context tokens", &mut chatgpt.max_context_tokens)
@@ -75,8 +78,12 @@ impl View for ViewChatTranslator<'_> {
7578
},
7679
);
7780
ui.table_next_column();
78-
ui.set_next_item_width(ui.current_font_size() * -8.0);
79-
combo_enum(ui, "Model", &mut chatgpt.model);
81+
ui.checkbox("Stream", &mut chatgpt.stream);
82+
ui.same_line();
83+
help_marker(
84+
ui,
85+
"Use streaming API (may require ID verification for some models)",
86+
);
8087
}
8188
}
8289
ui.child_window("context_window").build(|| {
@@ -194,13 +201,15 @@ impl View for ViewChatTranslation<'_> {
194201
let _wrap_token = ui.push_text_wrap_pos_with_pos(0.0);
195202
ui.text(""); // anchor for line wrapping
196203
ui.same_line();
197-
let ChatTranslation { exchange, .. } = self.0;
204+
let ChatTranslation {
205+
model, exchange, ..
206+
} = self.0;
198207
let exchange = exchange.blocking_lock();
199208
let draw_list = ui.get_window_draw_list();
200209
stroke_text_with_highlight(
201210
ui,
202211
&draw_list,
203-
"[ChatGPT]",
212+
&format!("[{}]", std::convert::Into::<&'static str>::into(model)),
204213
1.0,
205214
Some(StyleColor::NavHighlight),
206215
);

openai-chat/src/chat/chat_buffer.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub struct Exchange {
8989
usage: Option<Usage>,
9090
}
9191
impl Exchange {
92-
pub fn append(&mut self, partial: &PartialMessage) {
92+
pub fn append_partial(&mut self, partial: &PartialMessage) {
9393
if let Some(last) = &mut self.response {
9494
if let Some(content) = &mut last.content {
9595
content.push_str(&partial.content)
@@ -104,6 +104,10 @@ impl Exchange {
104104
}
105105
}
106106

107+
pub fn set_complete(&mut self, message: Message) {
108+
self.response = Some(message);
109+
}
110+
107111
pub fn prompt(&self) -> Vec<Message> {
108112
let mut messages = vec![];
109113
messages.push(self.system.clone());

openai-chat/src/chat/mod.rs

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub use crate::protocol::chat::{Message, Model, PartialMessage, Role, Usage};
1111
pub use chat_buffer::{ChatBuffer, Exchange};
1212

1313
use crate::{
14-
protocol::chat::{self, StreamResponse},
14+
protocol::chat::{self, ChatResponse, StreamOptions, StreamResponse},
1515
Client, Error,
1616
};
1717

@@ -37,7 +37,7 @@ pub struct Request {
3737
/// The maximum number of tokens allowed for the generated answer. By
3838
/// default, the number of tokens the model can return will be (4096 - prompt
3939
/// tokens).
40-
pub max_tokens: Option<u32>,
40+
pub max_completion_tokens: Option<u32>,
4141
/// Number between -2.0 and 2.0. Positive values penalize new tokens based
4242
/// on whether they appear in the text so far, increasing the model's
4343
/// likelihood to talk about new topics.
@@ -51,7 +51,7 @@ impl From<Request> for chat::Request {
5151
temperature,
5252
top_p,
5353
n,
54-
max_tokens,
54+
max_completion_tokens,
5555
presence_penalty,
5656
} = value;
5757
chat::Request {
@@ -60,7 +60,7 @@ impl From<Request> for chat::Request {
6060
temperature,
6161
top_p,
6262
n,
63-
max_tokens,
63+
max_completion_tokens,
6464
presence_penalty,
6565
..Default::default()
6666
}
@@ -71,7 +71,7 @@ impl Client {
7171
#[tracing::instrument(level = Level::DEBUG, skip_all, err)]
7272
pub async fn chat(&self, request: Request) -> Result<chat::Completion, Error> {
7373
let request: chat::Request = request.into();
74-
tracing::trace!(?request);
74+
tracing::debug!(?request);
7575
let response: chat::ChatResponse = self
7676
.shared
7777
.request(Method::POST, "/v1/chat/completions")
@@ -80,7 +80,7 @@ impl Client {
8080
.await?
8181
.json()
8282
.await?;
83-
tracing::trace!(?response);
83+
tracing::debug!(?response);
8484
Ok(response.0?)
8585
}
8686

@@ -91,39 +91,68 @@ impl Client {
9191
) -> Result<impl Stream<Item = Result<chat::PartialCompletion, Error>>, Error> {
9292
let mut request: chat::Request = request.into();
9393
request.stream = Some(true);
94+
request.stream_options = Some(StreamOptions {
95+
include_obfuscation: false,
96+
include_usage: false,
97+
});
9498

95-
tracing::trace!(?request);
96-
let stream = self
99+
tracing::debug!(?request);
100+
let response = self
97101
.shared
98102
.request(Method::POST, "/v1/chat/completions")
99103
.body(&request)
100104
.send()
101-
.await?
102-
.bytes_stream()
103-
.eventsource();
104-
Ok(stream.map_while(|event| match event {
105-
Ok(event) => {
106-
if event.data == "[DONE]" {
107-
None
108-
} else {
109-
let response = match serde_json::from_str::<StreamResponse>(&event.data) {
110-
Ok(response) => {
111-
tracing::trace!(?response);
112-
Ok::<_, Error>(response.0)
113-
}
114-
Err(err) => {
115-
tracing::error!(?err, ?event.data);
116-
Err(err.into())
105+
.await?;
106+
let status = response.status();
107+
108+
if status.is_success() {
109+
// HTTP success: Expect SSE response
110+
let stream = response.bytes_stream().eventsource();
111+
Ok(stream.map_while(|event| {
112+
tracing::trace!(?event);
113+
match event {
114+
Ok(event) => {
115+
if event.data == "[DONE]" {
116+
None
117+
} else {
118+
let response = match serde_json::from_str::<StreamResponse>(&event.data)
119+
{
120+
Ok(response) => {
121+
tracing::debug!(?response);
122+
Ok::<_, Error>(response.0)
123+
}
124+
Err(err) => {
125+
// Serde error
126+
tracing::error!(?err, ?event.data);
127+
Err(err.into())
128+
}
129+
};
130+
Some(response)
117131
}
118-
};
119-
Some(response)
132+
}
133+
Err(err) => {
134+
// SSE error
135+
tracing::error!(?err);
136+
Some(Err(err.into()))
137+
}
138+
}
139+
}))
140+
} else {
141+
// HTTP error: Expect JSON response
142+
let response_err = response.error_for_status_ref().unwrap_err();
143+
let chat_response = response.json::<ChatResponse>().await;
144+
match chat_response {
145+
Ok(err) => {
146+
// OpenAI application error
147+
Err(Error::Protocol(err.0.unwrap_err()))
148+
}
149+
Err(err) => {
150+
// Not application error, return HTTP error
151+
tracing::error!(?response_err, ?err, "unexpected stream response");
152+
Err(response_err.into())
120153
}
121154
}
122-
Err(err) => {
123-
tracing::error!(?err);
124-
Some(Err(err.into()))
125-
}
126-
}))
155+
}
127156
}
128157
}
129158

0 commit comments

Comments
 (0)