mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
Add data-use disclosures to the user report dialog (#42798)
## What changed - Show audience-specific data-use disclosure copy and links before report submission. - Keep the note editor and submit/cancel controls visible in short panes while allowing the disclosure to scroll with `PageUp` and `PageDown`. - Clarify the employee completion message with its report ID and link. ## Testing - Add snapshot coverage for both audiences, narrow widths, short panes, and four-row panes. - Cover submit and cancel behavior in the report note view. GitOrigin-RevId: c025bcfbbd39e462685b211ef9392664e7de4f71
This commit is contained in:
@@ -1695,7 +1695,7 @@ impl App {
|
||||
category,
|
||||
include_logs,
|
||||
} => {
|
||||
self.chat_widget.open_feedback_note(category, include_logs);
|
||||
self.chat_widget.open_feedback_note(category, include_logs, self.feedback_audience);
|
||||
}
|
||||
AppEvent::OpenFeedbackConsent { category } => {
|
||||
self.chat_widget.open_feedback_consent(category);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Collects an optional feedback note and submits it with the selected log consent.
|
||||
//! Collects a feedback note with audience-specific disclosure and independent log consent.
|
||||
//! The editor and submission controls remain visible when the disclosure must scroll.
|
||||
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
@@ -12,19 +13,33 @@ use ratatui::widgets::Clear;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::widgets::StatefulWidgetRef;
|
||||
use ratatui::widgets::Widget;
|
||||
use std::cell::Cell;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::app_event::FeedbackCategory;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use crate::key_hint;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::terminal_hyperlinks::mark_underlined_hyperlink;
|
||||
use crate::wrapping::RtOptions;
|
||||
use crate::wrapping::word_wrap_lines;
|
||||
|
||||
use super::CancellationEvent;
|
||||
use super::bottom_pane_view::BottomPaneView;
|
||||
use super::popup_consts::standard_popup_hint_line;
|
||||
use super::feedback_view::FeedbackAudience;
|
||||
use super::popup_consts::accept_cancel_hint_line;
|
||||
use super::textarea::TextArea;
|
||||
use super::textarea::TextAreaState;
|
||||
|
||||
const FEEDBACK_POLICY_URL: &str = "https://help.openai.com/en/articles/5722486-how-your-data-is-used-to-improve-model-performance";
|
||||
const EMPLOYEE_FEEDBACK_FAQ_URL: &str = "https://go/codex-employee-feedback-faq";
|
||||
|
||||
enum IntroContent {
|
||||
Full,
|
||||
DisclosureOnly,
|
||||
}
|
||||
|
||||
/// Minimal input overlay to collect an optional feedback note, then submit it
|
||||
/// through the app-server-managed feedback flow.
|
||||
pub(crate) struct FeedbackNoteView {
|
||||
@@ -32,10 +47,13 @@ pub(crate) struct FeedbackNoteView {
|
||||
turn_id: Option<String>,
|
||||
app_event_tx: AppEventSender,
|
||||
include_logs: bool,
|
||||
feedback_audience: FeedbackAudience,
|
||||
|
||||
// UI state
|
||||
textarea: TextArea,
|
||||
textarea_state: RefCell<TextAreaState>,
|
||||
intro_scroll: Cell<usize>,
|
||||
intro_page_height: Cell<usize>,
|
||||
complete: bool,
|
||||
}
|
||||
|
||||
@@ -45,14 +63,18 @@ impl FeedbackNoteView {
|
||||
turn_id: Option<String>,
|
||||
app_event_tx: AppEventSender,
|
||||
include_logs: bool,
|
||||
feedback_audience: FeedbackAudience,
|
||||
) -> Self {
|
||||
Self {
|
||||
category,
|
||||
turn_id,
|
||||
app_event_tx,
|
||||
include_logs,
|
||||
feedback_audience,
|
||||
textarea: TextArea::new(),
|
||||
textarea_state: RefCell::new(TextAreaState::default()),
|
||||
intro_scroll: Cell::new(0),
|
||||
intro_page_height: Cell::new(1),
|
||||
complete: false,
|
||||
}
|
||||
}
|
||||
@@ -73,6 +95,26 @@ impl FeedbackNoteView {
|
||||
impl BottomPaneView for FeedbackNoteView {
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
match key_event {
|
||||
KeyEvent {
|
||||
code: KeyCode::PageUp,
|
||||
..
|
||||
} => {
|
||||
self.intro_scroll.set(
|
||||
self.intro_scroll
|
||||
.get()
|
||||
.saturating_sub(self.intro_page_height.get()),
|
||||
);
|
||||
}
|
||||
KeyEvent {
|
||||
code: KeyCode::PageDown,
|
||||
..
|
||||
} => {
|
||||
self.intro_scroll.set(
|
||||
self.intro_scroll
|
||||
.get()
|
||||
.saturating_add(self.intro_page_height.get()),
|
||||
);
|
||||
}
|
||||
KeyEvent {
|
||||
code: KeyCode::Esc, ..
|
||||
} => {
|
||||
@@ -117,21 +159,21 @@ impl BottomPaneView for FeedbackNoteView {
|
||||
|
||||
impl Renderable for FeedbackNoteView {
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
self.intro_lines(width).len() as u16 + self.input_height(width) + 2u16
|
||||
self.intro_lines(width, IntroContent::Full).len() as u16 + self.input_height(width) + 2u16
|
||||
}
|
||||
|
||||
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
|
||||
if area.height < 2 || area.width <= 2 {
|
||||
return None;
|
||||
}
|
||||
let intro_height = self.intro_lines(area.width).len() as u16;
|
||||
let text_area_height = self.input_height(area.width).saturating_sub(1);
|
||||
let input_area = self.input_area(area);
|
||||
let text_area_height = input_area.height.saturating_sub(1);
|
||||
if text_area_height == 0 {
|
||||
return None;
|
||||
}
|
||||
let textarea_rect = Rect {
|
||||
x: area.x.saturating_add(2),
|
||||
y: area.y.saturating_add(intro_height).saturating_add(1),
|
||||
y: input_area.y.saturating_add(1),
|
||||
width: area.width.saturating_sub(2),
|
||||
height: text_area_height,
|
||||
};
|
||||
@@ -144,11 +186,34 @@ impl Renderable for FeedbackNoteView {
|
||||
return;
|
||||
}
|
||||
|
||||
let intro_lines = self.intro_lines(area.width);
|
||||
// Four rows leave room for one intro line; show the disclosure before submission.
|
||||
let intro_content = if area.height == 4 {
|
||||
IntroContent::DisclosureOnly
|
||||
} else {
|
||||
IntroContent::Full
|
||||
};
|
||||
let intro_lines = self.intro_lines(area.width, intro_content);
|
||||
let input_area = self.input_area(area);
|
||||
let intro_area = Rect {
|
||||
height: input_area.y.saturating_sub(area.y),
|
||||
..area
|
||||
};
|
||||
let (_, placeholder) = feedback_title_and_placeholder(self.category);
|
||||
let input_height = self.input_height(area.width);
|
||||
let input_height = input_area.height;
|
||||
let max_scroll = intro_lines
|
||||
.len()
|
||||
.saturating_sub(usize::from(intro_area.height));
|
||||
self.intro_scroll
|
||||
.set(self.intro_scroll.get().min(max_scroll));
|
||||
self.intro_page_height
|
||||
.set(usize::from(intro_area.height.max(1)));
|
||||
|
||||
for (offset, line) in intro_lines.iter().enumerate() {
|
||||
for (offset, line) in intro_lines
|
||||
.iter()
|
||||
.skip(self.intro_scroll.get())
|
||||
.take(usize::from(intro_area.height))
|
||||
.enumerate()
|
||||
{
|
||||
Paragraph::new(line.clone()).render(
|
||||
Rect {
|
||||
x: area.x,
|
||||
@@ -159,14 +224,13 @@ impl Renderable for FeedbackNoteView {
|
||||
buf,
|
||||
);
|
||||
}
|
||||
let disclosure_url = match self.feedback_audience {
|
||||
FeedbackAudience::OpenAiEmployee => EMPLOYEE_FEEDBACK_FAQ_URL,
|
||||
FeedbackAudience::External => FEEDBACK_POLICY_URL,
|
||||
};
|
||||
mark_underlined_hyperlink(buf, intro_area, disclosure_url);
|
||||
|
||||
// Input line
|
||||
let input_area = Rect {
|
||||
x: area.x,
|
||||
y: area.y.saturating_add(intro_lines.len() as u16),
|
||||
width: area.width,
|
||||
height: input_height,
|
||||
};
|
||||
if input_area.width >= 2 {
|
||||
for row in 0..input_area.height {
|
||||
Paragraph::new(Line::from(vec![gutter()])).render(
|
||||
@@ -206,7 +270,8 @@ impl Renderable for FeedbackNoteView {
|
||||
}
|
||||
|
||||
let hint_blank_y = input_area.y.saturating_add(input_height);
|
||||
if hint_blank_y < area.y.saturating_add(area.height) {
|
||||
let hint_spacing = u16::from(area.height > 4);
|
||||
if hint_spacing > 0 && hint_blank_y < area.y.saturating_add(area.height) {
|
||||
let blank_area = Rect {
|
||||
x: area.x,
|
||||
y: hint_blank_y,
|
||||
@@ -214,11 +279,26 @@ impl Renderable for FeedbackNoteView {
|
||||
height: 1,
|
||||
};
|
||||
Clear.render(blank_area, buf);
|
||||
if max_scroll > 0 {
|
||||
Paragraph::new(Line::from(vec![
|
||||
key_hint::plain(KeyCode::PageUp).into(),
|
||||
"/".into(),
|
||||
key_hint::plain(KeyCode::PageDown).into(),
|
||||
" scroll disclosure".into(),
|
||||
]))
|
||||
.render(blank_area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
let hint_y = hint_blank_y.saturating_add(1);
|
||||
let hint_y = hint_blank_y.saturating_add(hint_spacing);
|
||||
if hint_y < area.y.saturating_add(area.height) {
|
||||
Paragraph::new(standard_popup_hint_line()).render(
|
||||
Paragraph::new(accept_cancel_hint_line(
|
||||
Some(key_hint::plain(KeyCode::Enter).into()),
|
||||
"to submit",
|
||||
Some(key_hint::plain(KeyCode::Esc).into()),
|
||||
"to cancel",
|
||||
))
|
||||
.render(
|
||||
Rect {
|
||||
x: area.x,
|
||||
y: hint_y,
|
||||
@@ -232,15 +312,59 @@ impl Renderable for FeedbackNoteView {
|
||||
}
|
||||
|
||||
impl FeedbackNoteView {
|
||||
fn input_area(&self, area: Rect) -> Rect {
|
||||
// Reserve the editor and submit/cancel hints before allocating disclosure rows.
|
||||
let footer_height = if area.height > 4 { 2 } else { 1 };
|
||||
let input_height = self
|
||||
.input_height(area.width)
|
||||
.min(area.height.saturating_sub(footer_height + 1).max(2))
|
||||
.min(area.height);
|
||||
let intro_height = (self.intro_lines(area.width, IntroContent::Full).len() as u16)
|
||||
.min(area.height.saturating_sub(input_height + footer_height));
|
||||
Rect {
|
||||
y: area.y.saturating_add(intro_height),
|
||||
height: input_height,
|
||||
..area
|
||||
}
|
||||
}
|
||||
|
||||
fn input_height(&self, width: u16) -> u16 {
|
||||
let usable_width = width.saturating_sub(2);
|
||||
let text_height = self.textarea.desired_height(usable_width).clamp(1, 8);
|
||||
text_height.saturating_add(1).min(9)
|
||||
}
|
||||
|
||||
fn intro_lines(&self, _width: u16) -> Vec<Line<'static>> {
|
||||
fn intro_lines(&self, width: u16, content: IntroContent) -> Vec<Line<'static>> {
|
||||
let (title, _) = feedback_title_and_placeholder(self.category);
|
||||
vec![Line::from(vec![gutter(), title.bold()])]
|
||||
let mut lines = match content {
|
||||
IntroContent::Full => vec![title.bold().into(), "".into()],
|
||||
IntroContent::DisclosureOnly => Vec::new(),
|
||||
};
|
||||
match self.feedback_audience {
|
||||
FeedbackAudience::OpenAiEmployee => {
|
||||
lines.push(
|
||||
"Your data may be used to improve our models and products"
|
||||
.bold()
|
||||
.into(),
|
||||
);
|
||||
lines.push(Line::from(vec![
|
||||
"By submitting feedback, you agree that OpenAI can use your feedback for safety purposes and internal model training, as explained in more detail ".dim(),
|
||||
"here".cyan().underlined(),
|
||||
". Please avoid sharing tented information under NDA, or privileged, HR-related, or sensitive personal information.".dim(),
|
||||
]));
|
||||
}
|
||||
FeedbackAudience::External => lines.push(Line::from(vec![
|
||||
"Your feedback can be used to improve ChatGPT. ".dim(),
|
||||
"Learn more".cyan().underlined(),
|
||||
".".dim(),
|
||||
])),
|
||||
}
|
||||
word_wrap_lines(
|
||||
lines,
|
||||
RtOptions::new(usize::from(width.max(1)))
|
||||
.initial_indent(Line::from(gutter()))
|
||||
.subsequent_indent(Line::from(gutter())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ fn render_buffer(area: Rect, buf: &Buffer) -> String {
|
||||
if symbol.is_empty() {
|
||||
line.push(' ');
|
||||
} else {
|
||||
line.push_str(symbol);
|
||||
line.push_str(&crate::terminal_hyperlinks::strip_osc8(symbol));
|
||||
}
|
||||
}
|
||||
line.trim_end().to_string()
|
||||
@@ -38,7 +38,11 @@ fn make_view(category: FeedbackCategory) -> FeedbackNoteView {
|
||||
let (tx_raw, _rx) = tokio::sync::mpsc::unbounded_channel::<AppEvent>();
|
||||
let tx = AppEventSender::new(tx_raw);
|
||||
FeedbackNoteView::new(
|
||||
category, /*turn_id*/ None, tx, /*include_logs*/ true,
|
||||
category,
|
||||
/*turn_id*/ None,
|
||||
tx,
|
||||
/*include_logs*/ true,
|
||||
FeedbackAudience::External,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -86,12 +90,137 @@ fn feedback_view_with_connectivity_diagnostics() {
|
||||
/*turn_id*/ None,
|
||||
tx,
|
||||
/*include_logs*/ false,
|
||||
FeedbackAudience::External,
|
||||
);
|
||||
let rendered = render(&view, /*width*/ 60);
|
||||
|
||||
insta::assert_snapshot!("feedback_view_with_connectivity_diagnostics", rendered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feedback_disclosure_matches_audience_at_normal_and_narrow_widths() {
|
||||
let mut rendered = Vec::new();
|
||||
for (audience, name, url, link_label) in [
|
||||
(
|
||||
FeedbackAudience::External,
|
||||
"external",
|
||||
FEEDBACK_POLICY_URL,
|
||||
"Learnmore",
|
||||
),
|
||||
(
|
||||
FeedbackAudience::OpenAiEmployee,
|
||||
"employee",
|
||||
EMPLOYEE_FEEDBACK_FAQ_URL,
|
||||
"here",
|
||||
),
|
||||
] {
|
||||
let mut view = make_view(FeedbackCategory::Bug);
|
||||
view.feedback_audience = audience;
|
||||
for width in [80, 40] {
|
||||
let area = Rect::new(
|
||||
/*x*/ 0,
|
||||
/*y*/ 0,
|
||||
width,
|
||||
view.desired_height(width),
|
||||
);
|
||||
let mut buf = Buffer::empty(area);
|
||||
view.render(area, &mut buf);
|
||||
|
||||
let linked_text = buf
|
||||
.content
|
||||
.iter()
|
||||
.filter(|cell| cell.symbol().contains(&format!("\x1b]8;;{url}\x07")))
|
||||
.map(|cell| crate::terminal_hyperlinks::strip_osc8(cell.symbol()))
|
||||
.collect::<String>();
|
||||
assert_eq!(linked_text, link_label);
|
||||
rendered.push(format!(
|
||||
"{name} ({width} columns)\n{}",
|
||||
render_buffer(area, &buf)
|
||||
));
|
||||
}
|
||||
}
|
||||
insta::assert_snapshot!("feedback_disclosures", rendered.join("\n\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feedback_disclosure_scrolls_without_hiding_input_in_short_panes() {
|
||||
let mut rendered = Vec::new();
|
||||
for (width, height) in [(40, 12), (80, 8)] {
|
||||
let mut view = make_view(FeedbackCategory::Bug);
|
||||
view.feedback_audience = FeedbackAudience::OpenAiEmployee;
|
||||
view.handle_paste("A visible note.".to_string());
|
||||
let area = Rect::new(/*x*/ 0, /*y*/ 0, width, height);
|
||||
for (stage, key) in [
|
||||
("initial", None),
|
||||
("scrolled", Some(KeyCode::PageDown)),
|
||||
("restored", Some(KeyCode::PageUp)),
|
||||
] {
|
||||
if let Some(key) = key {
|
||||
view.handle_key_event(KeyEvent::from(key));
|
||||
}
|
||||
let mut buf = Buffer::empty(area);
|
||||
view.render(area, &mut buf);
|
||||
let text = render_buffer(area, &buf);
|
||||
assert!(text.contains("A visible note."));
|
||||
assert!(text.contains("enter to submit"));
|
||||
assert!(
|
||||
view.cursor_pos(area)
|
||||
.is_some_and(|(x, y)| x < width && y < height)
|
||||
);
|
||||
assert_eq!(view.is_complete(), false);
|
||||
if stage == "scrolled" {
|
||||
assert!(text.contains("sensitive personal"));
|
||||
}
|
||||
rendered.push(format!("{width}x{height} {stage}\n{text}"));
|
||||
}
|
||||
}
|
||||
insta::assert_snapshot!("feedback_disclosure_short_panes", rendered.join("\n\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feedback_disclosure_keeps_editor_visible_in_four_rows() {
|
||||
let mut view = make_view(FeedbackCategory::Bug);
|
||||
view.feedback_audience = FeedbackAudience::OpenAiEmployee;
|
||||
view.handle_paste("A visible note.".to_string());
|
||||
let area = Rect::new(
|
||||
/*x*/ 0, /*y*/ 0, /*width*/ 80, /*height*/ 4,
|
||||
);
|
||||
let mut buf = Buffer::empty(area);
|
||||
view.render(area, &mut buf);
|
||||
|
||||
assert!(
|
||||
view.cursor_pos(area)
|
||||
.is_some_and(|(x, y)| area.contains((x, y).into()))
|
||||
);
|
||||
let initial = render_buffer(area, &buf);
|
||||
assert!(initial.contains("Your data may be used to improve our models and products"));
|
||||
for _ in view.intro_lines(area.width, IntroContent::Full) {
|
||||
view.handle_key_event(KeyEvent::from(KeyCode::PageDown));
|
||||
buf = Buffer::empty(area);
|
||||
view.render(area, &mut buf);
|
||||
}
|
||||
let scrolled = render_buffer(area, &buf);
|
||||
assert!(scrolled.contains("personal information."));
|
||||
insta::assert_snapshot!(
|
||||
"feedback_disclosure_four_rows",
|
||||
format!("{initial}\n\n{scrolled}")
|
||||
);
|
||||
|
||||
let narrow_area = Rect::new(
|
||||
/*x*/ 0, /*y*/ 0, /*width*/ 24, /*height*/ 4,
|
||||
);
|
||||
for (audience, disclosure) in [
|
||||
(FeedbackAudience::OpenAiEmployee, "Your data"),
|
||||
(FeedbackAudience::External, "Your feedback"),
|
||||
] {
|
||||
let mut view = make_view(FeedbackCategory::SafetyCheck);
|
||||
view.feedback_audience = audience;
|
||||
let mut buf = Buffer::empty(narrow_area);
|
||||
view.render(narrow_area, &mut buf);
|
||||
assert!(render_buffer(narrow_area, &buf).contains(disclosure));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_feedback_emits_submit_event_with_trimmed_note() {
|
||||
let (tx_raw, mut rx) = tokio::sync::mpsc::unbounded_channel::<AppEvent>();
|
||||
@@ -101,10 +230,11 @@ fn submit_feedback_emits_submit_event_with_trimmed_note() {
|
||||
Some("turn-123".to_string()),
|
||||
tx,
|
||||
/*include_logs*/ true,
|
||||
FeedbackAudience::OpenAiEmployee,
|
||||
);
|
||||
view.textarea.insert_str(" something broke ");
|
||||
|
||||
view.submit();
|
||||
view.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
let event = rx.try_recv().expect("submit feedback event");
|
||||
assert!(matches!(
|
||||
@@ -128,6 +258,7 @@ fn submit_feedback_omits_empty_note() {
|
||||
/*turn_id*/ None,
|
||||
tx,
|
||||
/*include_logs*/ false,
|
||||
FeedbackAudience::External,
|
||||
);
|
||||
|
||||
view.submit();
|
||||
@@ -143,3 +274,21 @@ fn submit_feedback_omits_empty_note() {
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feedback_note_cancel_does_not_submit() {
|
||||
let (tx_raw, mut rx) = tokio::sync::mpsc::unbounded_channel::<AppEvent>();
|
||||
let mut view = FeedbackNoteView::new(
|
||||
FeedbackCategory::Bug,
|
||||
/*turn_id*/ None,
|
||||
AppEventSender::new(tx_raw),
|
||||
/*include_logs*/ false,
|
||||
FeedbackAudience::OpenAiEmployee,
|
||||
);
|
||||
view.textarea.insert_str("unfinished feedback");
|
||||
|
||||
view.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(view.is_complete(), true);
|
||||
assert!(rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
@@ -19,10 +19,9 @@ const BASE_CLI_BUG_ISSUE_URL: &str =
|
||||
/// Internal routing link for employee feedback follow-ups. This must not be shown to external users.
|
||||
const CODEX_FEEDBACK_INTERNAL_URL: &str = "http://go/codex-feedback-internal";
|
||||
|
||||
/// The target audience for feedback follow-up instructions.
|
||||
/// The target audience for feedback disclosure and follow-up instructions.
|
||||
///
|
||||
/// This is used strictly for messaging/links after feedback upload completes. It
|
||||
/// must not change feedback upload behavior itself.
|
||||
/// This controls displayed copy and links, not feedback upload behavior.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum FeedbackAudience {
|
||||
OpenAiEmployee,
|
||||
@@ -60,7 +59,7 @@ pub(crate) fn feedback_success_cell(
|
||||
let issue_url = issue_url_for_category(category, thread_id, feedback_audience);
|
||||
let mut lines = vec![Line::from(match issue_url.as_ref() {
|
||||
Some(_) if feedback_audience == FeedbackAudience::OpenAiEmployee => {
|
||||
format!("{prefix} Please report this in #codex-feedback:")
|
||||
format!("{prefix} You can share this in #codex-feedback:")
|
||||
}
|
||||
Some(_) => format!("{prefix} Please open an issue using the following URL:"),
|
||||
None => format!("{prefix} Thanks for the feedback!"),
|
||||
@@ -71,10 +70,15 @@ pub(crate) fn feedback_success_cell(
|
||||
"".into(),
|
||||
Line::from(vec![" ".into(), url.cyan().underlined()]),
|
||||
"".into(),
|
||||
Line::from(" Share this and add some info about your problem:"),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
format!("https://go/codex-feedback/{thread_id}").bold(),
|
||||
" Sentry Feedback ID: ".into(),
|
||||
thread_id.to_string().bold(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" Sentry URL: ".into(),
|
||||
format!("https://go/codex-feedback/{thread_id}")
|
||||
.cyan()
|
||||
.underlined(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -352,7 +356,7 @@ mod tests {
|
||||
if symbol.is_empty() {
|
||||
line.push(' ');
|
||||
} else {
|
||||
line.push_str(symbol);
|
||||
line.push_str(&crate::terminal_hyperlinks::strip_osc8(symbol));
|
||||
}
|
||||
}
|
||||
line.trim_end().to_string()
|
||||
@@ -520,10 +524,7 @@ mod tests {
|
||||
),
|
||||
/*width*/ 120,
|
||||
);
|
||||
assert_eq!(
|
||||
rendered,
|
||||
"• Feedback uploaded. Please report this in #codex-feedback:\n\n http://go/codex-feedback-internal\n\n Share this and add some info about your problem:\n https://go/codex-feedback/thread-2"
|
||||
);
|
||||
insta::assert_snapshot!("feedback_success_employee", rendered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/feedback_note_view_tests.rs
|
||||
expression: "format!(\"{initial}\\n\\n{scrolled}\")"
|
||||
---
|
||||
▌ Your data may be used to improve our models and products
|
||||
▌
|
||||
▌ A visible note.
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
▌ sensitive personal information.
|
||||
▌
|
||||
▌ A visible note.
|
||||
Press enter to submit or esc to cancel
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/feedback_note_view_tests.rs
|
||||
expression: "rendered.join(\"\\n\\n\")"
|
||||
---
|
||||
40x12 initial
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your data may be used to improve our
|
||||
▌ models and products
|
||||
▌ By submitting feedback, you agree that
|
||||
▌ OpenAI can use your feedback for
|
||||
▌ safety purposes and internal model
|
||||
▌ training, as explained in more detail
|
||||
▌
|
||||
▌ A visible note.
|
||||
pgup/pgdn scroll disclosure
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
40x12 scrolled
|
||||
▌ By submitting feedback, you agree that
|
||||
▌ OpenAI can use your feedback for
|
||||
▌ safety purposes and internal model
|
||||
▌ training, as explained in more detail
|
||||
▌ here. Please avoid sharing tented
|
||||
▌ information under NDA, or privileged,
|
||||
▌ HR-related, or sensitive personal
|
||||
▌ information.
|
||||
▌
|
||||
▌ A visible note.
|
||||
pgup/pgdn scroll disclosure
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
40x12 restored
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your data may be used to improve our
|
||||
▌ models and products
|
||||
▌ By submitting feedback, you agree that
|
||||
▌ OpenAI can use your feedback for
|
||||
▌ safety purposes and internal model
|
||||
▌ training, as explained in more detail
|
||||
▌
|
||||
▌ A visible note.
|
||||
pgup/pgdn scroll disclosure
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
80x8 initial
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your data may be used to improve our models and products
|
||||
▌ By submitting feedback, you agree that OpenAI can use your feedback for safety
|
||||
▌
|
||||
▌ A visible note.
|
||||
pgup/pgdn scroll disclosure
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
80x8 scrolled
|
||||
▌ By submitting feedback, you agree that OpenAI can use your feedback for safety
|
||||
▌ purposes and internal model training, as explained in more detail here. Please
|
||||
▌ avoid sharing tented information under NDA, or privileged, HR-related, or
|
||||
▌ sensitive personal information.
|
||||
▌
|
||||
▌ A visible note.
|
||||
pgup/pgdn scroll disclosure
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
80x8 restored
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your data may be used to improve our models and products
|
||||
▌ By submitting feedback, you agree that OpenAI can use your feedback for safety
|
||||
▌
|
||||
▌ A visible note.
|
||||
pgup/pgdn scroll disclosure
|
||||
Press enter to submit or esc to cancel
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/feedback_note_view_tests.rs
|
||||
expression: "rendered.join(\"\\n\\n\")"
|
||||
---
|
||||
external (80 columns)
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your feedback can be used to improve ChatGPT. Learn more.
|
||||
▌
|
||||
▌ (optional) Write a short description to help us further
|
||||
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
external (40 columns)
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your feedback can be used to improve
|
||||
▌ ChatGPT. Learn more.
|
||||
▌
|
||||
▌ (optional) Write a short description t
|
||||
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
employee (80 columns)
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your data may be used to improve our models and products
|
||||
▌ By submitting feedback, you agree that OpenAI can use your feedback for safety
|
||||
▌ purposes and internal model training, as explained in more detail here. Please
|
||||
▌ avoid sharing tented information under NDA, or privileged, HR-related, or
|
||||
▌ sensitive personal information.
|
||||
▌
|
||||
▌ (optional) Write a short description to help us further
|
||||
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
employee (40 columns)
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your data may be used to improve our
|
||||
▌ models and products
|
||||
▌ By submitting feedback, you agree that
|
||||
▌ OpenAI can use your feedback for
|
||||
▌ safety purposes and internal model
|
||||
▌ training, as explained in more detail
|
||||
▌ here. Please avoid sharing tented
|
||||
▌ information under NDA, or privileged,
|
||||
▌ HR-related, or sensitive personal
|
||||
▌ information.
|
||||
▌
|
||||
▌ (optional) Write a short description t
|
||||
|
||||
Press enter to submit or esc to cancel
|
||||
+3
-1
@@ -4,6 +4,8 @@ expression: rendered
|
||||
---
|
||||
▌ Tell us more (bad result)
|
||||
▌
|
||||
▌ Your feedback can be used to improve ChatGPT. Learn more.
|
||||
▌
|
||||
▌ (optional) Write a short description to help us further
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
+3
-1
@@ -4,6 +4,8 @@ expression: rendered
|
||||
---
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your feedback can be used to improve ChatGPT. Learn more.
|
||||
▌
|
||||
▌ (optional) Write a short description to help us further
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
+3
-1
@@ -4,6 +4,8 @@ expression: rendered
|
||||
---
|
||||
▌ Tell us more (good result)
|
||||
▌
|
||||
▌ Your feedback can be used to improve ChatGPT. Learn more.
|
||||
▌
|
||||
▌ (optional) Write a short description to help us further
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
+3
-1
@@ -4,6 +4,8 @@ expression: rendered
|
||||
---
|
||||
▌ Tell us more (other)
|
||||
▌
|
||||
▌ Your feedback can be used to improve ChatGPT. Learn more.
|
||||
▌
|
||||
▌ (optional) Write a short description to help us further
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
+3
-1
@@ -4,6 +4,8 @@ expression: rendered
|
||||
---
|
||||
▌ Tell us more (safety check)
|
||||
▌
|
||||
▌ Your feedback can be used to improve ChatGPT. Learn more.
|
||||
▌
|
||||
▌ (optional) Share what was refused and why it should have b
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
+3
-1
@@ -4,6 +4,8 @@ expression: rendered
|
||||
---
|
||||
▌ Tell us more (bug)
|
||||
▌
|
||||
▌ Your feedback can be used to improve ChatGPT. Learn more.
|
||||
▌
|
||||
▌ (optional) Write a short description to help us further
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
Press enter to submit or esc to cancel
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/feedback_view.rs
|
||||
expression: rendered
|
||||
---
|
||||
• Feedback uploaded. You can share this in #codex-feedback:
|
||||
|
||||
http://go/codex-feedback-internal
|
||||
|
||||
Sentry Feedback ID: thread-2
|
||||
Sentry URL: https://go/codex-feedback/thread-2
|
||||
@@ -997,20 +997,14 @@ impl ChatWidget {
|
||||
&mut self,
|
||||
category: crate::app_event::FeedbackCategory,
|
||||
include_logs: bool,
|
||||
) {
|
||||
self.show_feedback_note(category, include_logs);
|
||||
}
|
||||
|
||||
fn show_feedback_note(
|
||||
&mut self,
|
||||
category: crate::app_event::FeedbackCategory,
|
||||
include_logs: bool,
|
||||
feedback_audience: crate::bottom_pane::FeedbackAudience,
|
||||
) {
|
||||
let view = crate::bottom_pane::FeedbackNoteView::new(
|
||||
category,
|
||||
self.turn_lifecycle.last_turn_id.clone(),
|
||||
self.app_event_tx.clone(),
|
||||
include_logs,
|
||||
feedback_audience,
|
||||
);
|
||||
self.bottom_pane.show_view(Box::new(view));
|
||||
self.request_redraw();
|
||||
|
||||
@@ -813,6 +813,7 @@ async fn live_app_server_turn_started_sets_feedback_turn_id() {
|
||||
chat.open_feedback_note(
|
||||
crate::app_event::FeedbackCategory::Bug,
|
||||
/*include_logs*/ false,
|
||||
crate::bottom_pane::FeedbackAudience::External,
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user