Login flow polish (#3632)
# Description - Update sign in flow # Tests - Passes CI --------- Co-authored-by: Michael Bolin <mbolin@openai.com>
This commit is contained in:
@@ -3,14 +3,14 @@
|
||||
*'`+*+\~/_*,
|
||||
^_,||/~~-~+\,,
|
||||
|__/\|;_.\,''\\,
|
||||
/ ;||"|^ ',/_/|/
|
||||
|` '|*~//\ !`_"|
|
||||
/ ;||"|^ /_/|/
|
||||
|` '|*~//\ `_"|
|
||||
\ ~*"*||~|* |/,
|
||||
" ||\+/+||-_`.\||
|
||||
" ||\+/+||-_ .\||
|
||||
" ~\ \\|;~~+\+;||
|
||||
| ,|\,|_/_*___|*`
|
||||
, "|||||""!\,"\|`
|
||||
\`',\,*" _~"",//
|
||||
\`',\,*" "",//
|
||||
|' |||~*,:,/|/`
|
||||
;`**/|+;_!//'
|
||||
*, _*\_,;*
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
_=+"**+~_
|
||||
/^||*||\|=\
|
||||
|//"\/=\|| '\
|
||||
/// ;*_\|\||**|
|
||||
||;_/*'=||*/|`\
|
||||
|||*|' |\= !| ~.|
|
||||
\\| /`,||||/*", |
|
||||
/// ;' \|\||**|
|
||||
||;_ =||*/|`\
|
||||
|||*| /|= !| ~.|
|
||||
\\| ,||||/*", |
|
||||
|/; |`||/|||"; `|
|
||||
\\|~|+~/^||"*+ /
|
||||
*"__,==\*|._| ,_|
|
||||
|||+""/*\|;";.~|`
|
||||
||* |` `//, /
|
||||
||* | `//, /
|
||||
\|* | /,/_,|
|
||||
\|~"_*~//+_|
|
||||
':._=:__;*
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
,=+++;;~,_
|
||||
_;**|~~*=*|,"^,
|
||||
,*\/_==`+,"|||_"\
|
||||
/|/_/|"_` '|;\~||=\
|
||||
|/_ ~ |"/\=\// ,
|
||||
/|/_/|" |;\~||=\
|
||||
|/_ ~ "/\=\// ,
|
||||
`=*,/` ,:/| /,=/|./
|
||||
*!;/| ,//|_ *"||/=|
|
||||
-"=|! !//||/|,||=;*
|
||||
-"=|! !//||/ ,||=;*
|
||||
,/*/\==+~\_|\^:\||| |
|
||||
|"_;__|/*\/||\!\+'+\
|
||||
\\\/"""****\_|*//\ \'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
_==+==+;~,_
|
||||
_+"_;,++~__,"+;;_
|
||||
_/:|*"*=","._"+//\ *
|
||||
,||*.,^"* '\`_/=~\;\\\,
|
||||
_\// /| _"+_\/~/|_\;\\_
|
||||
/\| ,,~.___/,*,|'-/^/`/~!
|
||||
_/:|*"*=" "._"+//\ *
|
||||
,||*.,^" _/=~\;\\\,
|
||||
_\// /| _\/~/|_\;\\_
|
||||
/\| ,, _/,*,|'-/^/`/~!
|
||||
||\:/ +/*/|"_/"*|=|=,
|
||||
"\-~| ^\"||;^ |;|"
|
||||
\"" ,\==;=;+~|,|*/\, |*|`
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
_*\*/|,+| ' =^||\
|
||||
' /|/,\|/\ .'\||,
|
||||
|',|\^^_\|_* \+|||
|
||||
|*||| '_;\|`| |^|/|,
|
||||
|*||| '_;\|`| ^|/|,
|
||||
\,||/ |+\/|,*. .`/||
|
||||
\ \||_^ !/*=|~/+,+,,\~||
|
||||
! \*/=_|",|,||;|=__||='
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
/*_/||*"=!_-\_
|
||||
/ ,||/*^^=/!\_~,
|
||||
" ||/;=_ _^,|\^|+,
|
||||
/*";\*"|*, *.'+:+||
|
||||
| |||"^|\;__ |'|*|||
|
||||
` ~*\|**|\\,".,/ `||
|
||||
| ~/_,\~||_/=\_| !||
|
||||
/*";\*"|*, +:+||
|
||||
| |||"^|\;* '|*|||
|
||||
` ~*\ **|\\," / `||
|
||||
| ~/_ ~||_/= | !||
|
||||
! ",|" /|"|~~|+|~,||`
|
||||
|_|||_,|^|_||||__|||
|
||||
" `^/\\|/"****||"/\|
|
||||
|
||||
71
codex-rs/tui/src/frames.rs
Normal file
71
codex-rs/tui/src/frames.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use std::time::Duration;
|
||||
|
||||
// Embed animation frames for each variant at compile time.
|
||||
macro_rules! frames_for {
|
||||
($dir:literal) => {
|
||||
[
|
||||
include_str!(concat!("../frames/", $dir, "/frame_1.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_2.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_3.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_4.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_5.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_6.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_7.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_8.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_9.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_10.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_11.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_12.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_13.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_14.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_15.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_16.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_17.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_18.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_19.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_20.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_21.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_22.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_23.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_24.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_25.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_26.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_27.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_28.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_29.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_30.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_31.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_32.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_33.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_34.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_35.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_36.txt")),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) const FRAMES_DEFAULT: [&str; 36] = frames_for!("default");
|
||||
pub(crate) const FRAMES_CODEX: [&str; 36] = frames_for!("codex");
|
||||
pub(crate) const FRAMES_OPENAI: [&str; 36] = frames_for!("openai");
|
||||
pub(crate) const FRAMES_BLOCKS: [&str; 36] = frames_for!("blocks");
|
||||
pub(crate) const FRAMES_DOTS: [&str; 36] = frames_for!("dots");
|
||||
pub(crate) const FRAMES_HASH: [&str; 36] = frames_for!("hash");
|
||||
pub(crate) const FRAMES_HBARS: [&str; 36] = frames_for!("hbars");
|
||||
pub(crate) const FRAMES_VBARS: [&str; 36] = frames_for!("vbars");
|
||||
pub(crate) const FRAMES_SHAPES: [&str; 36] = frames_for!("shapes");
|
||||
pub(crate) const FRAMES_SLUG: [&str; 36] = frames_for!("slug");
|
||||
|
||||
pub(crate) const ALL_VARIANTS: &[&[&str]] = &[
|
||||
&FRAMES_DEFAULT,
|
||||
&FRAMES_CODEX,
|
||||
&FRAMES_OPENAI,
|
||||
&FRAMES_BLOCKS,
|
||||
&FRAMES_DOTS,
|
||||
&FRAMES_HASH,
|
||||
&FRAMES_HBARS,
|
||||
&FRAMES_VBARS,
|
||||
&FRAMES_SHAPES,
|
||||
&FRAMES_SLUG,
|
||||
];
|
||||
|
||||
pub(crate) const FRAME_TICK_DEFAULT: Duration = Duration::from_millis(80);
|
||||
@@ -42,6 +42,7 @@ pub mod custom_terminal;
|
||||
mod diff_render;
|
||||
mod exec_command;
|
||||
mod file_search;
|
||||
mod frames;
|
||||
mod get_git_diff;
|
||||
mod history_cell;
|
||||
pub mod insert_history;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::frames::ALL_VARIANTS as FRAME_VARIANTS;
|
||||
use crate::frames::FRAME_TICK_DEFAULT;
|
||||
use crate::tui::FrameRequester;
|
||||
use crate::tui::Tui;
|
||||
use crate::tui::TuiEvent;
|
||||
@@ -19,75 +21,7 @@ use ratatui::widgets::Wrap;
|
||||
use std::time::Duration;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
// Embed animation frames for each variant at compile time.
|
||||
macro_rules! frames_for {
|
||||
($dir:literal) => {
|
||||
[
|
||||
include_str!(concat!("../frames/", $dir, "/frame_1.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_2.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_3.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_4.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_5.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_6.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_7.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_8.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_9.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_10.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_11.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_12.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_13.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_14.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_15.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_16.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_17.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_18.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_19.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_20.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_21.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_22.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_23.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_24.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_25.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_26.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_27.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_28.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_29.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_30.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_31.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_32.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_33.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_34.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_35.txt")),
|
||||
include_str!(concat!("../frames/", $dir, "/frame_36.txt")),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const FRAMES_DEFAULT: [&str; 36] = frames_for!("default");
|
||||
const FRAMES_CODEX: [&str; 36] = frames_for!("codex");
|
||||
const FRAMES_OPENAI: [&str; 36] = frames_for!("openai");
|
||||
const FRAMES_BLOCKS: [&str; 36] = frames_for!("blocks");
|
||||
const FRAMES_DOTS: [&str; 36] = frames_for!("dots");
|
||||
const FRAMES_HASH: [&str; 36] = frames_for!("hash");
|
||||
const FRAMES_HBARS: [&str; 36] = frames_for!("hbars");
|
||||
const FRAMES_VBARS: [&str; 36] = frames_for!("vbars");
|
||||
const FRAMES_SHAPES: [&str; 36] = frames_for!("shapes");
|
||||
const FRAMES_SLUG: [&str; 36] = frames_for!("slug");
|
||||
|
||||
const VARIANTS: &[&[&str]] = &[
|
||||
&FRAMES_DEFAULT,
|
||||
&FRAMES_CODEX,
|
||||
&FRAMES_OPENAI,
|
||||
&FRAMES_BLOCKS,
|
||||
&FRAMES_DOTS,
|
||||
&FRAMES_HASH,
|
||||
&FRAMES_HBARS,
|
||||
&FRAMES_VBARS,
|
||||
&FRAMES_SHAPES,
|
||||
&FRAMES_SLUG,
|
||||
];
|
||||
|
||||
const FRAME_TICK: Duration = Duration::from_millis(60);
|
||||
const FRAME_TICK: Duration = FRAME_TICK_DEFAULT;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ModelUpgradeDecision {
|
||||
@@ -156,11 +90,11 @@ impl ModelUpgradePopup {
|
||||
}
|
||||
|
||||
fn frames(&self) -> &'static [&'static str] {
|
||||
VARIANTS[self.variant_idx]
|
||||
FRAME_VARIANTS[self.variant_idx]
|
||||
}
|
||||
|
||||
fn pick_random_variant(&mut self) {
|
||||
let total = VARIANTS.len();
|
||||
let total = FRAME_VARIANTS.len();
|
||||
if total <= 1 {
|
||||
return;
|
||||
}
|
||||
@@ -196,13 +130,11 @@ impl WidgetRef for &ModelUpgradePopup {
|
||||
lines.push("".into());
|
||||
|
||||
lines.push(
|
||||
format!(
|
||||
" Codex is now powered by {SWIFTFOX_MODEL_DISPLAY_NAME}, a new model that is"
|
||||
)
|
||||
.into(),
|
||||
format!(" Codex is now powered by {SWIFTFOX_MODEL_DISPLAY_NAME}, a new model that is")
|
||||
.into(),
|
||||
);
|
||||
lines.push(Line::from(vec![
|
||||
" ".into(),
|
||||
" ".into(),
|
||||
"faster, a better collaborator, ".bold(),
|
||||
"and ".into(),
|
||||
"more steerable.".bold(),
|
||||
@@ -226,6 +158,7 @@ impl WidgetRef for &ModelUpgradePopup {
|
||||
ModelUpgradeOption::TryNewModel,
|
||||
&format!("Yes, switch me to {SWIFTFOX_MODEL_DISPLAY_NAME}"),
|
||||
));
|
||||
lines.push("".into());
|
||||
lines.push(create_option(
|
||||
1,
|
||||
ModelUpgradeOption::KeepCurrent,
|
||||
|
||||
@@ -137,12 +137,12 @@ impl AuthModeWidget {
|
||||
fn render_pick_mode(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut lines: Vec<Line> = vec![
|
||||
Line::from(vec![
|
||||
"> ".into(),
|
||||
"Sign in with ChatGPT to use Codex as part of your paid plan".bold(),
|
||||
" ".into(),
|
||||
"Sign in with ChatGPT to use Codex as part of your paid plan".into(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
" ".into(),
|
||||
"or connect an API key for usage-based billing".bold(),
|
||||
"or connect an API key for usage-based billing".into(),
|
||||
]),
|
||||
"".into(),
|
||||
];
|
||||
@@ -182,6 +182,7 @@ impl AuthModeWidget {
|
||||
"Sign in with ChatGPT",
|
||||
"Usage included with Plus, Pro, and Team plans",
|
||||
));
|
||||
lines.push("".into());
|
||||
lines.extend(create_mode_item(
|
||||
1,
|
||||
AuthMode::ApiKey,
|
||||
@@ -205,7 +206,7 @@ impl AuthModeWidget {
|
||||
}
|
||||
|
||||
fn render_continue_in_browser(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut spans = vec!["> ".into()];
|
||||
let mut spans = vec![" ".into()];
|
||||
// Schedule a follow-up frame to keep the shimmer animation going.
|
||||
self.request_frame
|
||||
.schedule_frame_in(std::time::Duration::from_millis(100));
|
||||
@@ -217,7 +218,8 @@ impl AuthModeWidget {
|
||||
&& !state.auth_url.is_empty()
|
||||
{
|
||||
lines.push(" If the link doesn't open automatically, open the following link to authenticate:".into());
|
||||
lines.push(vec![" ".into(), state.auth_url.as_str().cyan().underlined()].into());
|
||||
lines.push("".into());
|
||||
lines.push(Line::from(state.auth_url.as_str().cyan().underlined()));
|
||||
lines.push("".into());
|
||||
}
|
||||
|
||||
@@ -231,7 +233,7 @@ impl AuthModeWidget {
|
||||
let lines = vec![
|
||||
"✓ Signed in with your ChatGPT account".fg(Color::Green).into(),
|
||||
"".into(),
|
||||
"> Before you start:".into(),
|
||||
" Before you start:".into(),
|
||||
"".into(),
|
||||
" Decide how much autonomy you want to grant Codex".into(),
|
||||
Line::from(vec![
|
||||
|
||||
@@ -24,6 +24,7 @@ use crate::tui::TuiEvent;
|
||||
use color_eyre::eyre::Result;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Instant;
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Step {
|
||||
@@ -74,6 +75,8 @@ impl OnboardingScreen {
|
||||
let codex_home = config.codex_home;
|
||||
let mut steps: Vec<Step> = vec![Step::Welcome(WelcomeWidget {
|
||||
is_logged_in: !matches!(login_status, LoginStatus::NotAuthenticated),
|
||||
request_frame: tui.frame_requester(),
|
||||
start: Instant::now(),
|
||||
})];
|
||||
if show_login_screen {
|
||||
steps.push(Step::Auth(AuthModeWidget {
|
||||
|
||||
@@ -3,23 +3,62 @@ use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Widget;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
use ratatui::widgets::Wrap;
|
||||
|
||||
use crate::frames::FRAME_TICK_DEFAULT;
|
||||
use crate::frames::FRAMES_DEFAULT;
|
||||
use crate::onboarding::onboarding_screen::StepStateProvider;
|
||||
use crate::tui::FrameRequester;
|
||||
|
||||
use super::onboarding_screen::StepState;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
const FRAME_TICK: Duration = FRAME_TICK_DEFAULT;
|
||||
|
||||
pub(crate) struct WelcomeWidget {
|
||||
pub is_logged_in: bool,
|
||||
pub request_frame: FrameRequester,
|
||||
pub start: Instant,
|
||||
}
|
||||
|
||||
impl WidgetRef for &WelcomeWidget {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let line = Line::from(vec![
|
||||
">_ ".into(),
|
||||
"Welcome to Codex, OpenAI's command-line coding agent".bold(),
|
||||
]);
|
||||
line.render(area, buf);
|
||||
let elapsed_ms = self.start.elapsed().as_millis();
|
||||
|
||||
// Align next draw to the next FRAME_TICK boundary to reduce jitter.
|
||||
{
|
||||
let tick_ms = FRAME_TICK.as_millis();
|
||||
let rem_ms = elapsed_ms % tick_ms;
|
||||
let delay_ms = if rem_ms == 0 {
|
||||
tick_ms
|
||||
} else {
|
||||
tick_ms - rem_ms
|
||||
};
|
||||
// Safe cast: delay_ms < tick_ms and FRAME_TICK is small.
|
||||
self.request_frame
|
||||
.schedule_frame_in(Duration::from_millis(delay_ms as u64));
|
||||
}
|
||||
|
||||
let frames = &FRAMES_DEFAULT;
|
||||
let idx = ((elapsed_ms / FRAME_TICK.as_millis()) % frames.len() as u128) as usize;
|
||||
|
||||
let mut lines: Vec<Line> = Vec::with_capacity(frames.len() + 2);
|
||||
lines.extend(frames[idx].lines().map(|l| l.into()));
|
||||
|
||||
lines.push("".into());
|
||||
lines.push(Line::from(vec![
|
||||
" ".into(),
|
||||
"Welcome to ".into(),
|
||||
"Codex".bold(),
|
||||
", OpenAI's command-line coding agent".into(),
|
||||
]));
|
||||
|
||||
Paragraph::new(lines)
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,3 +70,14 @@ impl StepStateProvider for WelcomeWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A number of things break down if FRAME_TICK is zero.
|
||||
#[test]
|
||||
fn frame_tick_must_be_nonzero() {
|
||||
assert!(FRAME_TICK.as_millis() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user