@atmem/architect 0.2.1 → 0.2.2
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 +5 -1
- package/dist/assets/{index-D4CZ_n8f.js → index-CWUupGC1.js} +1432 -1437
- package/dist/assets/{index-DuveVTIx.css → index-DBMP6Pxk.css} +1 -1
- package/dist/assets/{index.es-Fcqg-orY.js → index.es-H4csXVqE.js} +1 -1
- package/dist/assets/{visual-export-4PDDTAP7.js → visual-export-D0f1PbRp.js} +3 -3
- package/dist/index.html +2 -2
- package/dist/reference-example.zip +0 -0
- package/package.json +3 -1
- package/src/OpenAIVoicePanel.jsx +3 -2
- package/src/OutputsView.jsx +4 -1
- package/src/ReadyExample.jsx +16 -0
- package/src/main.jsx +47 -20
- package/src/onboarding.css +10 -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/OutputsView.jsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {ReadyExampleChecklist} from './ReadyExample.jsx';
|
|
2
|
+
import {isReadyExample} from './ready-example.mjs';
|
|
1
3
|
import {FileTree} from './FileTree.jsx';
|
|
2
4
|
import {strFromU8} from 'fflate';
|
|
3
5
|
import {CodePreview} from './CodePreview.jsx';
|
|
@@ -57,7 +59,8 @@ export function OutputsView({project,run,environment,session,onDownload,onBack,e
|
|
|
57
59
|
return <section className="doc outputs-view" aria-label="Outputs">
|
|
58
60
|
<header className="doc-head"><h2>Delivery</h2></header>
|
|
59
61
|
<button className="link" onClick={onReview||onBack}>Back to Review</button>
|
|
60
|
-
{
|
|
62
|
+
{isReadyExample(project)&&<ReadyExampleChecklist/>}
|
|
63
|
+
{session?.engineeringEnabled&&(isReadyExample(project)?<details><summary>Checks for your edited project</summary><ImplementationDelivery key={project.id} project={project} session={session} onBuild={onBuild} onSave={onSave} contextBusy={discussionBusy} designBundle={loading?null:bundle} onDesignDownload={onDownload} onDownloaded={onDownloaded} checkReceipt={checkReceipt} onExplore={setExploration} onNavigate={onNavigate}/></details>:<ImplementationDelivery key={project.id} project={project} session={session} onBuild={onBuild} onSave={onSave} contextBusy={discussionBusy} designBundle={loading?null:bundle} onDesignDownload={onDownload} onDownloaded={onDownloaded} checkReceipt={checkReceipt} onExplore={setExploration} onNavigate={onNavigate}/>)}
|
|
61
64
|
<section aria-label="Project file explorer"><h3>Explore files</h3>
|
|
62
65
|
|
|
63
66
|
{!session?.engineeringEnabled&&<><p>Design package · no implementation generated.</p><button className="btn" disabled={!bundle||loading||!!error||packing} onClick={downloadDesign}>{packing?'Preparing ZIP…':'Download project ZIP'}</button></>}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React,{useState} from 'react';
|
|
2
|
+
import receipt from './reference-example/receipt.json';
|
|
3
|
+
export function ReadyExampleDownload({compact=false}){
|
|
4
|
+
const [busy,setBusy]=useState(false),[error,setError]=useState('');
|
|
5
|
+
async function download(){setBusy(true);setError('');try{
|
|
6
|
+
const response=await fetch(receipt.url);if(!response.ok)throw Error('Example download unavailable. Retry.');
|
|
7
|
+
const bytes=await response.arrayBuffer();const hash=[...new Uint8Array(await crypto.subtle.digest('SHA-256',bytes))].map(n=>n.toString(16).padStart(2,'0')).join('');
|
|
8
|
+
if(hash!==receipt.sha256)throw Error('Example integrity check failed. Retry.');
|
|
9
|
+
const url=URL.createObjectURL(new Blob([bytes],{type:'application/zip'})),link=document.createElement('a');link.href=url;link.download='atmem-ai-complete-example.zip';link.click();setTimeout(()=>URL.revokeObjectURL(url),60000);
|
|
10
|
+
}catch(e){setError(e.message)}finally{setBusy(false)}}
|
|
11
|
+
return <><button type="button" className={compact?'btn ready-download':'btn primary'} onClick={download} disabled={busy}>{busy?'Preparing…':compact?'Download example ZIP':'Download complete example'}</button>{error&&<p role="alert">{error}</p>}</>;
|
|
12
|
+
}
|
|
13
|
+
export function ReadyExampleChecklist(){
|
|
14
|
+
const rows=[['Requirements','Recorded'],['Cloud','AWS'],['Data country','Australia'],['Cloud region','Sydney'],['Cost worksheet','USD 7.56/month · sample'],['Implementation','Source + built runtime'],['Application checks','13 local tests passed'],['Terraform checks','Validated · 2 mock plans passed']];
|
|
15
|
+
return <section className="final-checklist ready-reference" aria-label="Prepared example checklist"><h3>Complete reference example</h3><p>These checks apply to the bundled example ZIP. Your edited project has separate checks.</p><ul>{rows.map(([name,value])=><li key={name}><span className="check-complete">✓</span><span>{name}</span><strong>{value}</strong></li>)}</ul><ReadyExampleDownload/><details><summary>Evidence and scope</summary><p>Local fixtures and mocked AWS. Live deployment and real tenant integration have not run.</p><p>Illustrative AWS usage; assumptions and official price snapshots are included.</p></details></section>;
|
|
16
|
+
}
|
package/src/main.jsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {readyExample,isReadyExample} from './ready-example.mjs';
|
|
2
|
+
import {ReadyExampleDownload} from './ReadyExample.jsx';
|
|
1
3
|
import {StarPrompt,STAR_URL} from './StarPrompt.jsx';
|
|
2
4
|
import {prefillAcceptedSettings} from './engineering-defaults.mjs';
|
|
3
5
|
import {LocationChoices} from './LocationChoices.jsx';
|
|
@@ -101,6 +103,7 @@ import {
|
|
|
101
103
|
Rocket,
|
|
102
104
|
BrainCircuit,
|
|
103
105
|
Waypoints,
|
|
106
|
+
Volume2,
|
|
104
107
|
Mic,
|
|
105
108
|
Square,
|
|
106
109
|
Eraser,
|
|
@@ -167,6 +170,7 @@ import { zipSync, strToU8 } from "fflate";
|
|
|
167
170
|
// Each browser starts fresh; earlier projects are opened deliberately (⋯ → Open project file).
|
|
168
171
|
const STORAGE = "atforma-workspace-v3";
|
|
169
172
|
// A new project starts empty: the user describes what they want first.
|
|
173
|
+
import './onboarding.css';
|
|
170
174
|
const emptyProject = () => ({ id: crypto.randomUUID(), version: 1, revision: 1, name: "New project", provider: "tools", requirements: "", nodes: [], edges: [] });
|
|
171
175
|
const ALL_CATALOG = [...CATALOG, ...UPSTREAM_CATALOG];
|
|
172
176
|
const KIND_ICON = {
|
|
@@ -646,6 +650,7 @@ function harnessSignals(was, now) {
|
|
|
646
650
|
}
|
|
647
651
|
const welcome = (project) => ({
|
|
648
652
|
role: "assistant",
|
|
653
|
+
introduction: true,
|
|
649
654
|
text: project.nodes.length || project.brief?.application?.trim()
|
|
650
655
|
? `Good to see you again — let's keep going.\n\n${opening(project)}`
|
|
651
656
|
: `What would you like to build?`,
|
|
@@ -666,7 +671,8 @@ function readChatView(id){try{const value=JSON.parse(localStorage.getItem('atmem
|
|
|
666
671
|
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
672
|
function App() {
|
|
668
673
|
const [starOffer,setStarOffer]=useState(0);
|
|
669
|
-
const [
|
|
674
|
+
const [introOpen,setIntroOpen]=useState(false),[voiceOpenRequest,setVoiceOpenRequest]=useState(0);
|
|
675
|
+
const [project, setProject] = useState(()=>new URL(location.href).searchParams.get('example')==='1'&&!projectIdFromUrl(location.href)?readyExample():readSaved());
|
|
670
676
|
const projectRef = useRef(project);
|
|
671
677
|
projectRef.current = project;
|
|
672
678
|
const [initialCanvasView] = useState(()=>readCanvasView(project));
|
|
@@ -698,7 +704,7 @@ function App() {
|
|
|
698
704
|
[history, setHistory] = useState([]),
|
|
699
705
|
[future, setFuture] = useState([]),
|
|
700
706
|
[toast, setToast] = useState(""),
|
|
701
|
-
[modal, setModal] = useState(
|
|
707
|
+
[modal, setModal] = useState(null),
|
|
702
708
|
[menu, setMenu] = useState(false),
|
|
703
709
|
[palette, setPalette] = useState(false),
|
|
704
710
|
[issuesOpen, setIssuesOpen] = useState(false),
|
|
@@ -782,6 +788,12 @@ function App() {
|
|
|
782
788
|
const consultationAttempts = useRef(new Set());
|
|
783
789
|
const agentAbort = useRef(null);
|
|
784
790
|
const messageInput = useRef(null);
|
|
791
|
+
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'));
|
|
792
|
+
const idleChat=hasIntroduction&&!introOpen&&architectureChatEntries(project,messages).length===1;
|
|
793
|
+
useEffect(()=>{
|
|
794
|
+
const resize=()=>{const field=messageInput.current;if(field){field.style.height='32px';field.style.height=Math.min(120,Math.max(32,field.scrollHeight))+'px';}};
|
|
795
|
+
resize();window.addEventListener('resize',resize);return()=>window.removeEventListener('resize',resize);
|
|
796
|
+
},[input,chatWidth,chatOpen]);
|
|
785
797
|
const intakeRef=useRef(restoreQueuedMessages(messages,project.id));
|
|
786
798
|
const [intake,setIntake]=useState(intakeRef.current),[intakePaused,setIntakePausedState]=useState(()=>restoreIntakePaused(messages)||intakeRef.current.length>0);
|
|
787
799
|
const [intakeIssue,setIntakeIssue]=useState(null);
|
|
@@ -1667,6 +1679,8 @@ function App() {
|
|
|
1667
1679
|
}
|
|
1668
1680
|
}
|
|
1669
1681
|
function switchTo(next) {
|
|
1682
|
+
setIntroOpen(false);setVoiceOpenRequest(0);
|
|
1683
|
+
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
1684
|
chooseWorkspace('canvas');setBuildRequest(null);
|
|
1671
1685
|
next = withDerived(next);
|
|
1672
1686
|
const leaving = projectRef.current;
|
|
@@ -1725,8 +1739,14 @@ function App() {
|
|
|
1725
1739
|
})();
|
|
1726
1740
|
return () => { active = false; };
|
|
1727
1741
|
}, [session?.token]);
|
|
1728
|
-
function newProject() {
|
|
1729
|
-
|
|
1742
|
+
function newProject() { if(!busy&&!agentBusy&&!intake.length)setModal("start-over"); }
|
|
1743
|
+
function createOwnProject(){
|
|
1744
|
+
switchTo(validateImport(emptyProject()));setModal(connection?null:'connections');
|
|
1745
|
+
requestAnimationFrame(()=>document.querySelector('input[aria-label="Project name"]')?.focus());
|
|
1746
|
+
}
|
|
1747
|
+
function openReadyExample(){
|
|
1748
|
+
if(busy||agentBusy||intake.length)return;
|
|
1749
|
+
const example=readyExample();example.id=crypto.randomUUID();switchTo(example);setModal(null);
|
|
1730
1750
|
}
|
|
1731
1751
|
async function binProject(id) {
|
|
1732
1752
|
try {
|
|
@@ -1840,7 +1860,7 @@ function App() {
|
|
|
1840
1860
|
/>
|
|
1841
1861
|
);
|
|
1842
1862
|
return (
|
|
1843
|
-
<div style={{"--chat-panel-width":`${chatWidth}px`}} className={`app ${chatOpen&&!buildView ? "" : "chat-closed"} ${buildView?"build-workspace":""}`}>
|
|
1863
|
+
<div style={{"--chat-panel-width":`${chatWidth}px`}} className={`app ${idleChat?"idle-chat":""} ${chatOpen&&!buildView ? "" : "chat-closed"} ${buildView?"build-workspace":""}`}>
|
|
1844
1864
|
<header className="bar">
|
|
1845
1865
|
<button aria-label="About AtMem AI Architect" className="brand" onClick={() => setModal("about")} title="About AtMem AI Architect">
|
|
1846
1866
|
<Mark />
|
|
@@ -1859,6 +1879,7 @@ function App() {
|
|
|
1859
1879
|
/><PencilRuler size={14} aria-hidden="true"/></label>
|
|
1860
1880
|
</nav>
|
|
1861
1881
|
<div className="bar-actions">
|
|
1882
|
+
{isReadyExample(project)&&<><ReadyExampleDownload compact/><button className="btn" onClick={newProject} disabled={busy||agentBusy}>Create new project</button></>}
|
|
1862
1883
|
{storageError && <span className="warn-text">Not saved</span>}
|
|
1863
1884
|
<button
|
|
1864
1885
|
className="icon"
|
|
@@ -1883,6 +1904,9 @@ function App() {
|
|
|
1883
1904
|
{menu && (
|
|
1884
1905
|
<div className="menu" role="menu" onClick={() => setMenu(false)}>
|
|
1885
1906
|
<button role="menuitem" onClick={() => (refreshProjects(), setModal("projects"))} disabled={busy}><FolderOpen size={16}/>Projects</button>
|
|
1907
|
+
<a role="menuitem" href="https://atmem.ai" target="_blank" rel="noopener noreferrer">atmem.ai ↗</a>
|
|
1908
|
+
<a role="menuitem" href="mailto:hello@atmem.ai">Contact us: hello@atmem.ai</a>
|
|
1909
|
+
<button role="menuitem" onClick={openReadyExample} disabled={busy||agentBusy}><LayoutGrid size={16}/>Open complete AI example</button>
|
|
1886
1910
|
<button role="menuitem" onClick={newProject} disabled={busy}><Plus size={16}/>New project</button>
|
|
1887
1911
|
<hr/><button role="menuitem" onClick={() => setPalette(true)} disabled={busy}><Plus size={16}/>Add a block</button>
|
|
1888
1912
|
<button role="menuitem" onClick={() => setModal("templates")} disabled={busy}><LayoutTemplate size={16}/>Templates</button>
|
|
@@ -1920,7 +1944,7 @@ function App() {
|
|
|
1920
1944
|
{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
1945
|
{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
1946
|
{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>}
|
|
1947
|
+
{!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
1948
|
<ReactFlow
|
|
1925
1949
|
nodes={canvasNodes}
|
|
1926
1950
|
edges={canvasEdges}
|
|
@@ -1986,8 +2010,8 @@ function App() {
|
|
|
1986
2010
|
>
|
|
1987
2011
|
<LayoutGrid size={17} />
|
|
1988
2012
|
</button>
|
|
1989
|
-
<button className="
|
|
1990
|
-
<
|
|
2013
|
+
<button className="btn reset-board" title="Reset board and context" aria-label="Reset board and context" disabled={busy||agentBusy||intake.length>0} onClick={newProject}>
|
|
2014
|
+
<Trash2 size={17} /> Reset board
|
|
1991
2015
|
</button>
|
|
1992
2016
|
<button className="icon" title="Undo board change" aria-label="Undo" disabled={!history.length || busy || agentBusy} onClick={undo}>
|
|
1993
2017
|
<Undo2 size={17} />
|
|
@@ -2033,8 +2057,8 @@ function App() {
|
|
|
2033
2057
|
<Scan size={16}/> Select all
|
|
2034
2058
|
</button>
|
|
2035
2059
|
<div className="ctx-divider" role="separator"/>
|
|
2036
|
-
<button role="menuitem" className="danger" disabled={!project.nodes.length} onClick={() => (
|
|
2037
|
-
<
|
|
2060
|
+
<button role="menuitem" className="danger" disabled={!project.nodes.length} onClick={() => (newProject(), setCtx(null))}>
|
|
2061
|
+
<Trash2 size={16}/> Reset board and context
|
|
2038
2062
|
</button>
|
|
2039
2063
|
</>
|
|
2040
2064
|
)}
|
|
@@ -2199,7 +2223,7 @@ function App() {
|
|
|
2199
2223
|
|
|
2200
2224
|
{!chatOpen&&!buildView && <button className="icon chat-reopen" title="Show chat" aria-label="Show chat" onClick={()=>setChatOpen(true)}><PanelRightOpen size={18}/></button>}
|
|
2201
2225
|
{chatOpen&&!buildView && (
|
|
2202
|
-
<aside className=
|
|
2226
|
+
<aside className={`chat ${idleChat?"idle":""}`} aria-label="Chat"><ChatResizeHandle width={chatWidth} onChange={resizeChat}/>
|
|
2203
2227
|
<div className="chat-head">
|
|
2204
2228
|
<div className="chat-heading-row"><strong className="chat-title" aria-label="AI Architect">
|
|
2205
2229
|
<Mark size={24} />
|
|
@@ -2221,6 +2245,10 @@ function App() {
|
|
|
2221
2245
|
<span className={`led ${connection && session ? "on" : ""}`} />
|
|
2222
2246
|
{connection ? `${connection.model}${connection.effort ? ` · ${EFFORT_LABEL[connection.effort] || connection.effort}` : ""}` : "Connect a model"}
|
|
2223
2247
|
</button>
|
|
2248
|
+
<span className="chat-top-utilities">
|
|
2249
|
+
{hasIntroduction&&<button type="button" aria-expanded={introOpen} onClick={()=>setIntroOpen(value=>!value)}>{introOpen?'▾':'▸'} Introduction</button>}
|
|
2250
|
+
{session&&<button type="button" className="icon" aria-label="Voice settings" title="Voice settings" onClick={()=>setVoiceOpenRequest(value=>value+1)}><Volume2 size={15}/></button>}
|
|
2251
|
+
</span>
|
|
2224
2252
|
</div>
|
|
2225
2253
|
<div className="skills" aria-label="Active skills">
|
|
2226
2254
|
<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>
|
|
@@ -2270,6 +2298,7 @@ function App() {
|
|
|
2270
2298
|
if(entry.kind==='activity-error')return <p key="activity-error" role="alert">{entry.error}</p>;
|
|
2271
2299
|
if(entry.kind==='legacy-activity')return <EarlierArchitectureActivity key={entry.id} entries={entry.entries} projectId={project.id} session={session}/>;
|
|
2272
2300
|
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()}/>;
|
|
2301
|
+
if(entry.index===0&&hasIntroduction&&!introOpen)return null;
|
|
2273
2302
|
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
2303
|
return (
|
|
2275
2304
|
<div key={i} className={`msg ${m.role}`}>
|
|
@@ -2376,7 +2405,7 @@ function App() {
|
|
|
2376
2405
|
</button>
|
|
2377
2406
|
</div>
|
|
2378
2407
|
</form>
|
|
2379
|
-
{session&&<OpenAIVoicePanel key={project.id} session={session} connection={connection} transportRef={openaiVoice} onConnections={()=>setModal('connections')} onStart={()=>{voice.current?.cancel();setVoiceError('');}} onTranscript={(text,automatic)=>{
|
|
2408
|
+
{session&&<OpenAIVoicePanel hideTrigger openRequest={voiceOpenRequest} key={project.id} session={session} connection={connection} transportRef={openaiVoice} onConnections={()=>setModal('connections')} onStart={()=>{voice.current?.cancel();setVoiceError('');}} onTranscript={(text,automatic)=>{
|
|
2380
2409
|
if(automatic&&!input.trim()&&!attachments.length){setInput(text);void send(text);}
|
|
2381
2410
|
else setInput(current=>current+(current.trim()?' ':'')+text);
|
|
2382
2411
|
}}/>}
|
|
@@ -2429,22 +2458,20 @@ function App() {
|
|
|
2429
2458
|
/></div>
|
|
2430
2459
|
) : modal === "start-over" ? (
|
|
2431
2460
|
<>
|
|
2432
|
-
<h2>
|
|
2433
|
-
<p>Start
|
|
2461
|
+
<h2>Reset board and context?</h2>
|
|
2462
|
+
<p>Start fresh with an empty board, chat, requirements and decisions. Your saved projects and model connection stay.</p>
|
|
2434
2463
|
<div className="reset-actions">
|
|
2435
2464
|
<button className="btn primary" title="Download your project, then start a new one" aria-label="Save project and start over" onClick={() => {
|
|
2436
2465
|
if (!saveProjectFile()) return;
|
|
2437
|
-
|
|
2438
|
-
setModal(null);
|
|
2466
|
+
createOwnProject();
|
|
2439
2467
|
}}><Package size={18}/> Save & start new</button>
|
|
2440
2468
|
<button
|
|
2441
|
-
className="btn" title="Start a new project without downloading this one" aria-label="
|
|
2469
|
+
className="btn" title="Start a new project without downloading this one" aria-label="Confirm reset board and context"
|
|
2442
2470
|
onClick={() => {
|
|
2443
|
-
|
|
2444
|
-
setModal(null);
|
|
2471
|
+
createOwnProject();
|
|
2445
2472
|
}}
|
|
2446
2473
|
>
|
|
2447
|
-
<
|
|
2474
|
+
<Trash2 size={18}/> Reset board and context
|
|
2448
2475
|
</button>
|
|
2449
2476
|
<button className="btn ghost" onClick={() => setModal(null)}>
|
|
2450
2477
|
<ArrowLeft size={18}/> Keep working
|
|
@@ -0,0 +1,10 @@
|
|
|
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}
|
|
@@ -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';}
|