pycodex-ws 0.5.0__tar.gz → 0.5.1__tar.gz

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.
Files changed (47) hide show
  1. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/PKG-INFO +2 -2
  2. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_ws/feishu_card.py +2 -2
  3. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_ws/feishu_link.py +1 -1
  4. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pyproject.toml +2 -2
  5. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/app.py +8 -0
  6. pycodex_ws-0.5.1/workspace_server/content_views.css +1 -0
  7. pycodex_ws-0.5.1/workspace_server/content_views.mjs +32 -0
  8. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/composer.mjs +28 -2
  9. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/content_views.css +27 -3
  10. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/content_views.mjs +38 -2
  11. pycodex_ws-0.5.1/workspace_server/frontend/prompt_history.mjs +85 -0
  12. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/workspace.html +26 -5
  13. pycodex_ws-0.5.0/workspace_server/content_views.css +0 -1
  14. pycodex_ws-0.5.0/workspace_server/content_views.mjs +0 -31
  15. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/.gitignore +0 -0
  16. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/LICENSE +0 -0
  17. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/README.md +0 -0
  18. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_router/__init__.py +0 -0
  19. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_router/__main__.py +0 -0
  20. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_router/browser.py +0 -0
  21. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_router/client.py +0 -0
  22. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_router/index.html +0 -0
  23. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_router/protocol.py +0 -0
  24. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_router/server.py +0 -0
  25. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_ws/__init__.py +0 -0
  26. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/pycodex_ws/feishu_oauth.py +0 -0
  27. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/__init__.py +0 -0
  28. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/__main__.py +0 -0
  29. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/agent_access.py +0 -0
  30. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/content_views.LICENSE.txt +0 -0
  31. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/customization.py +0 -0
  32. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/editor.css +0 -0
  33. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/editor.mjs +0 -0
  34. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/.gitignore +0 -0
  35. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/build.mjs +0 -0
  36. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/editor.css +0 -0
  37. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/editor.mjs +0 -0
  38. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/editor_context.mjs +0 -0
  39. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/package-lock.json +0 -0
  40. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/package.json +0 -0
  41. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/touch_selection.mjs +0 -0
  42. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/frontend/view_labels.mjs +0 -0
  43. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/owned_process.py +0 -0
  44. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/runtime_info.py +0 -0
  45. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/workspace_views.py +0 -0
  46. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/workspaces.html +0 -0
  47. {pycodex_ws-0.5.0 → pycodex_ws-0.5.1}/workspace_server/workspaces.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycodex-ws
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Browser workspace and optional Feishu integration for python-codex
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.7
7
7
  Requires-Dist: pillow>=9.0.0
8
- Requires-Dist: python-codex==0.5.0
8
+ Requires-Dist: python-codex==0.5.1
9
9
  Requires-Dist: websockets<16,>=11
10
10
  Description-Content-Type: text/markdown
11
11
 
@@ -357,9 +357,9 @@ class PycodexCard:
357
357
  or getattr(action_payload, "input_value", None)
358
358
  or value.get("input_value")
359
359
  or ""
360
- ).strip()
360
+ )
361
361
  if not prompt and form_values:
362
- prompt = str(next(iter(form_values.values()))).strip()
362
+ prompt = str(next(iter(form_values.values())))
363
363
  action = str(
364
364
  value.get("action") or getattr(action_payload, "name", None) or "send"
365
365
  ).strip()
@@ -105,7 +105,7 @@ class PycodexRuntimeLink:
105
105
  "content": "Unsupported action: {0}".format(action.get("action")),
106
106
  }
107
107
  }
108
- prompt = str(action.get("prompt") or "").strip()
108
+ prompt = str(action.get("prompt") or "")
109
109
  future = asyncio.run_coroutine_threadsafe(
110
110
  self.runtime.submit_input(
111
111
  prompt, sender=str(action.get("sender") or "feishu")
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pycodex-ws"
7
- version = "0.5.0"
7
+ version = "0.5.1"
8
8
  description = "Browser workspace and optional Feishu integration for python-codex"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
11
11
  dependencies = [
12
- "python-codex==0.5.0",
12
+ "python-codex==0.5.1",
13
13
  "pillow>=9.0.0",
14
14
  "websockets>=11,<16",
15
15
  ]
@@ -397,6 +397,9 @@ class WebSessionView:
397
397
  active["submission_id"],
398
398
  )
399
399
  )
400
+ self._turns_by_turn_id[active["turn_id"]]["model_errors"] = list(
401
+ active.get("model_errors", [])
402
+ )
400
403
  elif event.reason == "title":
401
404
  self.set_session_title(state["title"])
402
405
  else:
@@ -464,6 +467,10 @@ class WebSessionView:
464
467
  turn["_thinking_active"] = True
465
468
  return
466
469
 
470
+ if isinstance(event, StreamErrorEvent):
471
+ turn.setdefault("model_errors", []).append(event.detail())
472
+ return
473
+
467
474
  if isinstance(event, ToolStartedEvent):
468
475
  turn["status"] = "tool"
469
476
  turn["tool_name"] = event.call.name
@@ -1815,6 +1822,7 @@ def _public_turn(turn: "typing.Dict[str, object]") -> "typing.Dict[str, object]"
1815
1822
  "queue": turn.get("queue", ""),
1816
1823
  "sender": turn.get("sender", ""),
1817
1824
  "kind": turn.get("kind", "assistant"),
1825
+ "model_errors": turn.get("model_errors", []),
1818
1826
  }
1819
1827
  ),
1820
1828
  )
