@atmem/architect 0.2.0-alpha.4.1 → 0.2.1
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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +21 -21
- package/cli/runtime.mjs +3 -1
- package/dist/assets/{index-qtCM2D7O.js → index-D4CZ_n8f.js} +19166 -19094
- package/dist/assets/index-DuveVTIx.css +1 -0
- package/dist/assets/{index.es-C6C0sneB.js → index.es-Fcqg-orY.js} +1 -1
- package/dist/assets/{visual-export-DjbjgQbA.js → visual-export-4PDDTAP7.js} +3 -3
- package/dist/index.html +2 -2
- package/dist/licenses/atmem-APACHE-2.0.txt +202 -0
- package/dist/licenses/memory-dependency-notices.txt +149 -2241
- package/package.json +3 -3
- package/public/licenses/atmem-APACHE-2.0.txt +202 -0
- package/public/licenses/memory-dependency-notices.txt +149 -2241
- package/server/app.py +1 -1
- package/server/codex_app_server.py +5 -5
- package/server/codex_worker_policy.py +27 -4
- package/server/engineering_artifacts.py +7 -1
- package/server/engineering_handoff.py +9 -0
- package/server/engineering_routes.py +16 -2
- package/server/local_providers.py +7 -3
- package/server/memory-requirements.lock +6 -6
- package/server/memory-requirements.txt +2 -2
- package/server/memory_context.py +58 -0
- package/server/memory_routes.py +67 -8
- package/server/memory_service.py +40 -5
- package/server/memory_worker.py +76 -11
- package/server/orca_handoff.py +90 -0
- package/server/pricing.py +161 -1
- package/server/prompts.py +13 -0
- package/server/request_uploads.py +65 -0
- package/server/terraform_routes.py +47 -1
- package/server/windows_snapshot.py +119 -0
- package/src/ArchitectureWorkPanel.jsx +0 -7
- package/src/ArtifactChecks.jsx +2 -2
- package/src/ChatMessage.jsx +12 -2
- package/src/EngagementViews.jsx +18 -16
- package/src/EngineeringPanel.jsx +35 -28
- package/src/FileTree.jsx +7 -0
- package/src/FinalChecklist.jsx +23 -0
- package/src/HelpNote.jsx +3 -0
- package/src/ImplementationDelivery.jsx +56 -17
- package/src/JourneyViews.jsx +20 -17
- package/src/LocationChoices.jsx +12 -0
- package/src/MemoryPanel.jsx +43 -31
- package/src/OpenAIVoicePanel.jsx +15 -14
- package/src/OutputsView.jsx +28 -16
- package/src/ProjectChecks.jsx +58 -0
- package/src/StarPrompt.jsx +26 -0
- package/src/WorkbenchDialogs.jsx +1 -1
- package/src/conversation.mjs +1 -0
- package/src/delivery.mjs +1 -1
- package/src/engineering.css +10 -0
- package/src/estimate.mjs +5 -3
- package/src/final-package.mjs +34 -0
- package/src/journey-workspace.css +39 -0
- package/src/journey.mjs +12 -2
- package/src/large-request.mjs +25 -0
- package/src/main.jsx +129 -98
- package/src/output-bundle.mjs +18 -4
- package/src/pricing.mjs +59 -3
- package/src/project-file.mjs +1 -1
- package/src/useChatScroll.jsx +5 -4
- package/src/useEngineeringStatus.mjs +17 -0
- package/src/validation-receipt.mjs +1 -1
- package/dist/assets/index-B97MkVMs.css +0 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import{unzipSync,zipSync,strToU8,strFromU8}from'fflate';
|
|
2
|
+
const safe=name=>name&&!name.startsWith('/')&&!name.includes('\\')&&!name.split('/').some(p=>p==='..'||p==='.')&&!name.includes('\0');
|
|
3
|
+
function attributes(bytes){
|
|
4
|
+
const view=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength);let end=bytes.length-22;
|
|
5
|
+
while(end>=Math.max(0,bytes.length-65557)&&view.getUint32(end,true)!==0x06054b50)end--;
|
|
6
|
+
if(end<0||end+22+view.getUint16(end+20,true)!==bytes.length||view.getUint16(end+4,true)||view.getUint16(end+6,true))throw Error('Unsupported package directory.');
|
|
7
|
+
const result=new Map(),count=view.getUint16(end+10,true);let offset=view.getUint32(end+16,true);
|
|
8
|
+
for(let i=0;i<count;i++){
|
|
9
|
+
if(offset+46>end||view.getUint32(offset,true)!==0x02014b50)throw Error('Invalid package directory.');
|
|
10
|
+
const length=view.getUint16(offset+28,true),extra=view.getUint16(offset+30,true),comment=view.getUint16(offset+32,true);
|
|
11
|
+
if(offset+46+length+extra+comment>end)throw Error('Invalid package entry.');
|
|
12
|
+
const name=strFromU8(bytes.subarray(offset+46,offset+46+length));if(!safe(name)||result.has(name))throw Error('Unsafe or duplicate package entry.');
|
|
13
|
+
result.set(name,{os:view.getUint16(offset+4,true)>>>8,attrs:view.getUint32(offset+38,true)});offset+=46+length+extra+comment;
|
|
14
|
+
}
|
|
15
|
+
if(offset!==end)throw Error('Package directory does not match its receipt.');return result;
|
|
16
|
+
}
|
|
17
|
+
export async function completeProjectZip(verified,design){
|
|
18
|
+
if(!design?.files)throw Error('Wait for the design documents to finish preparing.');
|
|
19
|
+
const bytes=new Uint8Array(await verified.arrayBuffer()),attrs=attributes(bytes);let expanded=0;
|
|
20
|
+
const original=unzipSync(bytes,{filter:entry=>{expanded+=entry.originalSize;if(expanded>150000000)throw Error('Package exceeds its receiving limit.');return true;}});
|
|
21
|
+
const output=Object.fromEntries(Object.entries(original).map(([name,data])=>[name,[data,attrs.get(name)]]));
|
|
22
|
+
const documents={};for(const[name,value]of Object.entries(design.files)){
|
|
23
|
+
if(!safe(name))throw Error('Invalid design document path.');
|
|
24
|
+
const target='design-documents/'+name;if(output[target])throw Error('Design documents conflict with captured files.');
|
|
25
|
+
const content=typeof value==='string'?strToU8(value):value;expanded+=content.byteLength;if(expanded>150000000)throw Error('Combined package exceeds its receiving limit.');
|
|
26
|
+
output[target]=[content,{os:3,attrs:0o100644<<16}];documents[target]=content;
|
|
27
|
+
}
|
|
28
|
+
const hash=async data=>[...new Uint8Array(await crypto.subtle.digest('SHA-256',data))].map(b=>b.toString(16).padStart(2,'0')).join('');
|
|
29
|
+
const manifest={version:1,designRevision:design.manifest.designRevision,sourceArtifactPreserved:'evidence/original-implementation.zip',documents:await Promise.all(Object.entries(documents).map(async([path,data])=>({path,bytes:data.byteLength,sha256:await hash(data)})))};
|
|
30
|
+
output['package-contents.json']=strToU8(JSON.stringify(manifest,null,2));
|
|
31
|
+
const readme=strFromU8(original['START_HERE.md']||new Uint8Array());
|
|
32
|
+
output['START_HERE.md']=strToU8('# Open this project\n\nUnzip this package, then open this folder as a project in Orca or your editor. Read this file and the implementation README before running commands.\n\n- implementation/: captured application, infrastructure and tests.\n- design-documents/: requirements, Spec Kit, diagram, cost assumptions and generated design references.\n- evidence/: captured source ZIP and recorded checks.\n\nDesign references are not proof that changed requirements were implemented.\n\n'+readme);
|
|
33
|
+
return new Blob([zipSync(output)],{type:'application/zip'});
|
|
34
|
+
}
|
|
@@ -70,3 +70,42 @@
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
.chat-width-toggle{display:none}@media(min-width:901px){.chat-width-toggle{display:inline-flex;margin-left:auto;padding:5px 8px}}
|
|
73
|
+
|
|
74
|
+
.review-costs,.review-generation{padding:20px;border:1px solid var(--line);border-radius:16px;background:var(--paper);margin:16px 0}
|
|
75
|
+
.review-costs>.doc{padding:0}.review-costs h2,.review-generation h2{font-size:1.25rem;margin-top:0}
|
|
76
|
+
.review-finish{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:20px 0;border-top:1px solid var(--line)}
|
|
77
|
+
.review-finish p{margin:5px 0;color:var(--muted);font-size:.9rem}.review-finish>.btn{flex-shrink:0}
|
|
78
|
+
.chat-working-status{display:flex;align-items:center;gap:10px;padding:12px 16px;border-top:1px solid var(--line);background:var(--paper);font-size:.9rem}
|
|
79
|
+
.chat-working-status .brain-activity{flex-shrink:0}.generation-status{display:flex;gap:8px;align-items:center}
|
|
80
|
+
.engineering-panel .generation-conversation{max-height:none;overflow:visible}.engineering-panel .generation-conversation[open]>.engineering-conversation{max-height:300px;overflow:auto}
|
|
81
|
+
.review-generation .engineering-panel textarea{min-height:72px}.review-generation .engineering-actions{display:block}
|
|
82
|
+
@media(max-width:650px){.review-finish{align-items:stretch;flex-direction:column}.review-finish>.btn{width:100%}}
|
|
83
|
+
.chat-jump-latest{position:absolute;right:24px;bottom:230px;z-index:40;border:1px solid var(--line);border-radius:50%;background:var(--paper);color:var(--ink);box-shadow:0 3px 12px #0002;width:40px;height:40px;font-size:24px;cursor:pointer}
|
|
84
|
+
.project-checks{padding:20px;border:1px solid var(--line);border-radius:16px;background:var(--paper);margin:16px 0}.section-action{display:flex;align-items:center;gap:8px}.section-action h3{margin:0}.section-action>.btn{margin-left:auto}
|
|
85
|
+
.check-rows,.final-checklist ul{padding:0;list-style:none}.check-rows li,.final-checklist li{display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid var(--line);font-size:.9rem}.check-rows strong,.final-checklist strong{margin-left:auto;font-size:.85rem}.check-complete{color:#217143}.check-pending{color:#946000}.final-checklist{margin:20px 0}.final-checklist li>span:first-child{width:24px;height:24px;display:grid;place-items:center;border:1px solid currentColor;border-radius:50%;flex-shrink:0}
|
|
86
|
+
[data-theme=dark] .check-complete{color:#80d6a2}[data-theme=dark] .check-pending{color:#f0c575}
|
|
87
|
+
.project-checks pre{max-height:300px;overflow:auto;white-space:pre-wrap}.delivery-state{font-weight:600}
|
|
88
|
+
.message-list>.msg{border-top:1px solid var(--line);padding-top:14px;margin-top:14px}.message-meta{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:.75rem;color:var(--muted);margin-bottom:6px}.message-meta time{font-variant-numeric:tabular-nums}
|
|
89
|
+
.memory-modal{width:min(1280px,96vw)!important;max-width:1280px!important;height:min(88vh,1000px);overflow:auto}.memory-panel{width:100%}
|
|
90
|
+
.file-tree{min-width:0;overflow:auto;border-right:1px solid var(--line);padding:8px 0;max-height:650px;background:var(--paper)}.file-tree ul{list-style:none;padding:0;margin:0}.file-tree button{display:flex;align-items:center;gap:5px;width:100%;min-height:30px;text-align:left;background:transparent;color:var(--ink);border:0;border-radius:0;font:inherit;font-size:.8rem;cursor:pointer;white-space:nowrap}.file-tree button:hover{background:var(--accent-soft)}.file-tree button.selected{background:var(--accent-soft);box-shadow:inset 2px 0 var(--accent)}.file-tree button>svg{flex-shrink:0}.tree-indent{width:13px;flex-shrink:0}.file-tree button>span:last-child{overflow:hidden;text-overflow:ellipsis}
|
|
91
|
+
.concise-answer{min-width:0}.message-details{display:inline-flex;align-items:center;min-height:32px;padding:2px 0;text-decoration:underline;text-underline-offset:3px;cursor:pointer;font-size:.85rem}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
.final-checklist li{padding:0}.final-checklist li>button{display:flex;align-items:center;gap:12px;width:100%;padding:12px 4px;border:0;border-radius:6px;background:transparent;color:var(--ink);font:inherit;text-align:left;cursor:pointer}.final-checklist li>button:hover,.final-checklist li>button:focus-visible{background:var(--accent-soft);outline:2px solid var(--ink);outline-offset:-2px}.final-checklist button>span:first-child{width:24px;height:24px;display:grid;place-items:center;border:1px solid currentColor;border-radius:50%;flex-shrink:0}.checklist-arrow{font-size:1.15em}
|
|
95
|
+
|
|
96
|
+
.inline-note{display:block;max-width:100%;margin:6px 0 10px;line-height:1.5;color:var(--muted);overflow-wrap:anywhere}
|
|
97
|
+
|
|
98
|
+
.cost-size-selector .btn{display:inline-flex;align-items:center;gap:6px}.cost-size-selector .btn[aria-pressed="true"]{font-weight:600}.cost-selection-status{font-size:.9rem;color:var(--muted)}
|
|
99
|
+
|
|
100
|
+
.cost-heading{display:flex;align-items:center;justify-content:space-between;gap:12px}.cost-heading h2{margin:0}.cost-heading>.btn{flex-shrink:0}
|
|
101
|
+
|
|
102
|
+
.memory-heading{display:flex;align-items:center;justify-content:space-between;gap:12px}.memory-heading h2,.memory-heading h3{margin:0}.memory-profile-bar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid var(--line);margin-bottom:12px}.memory-profile-bar label{display:flex;align-items:center;gap:8px}.memory-profile-bar input{max-width:190px}.memory-profile-bar code{font-size:.72rem;overflow-wrap:anywhere}.memory-state{margin-left:auto}.memory-toolbar{display:flex;gap:10px;align-items:center;margin:16px 0 10px}.memory-toolbar input{flex:1;min-width:100px}.memory-toolbar strong{white-space:nowrap;font-size:.85rem}.memory-table-wrap{max-height:50vh;overflow:auto;border:1px solid var(--line);border-radius:10px}.memory-table{width:100%;border-collapse:collapse;font-size:.8rem;table-layout:fixed}.memory-table th{position:sticky;top:0;background:var(--paper);z-index:1;font-weight:600;text-align:left}.memory-table td,.memory-table th{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top;overflow-wrap:anywhere}.memory-table th:first-child{width:20%}.memory-table th:nth-child(2){width:35%}.memory-table th:last-child{width:60px}.memory-table td:nth-child(5){font-variant-numeric:tabular-nums;font-size:.73rem}.memory-content{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;border:0;padding:0;background:none;color:var(--ink);font:inherit;text-align:left;cursor:pointer}.memory-table .link{padding:0;font:inherit;text-align:left;overflow-wrap:anywhere}.memory-badge{font-size:.72rem;border-radius:20px;padding:3px 6px;background:var(--board)}.memory-badge.active{color:#21633a;background:#e8f4eb}.memory-pagination{display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:12px 0}.memory-pagination>span{margin-right:auto;font-size:.8rem}.memory-preference-form{display:flex;gap:10px;flex-wrap:wrap;padding:12px 0}.memory-preference-form input{flex:1;min-width:140px}.memory-record-detail{padding:16px;border:1px solid var(--line);border-radius:10px;margin:12px 0}.memory-record-detail dl{display:grid;grid-template-columns:140px 1fr;gap:8px;font-size:.8rem}.memory-record-detail dd{margin:0;overflow-wrap:anywhere}.memory-record-detail p{white-space:pre-wrap}.memory-feedback{display:flex;align-items:center;gap:6px;font-size:.8rem;margin:8px 0}.memory-empty{padding:32px;text-align:center;color:var(--muted)}.memory-table-wrap[aria-busy=true]{opacity:.65}.memory-preview{padding:16px;border:1px solid var(--line);border-radius:10px}.memory-preview .btn{margin-right:8px}@media(max-width:700px){.memory-table{min-width:800px}.memory-profile-bar code{font-size:.65rem}.memory-record-detail dl{grid-template-columns:1fr}.memory-toolbar{flex-wrap:wrap}}
|
|
103
|
+
|
|
104
|
+
.memory-modal{height:auto!important;max-height:88vh!important}.memory-profile-bar input,.memory-toolbar input,.memory-toolbar select,.memory-preference-form input,.memory-preference-form select{font:inherit;font-size:.85rem;min-height:36px;box-sizing:border-box;padding:7px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink)}.memory-table th:last-child{width:84px;white-space:nowrap}.memory-table td:last-child{white-space:nowrap}.memory-table th:nth-child(4){width:90px}.memory-table th:nth-child(5){width:130px}.memory-table th:nth-child(3){width:84px}.memory-toolbar>strong{color:var(--muted);font-weight:500}.memory-pagination .btn{min-height:32px}.memory-record-detail .memory-table{table-layout:auto}.memory-table .memory-content:hover{text-decoration:underline;text-underline-offset:3px}
|
|
105
|
+
|
|
106
|
+
.location-region-form{display:flex;align-items:flex-end;gap:12px}.location-region-form label{display:flex;flex-direction:column;gap:6px}.location-region-form input{font:inherit;padding:9px 12px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink)}
|
|
107
|
+
|
|
108
|
+
.final-checklist li>button:not(:disabled),.final-checklist li>button:not(:disabled) *{cursor:pointer}.final-checklist[aria-busy="true"] li>button,.final-checklist[aria-busy="true"] li>button *{cursor:wait}.final-checklist li>button:disabled{opacity:1}.checklist-label{display:flex;flex-direction:column;gap:4px;min-width:0}.checklist-label small{font-size:.75rem;color:var(--muted);font-weight:400;overflow-wrap:anywhere}.checklist-loading{font-size:.85rem;color:var(--muted)}
|
|
109
|
+
.star-prompt{width:min(460px,calc(100vw - 32px));max-width:460px;border:1px solid var(--line);border-radius:16px;padding:22px;background:var(--paper);color:var(--ink);box-shadow:0 18px 60px #0003}.star-prompt::backdrop{background:#0005}.star-prompt header,.star-prompt h2,.star-prompt-actions{display:flex;align-items:center;gap:10px}.star-prompt header{justify-content:space-between}.star-prompt h2{font-size:1.2rem}.star-prompt h2>svg{color:var(--accent);fill:var(--accent-soft)}.star-prompt p{margin:18px 0;line-height:1.5}.star-prompt-actions>a{flex:1;text-decoration:none}.star-prompt-actions>button{min-width:90px}
|
|
110
|
+
|
|
111
|
+
.check-run-action{display:inline-flex;margin-left:12px;flex-shrink:0}.check-run-action>button:disabled{pointer-events:none}.check-run-action:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
|
package/src/journey.mjs
CHANGED
|
@@ -20,7 +20,7 @@ export function completedStages(p){
|
|
|
20
20
|
return {board:p.nodes.some(n=>n.data.kind!=='note'),scope:saved.scope===keys.scope,
|
|
21
21
|
cloud:cloudMapping(p).ready&&saved.architecture===keys.architecture,
|
|
22
22
|
review:saved.review===keys.architecture&&saved.estimate===keys.estimate,
|
|
23
|
-
delivery:
|
|
23
|
+
delivery:saved.delivery===keys.delivery}; // Delivery means the exact package was downloaded, not deployed.
|
|
24
24
|
}
|
|
25
25
|
export function journeyAction(guidance,section='board'){
|
|
26
26
|
if(section===journeySectionId(guidance.id)&&guidance.checkpoint&§ion!=='board')return {kind:'continue',label:guidance.continueLabel};
|
|
@@ -42,7 +42,7 @@ const snapshot=value=>{const text=JSON.stringify(value);let a=2166136261,b=5381;
|
|
|
42
42
|
export function journeyKeys(p){
|
|
43
43
|
const scope=snapshot([p.brief?.application||p.requirements||'',(p.needs||[]).filter(r=>r.source==='user').map(r=>[r.id,r.statement,r.measure]).sort()]);
|
|
44
44
|
const architecture=snapshot([scope,p.nodes.filter(n=>n.data.kind!=='note').map(n=>[n.id,n.data.label,n.data.service,n.data.provider,n.data.config,n.data.description]).sort(),p.edges.map(e=>[e.source,e.target,e.label]).sort(),choice(p,'platform.cloud'),choice(p,'cloud.region'),choice(p,'data.country')]);
|
|
45
|
-
return {scope,architecture,estimate:snapshot([architecture,p.
|
|
45
|
+
const estimate=snapshot([architecture,p.estimate||{}]);return {scope,architecture,estimate,delivery:snapshot([architecture,estimate,p.revision])};
|
|
46
46
|
}
|
|
47
47
|
export function cloudMapping(p){
|
|
48
48
|
const provider=choice(p,'platform.cloud');
|
|
@@ -85,3 +85,13 @@ export function advanceJourney(p,checkpoint){
|
|
|
85
85
|
const key=checkpoint==='review'?'architecture':checkpoint;
|
|
86
86
|
return {...p,extensions:{...p.extensions,journey:{...p.extensions?.journey,[checkpoint]:state.keys[key]}}};
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
// Explicit completion actions acknowledge a working draft; navigation never does.
|
|
90
|
+
export function acknowledgeStage(p,stage){
|
|
91
|
+
const keys=journeyKeys(p),previous=p.extensions?.journey||{},saved={...previous};
|
|
92
|
+
if(stage==='scope'&&(p.needs?.length||p.brief?.application?.trim()))saved.scope=keys.scope;
|
|
93
|
+
if(stage==='cloud'&&cloudMapping(p).ready)saved.architecture=keys.architecture;
|
|
94
|
+
if(stage==='review')saved.review=keys.architecture;
|
|
95
|
+
if(stage==='delivery')saved.delivery=keys.delivery;
|
|
96
|
+
return {...p,extensions:{...p.extensions,journey:saved}};
|
|
97
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Preserve the entire JSON payload; chunk transport changes no project content.
|
|
2
|
+
export async function postLargeJson(url,payload,{token,signal,onProgress,fetcher=fetch}={}){
|
|
3
|
+
const data=new TextEncoder().encode(JSON.stringify(payload));
|
|
4
|
+
if(data.length>64*1024*1024)throw Error('Project exceeds the 64 MiB validation upload limit.');
|
|
5
|
+
const post=async(path,value,requestSignal=signal)=>{
|
|
6
|
+
const response=await fetcher(path,{method:'POST',headers:{'Content-Type':'application/json',Authorization:`Bearer ${token}`},body:JSON.stringify(value),signal:requestSignal});
|
|
7
|
+
const result=await response.json();if(!response.ok)throw Error(result.detail||'Validation request could not start.');return result;
|
|
8
|
+
};
|
|
9
|
+
if(data.length<=192000)return post(url,payload);
|
|
10
|
+
const sha256=[...new Uint8Array(await crypto.subtle.digest('SHA-256',data))].map(x=>x.toString(16).padStart(2,'0')).join('');
|
|
11
|
+
const {uploadId,chunkBytes}=await post('/api/terraform/uploads/start',{bytes:data.length,sha256});
|
|
12
|
+
try{
|
|
13
|
+
if(chunkBytes!==131072)throw Error('The upload service returned an unsupported chunk size.');
|
|
14
|
+
for(let offset=0,index=0;offset<data.length;offset+=chunkBytes,index++){
|
|
15
|
+
const chunk=data.subarray(offset,offset+chunkBytes);let text='';
|
|
16
|
+
for(let i=0;i<chunk.length;i+=8192)text+=String.fromCharCode(...chunk.subarray(i,i+8192));
|
|
17
|
+
await post('/api/terraform/uploads/chunk',{uploadId,index,data:btoa(text)});
|
|
18
|
+
onProgress?.(Math.round(Math.min(offset+chunkBytes,data.length)/data.length*100));
|
|
19
|
+
}
|
|
20
|
+
onProgress?.(null);
|
|
21
|
+
return await post(url+'-upload',{uploadId});
|
|
22
|
+
}finally{
|
|
23
|
+
await post('/api/terraform/uploads/cancel',{uploadId},AbortSignal.timeout(5000)).catch(()=>{});
|
|
24
|
+
}
|
|
25
|
+
}
|