codexunlimit 0.1.0-web.13 → 0.1.0-web.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/messages.js DELETED
@@ -1,68 +0,0 @@
1
- export const errors={
2
- connection_busy:'Các tác vụ trước vẫn đang chạy. Chờ chúng hoàn tất rồi gửi tiếp; yêu cầu này chưa được gửi tới mô hình.',
3
- request_clock_error:'Chưa xác thực được thời điểm gửi yêu cầu. Bật đồng bộ ngày giờ trên máy rồi thử lại.',
4
- managed_provider_conflict:'Tên kết nối Codex Unlimit đang được dùng trong cấu hình khác. Liên hệ hỗ trợ để giữ nguyên kết nối hiện có.',
5
- pool_platform_unavailable:'Chức năng đăng nhập pool chưa hỗ trợ hệ điều hành hoặc kiến trúc máy này.',
6
- pool_runtime_missing:'Thiếu công cụ đăng nhập đi kèm. Cài lại gói Codex Unlimit bằng npm, giữ các dependency tùy chọn.',
7
- pool_runtime_unavailable:'Chưa mở được bộ quản lý tài khoản. Khởi động lại Codex Unlimit rồi thử lại.',
8
- pool_provider_rejected:'Chưa thực hiện được yêu cầu với tài khoản. Đăng nhập lại tài khoản này rồi thử lại.',
9
- pool_request_timeout:'Tài khoản chưa phản hồi kịp. Kiểm tra mạng rồi thử lại.',
10
- pool_account_missing:'Tài khoản không còn trong pool. Tải lại danh sách rồi chọn lại.',
11
- pool_account_expired:'Phiên tài khoản cần đăng nhập lại. Chọn Đăng nhập lại trong thẻ tài khoản.',
12
- pool_account_exists:'Tài khoản này đã có trong pool. Chọn tài khoản đã lưu để sử dụng.',
13
- pool_storage_invalid:'Không mở được vùng lưu tài khoản riêng. Kiểm tra thư mục dữ liệu của ứng dụng.',
14
- pool_login_pending:'Đang có một phiên đăng nhập. Hoàn tất hoặc hủy phiên đó trước.',
15
- pool_full:'Pool đã có 10 tài khoản. Gỡ tài khoản không còn dùng trước khi thêm mới.',
16
- pool_login_unavailable:'Chưa mở được trang đăng nhập OpenAI. Kiểm tra mạng hoặc thử cách đăng nhập bằng mã.',
17
- pool_login_failed:'Chưa hoàn tất đăng nhập. Thử lại và xác nhận đúng tài khoản trên trang OpenAI.',
18
- pool_login_expired:'Phiên đăng nhập đã hết thời gian. Chọn Thêm tài khoản để bắt đầu lại.',
19
- pool_login_interrupted:'Lần đăng nhập trước bị gián đoạn. Chọn Đăng nhập lại để tiếp tục.',
20
- pool_quota_unavailable:'Chưa đọc được quota 5 giờ/tuần của tài khoản. Số liệu cũ vẫn được giữ; thử lại hoặc đăng nhập lại nếu phiên đã hết hạn.',
21
- connector_update_required:'Bản đang chạy cần được cập nhật. Mở trang cài đặt, cài gói mới rồi mở lại Codex Unlimit.',
22
- request_integrity_failed:'Yêu cầu chưa được xác thực. Cập nhật Codex Unlimit rồi thử lại.',
23
- license_server_unavailable:'Chưa liên lạc được máy chủ kiểm tra key. Kiểm tra mạng rồi chọn Kiểm tra thời hạn.',
24
- license_service_busy:'Máy chủ kiểm tra key đang bận. Chờ vài giây rồi thử lại; đây không phải thông báo key hết hạn.',
25
- license_lease_expired:'Phiên xác thực tạm thời đã hết hạn. Thử lại thao tác; nếu lỗi lặp lại, đồng bộ ngày giờ rồi khởi động lại ứng dụng.',
26
- license_clock_skew:'Ngày giờ trên máy chưa khớp với máy chủ xác thực. Bật đồng bộ ngày giờ tự động, khởi động lại Codex Unlimit rồi thử lại thao tác.',
27
- license_response_invalid:'Thông tin xác thực nhận được không hợp lệ. Thử lại thao tác; nếu vẫn lỗi, cập nhật Codex Unlimit và liên hệ hỗ trợ.',
28
- license_invalid_or_expired:'Key không hợp lệ, đã hết hạn hoặc bị thu hồi. Kiểm tra lại mã hoặc liên hệ nơi cấp key.',
29
- license_revoked:'Key đã bị thu hồi. Liên hệ nơi cấp key.',
30
- device_limit_reached:'Key đã đủ số máy được phép dùng. Mở trang khách hàng để kiểm tra thiết bị.',
31
- device_revoked:'Thiết bị này đã bị thu hồi quyền sử dụng. Liên hệ nơi cấp key.',
32
- activation_invalid:'Lần kích hoạt này không còn hợp lệ. Nhập lại key để kích hoạt.',
33
- privacy_required:'Đánh dấu đồng ý sử dụng phiên đăng nhập trước khi kết nối.',
34
- request_too_large:'Cuộc trò chuyện đã quá dài. Hãy mở cuộc trò chuyện mới.',
35
- open_from_command:'Phiên mở trang đã hết hiệu lực. Chọn Mở Codex Unlimit trong khay Windows hoặc chạy lại lệnh codexunlimit.',
36
- session_expired:'Phiên làm việc đã hết hạn. Mở lại Codex Unlimit từ khay Windows hoặc lệnh codexunlimit.',
37
- account_required:'Chưa đọc được phiên ChatGPT trên máy này. Mở Codex, đăng nhập ChatGPT rồi chọn Kết nối lại.',
38
- account_expired:'Phiên ChatGPT đã hết hạn. Đăng nhập lại trong Codex rồi chọn Kết nối lại.',
39
- activation_required:'Kích hoạt key trong phần Mã sử dụng trước khi kết nối Codex.',
40
- invalid_activation_key:'Key chưa đúng định dạng. Kiểm tra lại mã bạn nhận được.',
41
- usage_limit:'Bạn đã đến giới hạn sử dụng hiện tại. Vui lòng thử lại sau.',
42
- service_unavailable:'Máy chủ xử lý chưa phản hồi. Thử lại sau hoặc liên hệ hỗ trợ.',
43
- local_service_unavailable:'Ứng dụng trên máy chưa phản hồi. Mở lại từ biểu tượng CU trong khay Windows hoặc chạy codexunlimit.',
44
- operation_failed:'Chưa hoàn tất thao tác. Chọn Kiểm tra kết nối để xem phần cần xử lý.',
45
- models_unavailable:'Chưa tải được danh sách mô hình. Kiểm tra thời hạn và kết nối lại tài khoản.',
46
- invalid_message:'Tin nhắn quá dài hoặc cuộc trò chuyện đã đầy. Hãy mở cuộc trò chuyện mới.',
47
- existing_connector_review_required:'Codex đang dùng kết nối do bản native cũ hoặc TDH Relay quản lý. Mở bản đó, chọn Khôi phục kết nối ban đầu rồi Tắt ứng dụng; sau đó kết nối lại tại đây.',
48
- active_codex_profile_requires_manual_setup:'Codex đang chọn một profile riêng. Bỏ lựa chọn profile trong cấu hình Codex trước khi bật kết nối, hoặc nhờ hỗ trợ kiểm tra profile đó.',
49
- multiline_root_config_requires_manual_setup:'Cấu hình Codex có nội dung nhiều dòng cần được kiểm tra trước khi sửa. Chưa thay đổi file; hãy liên hệ hỗ trợ.',
50
- duplicate_provider_settings_review_required:'Cấu hình Codex có thiết lập kết nối bị lặp. Chưa thay đổi file; hãy nhờ hỗ trợ xử lý mục bị lặp.',
51
- config_backup_missing_or_changed_review_required:'Bản sao cấu hình gốc không còn nguyên vẹn. Chưa ghi đè cấu hình Codex; hãy liên hệ hỗ trợ để khôi phục.',
52
- managed_configuration_changed_review_required:'Kết nối Codex đã được một ứng dụng khác chỉnh sửa. Chưa ghi đè thay đổi đó; hãy nhờ hỗ trợ kiểm tra cấu hình.',
53
- managed_configuration_already_present:'Có cấu hình Codex Unlimit từ lần cài khác. Mở bản đang quản lý kết nối để khôi phục trước.',
54
- config_target_changed_review_required:'Thư mục hoặc cổng kết nối đã thay đổi. Khôi phục kết nối ở bản cũ trước khi kết nối lại.',
55
- new_provider_settings_review_required:'Codex có danh sách mô hình riêng được thêm sau khi kết nối. Hãy nhờ hỗ trợ kiểm tra trước khi khôi phục.',
56
- config_symlink_requires_manual_setup:'File cấu hình Codex đang là liên kết tới vị trí khác. Chưa sửa file để tránh tác động nhầm; hãy liên hệ hỗ trợ.',
57
- config_permission_denied:'Windows chưa cho phép sửa cấu hình Codex. Đóng Codex, kiểm tra quyền ghi hoặc phần mềm bảo vệ thư mục rồi thử lại.',
58
- config_missing:'Không tìm thấy cấu hình cần khôi phục. Chọn Kiểm tra kết nối và liên hệ hỗ trợ nếu vẫn lỗi.',
59
- codex_home_missing:'Chưa xác định được thư mục cấu hình Codex. Mở Codex một lần rồi thử lại.',
60
- saved_root_line_corrupt:'Bản lưu thiết lập kết nối không còn nguyên vẹn. Chưa sửa cấu hình; hãy liên hệ hỗ trợ.',
61
- saved_root_line_moved:'Vị trí thiết lập kết nối đã thay đổi. Chưa sửa cấu hình; hãy liên hệ hỗ trợ.',
62
- startup_unavailable:'Tự khởi động cùng hệ điều hành hiện hỗ trợ Windows.',
63
- startup_update_failed:'Chưa cập nhật được tùy chọn khởi động cùng Windows. Kiểm tra quyền ghi thư mục Startup rồi thử lại.',
64
- tray_unavailable:'Chưa mở được biểu tượng khay Windows. Bạn vẫn có thể mở và tắt ứng dụng tại trang này.',
65
- invalid_input:'Thông tin gửi chưa hợp lệ. Tải lại trang rồi thử lại.',
66
- clock_rollback_detected:'Ngày giờ trên máy đã thay đổi hoặc chưa khớp. Bật đồng bộ ngày giờ tự động, khởi động lại Codex Unlimit rồi thử lại thao tác.',
67
- license_signature_invalid:'Chưa xác minh được thông tin key. Chọn Kiểm tra thời hạn; nếu vẫn lỗi hãy cập nhật ứng dụng.'
68
- };
package/web/pool-ui.js DELETED
@@ -1,53 +0,0 @@
1
- import {errors} from './messages.js';
2
- const escape=value=>String(value??'').replace(/[&<>"']/g,letter=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[letter]));
3
- const date=value=>value?new Date(value).toLocaleString('vi-VN'):'Chưa có thời gian reset';
4
- export const accountName=row=>row?.email||row?.label||'Chưa chọn tài khoản';
5
- export function quotaCards(row){
6
- return '<div class="local-quota-grid">'+[300,10080].map(minutes=>{
7
- const window=row?.windows?.find(item=>item.minutes===minutes),label=minutes===300?'5 giờ':'1 tuần';
8
- return '<div class="local-quota-window '+(window?.used>=90?'quota-warning':'')+'"><span>Quota '+label+'</span><strong>'+(window?escape(window.used.toLocaleString('vi-VN'))+'% <small>đã dùng</small>':'Chưa có số liệu')+'</strong>'+(window?'<progress max="100" value="'+window.used+'" aria-label="Quota '+label+' đã dùng"></progress><p>Reset: '+escape(date(window.reset_at))+'</p><small>'+escape(date(window.observed_at))+'</small>'+(window.stale?'<p class="quota-stale">Số liệu cũ · hãy cập nhật lại</p>':''):'<p>Chọn Cập nhật quota để kiểm tra.</p>')+'</div>';
9
- }).join('')+'</div>';
10
- }
11
- export function quotaSummary(data){
12
- const row=data?.rows?.find(item=>item.active);
13
- return '<section class="surface local-quota-summary"><div class="section-title"><div><span class="eyebrow">Tài khoản đang dùng</span><h2>'+escape(accountName(row))+'</h2></div><a class="text-button" href="#accounts">Tài khoản & quota</a></div>'+quotaCards(row)+'<p class="subtext">Hạn mức tài khoản ChatGPT, khác thời hạn key. Số liệu chưa có không có nghĩa là đã dùng 0%.</p></section>';
14
- }
15
- export function accountOptions(data){return data?.rows?.length?data.rows.map(row=>'<option value="'+escape(row.id)+'" '+(row.active?'selected':'')+' '+(row.state!=='ready'?'disabled':'')+'>'+escape(accountName(row))+(row.state!=='ready'?' · cần đăng nhập':'')+'</option>').join(''):'<option value="codex">Tài khoản trong Codex</option>';}
16
- function confirmRemoval(row,action){
17
- const dialog=document.createElement('dialog');dialog.className='stop-dialog';dialog.setAttribute('aria-labelledby','remove-account-title');
18
- dialog.innerHTML='<h2 id="remove-account-title">Gỡ tài khoản khỏi máy?</h2><p>'+escape(accountName(row))+' sẽ được gỡ khỏi pool. Phiên đăng nhập gốc trong Codex và lịch sử chat được giữ lại.</p><div class="settings-actions"><button data-cancel autofocus>Giữ tài khoản</button><button data-confirm class="primary">Gỡ khỏi pool</button></div><p class="error" role="alert"></p>';
19
- document.body.append(dialog);dialog.addEventListener('close',()=>dialog.remove());dialog.showModal();dialog.querySelector('[data-cancel]').onclick=()=>dialog.close();
20
- dialog.querySelector('[data-confirm]').onclick=async event=>{event.currentTarget.disabled=true;try{await action();dialog.close();}catch(error){dialog.querySelector('.error').textContent=error.message;event.target.disabled=false;}};
21
- }
22
- export function mountAccounts(target,{api,initial,onChange,isBusy=()=>false}){
23
- let data=initial,disposed=false,timer=null,working=false;
24
- const current=()=>!disposed&&target.isConnected;
25
- function showError(error){if(current())target.querySelector('#pool-error').textContent=error.message;}
26
- function update(value,selection=false){data=value;onChange(value,selection);if(current())draw();}
27
- async function run(button,route,input,selection=false){
28
- if(working)return;working=true;const label=button.textContent;button.disabled=true;button.textContent='Đang xử lý…';target.querySelector('#pool-error').textContent='';
29
- try{update(await api(route,input),selection);}catch(error){showError(error);}finally{working=false;if(button.isConnected){button.disabled=false;button.textContent=label;}schedule();}
30
- }
31
- function schedule(){
32
- clearTimeout(timer);if(!current()||data?.pending?.status!=='pending')return;
33
- timer=setTimeout(async()=>{if(working){schedule();return;}try{const next=await api('accounts');if(current())update(next);}catch(error){showError(error);}schedule();},1500);
34
- }
35
- function draw(){
36
- if(!current())return;
37
- const rows=data?.rows??[],pending=data?.pending;
38
- target.innerHTML='<div class="page-title"><span class="eyebrow">Dễ theo dõi, dễ chuyển đổi</span><h1>Tài khoản & quota</h1><p>Đăng nhập riêng từng tài khoản một lần. Chọn tài khoản để dùng cho chat và các yêu cầu mới từ Codex.</p></div><p id="pool-error" class="error" role="alert"></p>'+
39
- (pending?.status==='pending'?'<section class="pool-login-pending" aria-live="polite"><span class="eyebrow">Đang chờ bạn đăng nhập</span><h2>Hoàn tất trên trang OpenAI</h2><p>Mở trang đăng nhập và chọn tài khoản cần thêm. Các tài khoản đã lưu vẫn được giữ nguyên.</p>'+(pending.userCode?'<div class="device-code"><code>'+escape(pending.userCode)+'</code><button data-copy-login-code>Sao chép mã</button></div>':'')+'<div class="settings-actions"><a class="button primary" href="'+escape(pending.url)+'" target="_blank" rel="noreferrer noopener">Mở trang đăng nhập</a><button data-cancel-login>Hủy đăng nhập</button></div><p class="subtext">Nếu trình duyệt đang giữ tài khoản khác, mở liên kết trong cửa sổ riêng tư. Trang này tự cập nhật khi hoàn tất.</p></section>':pending?.status==='failed'||pending?.error?'<p class="connection-warning" role="alert">'+escape(errors[pending.error]??errors.pool_login_failed)+'</p>':pending?.status==='complete'?'<p class="pool-success" role="status">Đã thêm tài khoản. Chọn Dùng tài khoản này để bắt đầu.</p>':'')+
40
- '<div class="pool-accounts">'+rows.map(row=>'<article class="surface pool-account '+(row.active?'pool-active':'')+'" data-account-card="'+escape(row.id)+'"><div class="section-title"><div><h2>'+escape(row.label)+'</h2>'+(row.email?'<p class="pool-email">'+escape(row.email)+'</p>':'')+'</div><span class="tag">'+(row.active?'Đang dùng':row.state==='ready'?'Sẵn sàng':'Cần đăng nhập')+'</span></div>'+(row.plan?'<p class="subtext">Gói ChatGPT: '+escape(row.plan)+'</p>':'')+quotaCards(row)+(row.quotaError?'<p class="error" role="alert">'+escape(errors[row.quotaError]??errors.pool_quota_unavailable)+'</p>':'')+'<div class="settings-actions"><button data-quota-refresh="'+escape(row.id)+'" '+(row.state!=='ready'?'disabled':'')+'>Cập nhật quota</button>'+(!row.active&&row.state==='ready'?'<button class="primary" data-select-account="'+escape(row.id)+'">Dùng tài khoản này</button>':'')+(row.id!=='codex'?'<button class="text-button" data-relogin="'+escape(row.id)+'">Đăng nhập lại</button><button class="text-button" data-remove-account="'+escape(row.id)+'">Gỡ</button>':'<a class="text-button" href="#settings">Kết nối trong cài đặt</a>')+'</div></article>').join('')+'</div>'+
41
- '<section class="surface pool-add"><div><span class="eyebrow">Thêm vào không gian của bạn</span><h2>Đăng nhập tài khoản khác</h2><p>Phiên được lưu riêng trên máy, không ghi đè tài khoản gốc trong Codex. Bạn có thể lưu tối đa '+(data?.maxAccounts??10)+' tài khoản.</p></div><form id="add-account"><label for="account-label">Tên gợi nhớ</label><input id="account-label" maxlength="60" placeholder="Ví dụ: Cá nhân, Công việc"><label for="login-method">Cách đăng nhập</label><select id="login-method"><option value="chatgpt">Mở trang đăng nhập</option><option value="chatgptDeviceCode">Dùng mã đăng nhập</option></select><label class="privacy-choice"><input id="pool-consent" type="checkbox" required> Tôi đồng ý dùng phiên của tài khoản này để xử lý yêu cầu qua Codex Unlimit.</label><button class="primary" type="submit" '+(pending?.status==='pending'?'disabled':'')+'>Thêm tài khoản</button></form></section><p class="pool-footnote">Quota được đọc từ tài khoản và phản hồi yêu cầu thực tế. Cập nhật quota không gửi tin nhắn chat. Khi đổi tài khoản, yêu cầu đang chạy giữ nguyên tài khoản ban đầu.</p>';
42
- target.querySelector('#add-account').onsubmit=event=>{event.preventDefault();run(event.submitter,'accounts/login',{label:target.querySelector('#account-label').value.trim()||'Tài khoản '+rows.length,type:target.querySelector('#login-method').value,accepted:target.querySelector('#pool-consent').checked});};
43
- target.querySelectorAll('[data-quota-refresh]').forEach(button=>button.onclick=()=>run(button,'quota/refresh',{accountId:button.dataset.quotaRefresh}));
44
- target.querySelectorAll('[data-select-account]').forEach(button=>button.onclick=()=>{if(isBusy())return showError(Error('Dừng câu trả lời đang chạy trước khi đổi tài khoản.'));run(button,'accounts/select',{accountId:button.dataset.selectAccount},true);});
45
- target.querySelectorAll('[data-relogin]').forEach(button=>button.onclick=()=>{if(data.pending?.status==='pending')return showError(Error(errors.pool_login_pending));run(button,'accounts/login',{accountId:button.dataset.relogin,type:'chatgpt',accepted:true});});
46
- target.querySelectorAll('[data-remove-account]').forEach(button=>button.onclick=()=>{if(isBusy())return showError(Error('Dừng câu trả lời trước khi gỡ tài khoản.'));const row=rows.find(item=>item.id===button.dataset.removeAccount);confirmRemoval(row,async()=>update(await api('accounts/remove',{accountId:row.id}),row.active));});
47
- const cancel=target.querySelector('[data-cancel-login]');if(cancel)cancel.onclick=()=>run(cancel,'accounts/cancel',{});
48
- const copy=target.querySelector('[data-copy-login-code]');if(copy)copy.onclick=async()=>{try{await navigator.clipboard.writeText(pending.userCode);copy.textContent='Đã sao chép';}catch{showError(Error('Hãy chọn và sao chép mã đang hiển thị.'));}};
49
- schedule();
50
- }
51
- draw();api('accounts').then(value=>{if(current())update(value);}).catch(showError);
52
- return ()=>{disposed=true;clearTimeout(timer);};
53
- }
package/web/stream.js DELETED
@@ -1,25 +0,0 @@
1
- // SSE decoder tolerates arbitrary byte boundaries, CRLF and multi-line data.
2
- export async function readResponseStream(body,onText,{maxText=256000,maxBytes=4*1024*1024}={}){
3
- const reader=body.getReader(),decoder=new TextDecoder();let buffer='',complete=false,text='';
4
- let bytes=0;
5
- const tooLarge=()=>Error('Câu trả lời quá dài. Hãy chia yêu cầu thành các phần nhỏ hơn.');
6
- function frame(value){
7
- const data=value.split('\n').filter(l=>l.startsWith('data:')).map(l=>l.slice(5).trimStart()).join('\n');
8
- if(!data||data==='[DONE]')return;
9
- let event;try{event=JSON.parse(data);}catch{throw Error('Phản hồi bị gián đoạn. Bạn có thể gửi lại tin nhắn.');}
10
- if(event.type==='response.output_text.delta'){if(typeof event.delta!=='string')throw Error('Phản hồi không hợp lệ.');if(text.length+event.delta.length>maxText)throw tooLarge();text+=event.delta;onText(text);}
11
- if(event.type==='response.completed'){
12
- complete=true;if(!text){text=(event.response?.output??[]).flatMap(x=>x.content??[]).filter(x=>x.type==='output_text').map(x=>x.text).join('');if(text.length>maxText)throw tooLarge();onText(text);}
13
- }
14
- if(['error','response.failed','response.incomplete'].includes(event.type))throw Error('Chưa hoàn tất câu trả lời. Vui lòng thử lại.');
15
- }
16
- try{
17
- for(;;){const {value,done}=await reader.read();bytes+=value?.byteLength??0;if(bytes>maxBytes)throw tooLarge();buffer+=decoder.decode(value,{stream:!done}).replace(/\r\n/g,'\n');buffer=buffer.replace(/\r\n/g,'\n');let at;
18
- while(!complete&&(at=buffer.indexOf('\n\n'))>=0){frame(buffer.slice(0,at));buffer=buffer.slice(at+2);}
19
- if(buffer.length>2*1024*1024)throw Error('Phản hồi quá dài. Hãy thử một yêu cầu ngắn hơn.');
20
- if(done||complete)break;
21
- }
22
- if(!complete&&buffer.trim())frame(buffer);if(!complete)throw Error('Kết nối bị gián đoạn trước khi hoàn tất.');
23
- if(!text)throw Error('Chưa nhận được nội dung trả lời. Hãy thử lại.');return text;
24
- }finally{await reader.cancel().catch(()=>{});reader.releaseLock();}
25
- }
package/web/updates.js DELETED
@@ -1,26 +0,0 @@
1
- const escape=value=>String(value??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
2
- export function mountUpdates(element,{api,version,notice}){
3
- let data=null,checking=false,disposed=false,lastCheck=0;
4
- function render(){
5
- if(disposed)return;
6
- const expanded=element.querySelector('details')?.open??false;
7
- const message=checking?'Đang kiểm tra phiên bản…':data?.status==='unavailable'?'Chưa kiểm tra được phiên bản':data?.available?'Có bản mới · '+data.latestVersion:'Chưa có bản cập nhật mới';
8
- element.innerHTML='<details class="update-details"'+(expanded?' open':'')+'><summary><span class="update-current">Phiên bản '+escape(version)+'</span><span class="update-state'+(data?.available?' update-available':'')+'" role="status">'+escape(message)+'</span><span class="update-chevron" aria-hidden="true">⌃</span></summary><div class="update-content"><p>Chạy lần lượt trong PowerShell hoặc Terminal. Sau khi cập nhật, ứng dụng sẽ mở lại.</p>'+(data?.installCommand&&data?.restartCommand?'<pre class="update-command"><code>'+escape(data.installCommand)+'\n'+escape(data.restartCommand)+'</code></pre><button type="button" class="copy-update">Sao chép lệnh cập nhật</button>':'')+'<button type="button" class="check-update" '+(checking?'disabled':'')+'>Kiểm tra lại</button><small>Đóng cuộc trò chuyện đang chạy trước khi khởi động lại.</small></div></details>';
9
- element.querySelector('.check-update').onclick=()=>check();
10
- const copy=element.querySelector('.copy-update');
11
- if(copy)copy.onclick=async()=>{
12
- try{await navigator.clipboard.writeText(data.installCommand+'\n'+data.restartCommand);copy.textContent='Đã sao chép';notice('Đã sao chép 2 lệnh cập nhật. Chạy lần lượt trong PowerShell hoặc Terminal.');}
13
- catch{notice('Chưa sao chép được. Bạn có thể chọn và sao chép 2 dòng lệnh bên trên.');}
14
- };
15
- }
16
- async function check(){
17
- if(checking||disposed)return;
18
- checking=true;render();
19
- try{data=await api('updates');}catch{data={...data,status:'unavailable',available:false};}
20
- lastCheck=Date.now();checking=false;render();
21
- }
22
- const onVisible=()=>{if(document.visibilityState==='visible'&&Date.now()-lastCheck>=3600000)check();};
23
- document.addEventListener('visibilitychange',onVisible);
24
- const timer=setInterval(onVisible,3600000);check();
25
- return ()=>{disposed=true;clearInterval(timer);document.removeEventListener('visibilitychange',onVisible);};
26
- }
package/windows.mjs DELETED
@@ -1,53 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import crypto from 'node:crypto';
4
- import {spawn} from 'node:child_process';
5
- import {fileURLToPath} from 'node:url';
6
- import {VERSION} from './product.mjs';
7
-
8
- export function createWindowsIntegration(state,{entry=fileURLToPath(new URL('./cli.mjs',import.meta.url)),startupDirectory,platform=process.platform}={}){
9
- const supported=platform==='win32',identifier=crypto.createHash('sha256').update(state.home.toLowerCase()).digest('hex').slice(0,12);
10
- const startupFolder=startupDirectory??path.join(process.env.APPDATA??path.join(state.home,'AppData'),'Microsoft/Windows/Start Menu/Programs/Startup');
11
- const shortcut=path.join(startupFolder,'Codex Unlimit-'+identifier+'.lnk'),configPath=path.join(state.home,'windows-startup.json');
12
- const script=fileURLToPath(new URL('./windows.ps1',import.meta.url));
13
- const executable=path.join(process.env.SystemRoot??'C:/Windows','System32/WindowsPowerShell/v1.0/powershell.exe');
14
- const config={node:process.execPath,entry,home:state.home,version:VERSION,parentPid:process.pid,shortcut,configPath,script,icon:fileURLToPath(new URL('./web/assets/favicon.ico',import.meta.url))};
15
- let tray=null,trayReady=false,pendingTray=null;
16
- function invoke(mode,input){
17
- return new Promise((resolve,reject)=>{
18
- const child=spawn(executable,['-NoProfile','-NonInteractive','-WindowStyle','Hidden','-ExecutionPolicy','Bypass','-File',script,'-Mode',mode],{windowsHide:true,stdio:['pipe','pipe','pipe']});
19
- let output='';const timeout=setTimeout(()=>{child.kill();reject(Error('startup_update_failed'));},10000);
20
- child.stdout.on('data',chunk=>{output=(output+chunk).slice(-4096);});child.stderr.resume();
21
- child.once('error',()=>{clearTimeout(timeout);reject(Error('startup_update_failed'));});
22
- child.once('exit',code=>{clearTimeout(timeout);if(code!==0)reject(Error('startup_update_failed'));else resolve(output);});
23
- child.stdin.on('error',()=>{});child.stdin.end(JSON.stringify(input));
24
- });
25
- }
26
- async function setStartup(enabled){
27
- if(!supported)throw Error('startup_unavailable');
28
- if(enabled){
29
- const temporary=configPath+'.tmp-'+process.pid;
30
- fs.writeFileSync(temporary,JSON.stringify(config),{mode:0o600});
31
- fs.renameSync(temporary,configPath);
32
- }
33
- await invoke('Configure',{...config,enabled});
34
- if(fs.existsSync(shortcut)!==enabled)throw Error('startup_update_failed');
35
- }
36
- function showTray(){
37
- if(!supported)return Promise.reject(Error('tray_unavailable'));
38
- if(trayReady&&tray?.exitCode===null)return Promise.resolve();
39
- if(pendingTray)return pendingTray;
40
- pendingTray=new Promise((resolve,reject)=>{
41
- const child=spawn(executable,['-NoProfile','-NonInteractive','-STA','-WindowStyle','Hidden','-ExecutionPolicy','Bypass','-File',script,'-Mode','Tray'],{windowsHide:true,stdio:['pipe','pipe','pipe']});
42
- tray=child;let output='',settled=false;
43
- const timeout=setTimeout(()=>{child.kill();finish(Error('tray_unavailable'));},15000);
44
- function finish(error){if(settled)return;settled=true;clearTimeout(timeout);error?reject(error):resolve();}
45
- child.stdout.on('data',chunk=>{output=(output+chunk).slice(-1024);if(output.includes('CU_TRAY_READY')){trayReady=true;finish();}});
46
- child.stderr.resume();child.once('error',()=>finish(Error('tray_unavailable')));
47
- child.once('exit',()=>{if(tray===child){trayReady=false;tray=null;}finish(Error('tray_unavailable'));});
48
- child.stdin.on('error',()=>{});child.stdin.end(JSON.stringify(config));child.unref();
49
- }).finally(()=>{pendingTray=null;});
50
- return pendingTray;
51
- }
52
- return {status:()=>({supported,startup:supported&&fs.existsSync(shortcut),tray:trayReady}),setStartup,showTray};
53
- }