codexunlimit 0.1.0-web.12 → 0.1.0-web.15

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/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
- }