refactor onboarding screen to a separate "app" (#2524)
this is in preparation for adding more separate "modes" to the tui, in particular, a "transcript mode" to view a full history once #2316 lands. 1. split apart "tui events" from "app events". 2. remove onboarding-related events from AppEvent. 3. move several general drawing tools out of App and into a new Tui class
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use codex_login::CLIENT_ID;
|
||||
use codex_login::ServerOptions;
|
||||
use codex_login::ShutdownHandle;
|
||||
@@ -18,19 +20,19 @@ use ratatui::widgets::WidgetRef;
|
||||
use ratatui::widgets::Wrap;
|
||||
|
||||
use codex_login::AuthMode;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use crate::LoginStatus;
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use crate::onboarding::onboarding_screen::KeyboardHandler;
|
||||
use crate::onboarding::onboarding_screen::StepStateProvider;
|
||||
use crate::shimmer::shimmer_spans;
|
||||
use crate::tui::FrameRequester;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::onboarding_screen::StepState;
|
||||
// no additional imports
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum SignInState {
|
||||
PickMode,
|
||||
ChatGptContinueInBrowser(ContinueInBrowserState),
|
||||
@@ -40,18 +42,17 @@ pub(crate) enum SignInState {
|
||||
EnvVarFound,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone)]
|
||||
/// Used to manage the lifecycle of SpawnedLogin and ensure it gets cleaned up.
|
||||
pub(crate) struct ContinueInBrowserState {
|
||||
auth_url: String,
|
||||
shutdown_handle: Option<ShutdownHandle>,
|
||||
_login_wait_handle: Option<tokio::task::JoinHandle<()>>,
|
||||
shutdown_flag: Option<ShutdownHandle>,
|
||||
}
|
||||
|
||||
impl Drop for ContinueInBrowserState {
|
||||
fn drop(&mut self) {
|
||||
if let Some(flag) = &self.shutdown_handle {
|
||||
flag.shutdown();
|
||||
if let Some(handle) = &self.shutdown_flag {
|
||||
handle.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,20 +70,32 @@ impl KeyboardHandler for AuthModeWidget {
|
||||
self.start_chatgpt_login();
|
||||
}
|
||||
KeyCode::Char('2') => self.verify_api_key(),
|
||||
KeyCode::Enter => match self.sign_in_state {
|
||||
SignInState::PickMode => match self.highlighted_mode {
|
||||
AuthMode::ChatGPT => self.start_chatgpt_login(),
|
||||
AuthMode::ApiKey => self.verify_api_key(),
|
||||
},
|
||||
SignInState::EnvVarMissing => self.sign_in_state = SignInState::PickMode,
|
||||
SignInState::ChatGptSuccessMessage => {
|
||||
self.sign_in_state = SignInState::ChatGptSuccess
|
||||
KeyCode::Enter => {
|
||||
let sign_in_state = { (*self.sign_in_state.read().unwrap()).clone() };
|
||||
match sign_in_state {
|
||||
SignInState::PickMode => match self.highlighted_mode {
|
||||
AuthMode::ChatGPT => {
|
||||
self.start_chatgpt_login();
|
||||
}
|
||||
AuthMode::ApiKey => {
|
||||
self.verify_api_key();
|
||||
}
|
||||
},
|
||||
SignInState::EnvVarMissing => {
|
||||
*self.sign_in_state.write().unwrap() = SignInState::PickMode;
|
||||
}
|
||||
SignInState::ChatGptSuccessMessage => {
|
||||
*self.sign_in_state.write().unwrap() = SignInState::ChatGptSuccess;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
if matches!(self.sign_in_state, SignInState::ChatGptContinueInBrowser(_)) {
|
||||
self.sign_in_state = SignInState::PickMode;
|
||||
tracing::info!("Esc pressed");
|
||||
let sign_in_state = { (*self.sign_in_state.read().unwrap()).clone() };
|
||||
if matches!(sign_in_state, SignInState::ChatGptContinueInBrowser(_)) {
|
||||
*self.sign_in_state.write().unwrap() = SignInState::PickMode;
|
||||
self.request_frame.schedule_frame();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -90,12 +103,12 @@ impl KeyboardHandler for AuthModeWidget {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AuthModeWidget {
|
||||
pub event_tx: AppEventSender,
|
||||
pub request_frame: FrameRequester,
|
||||
pub highlighted_mode: AuthMode,
|
||||
pub error: Option<String>,
|
||||
pub sign_in_state: SignInState,
|
||||
pub sign_in_state: Arc<RwLock<SignInState>>,
|
||||
pub codex_home: PathBuf,
|
||||
pub login_status: LoginStatus,
|
||||
pub preferred_auth_method: AuthMode,
|
||||
@@ -215,14 +228,13 @@ impl AuthModeWidget {
|
||||
fn render_continue_in_browser(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut spans = vec![Span::from("> ")];
|
||||
// Schedule a follow-up frame to keep the shimmer animation going.
|
||||
self.event_tx
|
||||
.send(AppEvent::ScheduleFrameIn(std::time::Duration::from_millis(
|
||||
100,
|
||||
)));
|
||||
self.request_frame
|
||||
.schedule_frame_in(std::time::Duration::from_millis(100));
|
||||
spans.extend(shimmer_spans("Finish signing in via your browser"));
|
||||
let mut lines = vec![Line::from(spans), Line::from("")];
|
||||
|
||||
if let SignInState::ChatGptContinueInBrowser(state) = &self.sign_in_state
|
||||
let sign_in_state = self.sign_in_state.read().unwrap();
|
||||
if let SignInState::ChatGptContinueInBrowser(state) = &*sign_in_state
|
||||
&& !state.auth_url.is_empty()
|
||||
{
|
||||
lines.push(Line::from(" If the link doesn't open automatically, open the following link to authenticate:"));
|
||||
@@ -315,35 +327,45 @@ impl AuthModeWidget {
|
||||
// If we're already authenticated with ChatGPT, don't start a new login –
|
||||
// just proceed to the success message flow.
|
||||
if matches!(self.login_status, LoginStatus::AuthMode(AuthMode::ChatGPT)) {
|
||||
self.sign_in_state = SignInState::ChatGptSuccess;
|
||||
self.event_tx.send(AppEvent::RequestRedraw);
|
||||
*self.sign_in_state.write().unwrap() = SignInState::ChatGptSuccess;
|
||||
self.request_frame.schedule_frame();
|
||||
return;
|
||||
}
|
||||
|
||||
self.error = None;
|
||||
let opts = ServerOptions::new(self.codex_home.clone(), CLIENT_ID.to_string());
|
||||
let server = run_login_server(opts);
|
||||
match server {
|
||||
match run_login_server(opts) {
|
||||
Ok(child) => {
|
||||
let auth_url = child.auth_url.clone();
|
||||
let shutdown_handle = child.cancel_handle();
|
||||
|
||||
let event_tx = self.event_tx.clone();
|
||||
let join_handle = tokio::spawn(async move {
|
||||
spawn_completion_poller(child, event_tx).await;
|
||||
let sign_in_state = self.sign_in_state.clone();
|
||||
let request_frame = self.request_frame.clone();
|
||||
tokio::spawn(async move {
|
||||
let auth_url = child.auth_url.clone();
|
||||
{
|
||||
*sign_in_state.write().unwrap() =
|
||||
SignInState::ChatGptContinueInBrowser(ContinueInBrowserState {
|
||||
auth_url,
|
||||
shutdown_flag: Some(child.cancel_handle()),
|
||||
});
|
||||
}
|
||||
request_frame.schedule_frame();
|
||||
let r = child.block_until_done().await;
|
||||
match r {
|
||||
Ok(()) => {
|
||||
*sign_in_state.write().unwrap() = SignInState::ChatGptSuccessMessage;
|
||||
request_frame.schedule_frame();
|
||||
}
|
||||
_ => {
|
||||
*sign_in_state.write().unwrap() = SignInState::PickMode;
|
||||
// self.error = Some(e.to_string());
|
||||
request_frame.schedule_frame();
|
||||
}
|
||||
}
|
||||
});
|
||||
self.sign_in_state =
|
||||
SignInState::ChatGptContinueInBrowser(ContinueInBrowserState {
|
||||
auth_url,
|
||||
shutdown_handle: Some(shutdown_handle),
|
||||
_login_wait_handle: Some(join_handle),
|
||||
});
|
||||
self.event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
Err(e) => {
|
||||
self.sign_in_state = SignInState::PickMode;
|
||||
*self.sign_in_state.write().unwrap() = SignInState::PickMode;
|
||||
self.error = Some(e.to_string());
|
||||
self.event_tx.send(AppEvent::RequestRedraw);
|
||||
self.request_frame.schedule_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,33 +375,18 @@ impl AuthModeWidget {
|
||||
if matches!(self.login_status, LoginStatus::AuthMode(AuthMode::ApiKey)) {
|
||||
// We already have an API key configured (e.g., from auth.json or env),
|
||||
// so mark this step complete immediately.
|
||||
self.sign_in_state = SignInState::EnvVarFound;
|
||||
*self.sign_in_state.write().unwrap() = SignInState::EnvVarFound;
|
||||
} else {
|
||||
self.sign_in_state = SignInState::EnvVarMissing;
|
||||
*self.sign_in_state.write().unwrap() = SignInState::EnvVarMissing;
|
||||
}
|
||||
|
||||
self.event_tx.send(AppEvent::RequestRedraw);
|
||||
self.request_frame.schedule_frame();
|
||||
}
|
||||
}
|
||||
|
||||
async fn spawn_completion_poller(
|
||||
child: codex_login::LoginServer,
|
||||
event_tx: AppEventSender,
|
||||
) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
if let Ok(()) = child.block_until_done().await {
|
||||
event_tx.send(AppEvent::OnboardingAuthComplete(Ok(())));
|
||||
} else {
|
||||
event_tx.send(AppEvent::OnboardingAuthComplete(Err(
|
||||
"login failed".to_string()
|
||||
)));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl StepStateProvider for AuthModeWidget {
|
||||
fn get_step_state(&self) -> StepState {
|
||||
match &self.sign_in_state {
|
||||
let sign_in_state = self.sign_in_state.read().unwrap();
|
||||
match &*sign_in_state {
|
||||
SignInState::PickMode
|
||||
| SignInState::EnvVarMissing
|
||||
| SignInState::ChatGptContinueInBrowser(_)
|
||||
@@ -391,7 +398,8 @@ impl StepStateProvider for AuthModeWidget {
|
||||
|
||||
impl WidgetRef for AuthModeWidget {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
match self.sign_in_state {
|
||||
let sign_in_state = self.sign_in_state.read().unwrap();
|
||||
match &*sign_in_state {
|
||||
SignInState::PickMode => {
|
||||
self.render_pick_mode(area, buf);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
|
||||
use crate::app::ChatWidgetArgs;
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use crate::onboarding::onboarding_screen::StepStateProvider;
|
||||
|
||||
use super::onboarding_screen::StepState;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// This doesn't render anything explicitly but serves as a signal that we made it to the end and
|
||||
/// we should continue to the chat.
|
||||
pub(crate) struct ContinueToChatWidget {
|
||||
pub event_tx: AppEventSender,
|
||||
pub chat_widget_args: Arc<Mutex<ChatWidgetArgs>>,
|
||||
}
|
||||
|
||||
impl StepStateProvider for ContinueToChatWidget {
|
||||
fn get_step_state(&self) -> StepState {
|
||||
StepState::Complete
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for &ContinueToChatWidget {
|
||||
fn render_ref(&self, _area: Rect, _buf: &mut Buffer) {
|
||||
if let Ok(args) = self.chat_widget_args.lock() {
|
||||
self.event_tx
|
||||
.send(AppEvent::OnboardingComplete(args.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
mod auth;
|
||||
mod continue_to_chat;
|
||||
pub mod onboarding_screen;
|
||||
mod trust_directory;
|
||||
pub use trust_directory::TrustDirectorySelection;
|
||||
mod welcome;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use codex_core::util::is_inside_git_repo;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Widget;
|
||||
@@ -9,25 +11,24 @@ use ratatui::widgets::WidgetRef;
|
||||
use codex_login::AuthMode;
|
||||
|
||||
use crate::LoginStatus;
|
||||
use crate::app::ChatWidgetArgs;
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use crate::onboarding::auth::AuthModeWidget;
|
||||
use crate::onboarding::auth::SignInState;
|
||||
use crate::onboarding::continue_to_chat::ContinueToChatWidget;
|
||||
use crate::onboarding::trust_directory::TrustDirectorySelection;
|
||||
use crate::onboarding::trust_directory::TrustDirectoryWidget;
|
||||
use crate::onboarding::welcome::WelcomeWidget;
|
||||
use crate::tui::FrameRequester;
|
||||
use crate::tui::Tui;
|
||||
use crate::tui::TuiEvent;
|
||||
use color_eyre::eyre::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Step {
|
||||
Welcome(WelcomeWidget),
|
||||
Auth(AuthModeWidget),
|
||||
TrustDirectory(TrustDirectoryWidget),
|
||||
ContinueToChat(ContinueToChatWidget),
|
||||
}
|
||||
|
||||
pub(crate) trait KeyboardHandler {
|
||||
@@ -45,43 +46,42 @@ pub(crate) trait StepStateProvider {
|
||||
}
|
||||
|
||||
pub(crate) struct OnboardingScreen {
|
||||
event_tx: AppEventSender,
|
||||
request_frame: FrameRequester,
|
||||
steps: Vec<Step>,
|
||||
is_done: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct OnboardingScreenArgs {
|
||||
pub event_tx: AppEventSender,
|
||||
pub chat_widget_args: ChatWidgetArgs,
|
||||
pub codex_home: PathBuf,
|
||||
pub cwd: PathBuf,
|
||||
pub show_trust_screen: bool,
|
||||
pub show_login_screen: bool,
|
||||
pub login_status: LoginStatus,
|
||||
pub preferred_auth_method: AuthMode,
|
||||
}
|
||||
|
||||
impl OnboardingScreen {
|
||||
pub(crate) fn new(args: OnboardingScreenArgs) -> Self {
|
||||
pub(crate) fn new(tui: &mut Tui, args: OnboardingScreenArgs) -> Self {
|
||||
let OnboardingScreenArgs {
|
||||
event_tx,
|
||||
chat_widget_args,
|
||||
codex_home,
|
||||
cwd,
|
||||
show_trust_screen,
|
||||
show_login_screen,
|
||||
login_status,
|
||||
preferred_auth_method,
|
||||
} = args;
|
||||
let mut steps: Vec<Step> = vec![Step::Welcome(WelcomeWidget {
|
||||
is_logged_in: !matches!(login_status, LoginStatus::NotAuthenticated),
|
||||
})];
|
||||
if show_login_screen {
|
||||
steps.push(Step::Auth(AuthModeWidget {
|
||||
event_tx: event_tx.clone(),
|
||||
request_frame: tui.frame_requester(),
|
||||
highlighted_mode: AuthMode::ChatGPT,
|
||||
error: None,
|
||||
sign_in_state: SignInState::PickMode,
|
||||
sign_in_state: Arc::new(RwLock::new(SignInState::PickMode)),
|
||||
codex_home: codex_home.clone(),
|
||||
login_status,
|
||||
preferred_auth_method: chat_widget_args.config.preferred_auth_method,
|
||||
preferred_auth_method,
|
||||
}))
|
||||
}
|
||||
let is_git_repo = is_inside_git_repo(&cwd);
|
||||
@@ -91,9 +91,6 @@ impl OnboardingScreen {
|
||||
// Default to not trusting the directory if it's not a git repo.
|
||||
TrustDirectorySelection::DontTrust
|
||||
};
|
||||
// Share ChatWidgetArgs between steps so changes in the TrustDirectory step
|
||||
// are reflected when continuing to chat.
|
||||
let shared_chat_args = Arc::new(Mutex::new(chat_widget_args));
|
||||
if show_trust_screen {
|
||||
steps.push(Step::TrustDirectory(TrustDirectoryWidget {
|
||||
cwd,
|
||||
@@ -102,39 +99,13 @@ impl OnboardingScreen {
|
||||
selection: None,
|
||||
highlighted,
|
||||
error: None,
|
||||
chat_widget_args: shared_chat_args.clone(),
|
||||
}))
|
||||
}
|
||||
steps.push(Step::ContinueToChat(ContinueToChatWidget {
|
||||
event_tx: event_tx.clone(),
|
||||
chat_widget_args: shared_chat_args,
|
||||
}));
|
||||
// TODO: add git warning.
|
||||
Self { event_tx, steps }
|
||||
}
|
||||
|
||||
pub(crate) fn on_auth_complete(&mut self, result: Result<(), String>) {
|
||||
let current_step = self.current_step_mut();
|
||||
if let Some(Step::Auth(state)) = current_step {
|
||||
match result {
|
||||
Ok(()) => {
|
||||
state.sign_in_state = SignInState::ChatGptSuccessMessage;
|
||||
self.event_tx.send(AppEvent::RequestRedraw);
|
||||
let tx1 = self.event_tx.clone();
|
||||
let tx2 = self.event_tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(150));
|
||||
tx1.send(AppEvent::RequestRedraw);
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
tx2.send(AppEvent::RequestRedraw);
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
state.sign_in_state = SignInState::PickMode;
|
||||
state.error = Some(e);
|
||||
self.event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
request_frame: tui.frame_requester(),
|
||||
steps,
|
||||
is_done: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,19 +139,57 @@ impl OnboardingScreen {
|
||||
out
|
||||
}
|
||||
|
||||
fn current_step_mut(&mut self) -> Option<&mut Step> {
|
||||
pub(crate) fn is_done(&self) -> bool {
|
||||
self.is_done
|
||||
|| !self
|
||||
.steps
|
||||
.iter()
|
||||
.any(|step| matches!(step.get_step_state(), StepState::InProgress))
|
||||
}
|
||||
|
||||
pub fn directory_trust_decision(&self) -> Option<TrustDirectorySelection> {
|
||||
self.steps
|
||||
.iter_mut()
|
||||
.find(|step| matches!(step.get_step_state(), StepState::InProgress))
|
||||
.iter()
|
||||
.find_map(|step| {
|
||||
if let Step::TrustDirectory(TrustDirectoryWidget { selection, .. }) = step {
|
||||
Some(*selection)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardHandler for OnboardingScreen {
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
if let Some(active_step) = self.current_steps_mut().into_iter().last() {
|
||||
active_step.handle_key_event(key_event);
|
||||
}
|
||||
self.event_tx.send(AppEvent::RequestRedraw);
|
||||
match key_event {
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('d'),
|
||||
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
}
|
||||
| KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
}
|
||||
| KeyEvent {
|
||||
code: KeyCode::Char('q'),
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
} => {
|
||||
self.is_done = true;
|
||||
}
|
||||
_ => {
|
||||
if let Some(active_step) = self.current_steps_mut().into_iter().last() {
|
||||
active_step.handle_key_event(key_event);
|
||||
}
|
||||
}
|
||||
};
|
||||
self.request_frame.schedule_frame();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +255,7 @@ impl WidgetRef for &OnboardingScreen {
|
||||
impl KeyboardHandler for Step {
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
match self {
|
||||
Step::Welcome(_) | Step::ContinueToChat(_) => (),
|
||||
Step::Welcome(_) => (),
|
||||
Step::Auth(widget) => widget.handle_key_event(key_event),
|
||||
Step::TrustDirectory(widget) => widget.handle_key_event(key_event),
|
||||
}
|
||||
@@ -259,7 +268,6 @@ impl StepStateProvider for Step {
|
||||
Step::Welcome(w) => w.get_step_state(),
|
||||
Step::Auth(w) => w.get_step_state(),
|
||||
Step::TrustDirectory(w) => w.get_step_state(),
|
||||
Step::ContinueToChat(w) => w.get_step_state(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,9 +284,39 @@ impl WidgetRef for Step {
|
||||
Step::TrustDirectory(widget) => {
|
||||
widget.render_ref(area, buf);
|
||||
}
|
||||
Step::ContinueToChat(widget) => {
|
||||
widget.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run_onboarding_app(
|
||||
args: OnboardingScreenArgs,
|
||||
tui: &mut Tui,
|
||||
) -> Result<Option<crate::onboarding::TrustDirectorySelection>> {
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
let mut onboarding_screen = OnboardingScreen::new(tui, args);
|
||||
|
||||
tui.draw(u16::MAX, |frame| {
|
||||
frame.render_widget_ref(&onboarding_screen, frame.area());
|
||||
})?;
|
||||
|
||||
let tui_events = tui.event_stream();
|
||||
tokio::pin!(tui_events);
|
||||
|
||||
while !onboarding_screen.is_done() {
|
||||
if let Some(event) = tui_events.next().await {
|
||||
match event {
|
||||
TuiEvent::Key(key_event) => {
|
||||
onboarding_screen.handle_key_event(key_event);
|
||||
}
|
||||
TuiEvent::Draw => {
|
||||
let _ = tui.draw(u16::MAX, |frame| {
|
||||
frame.render_widget_ref(&onboarding_screen, frame.area());
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(onboarding_screen.directory_trust_decision())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_core::config::set_project_trusted;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -22,9 +20,6 @@ use crate::onboarding::onboarding_screen::KeyboardHandler;
|
||||
use crate::onboarding::onboarding_screen::StepStateProvider;
|
||||
|
||||
use super::onboarding_screen::StepState;
|
||||
use crate::app::ChatWidgetArgs;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub(crate) struct TrustDirectoryWidget {
|
||||
pub codex_home: PathBuf,
|
||||
@@ -33,11 +28,10 @@ pub(crate) struct TrustDirectoryWidget {
|
||||
pub selection: Option<TrustDirectorySelection>,
|
||||
pub highlighted: TrustDirectorySelection,
|
||||
pub error: Option<String>,
|
||||
pub chat_widget_args: Arc<Mutex<ChatWidgetArgs>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum TrustDirectorySelection {
|
||||
pub enum TrustDirectorySelection {
|
||||
Trust,
|
||||
DontTrust,
|
||||
}
|
||||
@@ -156,13 +150,6 @@ impl TrustDirectoryWidget {
|
||||
// self.error = Some("Failed to set project trusted".to_string());
|
||||
}
|
||||
|
||||
// Update the in-memory chat config for this session to a more permissive
|
||||
// policy suitable for a trusted workspace.
|
||||
if let Ok(mut args) = self.chat_widget_args.lock() {
|
||||
args.config.approval_policy = AskForApproval::OnRequest;
|
||||
args.config.sandbox_policy = SandboxPolicy::new_workspace_write_policy();
|
||||
}
|
||||
|
||||
self.selection = Some(TrustDirectorySelection::Trust);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user