@@ -0,0 +1 @@
1
+ .content-tabs{flex:1}.tab.content-tab{flex:0 0 auto}.tab.content-tab:first-child{min-width:0;padding-right:12px}.content-tab:focus-visible{outline:2px solid var(--accent);outline-offset:-1px}.content-tab-label{display:flex;align-items:baseline;gap:6px}.content-tab-name{flex:0 0 auto;max-width:100%;overflow:hidden;text-overflow:ellipsis}.content-tab-description{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:11px;font-weight:400;opacity:.75}.content-status{max-width:50%;font-size:11px;color:var(--danger);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.content-mode-toggle{flex:0 0 auto;padding:4px 8px;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--accent);font-size:11px;white-space:nowrap;cursor:pointer}.content-mode-toggle:hover{background:var(--accent-soft)}.content-mode-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.content-area{position:relative;min-width:0;min-height:0;overflow:hidden}.content-area>[hidden]{display:none}.content-view{width:100%;height:100%;overflow:auto;background:var(--board-chrome);color:var(--ink)}.content-view.code-view,.content-view.html-view{overflow:hidden}.snapshot-html{display:block;width:100%;height:100%;border:0;background:#fff}.snapshot{min-height:100%}.snapshot-heading{display:flex;align-items:center;gap:8px;min-height:42px;padding:6px 16px;border-bottom:1px solid var(--line);font-size:12px}.markdown-heading{position:sticky;top:0;z-index:1;background:var(--board-chrome)}.snapshot-label{min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow-wrap:anywhere}.snapshot-stamp{flex:0 0 auto;margin-left:auto;color:var(--muted);font-size:11px;white-space:nowrap}.snapshot-markdown{max-width:900px;padding:24px 32px;margin:0 auto}.snapshot-image{display:block;max-width:calc(100% - 40px);height:auto;margin:20px auto}.content-placeholder{padding:40px 24px;text-align:center;color:var(--muted)}@media(max-width:760px){.snapshot-heading{flex-wrap:wrap;gap:4px}.snapshot-stamp{margin-left:0}.markdown-heading{padding-right:48px}.markdown-heading .snapshot-label{flex:1 0 100%}.markdown-heading .snapshot-stamp{margin-left:auto}.snapshot-markdown{padding:20px}}
@@ -0,0 +1,32 @@
1
+ // Generated by workspace_server/frontend/build.mjs. See content_views.LICENSE.txt.
2
+ function j(l){function t(n){let r=n.lastIndexOf("/");return r<0?".":n.slice(0,r)||"/"}let e=new Map,i=l.map(n=>{let o=(n.kind==="diff"?n.title.split(" <-> "):[n.title]).map(c=>{let h=/^(?:([0-9a-f]{40}|[0-9a-f]{64})|.+ \(([0-9a-f]{40}|[0-9a-f]{64})\)):(.+)$/i.exec(c),u=h?h[3]:c,p=u.slice(u.lastIndexOf("/")+1);return e.has(p)||e.set(p,new Set),e.get(p).add(u),{path:u,name:p,revision:h?(h[1]||h[2]).slice(0,7):null}});return{view:n,sources:o}}),s=new Map;for(let n of e.values()){if(n.size<2)continue;let r=Array.from(n),o=r.map(c=>t(c).split("/"));r.forEach((c,h)=>{let u=1;for(;u<o[h].length&&o.some((p,S)=>S!==h&&p.slice(-u).join("/")===o[h].slice(-u).join("/"));)u+=1;s.set(c,o[h].slice(-u).join("/")||"/")})}return new Map(i.map(({view:n,sources:r})=>{let o=r.every(p=>p.path===r[0].path),c=o?r[0].name:r.map(p=>p.name).join(" \u2194 "),h="";o?h=s.get(r[0].path)||"":r.some(p=>s.has(p.path))&&(h=r.map(p=>s.get(p.path)||t(p.path)).join(" \u2194 "));let u=r.some(p=>p.revision)?r.map(p=>p.revision||"working tree").join(" \u2194 "):n.kind==="diff"&&o?"diff":"";return[n.id,{name:c,description:[h,u].filter(Boolean).join(" \xB7 ")}]}))}var E=class l{constructor(t,e,i,s,n,r=null){this.element=t,this.changed=e,this.submit=i,this.editContext=s,this.error=n,this.recallInput=r,this.recalledParts=null,this.composing=!1,this.selection={start:0,end:0},this.undo=[],this.redo=[],this.previous=this.read(),t.addEventListener("input",o=>{o.isComposing||this.record(),this.changed()}),t.addEventListener("compositionstart",()=>{this.composing=!0}),t.addEventListener("compositionend",()=>{this.composing=!1,this.record(),this.changed()}),document.addEventListener("selectionchange",()=>{this.captureSelection()&&this.changed()}),t.addEventListener("keydown",o=>this.keydown(o)),t.addEventListener("beforeinput",o=>{o.inputType==="historyUndo"||o.inputType==="historyRedo"?(o.preventDefault(),this.history(o.inputType==="historyUndo")):o.inputType.startsWith("format")&&o.preventDefault()}),t.addEventListener("click",o=>{let c=o.target.closest(".context-token");c&&this.editContext(JSON.parse(c.dataset.context))}),t.addEventListener("mousedown",o=>{o.target.closest(".context-token")&&o.preventDefault()}),t.addEventListener("copy",o=>this.copy(o,!1)),t.addEventListener("cut",o=>this.copy(o,!0)),t.addEventListener("paste",o=>{o.preventDefault(),this.paste(o.clipboardData)}),t.addEventListener("dragover",o=>o.preventDefault()),t.addEventListener("drop",o=>{o.preventDefault();let c=document.caretRangeFromPoint(o.clientX,o.clientY);if(c&&t.contains(c.startContainer)){let h=window.getSelection();h.removeAllRanges(),h.addRange(c),this.captureSelection()}this.paste(o.dataTransfer)})}static size(t){return t.reduce((e,i)=>e+(i.type==="context"?1:i.text.length),0)}static text(t){return t.map(e=>e.text).join("")}static slice(t,e,i){let s=[],n=0;for(let r of t){let o=l.size([r]);e<n+o&&i>n&&s.push(r.type==="context"?{...r}:{type:"text",text:r.text.slice(Math.max(0,e-n),i-n)}),n+=o}return s}parts(t=this.element){let e=[],i=n=>{if(!n)return;let r=e.at(-1);(r==null?void 0:r.type)==="text"?r.text+=n:e.push({type:"text",text:n})},s=n=>{var r;for(let o of n.childNodes)o.nodeType===Node.TEXT_NODE?i(o.data):(r=o.dataset)!=null&&r.context?e.push(JSON.parse(o.dataset.context)):o.nodeName==="BR"?!o.hasAttribute("data-tail")&&n.childNodes.length>1&&i(`
3
+ `):(["DIV","P"].includes(o.nodeName)&&e.length&&!e.at(-1).text.endsWith(`
4
+ `)&&i(`
5
+ `),s(o))};return s(t),e}captureSelection(){if(document.activeElement!==this.element)return!1;let t=window.getSelection();if(!t.rangeCount)return!1;let e=t.getRangeAt(0);if(!this.element.contains(e.startContainer)||!this.element.contains(e.endContainer))return!1;let i=document.createRange();i.selectNodeContents(this.element),i.setEnd(e.startContainer,e.startOffset);let s=l.size(this.parts(i.cloneContents()));return this.selection={start:s,end:s+l.size(this.parts(e.cloneContents()))},!0}read(){return this.captureSelection(),{parts:this.parts(),selection:{...this.selection}}}set(t){this.recalledParts=null,this.undo=[],this.redo=[],this.render(t),document.activeElement===this.element&&this.focus(),this.previous=this.read()}render(t){let e=t.parts||[];this.element.replaceChildren();for(let s of e)if(s.type==="text")this.element.append(document.createTextNode(s.text));else{let n=document.createElement("span");n.className="context-token",n.contentEditable="false",n.dataset.context=JSON.stringify(s),n.textContent=s.label,n.title=s.label+`
6
+ Click to edit context; Backspace / Delete to remove`,this.element.append(n,document.createTextNode(""))}if(!e.length||e.at(-1).type==="text"&&e.at(-1).text.endsWith(`
7
+ `)){let s=document.createElement("br");s.dataset.tail="",this.element.append(s)}let i=l.size(e);this.selection=t.selection||{start:i,end:i},this.selection={start:Math.min(this.selection.start,i),end:Math.min(this.selection.end,i)},this.element.classList.toggle("empty",!e.length)}focus(){this.element.focus({preventScroll:!0});let t=s=>{let n=r=>{var o;for(let c=0;c<r.childNodes.length;c++){let h=r.childNodes[c];if(h.nodeType===Node.TEXT_NODE){if(s<=h.length)return[h,s];s-=h.length}else if((o=h.dataset)!=null&&o.context){if(s===0)return[r,c];if(s-=1,s===0)return[r,c+1]}else if(h.nodeName==="BR"){if(s===0)return[r,c];h.hasAttribute("data-tail")||(s-=1)}else{let u=n(h);if(u)return u}}};return n(this.element)||[this.element,this.element.childNodes.length]},e=document.createRange();e.setStart(...t(this.selection.start)),e.setEnd(...t(this.selection.end));let i=window.getSelection();i.removeAllRanges(),i.addRange(e)}record(){let t=this.read();JSON.stringify(t.parts)!==JSON.stringify(this.previous.parts)&&(this.undo.push(this.previous),this.undo.length>40&&this.undo.shift(),this.redo=[],this.previous=t),this.element.classList.toggle("empty",!t.parts.length)}replace(t,e,i){let s=this.read(),n=[...l.slice(s.parts,0,t),...i,...l.slice(s.parts,e,l.size(s.parts))],r=t+l.size(i);this.render({parts:n,selection:{start:r,end:r}}),this.focus(),this.record(),this.changed()}insert(t){this.captureSelection(),this.replace(this.selection.start,this.selection.end,t.map(e=>({...e,type:"context",id:crypto.getRandomValues(new Uint32Array(4)).join("-")})))}updateContext(t,e){for(let i of this.element.querySelectorAll(".context-token")){let s=JSON.parse(i.dataset.context);s.id===t&&(i.dataset.context=JSON.stringify({...s,text:e}))}this.record(),this.changed()}removeContext(t){let e=0;for(let i of this.parts()){if(i.type==="context"&&i.id===t){this.replace(e,e+1,[]);return}e+=l.size([i])}}history(t){let e=t?this.undo:this.redo;e.length&&((t?this.redo:this.undo).push(this.read()),this.render(e.pop()),this.focus(),this.previous=this.read(),this.changed())}keydown(t){var i;if(t.isComposing||this.composing||t.keyCode===229)return;let e=t.ctrlKey||t.metaKey;if(this.recallInput&&["ArrowUp","ArrowDown"].includes(t.key)&&!e&&!t.altKey&&!t.shiftKey){let s=this.read();if(s.selection.start===s.selection.end){let n=s.selection.start,r=l.text(s.parts),o=t.key==="ArrowUp",c=l.text(l.slice(s.parts,0,n)),h=l.text(l.slice(s.parts,n,l.size(s.parts)));if(this.recalledParts===JSON.stringify(s.parts)||!r||!(o?c:h).includes(`
8
+ `)){let p=this.recallInput(o,s);p&&(t.preventDefault(),this.render(p),this.recalledParts=JSON.stringify(p.parts),this.focus(),this.record(),this.changed())}}}else if(e&&(t.key.toLowerCase()==="z"||t.key.toLowerCase()==="y"))t.preventDefault(),this.history(t.key.toLowerCase()==="z"&&!t.shiftKey);else if(t.key==="Enter")t.preventDefault(),t.shiftKey?(this.captureSelection(),this.replace(this.selection.start,this.selection.end,[{type:"text",text:`
9
+ `}])):this.submit();else if(["Backspace","Delete"].includes(t.key)&&!e&&!t.altKey){this.captureSelection();let{start:s,end:n}=this.selection,r=t.key==="Backspace"?s-1:s;s===n&&((i=l.slice(this.parts(),r,r+1)[0])==null?void 0:i.type)==="context"&&(t.preventDefault(),this.replace(r,r+1,[]))}}copy(t,e){if(!this.captureSelection()||this.selection.start===this.selection.end)return;let i=l.slice(this.parts(),this.selection.start,this.selection.end);t.clipboardData.setData("text/plain",l.text(i)),t.clipboardData.setData("application/x-pycodex-parts",JSON.stringify(i)),t.preventDefault(),e&&this.replace(this.selection.start,this.selection.end,[])}paste(t){try{let e=t.getData("application/x-pycodex-parts"),i=e?JSON.parse(e):[{type:"text",text:t.getData("text/plain")}];if(!Array.isArray(i)||i.some(s=>!s||typeof s.text!="string"||!["text","context"].includes(s.type)||s.type==="context"&&typeof s.label!="string"))throw new Error("Invalid context format in clipboard.");this.captureSelection(),this.replace(this.selection.start,this.selection.end,i.map(s=>s.type==="context"?{...s,id:crypto.getRandomValues(new Uint32Array(4)).join("-")}:s))}catch(e){this.error(e.message)}}};function W(l){let t=l.trim().match(/^<(clock_tick|exec_command_completed)>\s*([\s\S]*?)\s*<\/\1>$/);if(!t)return!1;try{let e=JSON.parse(t[2]);return e!==null&&typeof e=="object"&&!Array.isArray(e)}catch(e){return!1}}var D=class{constructor(){this.entries=[],this.seen=new Set,this.pending=[],this.index=null,this.draft=null}remember(t){let e=t.parts.map(i=>({...i}));E.text(e).trim()&&(this.append({parts:e}),this.pending.push(E.text(e).trim()),this.pending.length>100&&this.pending.shift(),this.reset())}append(t){this.entries.push(t),this.entries.length>100&&(this.entries.shift(),this.index!==null&&(this.index=Math.max(0,this.index-1)))}update(t){for(let e of t||[]){let i=e.submission_id||e.turn_id;if(!i||this.seen.has(i))continue;this.seen.add(i);let s=e.prompt;if(!s||!s.trim())continue;let n=this.pending.indexOf(s.trim());if(n!==-1){this.pending.splice(n,1);continue}e.kind==="compaction"||W(s)||!["web","cli","feishu","resume","user"].includes(e.sender)||this.append({parts:[{type:"text",text:s}]})}}reset(){this.index=null,this.draft=null}recall(t,e){if(this.index!==null&&JSON.stringify(e.parts)!==JSON.stringify(this.entries[this.index].parts)&&this.reset(),t){if(!this.entries.length||this.index===0)return null;this.index===null&&(this.draft=e,this.index=this.entries.length),this.index-=1}else{if(this.index===null)return null;if(this.index+=1,this.index>=this.entries.length){let i=this.draft;return this.reset(),i}}return{parts:this.entries[this.index].parts.map(i=>({...i}))}}};function Q(l,t=!0){if(typeof l!="string"||!l||l!==l.trim()||/[\x00-\x1f\x7f]/.test(l))return!1;if(/^(?:diff:|git:|workspace:\/\/canvas\/?$)/i.test(l))return!0;if(!t||/^(?:https?|ftps?|mailto|tel|sms|javascript|vbscript|data|blob|about|file):/i.test(l))return!1;let e=l.split("#");if(e.length>2||e.length===2&&!/^L-?\d+$/i.test(e[1]))return!1;let i=e[0].replace(/:-?\d+$/,"");return!!i&&!i.startsWith("//")&&!i.includes("?")&&(!/^[a-z][a-z\d+.-]*:/i.test(i)||/^[a-z]:(?:[/\\]|%2f|%5c)/i.test(i))}var O=class{constructor({request:t,onChange:e,onSelect:i,selected:s=null,enableBoard:n=!0}){this.request=t,this.onChange=e,this.onSelect=i,this.enableBoard=n,this.views=[],this.activeId=n?"board":null,this.snapshot=null,this.selection=null,this.displayMode=null,this.loading=!1,this.error="",this.closing=new Set,this.cache=new Map,this.initialSelection=s,this.initialized=!1,this.mutation=0,this.pollRequest=null,this.contentRequest=null,this.openRequest=null,this.timer=null}start(){this.timer===null&&(this.poll(),this.timer=setInterval(()=>this.poll(),2e3))}stop(){clearInterval(this.timer),this.timer=null,this.cancelOpen(),this.pollRequest&&this.pollRequest.abort(),this.contentRequest&&this.contentRequest.abort(),this.pollRequest=null,this.contentRequest=null,this.loading=!1}async poll(){if(this.pollRequest||this.openRequest)return;let t=new AbortController,e=this.mutation;this.pollRequest=t;try{let i=await this.request("api/views",{signal:t.signal});if(this.pollRequest!==t||e!==this.mutation)return;this.updateViews(i.views);let s=new Set(this.views.map(r=>r.id));this.error="";let n=this.activeId;this.initialized||(n=this.initialSelection||this.defaultId(),this.initialized=!0),(n==="board"&&!this.enableBoard||n!==null&&n!=="board"&&!s.has(n))&&(n=this.defaultId()),n!==this.activeId||n!==null&&n!=="board"&&!this.snapshot&&!this.loading?this.select(n):(n!==null&&n!=="board"&&!this.cache.has(n)&&!this.contentRequest&&this.load(n),this.onChange(this))}catch(i){this.pollRequest===t&&e===this.mutation&&i.name!=="AbortError"&&(this.error="Unable to load tabs: "+i.message,this.onChange(this))}finally{this.pollRequest===t&&(this.pollRequest=null)}}defaultId(){return this.enableBoard?"board":this.views.length?this.views[0].id:null}updateViews(t){this.views=t;let e=new Map(t.map(i=>[i.id,i.revision]));for(let[i,s]of this.cache)(!e.has(i)||s.revision<e.get(i))&&this.cache.delete(i)}cancelOpen(){this.openRequest&&this.openRequest.abort(),this.openRequest=null}async open(t,e,i){if(this.cancelOpen(),/^workspace:\/\/canvas\/?$/i.test(t)){if(!this.enableBoard)throw new Error("Canvas is disabled for this workspace.");await this.select("board");return}let s=new AbortController;this.openRequest=s,this.mutation+=1,this.error="",this.onChange(this);try{let n=await this.request("api/views/open",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({href:t,src_path:e,session_id:i}),signal:s.signal});if(this.openRequest!==s)return;this.mutation+=1,this.updateViews(n.views);let r=n.view;return this.cache.set(r.id,r),this.initialized=!0,this.openRequest=null,this.select(r.id,r.selection?{...r.selection,revision:r.reveal_revision,contentRevision:r.revision}:null,r.selection?"source":"preview")}catch(n){if(this.openRequest!==s||n.name==="AbortError")return;throw this.error="Unable to open view: "+n.message,n}finally{this.openRequest===s&&(this.openRequest=null,this.onChange(this))}}async select(t,e=null,i=null){if(t==="board"&&!this.enableBoard||t!==null&&t!=="board"&&!this.views.some(n=>n.id===t))return;this.cancelOpen();let s=t===this.activeId?this.snapshot:null;this.contentRequest&&this.contentRequest.abort(),this.contentRequest=null,this.activeId=t,this.selection=e,this.displayMode=i,this.initialized||(this.initialSelection=t),this.snapshot=this.cache.get(t)||s,this.loading=t!==null&&t!=="board"&&!this.snapshot,this.error="",this.onSelect(t),this.onChange(this),!(t===null||t==="board"||this.cache.has(t))&&await this.load(t)}setDisplayMode(t){!this.snapshot||this.activeId===null||this.activeId==="board"||!["source","preview"].includes(t)||(this.cancelOpen(),this.selection=null,this.displayMode=t,this.onChange(this))}async load(t){let e=new AbortController;this.contentRequest=e;try{let i=await this.request("api/views/"+encodeURIComponent(t),{signal:e.signal});if(this.contentRequest!==e||this.activeId!==t)return;let s=this.views.find(n=>n.id===t);if(!s||i.revision<s.revision)return;this.cache.set(t,i),this.snapshot=i,this.error=""}catch(i){if(this.contentRequest!==e||this.activeId!==t||i.name==="AbortError")return;this.error="Unable to load view: "+i.message}finally{this.contentRequest===e&&(this.contentRequest=null,this.loading=!1,this.onChange(this))}}async close(t){if(!(t===null||t==="board"||this.closing.has(t))){this.cancelOpen(),this.closing.add(t),this.mutation+=1,this.onChange(this);try{await this.request("api/views/"+encodeURIComponent(t),{method:"DELETE"}),this.mutation+=1;let e=this.views.findIndex(i=>i.id===t);this.views=this.views.filter(i=>i.id!==t),this.cache.delete(t),this.activeId===t&&await this.select(e>0?this.views[e-1].id:this.defaultId())}catch(e){this.error="Unable to close tab: "+e.message}finally{this.closing.delete(t),this.onChange(this)}}}};function g(l,t,e,i){let s=l.createElement(t);return s.className=e,i!==void 0&&(s.textContent=i),s}var P=class{constructor(t,e=[0,0]){this.document=t.ownerDocument,this.window=this.document.defaultView,this.position=e,this.generation=0,this.frame=g(this.document,"iframe","snapshot-html"),this.frame.title="HTML preview",this.receive=i=>{if(i.source!==this.frame.contentWindow)return;let s=i.data;!s||s.generation!==this.generation||(s.type==="workspace-preview-ready"?this.frame.contentWindow.postMessage({type:"workspace-preview-restore",generation:this.generation,position:this.position},"*"):s.type==="workspace-preview-scroll"&&Array.isArray(s.position)&&s.position.length===2&&s.position.every(Number.isFinite)&&(this.position=s.position))},this.window.addEventListener("message",this.receive),t.appendChild(this.frame)}update(t){if(t===this.source)return;let e=new this.window.DOMParser().parseFromString(t,"text/html"),i=this.generation+1,s=e.createElement("script");s.textContent=`(() => {
10
+ const generation = ${i};
11
+ let restored = false;
12
+ function report() {
13
+ if (restored) parent.postMessage({
14
+ type: "workspace-preview-scroll", generation, position: [scrollX, scrollY],
15
+ }, "*");
16
+ }
17
+ addEventListener("message", event => {
18
+ const message = event.data;
19
+ if (event.source !== parent || !message ||
20
+ message.type !== "workspace-preview-restore" || message.generation !== generation) return;
21
+ scrollTo(message.position[0], message.position[1]);
22
+ restored = true;
23
+ report();
24
+ });
25
+ addEventListener("scroll", report, {passive: true});
26
+ addEventListener("load", () => parent.postMessage({
27
+ type: "workspace-preview-ready", generation,
28
+ }, "*"));
29
+ })();`,e.head.prepend(s);let n=e.doctype?new this.window.XMLSerializer().serializeToString(e.doctype)+`
30
+ `:"";this.frame.srcdoc=n+e.documentElement.outerHTML,this.generation=i,this.source=t}capture(){return this.position}destroy(){this.window.removeEventListener("message",this.receive)}},T=class{constructor({content:t,renderMarkdown:e,loadEditor:i=null,onInsertContext:s=null,onChangeMode:n=null,createHtmlPreview:r=(o,c)=>new P(o,c)}){this.content=t,this.document=t.ownerDocument,this.renderMarkdown=e,this.onInsertContext=s,this.onChangeMode=n,this.loadEditor=i||(()=>this.loadEditorAssets()),this.editorAssets=null,this.createHtmlPreview=r,this.activeId=null,this.editor=null,this.htmlPreview=null,this.generation=0,this.remaining=new Set,this.editorStates=new Map,this.previews=new Map,this.scrollPositions=new Map,this.sourceViews=new Set,this.pendingSelection=null,this.selectionRevision=0}loadEditorAssets(){if(!this.editorAssets){let t=this.document.createElement("link");t.rel="stylesheet",t.href=new URL("./editor.css",import.meta.url).href;let e=new Promise((i,s)=>{t.addEventListener("load",i,{once:!0}),t.addEventListener("error",()=>s(new Error("Editor stylesheet request failed.")),{once:!0})});this.document.head.insertBefore(t,this.document.head.querySelector("[data-pycodex-custom-style]")),this.editorAssets=Promise.all([import("./editor.mjs"),e]).then(([i])=>i).catch(i=>{throw t.remove(),this.editorAssets=null,i})}return this.editorAssets}retain(t){this.remaining=new Set(t);for(let e of[this.editorStates,this.previews,this.scrollPositions,this.sourceViews])for(let i of e.keys())this.remaining.has(i)||e.delete(i)}clear(t=""){this.generation+=1,this.editor?(this.remaining.has(this.activeId)&&this.editorStates.set(this.activeId,this.editor.capture()),this.editor.destroy(),this.editor=null):this.htmlPreview?(this.remaining.has(this.activeId)&&this.scrollPositions.set(this.activeId,this.htmlPreview.capture()),this.htmlPreview.destroy(),this.htmlPreview=null):this.remaining.has(this.activeId)&&this.previews.has(this.activeId)&&this.scrollPositions.set(this.activeId,[this.content.scrollLeft,this.content.scrollTop]),this.activeId=null,this.content.classList.remove("code-view"),this.content.classList.remove("html-view"),this.content.textContent="",t&&this.content.appendChild(g(this.document,"div","content-placeholder",t))}addModeToggle(t,e,i=null){if(!this.onChangeMode)return;let s=g(this.document,"button","content-mode-toggle",e?"Show preview":"Show source");s.type="button",s.title=e?"Show rendered Markdown":"Show Markdown source",s.setAttribute("aria-label",s.textContent),s.addEventListener("click",()=>this.onChangeMode(e?"preview":"source")),t.insertBefore(s,i)}async show(t,e,i=null,s=null){i&&e.revision<i.contentRevision&&(i=null),this.pendingSelection=i,s==="preview"?this.sourceViews.delete(t):(i||s==="source")&&this.sourceViews.add(t);let n=e.documents[0],r=e.kind==="file"&&!this.sourceViews.has(t),o=r&&/\.(md|markdown)$/i.test(n.label)&&this.renderMarkdown,c=r&&/\.html?$/i.test(n.label);if(this.activeId===t&&this.htmlPreview&&c)return this.htmlPreview.update(n.text);if(this.activeId===t&&this.editor&&!o&&!c){let w=this.generation;try{await this.editor.update(e),w===this.generation&&await this.applySelection(t,i)}catch(v){if(w===this.generation)throw v}return}this.clear(),this.activeId=t;let h=this.generation;if(e.kind==="diff"||!n.image_url&&!o&&!c){this.content.classList.add("code-view"),this.content.appendChild(g(this.document,"div","content-placeholder","Loading code viewer\u2026"));try{let w=await this.loadEditor();if(h!==this.generation)return;this.content.textContent="";let v=g(this.document,"div","workspace-editor");this.content.appendChild(v);let m=this.onInsertContext&&(x=>{h===this.generation&&this.activeId===t&&this.onInsertContext(x)}),I=await w.createSnapshotEditor(v,e,this.editorStates.get(t),m);if(h!==this.generation){I.destroy();return}if(this.editor=I,this.onChangeMode&&this.renderMarkdown&&e.kind==="file"&&/\.(md|markdown)$/i.test(n.label)){let x=v.querySelector(".editor-toolbar"),a=x.querySelector(".editor-actions");x.insertBefore(g(this.document,"span","snapshot-stamp","Read only"),a),this.addModeToggle(x,!0,a)}}catch(w){if(h!==this.generation)return;this.content.textContent="",this.content.appendChild(g(this.document,"div","content-placeholder","Unable to load code viewer: "+w.message));return}await this.applySelection(t,i);return}if(c)return this.content.classList.add("html-view"),this.htmlPreview=this.createHtmlPreview(this.content,this.scrollPositions.get(t)),this.htmlPreview.update(n.text);if(!this.previews.has(t)){let w=g(this.document,"div","snapshot"),v=g(this.document,"div","snapshot-heading");if(v.appendChild(g(this.document,"span","snapshot-label",n.label)),v.appendChild(g(this.document,"span","snapshot-stamp","Read only")),o&&this.onChangeMode&&(v.classList.add("markdown-heading"),this.addModeToggle(v,!1)),w.appendChild(v),n.image_url){let m=g(this.document,"img","snapshot-image");m.alt=n.label,w.appendChild(m)}else w.appendChild(g(this.document,"div","snapshot-markdown markdown"));this.previews.set(t,w)}this.content.appendChild(this.previews.get(t));let u=this.content.querySelector(".snapshot-image");if(u&&u.src!==n.image_url&&(u.src=n.image_url),o){let w=this.content.querySelector(".snapshot-markdown");n.source&&w.setAttribute("data-src-path",n.source.path),this.renderMarkdown(w,n.text)}let[p,S]=this.scrollPositions.get(t)||[0,0];this.content.scrollLeft=p,this.content.scrollTop=S}async applySelection(t,e){if(!e||e.revision<=this.selectionRevision)return;let i=this.editor,s=()=>this.activeId===t&&this.editor===i&&this.pendingSelection===e&&e.revision>this.selectionRevision;s()&&await i.revealLine(e.document,e.line,s)&&(this.selectionRevision=e.revision)}};function Z({tabs:l,frame:t,content:e,status:i,request:s,renderMarkdown:n,onInsertContext:r,enableBoard:o=!0}){let c=l.ownerDocument,h=new T({content:e,renderMarkdown:n,onInsertContext:r,onChangeMode:a=>m.setDisplayMode(a)}),u,p=null,S=!1,w=null,v=null,m;function I(a){let k=c.getElementById("content-tab-"+a);k&&(k.focus({preventScroll:!0}),k.scrollIntoView({block:"nearest",inline:"nearest"}))}function x(a){let k=a.snapshot?a.snapshot.documents.map(d=>d.error):[];i.textContent=[a.error,...k].filter(Boolean).join("; ")||(a.openRequest?"Opening view\u2026":""),i.title=i.textContent,i.hidden=!i.textContent;let R=new Map(Array.from(l.children,d=>[d.dataset.viewId,d])),$=(o?[{id:"board",title:"Canvas",kind:"board"}]:[]).concat(a.views),z=j(a.views);z.set("board",{name:"Canvas",description:""}),$.forEach((d,q)=>{let f=R.get(d.id);if(!f){f=g(c,"div","tab content-tab"),f.dataset.viewId=d.id,f.id="content-tab-"+d.id,f.setAttribute("role","tab"),f.setAttribute("aria-controls",d.id==="board"?t.id:e.id);let N=g(c,"span","tab-label content-tab-label");if(N.appendChild(g(c,"span","content-tab-name")),N.appendChild(g(c,"span","content-tab-description")),f.appendChild(N),f.addEventListener("click",()=>m.select(d.id)),f.addEventListener("keydown",y=>{if(y.target!==f)return;let b=(o?["board"]:[]).concat(m.views.map(K=>K.id)),V=b.indexOf(d.id),C=null;y.key==="ArrowRight"&&(C=b[(V+1)%b.length]),y.key==="ArrowLeft"&&(C=b[(V+b.length-1)%b.length]),y.key==="Home"&&(C=b[0]),y.key==="End"&&(C=b[b.length-1]),(y.key==="Enter"||y.key===" ")&&(C=d.id),y.key==="Delete"&&d.id!=="board"&&(y.preventDefault(),m.close(d.id)),C!==null&&(y.preventDefault(),m.select(C),c.getElementById("content-tab-"+C).focus())}),d.id!=="board"){let y=g(c,"button","tab-close content-tab-close");y.type="button",y.title="Close tab",y.setAttribute("aria-label","Close "+d.title),y.innerHTML='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>',y.addEventListener("click",b=>{b.stopPropagation(),m.close(d.id)}),f.appendChild(y)}}R.delete(d.id);let A=z.get(d.id);f.querySelector(".content-tab-name").textContent=A.name;let U=f.querySelector(".content-tab-description");U.textContent=A.description,U.hidden=!A.description,f.className="tab content-tab"+(d.id===a.activeId?" active":""),f.tabIndex=d.id===a.activeId?0:-1,f.title=d.id==="board"?l.dataset.boardTitle:d.title+(d.live?`
31
+ Read only \xB7 follows file changes \xB7 `:`
32
+ Read-only snapshot \xB7 `)+new Date(d.created_at).toLocaleString(),f.setAttribute("aria-selected",String(d.id===a.activeId)),f.setAttribute("aria-label",d.title);let M=f.querySelector(".content-tab-close");M&&(M.disabled=a.closing.has(d.id),M.setAttribute("aria-label","Close "+d.title)),l.children[q]!==f&&l.insertBefore(f,l.children[q]||null)}),R.forEach(d=>d.remove()),h.retain(a.views.map(d=>d.id));let L=a.activeId==="board",B=u!==a.activeId,J=B||p!==a.snapshot||S!==a.loading||w!==a.selection||v!==a.displayMode;if(L&&J&&h.clear(),t.hidden=!L,e.hidden=L,!!J&&(u=a.activeId,p=a.snapshot,S=a.loading,w=a.selection,v=a.displayMode,a.activeId?e.setAttribute("aria-labelledby","content-tab-"+a.activeId):e.removeAttribute("aria-labelledby"),e.setAttribute("aria-busy",String(a.loading)),!L)){if(a.snapshot){let d=a.activeId,q=a.snapshot;h.show(d,q,a.selection,a.displayMode).catch(f=>{u!==d||p!==q||(p=null,a.error="Unable to update view: "+f.message,i.textContent=i.title=a.error,i.hidden=!1)})}else h.clear(a.loading?"Loading view\u2026":a.activeId?"View unavailable.":"Open a file link to view it here.");B&&I(a.activeId)}}return m=new O({request:s,enableBoard:o,onChange:x,onSelect:a=>{e.closest(".workspace").dataset.mobilePane="board",I(a)}}),x(m),{start(){m.start(),x(m)},stop(){m.stop(),h.clear(),u=null},poll(){return m.poll()},open(a,k,R){return m.open(a,k,R)}}}export{E as Composer,O as ContentTabs,D as PromptHistory,T as SnapshotDisplay,Q as isWorkspaceLink,Z as mountContentViews};
@@ -1,11 +1,13 @@
1
1
  /** A plain-text editor whose context tokens occupy one cursor position each. */
