@@ -10,6 +10,7 @@ mod selfUpdate;
1010mod state;
1111mod logger;
1212
13+ use std:: process:: Command ;
1314use std:: sync:: mpsc;
1415use tao:: event:: { Event , WindowEvent } ;
1516use tao:: event_loop:: { ControlFlow , EventLoopBuilder } ;
@@ -89,24 +90,41 @@ const SETUP_HTML: &str = r#"<!DOCTYPE html>
8990 color: #ea4647;
9091 font-size: 12px;
9192 margin-top: 8px;
92- max-width: 400px ;
93- text-align: center ;
93+ max-width: 480px ;
94+ text-align: left ;
9495 display: none;
96+ white-space: pre-wrap;
97+ user-select: all;
98+ overflow-y: auto;
99+ max-height: 180px;
100+ line-height: 1.5;
101+ padding: 0 12px;
102+ word-break: break-all;
95103 }
96- #retry-btn {
104+ .action-btns {
97105 display: none;
98106 margin-top: 12px;
107+ gap: 8px;
108+ justify-content: center;
109+ flex-wrap: wrap;
110+ }
111+ .action-btns button {
99112 border: 1px solid #ea4647;
100113 background: transparent;
101114 color: #ea4647;
102115 border-radius: 6px;
103- padding: 8px 20px ;
104- font-size: 13px ;
116+ padding: 8px 16px ;
117+ font-size: 12px ;
105118 cursor: pointer;
106119 font-weight: 500;
107120 transition: background 0.2s;
108121 }
109- #retry-btn:hover { background: rgba(234,70,71,0.1); }
122+ .action-btns button:hover { background: rgba(234,70,71,0.1); }
123+ .action-btns .btn-secondary {
124+ border-color: #334155;
125+ color: #94a3b8;
126+ }
127+ .action-btns .btn-secondary:hover { background: rgba(148,163,184,0.1); }
110128 #update-banner {
111129 display: none;
112130 position: fixed;
@@ -148,7 +166,11 @@ const SETUP_HTML: &str = r#"<!DOCTYPE html>
148166 <div class="bar-wrap"><div class="bar" id="bar"></div></div>
149167 <div id="status"></div>
150168 <div id="error"></div>
151- <button id="retry-btn" onclick="window.ipc.postMessage('retry')">다시 시도</button>
169+ <div class="action-btns" id="action-btns">
170+ <button onclick="window.ipc.postMessage('retry')">다시 시도</button>
171+ <button class="btn-secondary" onclick="window.ipc.postMessage('open-log')">로그 열기</button>
172+ <button class="btn-secondary" onclick="window.ipc.postMessage('reset')">초기화 후 재시도</button>
173+ </div>
152174 <div id="update-banner">
153175 <div>
154176 <span class="label" id="update-label"></span><br>
@@ -166,11 +188,11 @@ const SETUP_HTML: &str = r#"<!DOCTYPE html>
166188 var el = document.getElementById('error');
167189 el.textContent = msg;
168190 el.style.display = 'block';
169- document.getElementById('retry-btn ').style.display = 'inline-block ';
191+ document.getElementById('action-btns ').style.display = 'flex ';
170192 }
171193 function clearError() {
172194 document.getElementById('error').style.display = 'none';
173- document.getElementById('retry-btn ').style.display = 'none';
195+ document.getElementById('action-btns ').style.display = 'none';
174196 }
175197 function showUpdate(type, ver) {
176198 var label = type === 'launcher' ? '런처' : 'DartLab';
@@ -247,6 +269,32 @@ fn main() {
247269 run_setup ( tx, p, us) ;
248270 } ) ;
249271 }
272+ "open-log" => {
273+ if let Some ( lp) = logger:: log_path ( ) {
274+ let dir = lp. parent ( ) . unwrap_or ( & lp) . to_path_buf ( ) ;
275+ std:: thread:: spawn ( move || {
276+ let _ = Command :: new ( "explorer" )
277+ . arg ( dir)
278+ . spawn ( ) ;
279+ } ) ;
280+ }
281+ }
282+ "reset" => {
283+ let _ = ipc_proxy. send_event ( AppEvent :: Log ( "clearError()" . to_string ( ) ) ) ;
284+ let p = ipc_proxy. clone ( ) ;
285+ let us = update_state_ipc. clone ( ) ;
286+ std:: thread:: spawn ( move || {
287+ let ad = paths:: app_dir ( ) ;
288+ state:: clear_state ( & ad) ;
289+ let venv = paths:: venv_dir ( & ad) ;
290+ if venv. exists ( ) {
291+ std:: fs:: remove_dir_all ( & venv) . ok ( ) ;
292+ }
293+ logger:: log ( "사용자 초기화 — venv + state 삭제 후 콜드 스타트" ) ;
294+ let ( tx, _rx) = mpsc:: channel :: < AppEvent > ( ) ;
295+ run_setup ( tx, p, us) ;
296+ } ) ;
297+ }
250298 "update:dartlab:yes" => {
251299 let state = update_state_ipc. lock ( ) . unwrap ( ) ;
252300 if let Some ( ref ad) = state. app_dir {
@@ -335,6 +383,7 @@ fn main() {
335383 ..
336384 } => {
337385 runner:: stop_server ( ) ;
386+ ollama:: stop_ollama ( ) ;
338387 * control_flow = ControlFlow :: Exit ;
339388 }
340389 _ => { }
@@ -365,7 +414,13 @@ fn run_setup(
365414 } ;
366415
367416 let fail = |msg : & str | {
368- let escaped = msg. replace ( '\'' , "\\ '" ) ;
417+ let mut full = msg. to_string ( ) ;
418+ if let Some ( lp) = logger:: log_path ( ) {
419+ if !full. contains ( "로그 파일:" ) {
420+ full. push_str ( & format ! ( "\n \n 런처 로그: {}" , lp. display( ) ) ) ;
421+ }
422+ }
423+ let escaped = full. replace ( '\\' , "\\ \\ " ) . replace ( '\'' , "\\ '" ) . replace ( '\n' , "\\ n" ) ;
369424 js ( & format ! ( "setError('{escaped}')" ) ) ;
370425 logger:: log ( & format ! ( "ERROR: {msg}" ) ) ;
371426 } ;
@@ -398,7 +453,13 @@ fn run_setup(
398453 } ) ;
399454 }
400455
401- let warm = state:: is_warm ( & app_dir) ;
456+ let mut warm = state:: is_warm ( & app_dir) ;
457+
458+ if warm && !state:: quick_health_check ( & app_dir) {
459+ logger:: log ( "웜 스타트 헬스체크 실패 — 콜드 스타트로 전환" ) ;
460+ state:: clear_state ( & app_dir) ;
461+ warm = false ;
462+ }
402463
403464 if warm {
404465 let gpu = ollama:: gpu_label ( ) ;
@@ -433,9 +494,9 @@ fn run_setup(
433494 }
434495
435496 progress ( 50 , "서버 시작 중..." ) ;
436- if let Err ( e ) = runner:: start_server ( & app_dir) {
437- fail ( & format ! ( "서버 시작 실패: {e}" ) ) ;
438- return ;
497+ match runner:: start_server ( & app_dir) {
498+ Ok ( _ ) => { }
499+ Err ( e ) => { fail ( & format ! ( "서버 시작 실패: {e}" ) ) ; return ; }
439500 }
440501
441502 progress ( 70 , "서버 응답 대기 중..." ) ;
@@ -493,13 +554,13 @@ fn run_setup(
493554 }
494555
495556 progress ( 85 , "서버 시작 중..." ) ;
496- if let Err ( e ) = runner:: start_server ( & app_dir) {
497- fail ( & format ! ( "서버 시작 실패: {e}" ) ) ;
498- return ;
557+ match runner:: start_server ( & app_dir) {
558+ Ok ( _ ) => { }
559+ Err ( e ) => { fail ( & format ! ( "서버 시작 실패: {e}" ) ) ; return ; }
499560 }
500561
501562 progress ( 90 , "서버 응답 대기 중..." ) ;
502- match runner:: wait_for_server ( 30 ) {
563+ match runner:: wait_for_server ( 60 ) {
503564 Ok ( ( ) ) => {
504565 progress ( 100 , "준비 완료!" ) ;
505566 state:: mark_success ( & app_dir) ;
0 commit comments