@atmem/architect 0.2.1 → 0.2.3
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/README.md +9 -1
- package/dist/assets/{index-D4CZ_n8f.js → index-Bn0-zM3N.js} +1419 -1424
- package/dist/assets/index-DABXOHnQ.css +1 -0
- package/dist/assets/{index.es-Fcqg-orY.js → index.es-d0Mtn3LG.js} +1 -1
- package/dist/assets/{visual-export-4PDDTAP7.js → visual-export-DPn_mbuF.js} +3 -3
- package/dist/index.html +2 -2
- package/dist/reference-example.zip +0 -0
- package/package.json +3 -1
- package/server/atbots_engineering_backend.py +214 -0
- package/server/codex_engineering_backend.py +3 -0
- package/server/engineering_handoff.py +16 -1
- package/server/engineering_routes.py +5 -3
- package/server/engineering_sessions.py +2 -0
- package/server/local_validation.py +5 -2
- package/server/terraform_routes.py +2 -1
- package/src/EngineeringPanel.jsx +2 -2
- package/src/ImplementationDelivery.jsx +7 -3
- package/src/OpenAIVoicePanel.jsx +3 -2
- package/src/OutputsView.jsx +5 -2
- package/src/ReadyExample.jsx +16 -0
- package/src/chat-composer.css +62 -0
- package/src/engineering-client.mjs +7 -0
- package/src/hosted-workspace.mjs +52 -0
- package/src/journey-workspace.css +5 -2
- package/src/local-agent.mjs +1 -1
- package/src/main.jsx +108 -39
- package/src/onboarding.css +24 -0
- package/src/ready-example.mjs +5 -0
- package/src/reference-example/project.json +1440 -0
- package/src/reference-example/receipt.json +24 -0
- package/src/visual-export.mjs +1 -1
- package/dist/assets/index-DuveVTIx.css +0 -1
package/src/main.jsx
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {HOSTED,connectHostedWorkspace,hostedInitial,hostedCall,hostedRevision,noteHostedChange} from './hosted-workspace.mjs';
|
|
2
|
+
import {readyExample,isReadyExample} from './ready-example.mjs';
|
|
3
|
+
import {ReadyExampleDownload} from './ReadyExample.jsx';
|
|
1
4
|
import {StarPrompt,STAR_URL} from './StarPrompt.jsx';
|
|
2
5
|
import {prefillAcceptedSettings} from './engineering-defaults.mjs';
|
|
3
6
|
import {LocationChoices} from './LocationChoices.jsx';
|
|
@@ -101,6 +104,7 @@ import {
|
|
|
101
104
|
Rocket,
|
|
102
105
|
BrainCircuit,
|
|
103
106
|
Waypoints,
|
|
107
|
+
Volume2,
|
|
104
108
|
Mic,
|
|
105
109
|
Square,
|
|
106
110
|
Eraser,
|
|
@@ -167,6 +171,8 @@ import { zipSync, strToU8 } from "fflate";
|
|
|
167
171
|
// Each browser starts fresh; earlier projects are opened deliberately (⋯ → Open project file).
|
|
168
172
|
const STORAGE = "atforma-workspace-v3";
|
|
169
173
|
// A new project starts empty: the user describes what they want first.
|
|
174
|
+
import './onboarding.css';
|
|
175
|
+
import './chat-composer.css';
|
|
170
176
|
const emptyProject = () => ({ id: crypto.randomUUID(), version: 1, revision: 1, name: "New project", provider: "tools", requirements: "", nodes: [], edges: [] });
|
|
171
177
|
const ALL_CATALOG = [...CATALOG, ...UPSTREAM_CATALOG];
|
|
172
178
|
const KIND_ICON = {
|
|
@@ -253,6 +259,7 @@ function Mark({ size = 20 }) {
|
|
|
253
259
|
// Which architect the user chose; absent on first visit, so we ask before anything else.
|
|
254
260
|
const CONNECTION = "atforma-connection-v1";
|
|
255
261
|
function readConnection() {
|
|
262
|
+
if(HOSTED)return hostedInitial().connection;
|
|
256
263
|
try {
|
|
257
264
|
const c = JSON.parse(localStorage.getItem(CONNECTION) || "null");
|
|
258
265
|
// Older builds stored "offline"; the architect now always needs a real model.
|
|
@@ -269,6 +276,7 @@ function saveConnection(c) {
|
|
|
269
276
|
} catch {}
|
|
270
277
|
}
|
|
271
278
|
function readSaved() {
|
|
279
|
+
if(HOSTED)return withDerived(fromProjectFile(hostedInitial().file).project);
|
|
272
280
|
try {
|
|
273
281
|
const raw = localStorage.getItem(STORAGE);
|
|
274
282
|
const p = validateImport(raw ? JSON.parse(raw) : emptyProject());
|
|
@@ -646,6 +654,7 @@ function harnessSignals(was, now) {
|
|
|
646
654
|
}
|
|
647
655
|
const welcome = (project) => ({
|
|
648
656
|
role: "assistant",
|
|
657
|
+
introduction: true,
|
|
649
658
|
text: project.nodes.length || project.brief?.application?.trim()
|
|
650
659
|
? `Good to see you again — let's keep going.\n\n${opening(project)}`
|
|
651
660
|
: `What would you like to build?`,
|
|
@@ -660,13 +669,14 @@ function savedTheme() {
|
|
|
660
669
|
return "light";
|
|
661
670
|
}
|
|
662
671
|
}
|
|
663
|
-
|
|
672
|
+
|
|
664
673
|
|
|
665
674
|
function readChatView(id){try{const value=JSON.parse(localStorage.getItem('atmem-chat-view:'+id)||'null');return value&&Number.isSafeInteger(value.start)&&value.start>=0&&Array.isArray(value.turnIds)?value:{start:0,turnIds:[]};}catch{return {start:0,turnIds:[]};}}
|
|
666
675
|
const pricingSignature=p=>JSON.stringify([p.id,p.estimate?.projectSize||'small',p.estimate?.usage,p.facts,p.decisions?.filter(d=>d.status==='Accepted'&&['cloud.region','data.country'].includes(d.subject)),[...storageNodes(p),...lambdaNodes(p),...googleNodes(p)].map(n=>[n.id,n.data.provider,n.data.service,n.data.config])]);
|
|
667
676
|
function App() {
|
|
668
677
|
const [starOffer,setStarOffer]=useState(0);
|
|
669
|
-
const [
|
|
678
|
+
const [introOpen,setIntroOpen]=useState(false),[voiceOpenRequest,setVoiceOpenRequest]=useState(0);
|
|
679
|
+
const [project, setProject] = useState(()=>!HOSTED&&new URL(location.href).searchParams.get('example')==='1'&&!projectIdFromUrl(location.href)?readyExample():readSaved());
|
|
670
680
|
const projectRef = useRef(project);
|
|
671
681
|
projectRef.current = project;
|
|
672
682
|
const [initialCanvasView] = useState(()=>readCanvasView(project));
|
|
@@ -698,7 +708,7 @@ function App() {
|
|
|
698
708
|
[history, setHistory] = useState([]),
|
|
699
709
|
[future, setFuture] = useState([]),
|
|
700
710
|
[toast, setToast] = useState(""),
|
|
701
|
-
[modal, setModal] = useState(
|
|
711
|
+
[modal, setModal] = useState(null),
|
|
702
712
|
[menu, setMenu] = useState(false),
|
|
703
713
|
[palette, setPalette] = useState(false),
|
|
704
714
|
[issuesOpen, setIssuesOpen] = useState(false),
|
|
@@ -782,6 +792,12 @@ function App() {
|
|
|
782
792
|
const consultationAttempts = useRef(new Set());
|
|
783
793
|
const agentAbort = useRef(null);
|
|
784
794
|
const messageInput = useRef(null);
|
|
795
|
+
const hasIntroduction=messages[0]?.role==='assistant'&&(messages[0].introduction===true||messages[0].text==='What would you like to build?'||messages[0].text.startsWith('Good to see you again'));
|
|
796
|
+
const idleChat=hasIntroduction&&!introOpen&&architectureChatEntries(project,messages).length===1;
|
|
797
|
+
useEffect(()=>{
|
|
798
|
+
const resize=()=>{const field=messageInput.current;if(field){field.style.height='32px';field.style.height=Math.min(120,Math.max(32,field.scrollHeight))+'px';}};
|
|
799
|
+
resize();window.addEventListener('resize',resize);return()=>window.removeEventListener('resize',resize);
|
|
800
|
+
},[input,chatWidth,chatOpen]);
|
|
785
801
|
const intakeRef=useRef(restoreQueuedMessages(messages,project.id));
|
|
786
802
|
const [intake,setIntake]=useState(intakeRef.current),[intakePaused,setIntakePausedState]=useState(()=>restoreIntakePaused(messages)||intakeRef.current.length>0);
|
|
787
803
|
const [intakeIssue,setIntakeIssue]=useState(null);
|
|
@@ -905,16 +921,19 @@ function App() {
|
|
|
905
921
|
return () => { clearTimeout(zoom); clearTimeout(clear); };
|
|
906
922
|
}, [attention, fitView]);
|
|
907
923
|
|
|
924
|
+
function showConnections(){if(HOSTED){void hostedCall('account').catch(error=>notify(error.message));return;}setModal('connections');}
|
|
908
925
|
const refreshSession = useCallback(async () => {
|
|
909
926
|
try {
|
|
910
927
|
const next = await discoverLocalAgent();
|
|
911
928
|
setSession(next);
|
|
929
|
+
if(HOSTED&&next.defaultConnection)setConnection(current=>current?.model===next.defaultConnection.model&¤t?.effort===next.defaultConnection.effort?current:next.defaultConnection);
|
|
912
930
|
return next;
|
|
913
931
|
} catch {
|
|
914
932
|
setSession(null);
|
|
915
933
|
return null;
|
|
916
934
|
}
|
|
917
935
|
}, []);
|
|
936
|
+
useEffect(()=>{if(HOSTED&&session?.token)void hostedCall('ready').catch(()=>{});},[session?.token]);
|
|
918
937
|
useEffect(() => {
|
|
919
938
|
refreshSession();
|
|
920
939
|
const timer=setInterval(refreshSession,30000);
|
|
@@ -1204,7 +1223,11 @@ function App() {
|
|
|
1204
1223
|
const raw = JSON.parse(content);
|
|
1205
1224
|
if (raw.format || raw.version === 1) {
|
|
1206
1225
|
const { project: opened, migrated, from } = parseProjectFile(content);
|
|
1207
|
-
|
|
1226
|
+
if(HOSTED){
|
|
1227
|
+
await saveEngineeringProject(projectRef.current.id);
|
|
1228
|
+
const imported=await hostedCall('import-project',{file:toProjectFile(opened)});
|
|
1229
|
+
loadDesign(fromProjectFile(imported.file).project,{own:true});
|
|
1230
|
+
}else loadDesign(fileImportAsCopy.current?copyProject(opened,crypto.randomUUID()):opened, { own: true });
|
|
1208
1231
|
notify(fileImportAsCopy.current?"New project created from the saved design. The original is preserved.":migrated ? `Project opened and upgraded from ${from}.` : "Project opened.");
|
|
1209
1232
|
} else {
|
|
1210
1233
|
loadDesign(importUpstreamProject(raw));
|
|
@@ -1637,13 +1660,15 @@ function App() {
|
|
|
1637
1660
|
updateEngagement({ ...skipped, lastAsked: next });
|
|
1638
1661
|
setMessages((m) => [...m, { role: "assistant", text: next ? `No problem, we'll come back to it.\n\n${next.question}\n${next.beginner}` : "That's everything I need for now." }]);
|
|
1639
1662
|
}
|
|
1640
|
-
|
|
1663
|
+
useEffect(()=>{if(HOSTED)void noteHostedChange(project.id).catch(()=>{});},[project,messages]);
|
|
1664
|
+
// Autosave through the selected local or hosted session service.
|
|
1641
1665
|
const binned = useRef(new Set());
|
|
1642
1666
|
useEffect(() => {
|
|
1643
|
-
if (!session || binned.current.has(project.id) || (!project.nodes.length && !project.brief?.application?.trim() && messages.length < 2)) return;
|
|
1667
|
+
if (!session || binned.current.has(project.id) || (!HOSTED && !project.nodes.length && !project.brief?.application?.trim() && messages.length < 2)) return;
|
|
1668
|
+
const bridgeVersion=HOSTED?hostedRevision(project.id):undefined;
|
|
1644
1669
|
const t = setTimeout(async () => {
|
|
1645
1670
|
try {
|
|
1646
|
-
const saved=await projectStore(session, "save", { id: project.id, file: toProjectFile(withConversation(project, messages)) });
|
|
1671
|
+
const saved=await projectStore(session, "save", { id: project.id, file: toProjectFile(withConversation(project, messages)),...(HOSTED?{bridgeVersion}:{}) });
|
|
1647
1672
|
if(saved.memory)setMemorySync({projectId:project.id,...saved.memory});
|
|
1648
1673
|
} catch (error) { setMemorySync(current=>({...((current?.projectId===project.id)?current:{}),projectId:project.id,status:'sync_failed'}));notify(`Project conversation could not be saved: ${error.message}`); }
|
|
1649
1674
|
}, 800);
|
|
@@ -1667,6 +1692,8 @@ function App() {
|
|
|
1667
1692
|
}
|
|
1668
1693
|
}
|
|
1669
1694
|
function switchTo(next) {
|
|
1695
|
+
setIntroOpen(false);setVoiceOpenRequest(0);
|
|
1696
|
+
const address=new URL(window.location.href);if(address.searchParams.get('project')!==next.id){address.searchParams.delete('project');address.searchParams.delete('example');window.history.replaceState(null,'',address);}
|
|
1670
1697
|
chooseWorkspace('canvas');setBuildRequest(null);
|
|
1671
1698
|
next = withDerived(next);
|
|
1672
1699
|
const leaving = projectRef.current;
|
|
@@ -1697,6 +1724,7 @@ function App() {
|
|
|
1697
1724
|
}
|
|
1698
1725
|
async function openSaved(id) {
|
|
1699
1726
|
try {
|
|
1727
|
+
if(HOSTED)await saveEngineeringProject(projectRef.current.id);
|
|
1700
1728
|
const { project: opened } = fromProjectFile(await projectStore(session, "get", { id }));
|
|
1701
1729
|
switchTo(opened);
|
|
1702
1730
|
setModal(null);
|
|
@@ -1725,14 +1753,30 @@ function App() {
|
|
|
1725
1753
|
})();
|
|
1726
1754
|
return () => { active = false; };
|
|
1727
1755
|
}, [session?.token]);
|
|
1728
|
-
function newProject() {
|
|
1729
|
-
|
|
1756
|
+
async function newProject() {
|
|
1757
|
+
if(HOSTED){
|
|
1758
|
+
if(agentBusy||busy||intake.length)return;
|
|
1759
|
+
try{await saveEngineeringProject(projectRef.current.id);const {file}=await hostedCall('new-project');switchTo(fromProjectFile(file).project);setModal(null);}catch(error){notify(error.message);}
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
if(!busy&&!agentBusy&&!intake.length)setModal("start-over");
|
|
1763
|
+
}
|
|
1764
|
+
function createOwnProject(){
|
|
1765
|
+
if(HOSTED){void newProject();return;}
|
|
1766
|
+
switchTo(validateImport(emptyProject()));setModal(connection?null:'connections');
|
|
1767
|
+
requestAnimationFrame(()=>document.querySelector('input[aria-label="Project name"]')?.focus());
|
|
1768
|
+
}
|
|
1769
|
+
async function openReadyExample(){
|
|
1770
|
+
if(busy||agentBusy||intake.length)return;
|
|
1771
|
+
const example=readyExample();example.id=crypto.randomUUID();
|
|
1772
|
+
if(HOSTED){try{await saveEngineeringProject(projectRef.current.id);const {file}=await hostedCall('import-project',{file:toProjectFile(example)});switchTo(fromProjectFile(file).project);setModal(null);}catch(error){notify(error.message);}return;}
|
|
1773
|
+
switchTo(example);setModal(null);
|
|
1730
1774
|
}
|
|
1731
1775
|
async function binProject(id) {
|
|
1732
1776
|
try {
|
|
1733
1777
|
binned.current.add(id);
|
|
1734
1778
|
await projectStore(session, "trash", { id });
|
|
1735
|
-
if
|
|
1779
|
+
if(id===projectRef.current.id){if(HOSTED){const {file}=await hostedCall('new-project');switchTo(fromProjectFile(file).project);}else switchTo(validateImport(emptyProject()));}
|
|
1736
1780
|
await refreshProjects();
|
|
1737
1781
|
notify("Moved to the bin. Restore it from Projects any time.");
|
|
1738
1782
|
} catch (e) {
|
|
@@ -1840,7 +1884,7 @@ function App() {
|
|
|
1840
1884
|
/>
|
|
1841
1885
|
);
|
|
1842
1886
|
return (
|
|
1843
|
-
<div style={{"--chat-panel-width":`${chatWidth}px`}} className={`app ${chatOpen&&!buildView ? "" : "chat-closed"} ${buildView?"build-workspace":""}`}>
|
|
1887
|
+
<div style={{"--chat-panel-width":`${chatWidth}px`}} className={`app ${idleChat?"idle-chat":""} ${chatOpen&&!buildView ? "" : "chat-closed"} ${buildView?"build-workspace":""}`}>
|
|
1844
1888
|
<header className="bar">
|
|
1845
1889
|
<button aria-label="About AtMem AI Architect" className="brand" onClick={() => setModal("about")} title="About AtMem AI Architect">
|
|
1846
1890
|
<Mark />
|
|
@@ -1848,7 +1892,7 @@ function App() {
|
|
|
1848
1892
|
</button>
|
|
1849
1893
|
<nav className="crumbs" aria-label="Level"><span className="workspace-label">PROJECT</span>
|
|
1850
1894
|
<button className="projects-btn" aria-label="Projects" title="Your projects" onClick={() => (refreshProjects(), setModal("projects"))}>
|
|
1851
|
-
<FolderOpen size={22}
|
|
1895
|
+
<FolderOpen size={22} /><span>Projects</span>
|
|
1852
1896
|
</button>
|
|
1853
1897
|
<label className="project-rename"><input
|
|
1854
1898
|
className="project-name"
|
|
@@ -1859,6 +1903,8 @@ function App() {
|
|
|
1859
1903
|
/><PencilRuler size={14} aria-hidden="true"/></label>
|
|
1860
1904
|
</nav>
|
|
1861
1905
|
<div className="bar-actions">
|
|
1906
|
+
{HOSTED&&<><button className="btn" onClick={newProject} disabled={busy||agentBusy}><Plus size={16}/>New project</button><button className="btn" onClick={()=>hostedCall('account')}>Account</button></>}
|
|
1907
|
+
{isReadyExample(project)&&<><ReadyExampleDownload compact/>{!HOSTED&&<button className="btn" onClick={newProject} disabled={busy||agentBusy}>Create new project</button>}</>}
|
|
1862
1908
|
{storageError && <span className="warn-text">Not saved</span>}
|
|
1863
1909
|
<button
|
|
1864
1910
|
className="icon"
|
|
@@ -1883,6 +1929,9 @@ function App() {
|
|
|
1883
1929
|
{menu && (
|
|
1884
1930
|
<div className="menu" role="menu" onClick={() => setMenu(false)}>
|
|
1885
1931
|
<button role="menuitem" onClick={() => (refreshProjects(), setModal("projects"))} disabled={busy}><FolderOpen size={16}/>Projects</button>
|
|
1932
|
+
<a role="menuitem" href="https://atmem.ai" target="_blank" rel="noopener noreferrer">atmem.ai ↗</a>
|
|
1933
|
+
<a role="menuitem" href="mailto:hello@atmem.ai">Contact us: hello@atmem.ai</a>
|
|
1934
|
+
<button role="menuitem" onClick={openReadyExample} disabled={busy||agentBusy}><LayoutGrid size={16}/>Open complete AI example</button>
|
|
1886
1935
|
<button role="menuitem" onClick={newProject} disabled={busy}><Plus size={16}/>New project</button>
|
|
1887
1936
|
<hr/><button role="menuitem" onClick={() => setPalette(true)} disabled={busy}><Plus size={16}/>Add a block</button>
|
|
1888
1937
|
<button role="menuitem" onClick={() => setModal("templates")} disabled={busy}><LayoutTemplate size={16}/>Templates</button>
|
|
@@ -1891,7 +1940,7 @@ function App() {
|
|
|
1891
1940
|
<hr/><button role="menuitem" onClick={() => setModal("memory")} disabled={!session}><BrainActivity size={18} active={false}/>AtMem memory</button>
|
|
1892
1941
|
<button role="menuitem" onClick={() => setModal("start-over")} disabled={busy || agentBusy}><RotateCcw size={16}/>Start over</button>
|
|
1893
1942
|
<hr />
|
|
1894
|
-
|
|
1943
|
+
{!HOSTED&&<button role="menuitem" onClick={() => showConnections()}><Waypoints size={16}/>AI providers</button>}
|
|
1895
1944
|
<button role="menuitem" onClick={() => setModal("licenses")}><ShieldCheck size={16}/>Licences</button>
|
|
1896
1945
|
<a role="menuitem" href={STAR_URL} target="_blank" rel="noopener noreferrer">Star on GitHub ↗</a>
|
|
1897
1946
|
</div>
|
|
@@ -1920,7 +1969,7 @@ function App() {
|
|
|
1920
1969
|
{level!=="block"&&<aside className="board-components component-navigator" data-expanded={treeOpen} aria-label="Board components"><button className="btn" aria-label="Toggle components and layers" aria-expanded={treeOpen} onClick={()=>setTreeOpen(!treeOpen)}><LayoutGrid size={16}/> Components & layers</button>
|
|
1921
1970
|
{treeOpen&&<nav className="board-component-tree" aria-label="Component tree"><strong>{project.name}</strong><div className="tree-views"><button disabled={!blockReviews.length} onClick={()=>{reviewBlock((blockReviews.find(r=>r.status!=='reviewed')||blockReviews[0]).id);}}><CheckCheck size={16}/> Review blocks · {blockReviews.filter(r=>r.status==='reviewed').length}/{blockReviews.length}</button></div>{[...new Map(project.nodes.filter(n=>n.data.kind!=='note').map(n=>[layerOf(n).id,layerOf(n)])).values()].map(layer=><details open key={layer.id}><summary>{layer.label}</summary>{project.nodes.filter(n=>n.data.kind!=='note'&&layerOf(n).id===layer.id).map(n=><button key={n.id} aria-current={selected===n.id?'true':undefined} onClick={()=>{setMode('design');setDesignView('board');setFocus(null);setLevel('flow');setSelected(n.id);setTimeout(()=>fitView({nodes:[{id:n.id}],padding:1,maxZoom:1.2,duration:350}),100);}}><Database size={14}/><span>{n.data.label}<small>{n.data.service||n.data.provider}</small><small className={`tree-review-state ${blockReviews.find(r=>r.id===n.id)?.status}`}>{blockReviews.find(r=>r.id===n.id)?.statusLabel}</small></span></button>)}</details>)}</nav>}
|
|
1922
1971
|
{treeOpen&&<button className="btn" disabled={busy} onClick={()=>setPalette(true)}><Plus size={16}/> Add component</button>}</aside>}
|
|
1923
|
-
{!hasDesign&&!palette&&!dismissedStarts.includes(project.id)&&<section className="board-start" aria-label="Start your project"><button type="button" className="icon board-start-close" aria-label="Dismiss welcome card" title="Dismiss welcome card" onClick={dismissStart}><X size={16}/></button><h2>What would you like to create?</h2><div className="row"><button className="btn primary" onClick={()=>{dismissStart();setChatOpen(true);document.querySelector('.composer textarea, .chat textarea')?.focus();}}>Start chatting</button><button className="btn" onClick={()=>{setChatOpen(true);requirementsInput.current.click();}}>Upload requirements or diagram</button><button className="btn" onClick={()=>setModal("project-file")}>Open project</button></div><InlineNote label="Supported starting files">Word, PDF, text, Markdown, draw.io, images and Architect project JSON.</InlineNote></section>}
|
|
1972
|
+
{!hasDesign&&!palette&&!dismissedStarts.includes(project.id)&&<section className="board-start" aria-label="Start your project"><button type="button" className="icon board-start-close" aria-label="Dismiss welcome card" title="Dismiss welcome card" onClick={dismissStart}><X size={16}/></button><h2>What would you like to create?</h2><div className="row"><button className="btn primary" onClick={openReadyExample}>Explore complete AI example</button><button className="btn primary" onClick={()=>{dismissStart();setChatOpen(true);document.querySelector('.composer textarea, .chat textarea')?.focus();}}>Start chatting</button><button className="btn" onClick={()=>{setChatOpen(true);requirementsInput.current.click();}}>Upload requirements or diagram</button><button className="btn" onClick={()=>setModal("project-file")}>Open project</button></div><InlineNote label="Supported starting files">Word, PDF, text, Markdown, draw.io, images and Architect project JSON.</InlineNote></section>}
|
|
1924
1973
|
<ReactFlow
|
|
1925
1974
|
nodes={canvasNodes}
|
|
1926
1975
|
edges={canvasEdges}
|
|
@@ -1986,8 +2035,8 @@ function App() {
|
|
|
1986
2035
|
>
|
|
1987
2036
|
<LayoutGrid size={17} />
|
|
1988
2037
|
</button>
|
|
1989
|
-
<button className="
|
|
1990
|
-
<
|
|
2038
|
+
<button className="btn reset-board" title="Reset board and context" aria-label="Reset board and context" disabled={busy||agentBusy||intake.length>0} onClick={newProject}>
|
|
2039
|
+
<Trash2 size={17} /> Reset board
|
|
1991
2040
|
</button>
|
|
1992
2041
|
<button className="icon" title="Undo board change" aria-label="Undo" disabled={!history.length || busy || agentBusy} onClick={undo}>
|
|
1993
2042
|
<Undo2 size={17} />
|
|
@@ -2033,8 +2082,8 @@ function App() {
|
|
|
2033
2082
|
<Scan size={16}/> Select all
|
|
2034
2083
|
</button>
|
|
2035
2084
|
<div className="ctx-divider" role="separator"/>
|
|
2036
|
-
<button role="menuitem" className="danger" disabled={!project.nodes.length} onClick={() => (
|
|
2037
|
-
<
|
|
2085
|
+
<button role="menuitem" className="danger" disabled={!project.nodes.length} onClick={() => (newProject(), setCtx(null))}>
|
|
2086
|
+
<Trash2 size={16}/> Reset board and context
|
|
2038
2087
|
</button>
|
|
2039
2088
|
</>
|
|
2040
2089
|
)}
|
|
@@ -2173,7 +2222,7 @@ function App() {
|
|
|
2173
2222
|
<section className="review-costs" aria-label="Project costs">
|
|
2174
2223
|
{["review","estimate"].includes(mode) && <EstimateView project={project} result={costs} onChange={updateEngagement} busy={busy||agentBusy||!!pending} onEstimate={refreshEstimate} onChooseRegion={()=>{visitJourney('cloud');requestAnimationFrame(()=>{const input=document.querySelector('.location-region-form input');input?.focus();input?.scrollIntoView({block:'center',behavior:'instant'});});}} pricing={pricingState} onNext={()=>{const section=document.querySelector('.review-generation');const target=section?.querySelector('button')||section;if(target){target.setAttribute('tabindex','-1');target.focus({preventScroll:true});target.scrollIntoView({block:'center'});}}} />}
|
|
2175
2224
|
</section>
|
|
2176
|
-
{mode==='review'&&session?.engineeringEnabled&&<section className="review-generation" aria-label="Generation and verification"><EngineeringPanel key={project.id} project={project} onBusyChange={setGenerationPending} onWorkModelChange={changeArchitectureWork} onIntentContext={syncEngineeringIntent} projectId={project.id} session={session} connection={connection} contextBusy={busy||agentBusy||!!pending} saveProject={()=>saveEngineeringProject(project.id)} buildRequest={buildRequest} onBuildRequestHandled={id=>setBuildRequest(current=>current?.id===id?null:current)} onConnections={()=>
|
|
2225
|
+
{mode==='review'&&session?.engineeringEnabled&&<section className="review-generation" aria-label="Generation and verification"><EngineeringPanel key={project.id} project={project} onBusyChange={setGenerationPending} onWorkModelChange={changeArchitectureWork} onIntentContext={syncEngineeringIntent} projectId={project.id} session={session} connection={connection} contextBusy={busy||agentBusy||!!pending} saveProject={()=>saveEngineeringProject(project.id)} buildRequest={buildRequest} onBuildRequestHandled={id=>setBuildRequest(current=>current?.id===id?null:current)} onConnections={()=>showConnections()}/></section>}
|
|
2177
2226
|
{mode==='review'&&!session?.engineeringEnabled&&<p>Generation and local checks require the engineering service. Your design is retained; Delivery can package the design and remaining tasks.</p>}
|
|
2178
2227
|
{mode==='review'&&<ProjectChecks generationPending={generationPending} receipt={checkReceipt} project={project} environment={environment} session={session} onReceipt={setCheckReceipt} onBusy={setChecking}/>}
|
|
2179
2228
|
{mode==='review'&&<footer className="review-finish"><div><strong>3. Package and download</strong><p>Explore all files and download one project ZIP in Delivery.</p></div><button className="btn primary" onClick={()=>{const next=acknowledgeStage(projectRef.current,'review');updateEngagement(costs.expected!==null&&!costs.mixedCurrencies?{...next,extensions:{...next.extensions,journey:{...next.extensions.journey,estimate:journey(next).keys.estimate}}}:next);visitJourney('delivery');}}>Complete →</button></footer>}
|
|
@@ -2184,7 +2233,7 @@ function App() {
|
|
|
2184
2233
|
|
|
2185
2234
|
{onBoard&&emulationOpen&&<div className="emulation-dock"><EmulationPanel project={project} session={session} emulation={emulation} onDiscuss={discussEmulation} onOpenComponent={id=>{reviewBlock(id);setEmulationOpen(false);}} discussionBusy={busy||agentBusy||!!pending} onClose={()=>setEmulationOpen(false)} onBuild={session?.engineeringEnabled?()=>{setEmulationOpen(false);buildCurrentDesign('test');}:undefined}/></div>}
|
|
2186
2235
|
|
|
2187
|
-
{mode === "design" && designView === "outputs" && <OutputsView onNavigate={async target=>{const [stage,selector]={requirements:['scope','.requirements-document'],location:['cloud','section[aria-label="Country and region"]'],cost:['review','.review-costs'],implementation:['review','.review-generation'],checks:['review','.project-checks']}[target];visitJourney(stage);await new Promise((resolve,reject)=>{const started=performance.now();function focus(){const element=document.querySelector(selector);if(element&&element.getClientRects().length){element.setAttribute('tabindex','-1');element.focus({preventScroll:true});element.scrollIntoView({block:'start',behavior:'instant'});resolve();}else if(performance.now()-started<5000)requestAnimationFrame(focus);else reject(Error('Section is unavailable. Use the stage tabs to continue.'));}requestAnimationFrame(focus);});}} checkReceipt={checkReceipt} onDownloaded={()=>{updateEngagement(acknowledgeStage(projectRef.current,'delivery'));setStarOffer(value=>value+1);}} onReview={()=>visitJourney('review')} onSave={expected=>saveEngineeringProject(project.id,expected)} project={project} run={run} environment={environment} session={session} emulation={emulation} onDiscuss={discussEmulation} discussionBusy={busy||agentBusy||!!pending} onDownload={downloadBundle} onBuild={session?.engineeringEnabled?()=>visitJourney('review'):undefined} onBack={()=>setDesignView("board")} />}
|
|
2236
|
+
{mode === "design" && designView === "outputs" && <OutputsView workStatus={engineeringStatus} onNavigate={async target=>{const [stage,selector]={requirements:['scope','.requirements-document'],location:['cloud','section[aria-label="Country and region"]'],cost:['review','.review-costs'],implementation:['review','.review-generation'],checks:['review','.project-checks']}[target];visitJourney(stage);await new Promise((resolve,reject)=>{const started=performance.now();function focus(){const element=document.querySelector(selector);if(element&&element.getClientRects().length){element.setAttribute('tabindex','-1');element.focus({preventScroll:true});element.scrollIntoView({block:'start',behavior:'instant'});resolve();}else if(performance.now()-started<5000)requestAnimationFrame(focus);else reject(Error('Section is unavailable. Use the stage tabs to continue.'));}requestAnimationFrame(focus);});}} checkReceipt={checkReceipt} onDownloaded={()=>{updateEngagement(acknowledgeStage(projectRef.current,'delivery'));setStarOffer(value=>value+1);}} onReview={()=>visitJourney('review')} onSave={expected=>saveEngineeringProject(project.id,expected)} project={project} run={run} environment={environment} session={session} emulation={emulation} onDiscuss={discussEmulation} discussionBusy={busy||agentBusy||!!pending} onDownload={downloadBundle} onBuild={session?.engineeringEnabled?()=>visitJourney('review'):undefined} onBack={()=>setDesignView("board")} />}
|
|
2188
2237
|
{mode === "design" && designView === "terraform" && <TerraformView project={project} environment={environment} session={session} emulation={emulation} onDiscuss={discussEmulation} discussionBusy={busy||agentBusy||!!pending} onDelivery={()=>visitJourney("delivery")} onBuild={session?.engineeringEnabled?()=>visitJourney('review'):undefined} onBack={()=>setDesignView("board")} />}
|
|
2189
2238
|
<div className="choices-workspace" hidden={!(mode==="design"&&["options","decisions","location"].includes(designView))}>
|
|
2190
2239
|
{mode === "design" && ["options","decisions"].includes(designView) && (
|
|
@@ -2199,7 +2248,7 @@ function App() {
|
|
|
2199
2248
|
|
|
2200
2249
|
{!chatOpen&&!buildView && <button className="icon chat-reopen" title="Show chat" aria-label="Show chat" onClick={()=>setChatOpen(true)}><PanelRightOpen size={18}/></button>}
|
|
2201
2250
|
{chatOpen&&!buildView && (
|
|
2202
|
-
<aside className=
|
|
2251
|
+
<aside className={`chat ${idleChat?"idle":""}`} aria-label="Chat"><ChatResizeHandle width={chatWidth} onChange={resizeChat}/>
|
|
2203
2252
|
<div className="chat-head">
|
|
2204
2253
|
<div className="chat-heading-row"><strong className="chat-title" aria-label="AI Architect">
|
|
2205
2254
|
<Mark size={24} />
|
|
@@ -2217,10 +2266,15 @@ function App() {
|
|
|
2217
2266
|
/>
|
|
2218
2267
|
Draw as we talk
|
|
2219
2268
|
</label>
|
|
2220
|
-
|
|
2269
|
+
{HOSTED?<span className="model"><span className={`led ${connection&&session?'on':''}`}/>{connection?.model?.split('/').at(-1)} · Medium</span>:<>
|
|
2270
|
+
<button className="model" onClick={() => showConnections()} title="AI providers">
|
|
2221
2271
|
<span className={`led ${connection && session ? "on" : ""}`} />
|
|
2222
2272
|
{connection ? `${connection.model}${connection.effort ? ` · ${EFFORT_LABEL[connection.effort] || connection.effort}` : ""}` : "Connect a model"}
|
|
2223
|
-
</button
|
|
2273
|
+
</button></>}
|
|
2274
|
+
<span className="chat-top-utilities">
|
|
2275
|
+
{hasIntroduction&&<button type="button" aria-expanded={introOpen} onClick={()=>setIntroOpen(value=>!value)}>{introOpen?'▾':'▸'} Introduction</button>}
|
|
2276
|
+
{session&&<button type="button" className="icon" aria-label="Voice settings" title="Voice settings" onClick={()=>setVoiceOpenRequest(value=>value+1)}><Volume2 size={15}/></button>}
|
|
2277
|
+
</span>
|
|
2224
2278
|
</div>
|
|
2225
2279
|
<div className="skills" aria-label="Active skills">
|
|
2226
2280
|
<button className="known-bar" disabled={!session} aria-label="AtMem memory" title="Open AtMem memory, confirmed decisions and sync activity" onClick={()=>setModal("memory")}><BrainActivity size={18} active={false}/>{memoryLabel}<ChevronRight size={14}/></button>
|
|
@@ -2264,12 +2318,15 @@ function App() {
|
|
|
2264
2318
|
))}
|
|
2265
2319
|
</ul>
|
|
2266
2320
|
)}
|
|
2321
|
+
<div className="chat-body">
|
|
2322
|
+
<section className="chat-history" aria-label="Chat history"><h2 className="chat-history-label">Chat history</h2>
|
|
2267
2323
|
<div className="messages" ref={chatScroll.viewport}>
|
|
2268
2324
|
<div className="message-list" ref={chatScroll.content}>
|
|
2269
2325
|
{architectureChatEntries(project,messages).filter(entry=>entry.kind==='message'?entry.index>=chatView.start:entry.kind==='activity'?!chatView.turnIds.includes(entry.turn.id):entry.kind==='legacy-activity'?entry.entries.some(item=>!chatView.turnIds.includes(item.turn.id)):true).map(entry=>{
|
|
2270
2326
|
if(entry.kind==='activity-error')return <p key="activity-error" role="alert">{entry.error}</p>;
|
|
2271
2327
|
if(entry.kind==='legacy-activity')return <EarlierArchitectureActivity key={entry.id} entries={entry.entries} projectId={project.id} session={session}/>;
|
|
2272
2328
|
if(entry.kind==='activity')return <ArchitectureTurn key={project.id+':'+entry.turn.id} turn={entry.turn} number={entry.number} projectId={project.id} session={session} active={agentBusy&&entry.turn.clientStatus==='submitted'&&entry.turn.id===latestArchitectureTurn?.id} working={agentBusy&&entry.turn.clientStatus==='submitted'} onStop={()=>agentAbort.current?.abort()}/>;
|
|
2329
|
+
if(entry.index===0&&hasIntroduction&&!introOpen)return null;
|
|
2273
2330
|
const m=entry.message,i=entry.index,turn=project.extensions?.architectureActivity?.turns?.find(t=>t.id===m.activityId),messageTime=m.createdAt||(m.role==='assistant'?turn?.completedAt:null)||turn?.startedAt;
|
|
2274
2331
|
return (
|
|
2275
2332
|
<div key={i} className={`msg ${m.role}`}>
|
|
@@ -2277,7 +2334,7 @@ function App() {
|
|
|
2277
2334
|
{(m.workState==='queued'||m.workState==='processing'&&intake.some(q=>q.id===m.requestId))&&<small className="message-work-state">Queued for the next update <button className="link" onClick={()=>{updateIntake(intakeRef.current.filter(q=>q.id!==m.requestId));setRequestState([m.requestId],'cancelled');}}>Remove from queue</button><button className="link" onClick={()=>{if(input||attachments.length){notify('Your current draft is preserved. Send or clear it before editing a queued update.');return;}const queued=intakeRef.current.find(q=>q.id===m.requestId);if(!queued)return;setInput(queued.text);setAttachments(queued.attachments);updateIntake(intakeRef.current.filter(q=>q.id!==m.requestId));setRequestState([m.requestId],'cancelled');}}>Edit update</button>{m.attachments?.some(a=>!a.data)&&<button className="link" onClick={()=>{updateIntake(intakeRef.current.map(q=>q.id===m.requestId?{...q,attachments:[]}:q));setIntakePaused(false);}}>Continue with text only</button>}</small>}
|
|
2278
2335
|
{!!m.attachments?.length&&<div className="sent-attachments">{m.attachments.map(a=><figure key={a.id}>{a.data&&a.mime.startsWith('image/')&&<img src={`data:${a.mime};base64,${a.data}`} alt={a.name}/>}<figcaption><Paperclip size={12}/>{a.name}{a.originalNotSaved && <small>Original file not saved; attach it again to review its contents.</small>}</figcaption></figure>)}</div>}
|
|
2279
2336
|
{m.fix && (
|
|
2280
|
-
<button className="btn fix" onClick={() =>
|
|
2337
|
+
<button className="btn fix" onClick={() => showConnections()}>
|
|
2281
2338
|
Fix connection
|
|
2282
2339
|
</button>
|
|
2283
2340
|
)}
|
|
@@ -2320,6 +2377,8 @@ function App() {
|
|
|
2320
2377
|
</div>
|
|
2321
2378
|
</div>
|
|
2322
2379
|
{chatScroll.away&&<button className="chat-jump-latest" aria-label="Jump to latest message" onClick={chatScroll.followLatest}>↓</button>}
|
|
2380
|
+
</section>
|
|
2381
|
+
<div className="chat-compose-pane">
|
|
2323
2382
|
{!!intake.filter(q=>q.projectId===project.id).length&&<div className="intake-status" role="status">{intake.filter(q=>q.projectId===project.id).length} update(s) {intakePaused?'saved · paused':'queued · combining next'}{intakeIssue&&<span> {intakeIssue}</span>}{intakePaused&&<button className="link" onClick={()=>setIntakePaused(false)}>Resume</button>}</div>}
|
|
2324
2383
|
<div className="prompts">
|
|
2325
2384
|
{mode === "design" && project.workflow && !project.nodes.length && !pending && (
|
|
@@ -2342,11 +2401,12 @@ function App() {
|
|
|
2342
2401
|
void postMessage();
|
|
2343
2402
|
}}
|
|
2344
2403
|
>
|
|
2345
|
-
<
|
|
2404
|
+
<label className="composer-label" htmlFor="architect-message">Your message</label>
|
|
2405
|
+
<textarea id="architect-message"
|
|
2346
2406
|
onPaste={e=>{const files=[...e.clipboardData.files];if(files.length){e.preventDefault();void attachFiles(files);}}}
|
|
2347
2407
|
ref={messageInput}
|
|
2348
2408
|
aria-label="Your message"
|
|
2349
|
-
placeholder=
|
|
2409
|
+
placeholder="Type here…"
|
|
2350
2410
|
rows={2}
|
|
2351
2411
|
value={input}
|
|
2352
2412
|
onChange={(e) => { stopVoice(); setInput(e.target.value); }}
|
|
@@ -2372,11 +2432,11 @@ function App() {
|
|
|
2372
2432
|
{voiceActive ? <Square size={13} /> : <Mic size={15} />}
|
|
2373
2433
|
</button>
|
|
2374
2434
|
<button type="submit" className="send" aria-label="Send message" disabled={(!input.trim() && !voiceActive && !attachments.length) || busy || readingAttachments || voiceState === "transcribing"}>
|
|
2375
|
-
<ArrowUp size={15}
|
|
2435
|
+
<ArrowUp size={15} /><span>Send</span>
|
|
2376
2436
|
</button>
|
|
2377
2437
|
</div>
|
|
2378
2438
|
</form>
|
|
2379
|
-
{session&&<OpenAIVoicePanel key={project.id} session={session} connection={connection} transportRef={openaiVoice} onConnections={()=>
|
|
2439
|
+
{session&&<OpenAIVoicePanel hideTrigger openRequest={voiceOpenRequest} key={project.id} session={session} connection={connection} transportRef={openaiVoice} onConnections={()=>showConnections()} onStart={()=>{voice.current?.cancel();setVoiceError('');}} onTranscript={(text,automatic)=>{
|
|
2380
2440
|
if(automatic&&!input.trim()&&!attachments.length){setInput(text);void send(text);}
|
|
2381
2441
|
else setInput(current=>current+(current.trim()?' ':'')+text);
|
|
2382
2442
|
}}/>}
|
|
@@ -2384,10 +2444,12 @@ function App() {
|
|
|
2384
2444
|
{!voiceError&&["starting","transcribing"].includes(voiceState)&&<BrainActivity size={22}/>}
|
|
2385
2445
|
{voiceError || ({ starting: "Opening microphone…", listening: "Dictating locally — review your words, then press Send.", transcribing: "Finishing your transcript…" }[voiceState] || "")}
|
|
2386
2446
|
</div>
|
|
2447
|
+
</div>
|
|
2448
|
+
</div>
|
|
2387
2449
|
</aside>
|
|
2388
2450
|
)}
|
|
2389
2451
|
|
|
2390
|
-
<input type="file" id="requirements-file-input" ref={requirementsInput} accept={ATTACHMENT_ACCEPT} multiple hidden onChange={async e=>{const files=[...e.target.files];e.target.value='';if(!files.length||readingAttachments)return;const uploadOwner=projectRef.current.id;setReadingAttachments(true);setAttachmentError('');try{const added=await readAttachments(files,attachments);if(projectRef.current.id!==uploadOwner)return;const all=[...attachments,...added];setAttachments(all);setChatOpen(true);setInput('Read these requirements and diagrams. Summarise what you understand, propose requirements and a connected design, and ask one focused question about anything important that is unclear. Preserve existing systems and distinguish source facts from assumptions.');if(connection&&session){setReadingAttachments(false);void send('Read these requirements and diagrams. Summarise what you understand, propose requirements and a connected design, and ask one focused question about anything important that is unclear. Preserve existing systems and distinguish source facts from assumptions.','chat',null,null,{attachments:all});}else{
|
|
2452
|
+
<input type="file" id="requirements-file-input" ref={requirementsInput} accept={ATTACHMENT_ACCEPT} multiple hidden onChange={async e=>{const files=[...e.target.files];e.target.value='';if(!files.length||readingAttachments)return;const uploadOwner=projectRef.current.id;setReadingAttachments(true);setAttachmentError('');try{const added=await readAttachments(files,attachments);if(projectRef.current.id!==uploadOwner)return;const all=[...attachments,...added];setAttachments(all);setChatOpen(true);setInput('Read these requirements and diagrams. Summarise what you understand, propose requirements and a connected design, and ask one focused question about anything important that is unclear. Preserve existing systems and distinguish source facts from assumptions.');if(connection&&session){setReadingAttachments(false);void send('Read these requirements and diagrams. Summarise what you understand, propose requirements and a connected design, and ask one focused question about anything important that is unclear. Preserve existing systems and distinguish source facts from assumptions.','chat',null,null,{attachments:all});}else{showConnections();}}catch(error){setAttachmentError(error.message);}finally{setReadingAttachments(false);}}}/>
|
|
2391
2453
|
<input id="project-file-input" type="file" ref={fileRef} accept="application/json,.json" hidden onChange={importProject} />
|
|
2392
2454
|
{toast && (
|
|
2393
2455
|
<div className="toast" role="status">
|
|
@@ -2429,22 +2491,20 @@ function App() {
|
|
|
2429
2491
|
/></div>
|
|
2430
2492
|
) : modal === "start-over" ? (
|
|
2431
2493
|
<>
|
|
2432
|
-
<h2>
|
|
2433
|
-
<p>Start
|
|
2494
|
+
<h2>Reset board and context?</h2>
|
|
2495
|
+
<p>Start fresh with an empty board, chat, requirements and decisions. Your saved projects and model connection stay.</p>
|
|
2434
2496
|
<div className="reset-actions">
|
|
2435
2497
|
<button className="btn primary" title="Download your project, then start a new one" aria-label="Save project and start over" onClick={() => {
|
|
2436
2498
|
if (!saveProjectFile()) return;
|
|
2437
|
-
|
|
2438
|
-
setModal(null);
|
|
2499
|
+
createOwnProject();
|
|
2439
2500
|
}}><Package size={18}/> Save & start new</button>
|
|
2440
2501
|
<button
|
|
2441
|
-
className="btn" title="Start a new project without downloading this one" aria-label="
|
|
2502
|
+
className="btn" title="Start a new project without downloading this one" aria-label="Confirm reset board and context"
|
|
2442
2503
|
onClick={() => {
|
|
2443
|
-
|
|
2444
|
-
setModal(null);
|
|
2504
|
+
createOwnProject();
|
|
2445
2505
|
}}
|
|
2446
2506
|
>
|
|
2447
|
-
<
|
|
2507
|
+
<Trash2 size={18}/> Reset board and context
|
|
2448
2508
|
</button>
|
|
2449
2509
|
<button className="btn ghost" onClick={() => setModal(null)}>
|
|
2450
2510
|
<ArrowLeft size={18}/> Keep working
|
|
@@ -2467,7 +2527,7 @@ function App() {
|
|
|
2467
2527
|
/>
|
|
2468
2528
|
) : modal === "licenses" ? (
|
|
2469
2529
|
<>
|
|
2470
|
-
<h2>AtMem</h2><a href=
|
|
2530
|
+
<h2>AtMem</h2><a href={(HOSTED?'/architect/workspace/':'/')+'licenses/atmem-APACHE-2.0.txt'} target="_blank" rel="noreferrer">Apache 2.0</a>
|
|
2471
2531
|
</>
|
|
2472
2532
|
) : (
|
|
2473
2533
|
<>
|
|
@@ -2482,8 +2542,17 @@ function App() {
|
|
|
2482
2542
|
);
|
|
2483
2543
|
}
|
|
2484
2544
|
|
|
2485
|
-
|
|
2545
|
+
async function mountArchitect(){
|
|
2546
|
+
await connectHostedWorkspace();
|
|
2547
|
+
document.documentElement.dataset.theme=savedTheme();
|
|
2548
|
+
createRoot(document.getElementById("root")).render(
|
|
2486
2549
|
<ReactFlowProvider>
|
|
2487
2550
|
<App />
|
|
2488
2551
|
</ReactFlowProvider>,
|
|
2489
2552
|
);
|
|
2553
|
+
}
|
|
2554
|
+
void mountArchitect().catch(error=>{
|
|
2555
|
+
const root=document.getElementById('root');root.replaceChildren();
|
|
2556
|
+
const message=document.createElement('p');message.textContent=error.message||'The workspace could not open.';
|
|
2557
|
+
const link=document.createElement('a');link.href='/architect';link.textContent='Return to Architect';root.append(message,link);
|
|
2558
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@media(max-width:900px){html,body,#root{height:100%;overflow:hidden}.app{height:100dvh;min-height:0;grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto minmax(100px,1fr) minmax(300px,50dvh);overflow:hidden}.app.chat-closed,.app.build-workspace{grid-template-rows:auto auto minmax(0,1fr)}.app>.canvas{min-height:0;height:auto;overflow:hidden}.app>.chat{height:auto;min-height:0;overflow:hidden}.chat>.messages{min-height:0;flex:1;overflow:auto}.chat>.composer{position:relative;flex-shrink:0;background:var(--paper)}}
|
|
2
|
+
.chat>.composer{padding:6px 8px;border-radius:14px}.chat>.composer textarea{min-height:32px;height:32px;max-height:120px;padding:4px 6px;font-size:14px;line-height:20px;resize:none;overflow-y:auto}.chat>.composer .composer-actions{padding-top:2px}.chat>.composer .composer-actions button{width:32px;height:32px;min-height:32px;padding:6px}
|
|
3
|
+
.chat>.chat-head{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;flex-wrap:wrap;gap:4px 6px;padding:4px 8px;min-height:0}.chat-head>.chat-heading-row{display:contents}.chat-head .chat-title{display:none}.chat-head>.model{order:0;max-width:100%;font-size:12px;line-height:18px;min-height:26px;padding:3px 7px;margin:0}.chat-head>.live-toggle{order:1;flex:0 0 auto;width:auto;display:inline-flex;align-items:center;gap:4px;white-space:nowrap;font-size:11px;line-height:18px;padding:0;margin:0}.chat-head .live-toggle input{width:13px;height:13px;margin:0}.chat-head .icon{order:2;width:26px;height:26px;min-height:26px;padding:4px;margin:0}.chat-head .icon svg{width:15px;height:15px}.chat>.skills{display:flex;align-items:center;flex-wrap:nowrap;overflow-x:auto;gap:3px;padding:2px 8px 4px;margin:0;min-height:0;background:transparent;border-radius:0}.chat>.skills .known-bar,.chat>.skills .skill{flex:0 0 auto;width:auto;font-size:11px;line-height:16px;min-height:24px;padding:3px 6px;margin:0;gap:4px;border-radius:7px}.chat>.skills svg{width:13px;height:13px}
|
|
4
|
+
.chat-head .chat-top-utilities{display:inline-flex;align-items:center;gap:4px;order:3;margin-left:auto}
|
|
5
|
+
.chat-head .chat-top-utilities button{font-size:11px;line-height:18px;min-height:26px;padding:3px 6px;border:0;background:transparent;color:var(--ink-2)}
|
|
6
|
+
.chat>.voice-status:empty{display:none}
|
|
7
|
+
.chat.idle>.messages{display:none}
|
|
8
|
+
@media(max-width:900px){.app.idle-chat{grid-template-rows:auto auto minmax(100px,1fr) auto}.app>.chat.idle{height:auto;min-height:0}}
|
|
9
|
+
.ready-reference{margin:20px 0}.ready-reference>p{margin:8px 0}.ready-reference li{display:flex;align-items:center;gap:10px;padding:8px 0}.ready-reference li>span:nth-child(2){flex:1}.ready-reference details{margin-top:10px}.ready-reference details p{margin:8px 0}
|
|
10
|
+
.bar-actions .ready-download{font-size:12px;padding:6px 10px}.toolbar .reset-board{font-size:12px;padding:6px 9px;gap:5px}
|
|
11
|
+
|
|
12
|
+
/* Keep project tools in one slim row; the diagram owns the remaining height. */
|
|
13
|
+
.app>.bar{flex-wrap:nowrap;gap:6px;padding:4px 8px;min-height:40px}
|
|
14
|
+
.app>.bar>.brand{padding:2px;flex-shrink:0}
|
|
15
|
+
.app>.bar>.crumbs{order:0;flex:1 1 auto;min-width:0;flex-wrap:nowrap;gap:4px!important}
|
|
16
|
+
.app>.bar .workspace-label{display:none}
|
|
17
|
+
.app>.bar .project-rename{flex:1;min-width:0}
|
|
18
|
+
.app>.bar .project-name{font-size:13px;padding:4px 6px;height:30px;min-width:0}
|
|
19
|
+
.app>.bar .projects-btn{padding:4px 6px;min-width:0;min-height:30px;font-size:12px}
|
|
20
|
+
.app>.bar>.bar-actions{margin-left:auto;padding:0;border:0;gap:4px;flex-shrink:0}
|
|
21
|
+
.app>.bar .bar-actions>.btn{padding:5px 8px;font-size:12px;min-height:30px;gap:4px}
|
|
22
|
+
.app>.bar .bar-actions>.icon{width:30px;height:30px;min-height:30px;padding:5px}
|
|
23
|
+
@media(max-width:900px){.app>.bar .bar-actions>.ready-download{display:none}}
|
|
24
|
+
@media(max-width:650px){.app>.bar .projects-btn>span{display:none}.app>.bar>.bar-actions{gap:2px}.app>.bar .bar-actions>.btn{font-size:11px;padding:5px 6px}.app>.bar .bar-actions>.icon{width:26px}.app>.bar .project-name{font-size:12px;padding:3px;}.app>.bar .project-rename>svg{display:none}}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import file from './reference-example/project.json' with {type:'json'};
|
|
2
|
+
import {fromProjectFile} from './project-file.mjs';
|
|
3
|
+
export const READY_EXAMPLE_ID='atmem-ai-ready-example-v1';
|
|
4
|
+
export function readyExample(){const {project}=fromProjectFile(structuredClone(file));return project;}
|
|
5
|
+
export function isReadyExample(project){return project?.extensions?.preparedExample?.version===1&&project?.extensions?.preparedExample?.owner==='AtMem example author';}
|