2
2
  export class Composer {
3
- constructor(element, changed, submit, editContext, error) {
3
+ constructor(element, changed, submit, editContext, error, recallInput = null) {
4
4
  this.element = element;
5
5
  this.changed = changed;
6
6
  this.submit = submit;
7
7
  this.editContext = editContext;
8
8
  this.error = error;
9
+ this.recallInput = recallInput;
10
+ this.recalledParts = null;
9
11
  this.composing = false;
10
12
  this.selection = { start: 0, end: 0 };
11
13
  this.undo = [];
@@ -124,6 +126,7 @@ export class Composer {
124
126
  }
125
127
 
126
128
  set(value) {
129
+ this.recalledParts = null;
127
130
  this.undo = [];
128
131
  this.redo = [];
129
132
  this.render(value);
@@ -256,7 +259,30 @@ export class Composer {
256
259
  keydown(event) {
257
260
  if (event.isComposing || this.composing || event.keyCode === 229) return;
258
261
  const modifier = event.ctrlKey || event.metaKey;
259
- if (modifier && (event.key.toLowerCase() === "z" || event.key.toLowerCase() === "y")) {
262
+ if (this.recallInput && ["ArrowUp", "ArrowDown"].includes(event.key) &&
263
+ !modifier && !event.altKey && !event.shiftKey) {
264
+ const current = this.read();
265
+ if (current.selection.start === current.selection.end) {
266
+ const offset = current.selection.start;
267
+ const text = Composer.text(current.parts);
268
+ // Keep vertical cursor movement inside multiline drafts.
269
+ const back = event.key === "ArrowUp";
270
+ const prefix = Composer.text(Composer.slice(current.parts, 0, offset));
271
+ const suffix = Composer.text(Composer.slice(current.parts, offset, Composer.size(current.parts)));
272
+ const browsing = this.recalledParts === JSON.stringify(current.parts);
273
+ if (browsing || !text || !(back ? prefix : suffix).includes("\n")) {
274
+ const recalled = this.recallInput(back, current);
275
+ if (recalled) {
276
+ event.preventDefault();
277
+ this.render(recalled);
278
+ this.recalledParts = JSON.stringify(recalled.parts);
279
+ this.focus();
280
+ this.record();
281
+ this.changed();
282
+ }
283
+ }
284
+ }
285
+ } else if (modifier && (event.key.toLowerCase() === "z" || event.key.toLowerCase() === "y")) {
260
286
  event.preventDefault();
261
287
  this.history(event.key.toLowerCase() === "z" && !event.shiftKey);
262
288
  } else if (event.key === "Enter") {
@@ -41,6 +41,19 @@
41
41
  overflow: hidden;
42
42
  text-overflow: ellipsis;
43
43
  }
44
+ .content-mode-toggle {
45
+ flex: 0 0 auto;
46
+ padding: 4px 8px;
47
+ border: 0;
48
+ border-radius: var(--radius-sm);
49
+ background: transparent;
50
+ color: var(--accent);
51
+ font-size: 11px;
52
+ white-space: nowrap;
53
+ cursor: pointer;
54
+ }
55
+ .content-mode-toggle:hover { background: var(--accent-soft); }
56
+ .content-mode-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
44
57
  .content-area { position: relative; min-width: 0; min-height: 0; overflow: hidden; }
45
58
  .content-area > [hidden] { display: none; }
46
59
  .content-view {
@@ -56,13 +69,21 @@
56
69
  .snapshot { min-height: 100%; }
57
70
  .snapshot-heading {
58
71
  display: flex;
59
- align-items: baseline;
60
- gap: 20px;
61
- padding: 10px 16px;
72
+ align-items: center;
73
+ gap: 8px;
74
+ min-height: 42px;
75
+ padding: 6px 16px;
62
76
  border-bottom: 1px solid var(--line);
63
77
  font-size: 12px;
64
78
  }
79
+ .markdown-heading {
80
+ position: sticky;
81
+ top: 0;
82
+ z-index: 1;
83
+ background: var(--board-chrome);
84
+ }
65
85
  .snapshot-label {
86
+ min-width: 0;
66
87
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
67
88
  overflow-wrap: anywhere;
68
89
  }
@@ -79,5 +100,8 @@
79
100
  @media (max-width: 760px) {
80
101
  .snapshot-heading { flex-wrap: wrap; gap: 4px; }
81
102
  .snapshot-stamp { margin-left: 0; }
103
+ .markdown-heading { padding-right: 48px; }
104
+ .markdown-heading .snapshot-label { flex: 1 0 100%; }
105
+ .markdown-heading .snapshot-stamp { margin-left: auto; }
82
106
  .snapshot-markdown { padding: 20px; }
83
107
  }
@@ -1,6 +1,7 @@
1
1
  // Workspace content tabs, live updates and editor lifecycle.
2
2
  import {tabLabels} from "./view_labels.mjs";
3
3
  export {Composer} from "./composer.mjs";
4
+ export {PromptHistory} from "./prompt_history.mjs";
4
5
 
5
6
  export function isWorkspaceLink(href, filePaths = true) {
6
7
  if (typeof href !== "string" || !href || href !== href.trim() || /[\x00-\x1f\x7f]/.test(href)) {
@@ -170,6 +171,15 @@ export class ContentTabs {
170
171
  await this.load(id);
171
172
  }
172
173
 
174
+ setDisplayMode(displayMode) {
175
+ if (!this.snapshot || this.activeId === null || this.activeId === "board" ||
176
+ !["source", "preview"].includes(displayMode)) return;
177
+ this.cancelOpen();
178
+ this.selection = null;
179
+ this.displayMode = displayMode;
180
+ this.onChange(this);
181
+ }
182
+
173
183
  async load(id) {
174
184
  const request = new AbortController();
175
185
  this.contentRequest = request;
@@ -296,12 +306,13 @@ class HtmlPreview {
296
306
  }
297
307
 
298
308
  export class SnapshotDisplay {
299
- constructor({content, renderMarkdown, loadEditor = null, onInsertContext = null,
309
+ constructor({content, renderMarkdown, loadEditor = null, onInsertContext = null, onChangeMode = null,
300
310
  createHtmlPreview = (parent, position) => new HtmlPreview(parent, position)}) {
301
311
  this.content = content;
302
312
  this.document = content.ownerDocument;
303
313
  this.renderMarkdown = renderMarkdown;
304
314
  this.onInsertContext = onInsertContext;
315
+ this.onChangeMode = onChangeMode;
305
316
  this.loadEditor = loadEditor || (() => this.loadEditorAssets());
306
317
  this.editorAssets = null;
307
318
  this.createHtmlPreview = createHtmlPreview;
@@ -377,6 +388,17 @@ export class SnapshotDisplay {
377
388
  }
378
389
  }
379
390
 
391
+ addModeToggle(parent, sourceMode, before = null) {
392
+ if (!this.onChangeMode) return;
393
+ const button = element(this.document, "button", "content-mode-toggle",
394
+ sourceMode ? "Show preview" : "Show source");
395
+ button.type = "button";
396
+ button.title = sourceMode ? "Show rendered Markdown" : "Show Markdown source";
397
+ button.setAttribute("aria-label", button.textContent);
398
+ button.addEventListener("click", () => this.onChangeMode(sourceMode ? "preview" : "source"));
399
+ parent.insertBefore(button, before);
400
+ }
401
+
380
402
  async show(id, snapshot, selection = null, displayMode = null) {
381
403
  if (selection && snapshot.revision < selection.contentRevision) selection = null;
382
404
  this.pendingSelection = selection;
@@ -422,6 +444,13 @@ export class SnapshotDisplay {
422
444
  return;
423
445
  }
424
446
  this.editor = editor;
447
+ if (this.onChangeMode && this.renderMarkdown && snapshot.kind === "file" &&
448
+ /\.(md|markdown)$/i.test(source.label)) {
449
+ const toolbar = parent.querySelector(".editor-toolbar");
450
+ const actions = toolbar.querySelector(".editor-actions");
451
+ toolbar.insertBefore(element(this.document, "span", "snapshot-stamp", "Read only"), actions);
452
+ this.addModeToggle(toolbar, true, actions);
453
+ }
425
454
  } catch (error) {
426
455
  if (generation !== this.generation) return;
427
456
  this.content.textContent = "";
@@ -444,6 +473,10 @@ export class SnapshotDisplay {
444
473
  const heading = element(this.document, "div", "snapshot-heading");
445
474
  heading.appendChild(element(this.document, "span", "snapshot-label", source.label));
446
475
  heading.appendChild(element(this.document, "span", "snapshot-stamp", "Read only"));
476
+ if (markdown && this.onChangeMode) {
477
+ heading.classList.add("markdown-heading");
478
+ this.addModeToggle(heading, false);
479
+ }
447
480
  root.appendChild(heading);
448
481
  if (source.image_url) {
449
482
  const image = element(this.document, "img", "snapshot-image");
@@ -484,7 +517,10 @@ export function mountContentViews({
484
517
  enableBoard = true,
485
518
  }) {
486
519
  const document = tabs.ownerDocument;
487
- const display = new SnapshotDisplay({content, renderMarkdown, onInsertContext});
520
+ const display = new SnapshotDisplay({
521
+ content, renderMarkdown, onInsertContext,
522
+ onChangeMode: mode => controller.setDisplayMode(mode),
523
+ });
488
524
  let displayedId;
489
525
  let displayedSnapshot = null;
490
526
  let displayedLoading = false;
@@ -0,0 +1,85 @@
1
+ import {Composer} from "./composer.mjs";
2
+
3
+ function automaticPrompt(text) {
4
+ const match = text.trim().match(/^<(clock_tick|exec_command_completed)>\s*([\s\S]*?)\s*<\/\1>$/);
5
+ if (!match) return false;
6
+ try {
7
+ const payload = JSON.parse(match[2]);
8
+ return payload !== null && typeof payload === "object" && !Array.isArray(payload);
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ /** Human input history belongs to one conversation and never submits on recall. */
15
+ export class PromptHistory {
16
+ constructor() {
17
+ this.entries = [];
18
+ this.seen = new Set();
19
+ this.pending = [];
20
+ this.index = null;
21
+ this.draft = null;
22
+ }
23
+
24
+ remember(value) {
25
+ const parts = value.parts.map(part => ({...part}));
26
+ if (!Composer.text(parts).trim()) return;
27
+ this.append({parts});
28
+ this.pending.push(Composer.text(parts).trim());
29
+ if (this.pending.length > 100) this.pending.shift();
30
+ this.reset();
31
+ }
32
+
33
+ append(value) {
34
+ this.entries.push(value);
35
+ if (this.entries.length > 100) {
36
+ this.entries.shift();
37
+ if (this.index !== null) this.index = Math.max(0, this.index - 1);
38
+ }
39
+ }
40
+
41
+ update(turns) {
42
+ for (const turn of turns || []) {
43
+ const id = turn.submission_id || turn.turn_id;
44
+ if (!id || this.seen.has(id)) continue;
45
+ this.seen.add(id);
46
+ const text = turn.prompt;
47
+ if (!text || !text.trim()) continue;
48
+ const pending = this.pending.indexOf(text.trim());
49
+ if (pending !== -1) {
50
+ this.pending.splice(pending, 1);
51
+ continue;
52
+ }
53
+ if (turn.kind === "compaction" || automaticPrompt(text) ||
54
+ !["web", "cli", "feishu", "resume", "user"].includes(turn.sender)) continue;
55
+ this.append({parts: [{type: "text", text}]});
56
+ }
57
+ }
58
+
59
+ reset() {
60
+ this.index = null;
61
+ this.draft = null;
62
+ }
63
+
64
+ recall(back, current) {
65
+ if (this.index !== null &&
66
+ JSON.stringify(current.parts) !== JSON.stringify(this.entries[this.index].parts)) this.reset();
67
+ if (back) {
68
+ if (!this.entries.length || this.index === 0) return null;
69
+ if (this.index === null) {
70
+ this.draft = current;
71
+ this.index = this.entries.length;
72
+ }
73
+ this.index -= 1;
74
+ } else {
75
+ if (this.index === null) return null;
76
+ this.index += 1;
77
+ if (this.index >= this.entries.length) {
78
+ const draft = this.draft;
79
+ this.reset();
80
+ return draft;
81
+ }
82
+ }
83
+ return {parts: this.entries[this.index].parts.map(part => ({...part}))};
84
+ }
85
+ }
@@ -686,6 +686,15 @@
686
686
  color: var(--muted);
687
687
  font-size: 12px;
688
688
  }
689
+ .entry.model-error {
690
+ border-left: 2px solid #c98100;
691
+ padding: 8px 10px;
692
+ background: color-mix(in srgb, #c98100 8%, transparent);
693
+ color: #7a4600;
694
+ font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
695
+ white-space: pre-wrap;
696
+ overflow-wrap: anywhere;
697
+ }
689
698
  .entry.compaction::before,
690
699
  .entry.compaction::after {
691
700
  content: "";
@@ -1331,7 +1340,7 @@
1331
1340
  </div>
1332
1341
  <div id="prompt" class="prompt-input empty" contenteditable="true" role="textbox"
1333
1342
  aria-multiline="true" aria-label="Message" spellcheck="false"
1334
- title="Enter to send · Shift + Enter for a new line" data-placeholder="Message…"></div>
1343
+ title="Enter to send · Shift + Enter for a new line · ↑ / ↓ for input history" data-placeholder="Message…"></div>
1335
1344
  </div>
1336
1345
  <div id="contextBlockDetail" class="context-block-detail" hidden>
1337
1346
  <div class="context-block-heading">
@@ -1414,7 +1423,7 @@
1414
1423
  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
1415
1424
  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/contrib/auto-render.min.js"></script>
1416
1425
  <script type="module">
1417
- import {Composer, isWorkspaceLink, mountContentViews} from "./assets/content_views.mjs";
1426
+ import {Composer, PromptHistory, isWorkspaceLink, mountContentViews} from "./assets/content_views.mjs";
1418
1427
 
1419
1428
  const log = document.getElementById("log");
1420
1429
  const spinner = document.getElementById("spinner");
@@ -1467,7 +1476,9 @@
1467
1476
  const composer = new Composer(prompt, () => {
1468
1477
  stateForSession(activeSessionId).draft = composer.read();
1469
1478
  resizePrompt();
1470
- }, () => form.requestSubmit(), editContextBlock, showToast);
1479
+ }, () => form.requestSubmit(), editContextBlock, showToast,
1480
+ (back, current) => stateForSession(activeSessionId).promptHistory.recall(back, current));
1481
+ prompt.addEventListener("input", () => stateForSession(activeSessionId).promptHistory.reset());
1471
1482
  composer.set({parts: []});
1472
1483
  const markdownRenderer = window.markdownit
1473
1484
  ? window.markdownit({html: false, linkify: true, breaks: false})
@@ -2114,6 +2125,7 @@
2114
2125
  lastRenderedSignature: "",
2115
2126
  scrollTop: null,
2116
2127
  draft: {parts: []},
2128
+ promptHistory: new PromptHistory(),
2117
2129
  notifyWhenDone: false,
2118
2130
  wasRunning: false,
2119
2131
  inputRequest: null,
@@ -2368,6 +2380,7 @@
2368
2380
  item.status || "",
2369
2381
  item.error || "",
2370
2382
  item.kind || "",
2383
+ item.model_errors || [],
2371
2384
  ]),
2372
2385
  snapshot.input_request || null,
2373
2386
  ]);
@@ -2468,6 +2481,7 @@
2468
2481
  updateSessionDirectory(snapshot);
2469
2482
  const state = stateForSession(activeSessionId);
2470
2483
  state.inputRequest = snapshot.input_request || null;
2484
+ state.promptHistory.update(snapshot.turns);
2471
2485
  renderQueueHint(snapshot.queued_inputs);
2472
2486
  requestAnimationFrame(markVisibleFinalRead);
2473
2487
  const signature = messageSignature(snapshot);
@@ -2594,6 +2608,11 @@
2594
2608
  response.dataset.sessionId = activeSessionId;
2595
2609
  }
2596
2610
  if (turn.thinking) renderEntry(`${key}:thinking`, turn.thinking, "thinking");
2611
+ if (Array.isArray(turn.model_errors)) {
2612
+ turn.model_errors.forEach((message, index) => {
2613
+ renderEntry(`${key}:model-error:${index}`, message, "model-error");
2614
+ });
2615
+ }
2597
2616
  if (turn.error) {
2598
2617
  const error = renderEntry(`${key}:error`, turn.error, "error");
2599
2618
  error.dataset.finalId = isControl ? "" : (turn.final_id || "");
@@ -2866,8 +2885,10 @@
2866
2885
 
2867
2886
  form.addEventListener("submit", (event) => {
2868
2887
  event.preventDefault();
2869
- const text = Composer.text(composer.parts()).trim();
2870
- if (!text && !stateForSession(activeSessionId).inputRequest) return;
2888
+ const draft = composer.read();
2889
+ const text = Composer.text(draft.parts);
2890
+ if (!text.trim() && !stateForSession(activeSessionId).inputRequest) return;
2891
+ stateForSession(activeSessionId).promptHistory.remember(draft);
2871
2892
  closeContextBlock();
2872
2893
  composer.set({parts: []});
2873
2894
  resizePrompt();
@@ -1 +0,0 @@
1
- .content-tabs{flex:1}.tab.content-tab{flex:0 0 auto}.tab.content-tab:first-child{min-width:0;padding-right:12px}.content-tab:focus-visible{outline:2px solid var(--accent);outline-offset:-1px}.content-tab-label{display:flex;align-items:baseline;gap:6px}.content-tab-name{flex:0 0 auto;max-width:100%;overflow:hidden;text-overflow:ellipsis}.content-tab-description{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:11px;font-weight:400;opacity:.75}.content-status{max-width:50%;font-size:11px;color:var(--danger);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.content-area{position:relative;min-width:0;min-height:0;overflow:hidden}.content-area>[hidden]{display:none}.content-view{width:100%;height:100%;overflow:auto;background:var(--board-chrome);color:var(--ink)}.content-view.code-view,.content-view.html-view{overflow:hidden}.snapshot-html{display:block;width:100%;height:100%;border:0;background:#fff}.snapshot{min-height:100%}.snapshot-heading{display:flex;align-items:baseline;gap:20px;padding:10px 16px;border-bottom:1px solid var(--line);font-size:12px}.snapshot-label{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow-wrap:anywhere}.snapshot-stamp{flex:0 0 auto;margin-left:auto;color:var(--muted);font-size:11px;white-space:nowrap}.snapshot-markdown{max-width:900px;padding:24px 32px;margin:0 auto}.snapshot-image{display:block;max-width:calc(100% - 40px);height:auto;margin:20px auto}.content-placeholder{padding:40px 24px;text-align:center;color:var(--muted)}@media(max-width:760px){.snapshot-heading{flex-wrap:wrap;gap:4px}.snapshot-stamp{margin-left:0}.snapshot-markdown{padding:20px}}
@@ -1,31 +0,0 @@
1
- // Generated by workspace_server/frontend/build.mjs. See content_views.LICENSE.txt.
2
- function U(c){function t(n){let o=n.lastIndexOf("/");return o<0?".":n.slice(0,o)||"/"}let e=new Map,i=c.map(n=>{let r=(n.kind==="diff"?n.title.split(" <-> "):[n.title]).map(d=>{let h=/^(?:([0-9a-f]{40}|[0-9a-f]{64})|.+ \(([0-9a-f]{40}|[0-9a-f]{64})\)):(.+)$/i.exec(d),u=h?h[3]:d,p=u.slice(u.lastIndexOf("/")+1);return e.has(p)||e.set(p,new Set),e.get(p).add(u),{path:u,name:p,revision:h?(h[1]||h[2]).slice(0,7):null}});return{view:n,sources:r}}),s=new Map;for(let n of e.values()){if(n.size<2)continue;let o=Array.from(n),r=o.map(d=>t(d).split("/"));o.forEach((d,h)=>{let u=1;for(;u<r[h].length&&r.some((p,k)=>k!==h&&p.slice(-u).join("/")===r[h].slice(-u).join("/"));)u+=1;s.set(d,r[h].slice(-u).join("/")||"/")})}return new Map(i.map(({view:n,sources:o})=>{let r=o.every(p=>p.path===o[0].path),d=r?o[0].name:o.map(p=>p.name).join(" \u2194 "),h="";r?h=s.get(o[0].path)||"":o.some(p=>s.has(p.path))&&(h=o.map(p=>s.get(p.path)||t(p.path)).join(" \u2194 "));let u=o.some(p=>p.revision)?o.map(p=>p.revision||"working tree").join(" \u2194 "):n.kind==="diff"&&r?"diff":"";return[n.id,{name:d,description:[h,u].filter(Boolean).join(" \xB7 ")}]}))}var M=class c{constructor(t,e,i,s,n){this.element=t,this.changed=e,this.submit=i,this.editContext=s,this.error=n,this.composing=!1,this.selection={start:0,end:0},this.undo=[],this.redo=[],this.previous=this.read(),t.addEventListener("input",o=>{o.isComposing||this.record(),this.changed()}),t.addEventListener("compositionstart",()=>{this.composing=!0}),t.addEventListener("compositionend",()=>{this.composing=!1,this.record(),this.changed()}),document.addEventListener("selectionchange",()=>{this.captureSelection()&&this.changed()}),t.addEventListener("keydown",o=>this.keydown(o)),t.addEventListener("beforeinput",o=>{o.inputType==="historyUndo"||o.inputType==="historyRedo"?(o.preventDefault(),this.history(o.inputType==="historyUndo")):o.inputType.startsWith("format")&&o.preventDefault()}),t.addEventListener("click",o=>{let r=o.target.closest(".context-token");r&&this.editContext(JSON.parse(r.dataset.context))}),t.addEventListener("mousedown",o=>{o.target.closest(".context-token")&&o.preventDefault()}),t.addEventListener("copy",o=>this.copy(o,!1)),t.addEventListener("cut",o=>this.copy(o,!0)),t.addEventListener("paste",o=>{o.preventDefault(),this.paste(o.clipboardData)}),t.addEventListener("dragover",o=>o.preventDefault()),t.addEventListener("drop",o=>{o.preventDefault();let r=document.caretRangeFromPoint(o.clientX,o.clientY);if(r&&t.contains(r.startContainer)){let d=window.getSelection();d.removeAllRanges(),d.addRange(r),this.captureSelection()}this.paste(o.dataTransfer)})}static size(t){return t.reduce((e,i)=>e+(i.type==="context"?1:i.text.length),0)}static text(t){return t.map(e=>e.text).join("")}static slice(t,e,i){let s=[],n=0;for(let o of t){let r=c.size([o]);e<n+r&&i>n&&s.push(o.type==="context"?{...o}:{type:"text",text:o.text.slice(Math.max(0,e-n),i-n)}),n+=r}return s}parts(t=this.element){let e=[],i=n=>{if(!n)return;let o=e.at(-1);(o==null?void 0:o.type)==="text"?o.text+=n:e.push({type:"text",text:n})},s=n=>{var o;for(let r of n.childNodes)r.nodeType===Node.TEXT_NODE?i(r.data):(o=r.dataset)!=null&&o.context?e.push(JSON.parse(r.dataset.context)):r.nodeName==="BR"?!r.hasAttribute("data-tail")&&n.childNodes.length>1&&i(`
3
- `):(["DIV","P"].includes(r.nodeName)&&e.length&&!e.at(-1).text.endsWith(`
4
- `)&&i(`
5
- `),s(r))};return s(t),e}captureSelection(){if(document.activeElement!==this.element)return!1;let t=window.getSelection();if(!t.rangeCount)return!1;let e=t.getRangeAt(0);if(!this.element.contains(e.startContainer)||!this.element.contains(e.endContainer))return!1;let i=document.createRange();i.selectNodeContents(this.element),i.setEnd(e.startContainer,e.startOffset);let s=c.size(this.parts(i.cloneContents()));return this.selection={start:s,end:s+c.size(this.parts(e.cloneContents()))},!0}read(){return this.captureSelection(),{parts:this.parts(),selection:{...this.selection}}}set(t){this.undo=[],this.redo=[],this.render(t),document.activeElement===this.element&&this.focus(),this.previous=this.read()}render(t){let e=t.parts||[];this.element.replaceChildren();for(let s of e)if(s.type==="text")this.element.append(document.createTextNode(s.text));else{let n=document.createElement("span");n.className="context-token",n.contentEditable="false",n.dataset.context=JSON.stringify(s),n.textContent=s.label,n.title=s.label+`
6
- Click to edit context; Backspace / Delete to remove`,this.element.append(n,document.createTextNode(""))}if(!e.length||e.at(-1).type==="text"&&e.at(-1).text.endsWith(`
7
- `)){let s=document.createElement("br");s.dataset.tail="",this.element.append(s)}let i=c.size(e);this.selection=t.selection||{start:i,end:i},this.selection={start:Math.min(this.selection.start,i),end:Math.min(this.selection.end,i)},this.element.classList.toggle("empty",!e.length)}focus(){this.element.focus({preventScroll:!0});let t=s=>{let n=o=>{var r;for(let d=0;d<o.childNodes.length;d++){let h=o.childNodes[d];if(h.nodeType===Node.TEXT_NODE){if(s<=h.length)return[h,s];s-=h.length}else if((r=h.dataset)!=null&&r.context){if(s===0)return[o,d];if(s-=1,s===0)return[o,d+1]}else if(h.nodeName==="BR"){if(s===0)return[o,d];h.hasAttribute("data-tail")||(s-=1)}else{let u=n(h);if(u)return u}}};return n(this.element)||[this.element,this.element.childNodes.length]},e=document.createRange();e.setStart(...t(this.selection.start)),e.setEnd(...t(this.selection.end));let i=window.getSelection();i.removeAllRanges(),i.addRange(e)}record(){let t=this.read();JSON.stringify(t.parts)!==JSON.stringify(this.previous.parts)&&(this.undo.push(this.previous),this.undo.length>40&&this.undo.shift(),this.redo=[],this.previous=t),this.element.classList.toggle("empty",!t.parts.length)}replace(t,e,i){let s=this.read(),n=[...c.slice(s.parts,0,t),...i,...c.slice(s.parts,e,c.size(s.parts))],o=t+c.size(i);this.render({parts:n,selection:{start:o,end:o}}),this.focus(),this.record(),this.changed()}insert(t){this.captureSelection(),this.replace(this.selection.start,this.selection.end,t.map(e=>({...e,type:"context",id:crypto.getRandomValues(new Uint32Array(4)).join("-")})))}updateContext(t,e){for(let i of this.element.querySelectorAll(".context-token")){let s=JSON.parse(i.dataset.context);s.id===t&&(i.dataset.context=JSON.stringify({...s,text:e}))}this.record(),this.changed()}removeContext(t){let e=0;for(let i of this.parts()){if(i.type==="context"&&i.id===t){this.replace(e,e+1,[]);return}e+=c.size([i])}}history(t){let e=t?this.undo:this.redo;e.length&&((t?this.redo:this.undo).push(this.read()),this.render(e.pop()),this.focus(),this.previous=this.read(),this.changed())}keydown(t){var i;if(t.isComposing||this.composing||t.keyCode===229)return;let e=t.ctrlKey||t.metaKey;if(e&&(t.key.toLowerCase()==="z"||t.key.toLowerCase()==="y"))t.preventDefault(),this.history(t.key.toLowerCase()==="z"&&!t.shiftKey);else if(t.key==="Enter")t.preventDefault(),t.shiftKey?(this.captureSelection(),this.replace(this.selection.start,this.selection.end,[{type:"text",text:`
8
- `}])):this.submit();else if(["Backspace","Delete"].includes(t.key)&&!e&&!t.altKey){this.captureSelection();let{start:s,end:n}=this.selection,o=t.key==="Backspace"?s-1:s;s===n&&((i=c.slice(this.parts(),o,o+1)[0])==null?void 0:i.type)==="context"&&(t.preventDefault(),this.replace(o,o+1,[]))}}copy(t,e){if(!this.captureSelection()||this.selection.start===this.selection.end)return;let i=c.slice(this.parts(),this.selection.start,this.selection.end);t.clipboardData.setData("text/plain",c.text(i)),t.clipboardData.setData("application/x-pycodex-parts",JSON.stringify(i)),t.preventDefault(),e&&this.replace(this.selection.start,this.selection.end,[])}paste(t){try{let e=t.getData("application/x-pycodex-parts"),i=e?JSON.parse(e):[{type:"text",text:t.getData("text/plain")}];if(!Array.isArray(i)||i.some(s=>!s||typeof s.text!="string"||!["text","context"].includes(s.type)||s.type==="context"&&typeof s.label!="string"))throw new Error("Invalid context format in clipboard.");this.captureSelection(),this.replace(this.selection.start,this.selection.end,i.map(s=>s.type==="context"?{...s,id:crypto.getRandomValues(new Uint32Array(4)).join("-")}:s))}catch(e){this.error(e.message)}}};function X(c,t=!0){if(typeof c!="string"||!c||c!==c.trim()||/[\x00-\x1f\x7f]/.test(c))return!1;if(/^(?:diff:|git:|workspace:\/\/canvas\/?$)/i.test(c))return!0;if(!t||/^(?:https?|ftps?|mailto|tel|sms|javascript|vbscript|data|blob|about|file):/i.test(c))return!1;let e=c.split("#");if(e.length>2||e.length===2&&!/^L-?\d+$/i.test(e[1]))return!1;let i=e[0].replace(/:-?\d+$/,"");return!!i&&!i.startsWith("//")&&!i.includes("?")&&(!/^[a-z][a-z\d+.-]*:/i.test(i)||/^[a-z]:(?:[/\\]|%2f|%5c)/i.test(i))}var N=class{constructor({request:t,onChange:e,onSelect:i,selected:s=null,enableBoard:n=!0}){this.request=t,this.onChange=e,this.onSelect=i,this.enableBoard=n,this.views=[],this.activeId=n?"board":null,this.snapshot=null,this.selection=null,this.displayMode=null,this.loading=!1,this.error="",this.closing=new Set,this.cache=new Map,this.initialSelection=s,this.initialized=!1,this.mutation=0,this.pollRequest=null,this.contentRequest=null,this.openRequest=null,this.timer=null}start(){this.timer===null&&(this.poll(),this.timer=setInterval(()=>this.poll(),2e3))}stop(){clearInterval(this.timer),this.timer=null,this.cancelOpen(),this.pollRequest&&this.pollRequest.abort(),this.contentRequest&&this.contentRequest.abort(),this.pollRequest=null,this.contentRequest=null,this.loading=!1}async poll(){if(this.pollRequest||this.openRequest)return;let t=new AbortController,e=this.mutation;this.pollRequest=t;try{let i=await this.request("api/views",{signal:t.signal});if(this.pollRequest!==t||e!==this.mutation)return;this.updateViews(i.views);let s=new Set(this.views.map(o=>o.id));this.error="";let n=this.activeId;this.initialized||(n=this.initialSelection||this.defaultId(),this.initialized=!0),(n==="board"&&!this.enableBoard||n!==null&&n!=="board"&&!s.has(n))&&(n=this.defaultId()),n!==this.activeId||n!==null&&n!=="board"&&!this.snapshot&&!this.loading?this.select(n):(n!==null&&n!=="board"&&!this.cache.has(n)&&!this.contentRequest&&this.load(n),this.onChange(this))}catch(i){this.pollRequest===t&&e===this.mutation&&i.name!=="AbortError"&&(this.error="Unable to load tabs: "+i.message,this.onChange(this))}finally{this.pollRequest===t&&(this.pollRequest=null)}}defaultId(){return this.enableBoard?"board":this.views.length?this.views[0].id:null}updateViews(t){this.views=t;let e=new Map(t.map(i=>[i.id,i.revision]));for(let[i,s]of this.cache)(!e.has(i)||s.revision<e.get(i))&&this.cache.delete(i)}cancelOpen(){this.openRequest&&this.openRequest.abort(),this.openRequest=null}async open(t,e,i){if(this.cancelOpen(),/^workspace:\/\/canvas\/?$/i.test(t)){if(!this.enableBoard)throw new Error("Canvas is disabled for this workspace.");await this.select("board");return}let s=new AbortController;this.openRequest=s,this.mutation+=1,this.error="",this.onChange(this);try{let n=await this.request("api/views/open",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({href:t,src_path:e,session_id:i}),signal:s.signal});if(this.openRequest!==s)return;this.mutation+=1,this.updateViews(n.views);let o=n.view;return this.cache.set(o.id,o),this.initialized=!0,this.openRequest=null,this.select(o.id,o.selection?{...o.selection,revision:o.reveal_revision,contentRevision:o.revision}:null,o.selection?"source":"preview")}catch(n){if(this.openRequest!==s||n.name==="AbortError")return;throw this.error="Unable to open view: "+n.message,n}finally{this.openRequest===s&&(this.openRequest=null,this.onChange(this))}}async select(t,e=null,i=null){if(t==="board"&&!this.enableBoard||t!==null&&t!=="board"&&!this.views.some(n=>n.id===t))return;this.cancelOpen();let s=t===this.activeId?this.snapshot:null;this.contentRequest&&this.contentRequest.abort(),this.contentRequest=null,this.activeId=t,this.selection=e,this.displayMode=i,this.initialized||(this.initialSelection=t),this.snapshot=this.cache.get(t)||s,this.loading=t!==null&&t!=="board"&&!this.snapshot,this.error="",this.onSelect(t),this.onChange(this),!(t===null||t==="board"||this.cache.has(t))&&await this.load(t)}async load(t){let e=new AbortController;this.contentRequest=e;try{let i=await this.request("api/views/"+encodeURIComponent(t),{signal:e.signal});if(this.contentRequest!==e||this.activeId!==t)return;let s=this.views.find(n=>n.id===t);if(!s||i.revision<s.revision)return;this.cache.set(t,i),this.snapshot=i,this.error=""}catch(i){if(this.contentRequest!==e||this.activeId!==t||i.name==="AbortError")return;this.error="Unable to load view: "+i.message}finally{this.contentRequest===e&&(this.contentRequest=null,this.loading=!1,this.onChange(this))}}async close(t){if(!(t===null||t==="board"||this.closing.has(t))){this.cancelOpen(),this.closing.add(t),this.mutation+=1,this.onChange(this);try{await this.request("api/views/"+encodeURIComponent(t),{method:"DELETE"}),this.mutation+=1;let e=this.views.findIndex(i=>i.id===t);this.views=this.views.filter(i=>i.id!==t),this.cache.delete(t),this.activeId===t&&await this.select(e>0?this.views[e-1].id:this.defaultId())}catch(e){this.error="Unable to close tab: "+e.message}finally{this.closing.delete(t),this.onChange(this)}}}};function y(c,t,e,i){let s=c.createElement(t);return s.className=e,i!==void 0&&(s.textContent=i),s}var O=class{constructor(t,e=[0,0]){this.document=t.ownerDocument,this.window=this.document.defaultView,this.position=e,this.generation=0,this.frame=y(this.document,"iframe","snapshot-html"),this.frame.title="HTML preview",this.receive=i=>{if(i.source!==this.frame.contentWindow)return;let s=i.data;!s||s.generation!==this.generation||(s.type==="workspace-preview-ready"?this.frame.contentWindow.postMessage({type:"workspace-preview-restore",generation:this.generation,position:this.position},"*"):s.type==="workspace-preview-scroll"&&Array.isArray(s.position)&&s.position.length===2&&s.position.every(Number.isFinite)&&(this.position=s.position))},this.window.addEventListener("message",this.receive),t.appendChild(this.frame)}update(t){if(t===this.source)return;let e=new this.window.DOMParser().parseFromString(t,"text/html"),i=this.generation+1,s=e.createElement("script");s.textContent=`(() => {
9
- const generation = ${i};
10
- let restored = false;
11
- function report() {
12
- if (restored) parent.postMessage({
13
- type: "workspace-preview-scroll", generation, position: [scrollX, scrollY],
14
- }, "*");
15
- }
16
- addEventListener("message", event => {
17
- const message = event.data;
18
- if (event.source !== parent || !message ||
19
- message.type !== "workspace-preview-restore" || message.generation !== generation) return;
20
- scrollTo(message.position[0], message.position[1]);
21
- restored = true;
22
- report();
23
- });
24
- addEventListener("scroll", report, {passive: true});
25
- addEventListener("load", () => parent.postMessage({
26
- type: "workspace-preview-ready", generation,
27
- }, "*"));
28
- })();`,e.head.prepend(s);let n=e.doctype?new this.window.XMLSerializer().serializeToString(e.doctype)+`
29
- `:"";this.frame.srcdoc=n+e.documentElement.outerHTML,this.generation=i,this.source=t}capture(){return this.position}destroy(){this.window.removeEventListener("message",this.receive)}},P=class{constructor({content:t,renderMarkdown:e,loadEditor:i=null,onInsertContext:s=null,createHtmlPreview:n=(o,r)=>new O(o,r)}){this.content=t,this.document=t.ownerDocument,this.renderMarkdown=e,this.onInsertContext=s,this.loadEditor=i||(()=>this.loadEditorAssets()),this.editorAssets=null,this.createHtmlPreview=n,this.activeId=null,this.editor=null,this.htmlPreview=null,this.generation=0,this.remaining=new Set,this.editorStates=new Map,this.previews=new Map,this.scrollPositions=new Map,this.sourceViews=new Set,this.pendingSelection=null,this.selectionRevision=0}loadEditorAssets(){if(!this.editorAssets){let t=this.document.createElement("link");t.rel="stylesheet",t.href=new URL("./editor.css",import.meta.url).href;let e=new Promise((i,s)=>{t.addEventListener("load",i,{once:!0}),t.addEventListener("error",()=>s(new Error("Editor stylesheet request failed.")),{once:!0})});this.document.head.insertBefore(t,this.document.head.querySelector("[data-pycodex-custom-style]")),this.editorAssets=Promise.all([import("./editor.mjs"),e]).then(([i])=>i).catch(i=>{throw t.remove(),this.editorAssets=null,i})}return this.editorAssets}retain(t){this.remaining=new Set(t);for(let e of[this.editorStates,this.previews,this.scrollPositions,this.sourceViews])for(let i of e.keys())this.remaining.has(i)||e.delete(i)}clear(t=""){this.generation+=1,this.editor?(this.remaining.has(this.activeId)&&this.editorStates.set(this.activeId,this.editor.capture()),this.editor.destroy(),this.editor=null):this.htmlPreview?(this.remaining.has(this.activeId)&&this.scrollPositions.set(this.activeId,this.htmlPreview.capture()),this.htmlPreview.destroy(),this.htmlPreview=null):this.remaining.has(this.activeId)&&this.previews.has(this.activeId)&&this.scrollPositions.set(this.activeId,[this.content.scrollLeft,this.content.scrollTop]),this.activeId=null,this.content.classList.remove("code-view"),this.content.classList.remove("html-view"),this.content.textContent="",t&&this.content.appendChild(y(this.document,"div","content-placeholder",t))}async show(t,e,i=null,s=null){i&&e.revision<i.contentRevision&&(i=null),this.pendingSelection=i,s==="preview"?this.sourceViews.delete(t):(i||s==="source")&&this.sourceViews.add(t);let n=e.documents[0],o=e.kind==="file"&&!this.sourceViews.has(t),r=o&&/\.(md|markdown)$/i.test(n.label)&&this.renderMarkdown,d=o&&/\.html?$/i.test(n.label);if(this.activeId===t&&this.htmlPreview&&d)return this.htmlPreview.update(n.text);if(this.activeId===t&&this.editor&&!r&&!d){let m=this.generation;try{await this.editor.update(e),m===this.generation&&await this.applySelection(t,i)}catch(v){if(m===this.generation)throw v}return}this.clear(),this.activeId=t;let h=this.generation;if(e.kind==="diff"||!n.image_url&&!r&&!d){this.content.classList.add("code-view"),this.content.appendChild(y(this.document,"div","content-placeholder","Loading code viewer\u2026"));try{let m=await this.loadEditor();if(h!==this.generation)return;this.content.textContent="";let v=y(this.document,"div","workspace-editor");this.content.appendChild(v);let g=this.onInsertContext&&(E=>{h===this.generation&&this.activeId===t&&this.onInsertContext(E)}),S=await m.createSnapshotEditor(v,e,this.editorStates.get(t),g);if(h!==this.generation){S.destroy();return}this.editor=S}catch(m){if(h!==this.generation)return;this.content.textContent="",this.content.appendChild(y(this.document,"div","content-placeholder","Unable to load code viewer: "+m.message));return}await this.applySelection(t,i);return}if(d)return this.content.classList.add("html-view"),this.htmlPreview=this.createHtmlPreview(this.content,this.scrollPositions.get(t)),this.htmlPreview.update(n.text);if(!this.previews.has(t)){let m=y(this.document,"div","snapshot"),v=y(this.document,"div","snapshot-heading");if(v.appendChild(y(this.document,"span","snapshot-label",n.label)),v.appendChild(y(this.document,"span","snapshot-stamp","Read only")),m.appendChild(v),n.image_url){let g=y(this.document,"img","snapshot-image");g.alt=n.label,m.appendChild(g)}else m.appendChild(y(this.document,"div","snapshot-markdown markdown"));this.previews.set(t,m)}this.content.appendChild(this.previews.get(t));let u=this.content.querySelector(".snapshot-image");if(u&&u.src!==n.image_url&&(u.src=n.image_url),r){let m=this.content.querySelector(".snapshot-markdown");n.source&&m.setAttribute("data-src-path",n.source.path),this.renderMarkdown(m,n.text)}let[p,k]=this.scrollPositions.get(t)||[0,0];this.content.scrollLeft=p,this.content.scrollTop=k}async applySelection(t,e){if(!e||e.revision<=this.selectionRevision)return;let i=this.editor,s=()=>this.activeId===t&&this.editor===i&&this.pendingSelection===e&&e.revision>this.selectionRevision;s()&&await i.revealLine(e.document,e.line,s)&&(this.selectionRevision=e.revision)}};function F({tabs:c,frame:t,content:e,status:i,request:s,renderMarkdown:n,onInsertContext:o,enableBoard:r=!0}){let d=c.ownerDocument,h=new P({content:e,renderMarkdown:n,onInsertContext:o}),u,p=null,k=!1,m=null,v=null,g;function S(a){let C=d.getElementById("content-tab-"+a);C&&(C.focus({preventScroll:!0}),C.scrollIntoView({block:"nearest",inline:"nearest"}))}function E(a){let C=a.snapshot?a.snapshot.documents.map(l=>l.error):[];i.textContent=[a.error,...C].filter(Boolean).join("; ")||(a.openRequest?"Opening view\u2026":""),i.title=i.textContent,i.hidden=!i.textContent;let R=new Map(Array.from(c.children,l=>[l.dataset.viewId,l])),J=(r?[{id:"board",title:"Canvas",kind:"board"}]:[]).concat(a.views),T=U(a.views);T.set("board",{name:"Canvas",description:""}),J.forEach((l,I)=>{let f=R.get(l.id);if(!f){f=y(d,"div","tab content-tab"),f.dataset.viewId=l.id,f.id="content-tab-"+l.id,f.setAttribute("role","tab"),f.setAttribute("aria-controls",l.id==="board"?t.id:e.id);let D=y(d,"span","tab-label content-tab-label");if(D.appendChild(y(d,"span","content-tab-name")),D.appendChild(y(d,"span","content-tab-description")),f.appendChild(D),f.addEventListener("click",()=>g.select(l.id)),f.addEventListener("keydown",w=>{if(w.target!==f)return;let b=(r?["board"]:[]).concat(g.views.map($=>$.id)),j=b.indexOf(l.id),x=null;w.key==="ArrowRight"&&(x=b[(j+1)%b.length]),w.key==="ArrowLeft"&&(x=b[(j+b.length-1)%b.length]),w.key==="Home"&&(x=b[0]),w.key==="End"&&(x=b[b.length-1]),(w.key==="Enter"||w.key===" ")&&(x=l.id),w.key==="Delete"&&l.id!=="board"&&(w.preventDefault(),g.close(l.id)),x!==null&&(w.preventDefault(),g.select(x),d.getElementById("content-tab-"+x).focus())}),l.id!=="board"){let w=y(d,"button","tab-close content-tab-close");w.type="button",w.title="Close tab",w.setAttribute("aria-label","Close "+l.title),w.innerHTML='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>',w.addEventListener("click",b=>{b.stopPropagation(),g.close(l.id)}),f.appendChild(w)}}R.delete(l.id);let L=T.get(l.id);f.querySelector(".content-tab-name").textContent=L.name;let V=f.querySelector(".content-tab-description");V.textContent=L.description,V.hidden=!L.description,f.className="tab content-tab"+(l.id===a.activeId?" active":""),f.tabIndex=l.id===a.activeId?0:-1,f.title=l.id==="board"?c.dataset.boardTitle:l.title+(l.live?`
30
- Read only \xB7 follows file changes \xB7 `:`
31
- Read-only snapshot \xB7 `)+new Date(l.created_at).toLocaleString(),f.setAttribute("aria-selected",String(l.id===a.activeId)),f.setAttribute("aria-label",l.title);let A=f.querySelector(".content-tab-close");A&&(A.disabled=a.closing.has(l.id),A.setAttribute("aria-label","Close "+l.title)),c.children[I]!==f&&c.insertBefore(f,c.children[I]||null)}),R.forEach(l=>l.remove()),h.retain(a.views.map(l=>l.id));let q=a.activeId==="board",z=u!==a.activeId,B=z||p!==a.snapshot||k!==a.loading||m!==a.selection||v!==a.displayMode;if(q&&B&&h.clear(),t.hidden=!q,e.hidden=q,!!B&&(u=a.activeId,p=a.snapshot,k=a.loading,m=a.selection,v=a.displayMode,a.activeId?e.setAttribute("aria-labelledby","content-tab-"+a.activeId):e.removeAttribute("aria-labelledby"),e.setAttribute("aria-busy",String(a.loading)),!q)){if(a.snapshot){let l=a.activeId,I=a.snapshot;h.show(l,I,a.selection,a.displayMode).catch(f=>{u!==l||p!==I||(p=null,a.error="Unable to update view: "+f.message,i.textContent=i.title=a.error,i.hidden=!1)})}else h.clear(a.loading?"Loading view\u2026":a.activeId?"View unavailable.":"Open a file link to view it here.");z&&S(a.activeId)}}return g=new N({request:s,enableBoard:r,onChange:E,onSelect:a=>{e.closest(".workspace").dataset.mobilePane="board",S(a)}}),E(g),{start(){g.start(),E(g)},stop(){g.stop(),h.clear(),u=null},poll(){return g.poll()},open(a,C,R){return g.open(a,C,R)}}}export{M as Composer,N as ContentTabs,P as SnapshotDisplay,X as isWorkspaceLink,F as mountContentViews};
File without changes
File without changes
File without changes