window.__ModuleLoader__.load({ id: '@local/dsh-queue-steer', factory(require) { const React = require('react'); const h = React.createElement; const NS = 'dshQueueSteer'; const P = require('@deepseek-ai/dsh-client-ui-primitives') || {}; const css = ` .dqs-dock{width:calc(100% - 32px);max-width:760px;margin:0 auto -3px;padding:0 8px;font:13px var(--dsw-font-family,Inter,sans-serif)} .dqs-panel{background:var(--dsw-specific-menu);border:1px solid var(--dsw-alias-border-l1);border-bottom:0;border-radius:12px 12px 0 0;padding:4px 8px;overflow:hidden} .dqs-header{width:100%;display:flex;align-items:center;gap:8px;border:0;background:none;color:var(--dsw-alias-label-primary);padding:7px 5px;cursor:pointer;text-align:left} .dqs-count{flex:1;font-weight:600}.dqs-tabs{display:flex;gap:4px;border-bottom:1px solid var(--dsw-alias-border-l2);padding:2px 0 5px} .dqs-tab{border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);padding:5px 8px;cursor:pointer}.dqs-tab.active{background:var(--dsw-alias-bg-l2);color:var(--dsw-alias-label-primary);font-weight:600} .dqs-list{list-style:none;padding:3px 0;margin:0;display:grid;gap:2px}.dqs-row{display:flex;align-items:center;gap:7px;padding:6px;border-radius:7px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-l1)} .dqs-row.editing{outline:2px solid var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-l2)}.dqs-row.over{border-top:2px solid var(--dsw-alias-state-business-primary)}.dqs-grip{cursor:grab;opacity:.6}.dqs-preview{white-space:pre-wrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0;max-height:3.2em}.dqs-editor{flex:1;min-width:0;resize:none;border:1px solid var(--dsw-alias-border-l1);border-radius:6px;background:var(--dsw-alias-bg-base);color:inherit;padding:5px;font:inherit}.dqs-actions{display:flex;gap:2px}.dqs-action{border:0;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;padding:4px;border-radius:5px}.dqs-action:hover{background:var(--dsw-alias-bg-l2);color:var(--dsw-alias-label-primary)}.dqs-empty{padding:12px;color:var(--dsw-alias-label-tertiary);text-align:center}.dqs-draft{font-size:10px;color:var(--dsw-alias-state-business-primary)} .dqs-settings{padding:4px 0}.dqs-settings h2{font-size:18px;margin:0 0 6px}.dqs-settings p{color:var(--dsw-alias-label-secondary);line-height:1.45}.dqs-field{display:grid;gap:5px;margin:14px 0}.dqs-field select,.dqs-field textarea{font:inherit;color:inherit;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:7px;padding:8px}.dqs-field textarea{min-height:80px}.dqs-save{border:0;border-radius:7px;background:var(--dsw-alias-state-business-primary);color:white;padding:8px 14px;cursor:pointer}.dqs-status{margin-left:10px;color:var(--dsw-alias-state-positive)} `; let style; const installCss = () => { if (!style) { style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); } }; const icon = (name, fallback) => P[name] ? h(P[name], { size: 14 }) : h('span', null, fallback); const key = (id) => `dqs:${id}`; const read = (id, fallback) => { try { const v = localStorage.getItem(key(id)); return v == null ? fallback : JSON.parse(v); } catch { return fallback; } }; const write = (id, v) => { try { localStorage.setItem(key(id), JSON.stringify(v)); } catch {} }; const rowText = (row) => (row.content || []).filter((b) => b.type === 'text').map((b) => b.text).join(''); function SettingsPanel() { const [value, setValue] = React.useState({ defaultMode: 'queue', defaultQueueList: 'batch', batchHeaderTemplate: 'Queued messages batch ({count}):\\n\\n{messages}' }); const [status, setStatus] = React.useState(''); React.useEffect(() => { fetch('dsh-queue-steer/settings', { credentials: 'same-origin' }).then((r) => r.json()).then(setValue).catch(() => {}); }, []); const save = () => { setStatus('Saving…'); fetch('dsh-queue-steer/settings', { method: 'POST', credentials: 'same-origin', headers: {'content-type':'application/json'}, body: JSON.stringify(value) }).then((r) => r.json()).then((v) => { setValue(v); setStatus('Saved'); }).catch(() => setStatus('Save failed')); }; return h('section', { className: 'dqs-settings', 'data-dsh-plugin': 'dsh-queue-steer' }, h('h2', null, 'Queue & Steering'), h('p', null, 'Choose how busy-composer messages are delivered and which queue list receives new entries.'), h('label', { className: 'dqs-field' }, 'Default busy message action', h('select', { value: value.defaultMode, onChange: (e) => setValue({...value, defaultMode:e.target.value}) }, h('option', {value:'queue'}, 'Queue'), h('option', {value:'steer'}, 'Steer'))), h('label', { className: 'dqs-field' }, 'Default queue list', h('select', { value: value.defaultQueueList, onChange: (e) => setValue({...value, defaultQueueList:e.target.value}) }, h('option', {value:'batch'}, 'List 1 — send all entries at once'), h('option', {value:'sequential'}, 'List 2 — send one entry at a time'))), h('label', { className: 'dqs-field' }, 'Batch message template', h('textarea', { value: value.batchHeaderTemplate, onChange: (e) => setValue({...value, batchHeaderTemplate:e.target.value}) }), h('small', null, 'Placeholders: {count} and {messages}')), h('button', { className: 'dqs-save', onClick: save }, 'Save settings'), status && h('span', {className:'dqs-status'}, status) ); } function QueueDock({ useSession, useProjection, updateQueue, notify }) { const inbox = useProjection('inbox'); const rows = inbox?.['next-turn'] || []; const sessionId = useSession((s) => s.sessionId || s.id || 'current'); const running = useSession((s) => !!s.running); const [tab, setTab] = React.useState(() => read(`${sessionId}:tab`, 'batch')); const [open, setOpen] = React.useState(true); const [editing, setEditing] = React.useState(null); const [busy, setBusy] = React.useState(null); const [drag, setDrag] = React.useState(null); const [assign, setAssign] = React.useState(() => read(`${sessionId}:assign`, {})); const [order, setOrder] = React.useState(() => read(`${sessionId}:order`, [])); const [drafts, setDrafts] = React.useState(() => read(`${sessionId}:drafts`, {})); React.useEffect(installCss, []); React.useEffect(() => { write(`${sessionId}:assign`, assign); }, [sessionId, assign]); React.useEffect(() => { write(`${sessionId}:order`, order); }, [sessionId, order]); React.useEffect(() => { write(`${sessionId}:drafts`, drafts); }, [sessionId, drafts]); const sorted = React.useMemo(() => { const rank = new Map(order.map((id, i) => [id, i])); return [...rows].sort((a,b) => (rank.get(a.id) ?? 1e9) - (rank.get(b.id) ?? 1e9)); }, [rows, order]); const listOf = (row) => assign[row.id] || 'batch'; const visible = sorted.filter((r) => listOf(r) === tab); const move = (id, target) => { setAssign((v) => ({...v, [id]: target})); setTab(target); }; const startEdit = (row) => setEditing({ id: row.id, value: drafts[row.id] ?? rowText(row) }); const cancelEdit = () => { if (editing) setDrafts((v) => ({...v, [editing.id]: editing.value})); setEditing(null); }; const saveEdit = async () => { if (!editing || !editing.value.trim()) return false; const id = editing.id; const value = editing.value; setBusy(id); try { await Promise.resolve(updateQueue(id, {kind:'edit', content:[{type:'text', text:value}]})); setDrafts((v) => { const n={...v}; delete n[id]; return n; }); setEditing(null); return true; } catch { notify('error', 'Queue edit failed'); return false; } finally { setBusy(null); } }; const action = async (id, value) => { setBusy(id); try { await Promise.resolve(updateQueue(id, value)); setDrafts((v) => { const n={...v}; delete n[id]; return n; }); } catch { notify('error', 'Queue action failed'); } finally { setBusy(null); } }; const drop = (targetId) => { if (!drag || drag.id === targetId) return; const ids = sorted.map((r) => r.id).filter((id) => id !== drag.id); const at = ids.indexOf(targetId); ids.splice(Math.max(0, at), 0, drag.id); setOrder(ids); if (drag.list !== tab) move(drag.id, tab); setDrag(null); }; if (!rows.length) return null; const renderItem = (row) => { const isEdit = editing?.id === row.id; const draft = drafts[row.id]; return h('li', { className: `dqs-row${isEdit?' editing':''}${drag?.over===row.id?' over':''}`, draggable: !isEdit, onDragStart: () => setDrag({id:row.id,list:tab}), onDragOver: (e) => { e.preventDefault(); setDrag((v) => v && ({...v, over:row.id})); }, onDrop: () => drop(row.id), onDragEnd: () => setDrag(null) }, h('span',{className:'dqs-grip','aria-hidden':true},'⋮⋮'), isEdit ? h('textarea',{className:'dqs-editor',autoFocus:true,value:editing.value,onChange:(e)=>setEditing({...editing,value:e.target.value}),onKeyDown:(e)=>{if(e.key==='Escape')cancelEdit();if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();saveEdit();}}}) : h('span',{className:'dqs-preview'}, draft || rowText(row)), draft && !isEdit && h('small',{className:'dqs-draft'},'draft'), h('span',{className:'dqs-actions'}, isEdit ? [h('button',{key:'save',className:'dqs-action',disabled:!!busy,onClick:saveEdit},icon('IconCheckOutlineRegular','✓')),h('button',{key:'cancel',className:'dqs-action',disabled:!!busy,onClick:cancelEdit},icon('IconCloseOutlineRegular','×'))] : [h('button',{key:'edit',className:'dqs-action',disabled:!!busy,onClick:()=>startEdit(row)},icon('IconEditOutlineRegular','✎')),h('button',{key:'move',className:'dqs-action',disabled:!!busy,onClick:()=>move(row.id,tab==='batch'?'sequential':'batch')},tab==='batch'?'⇢':'⇠'),h('button',{key:'steer',className:'dqs-action',disabled:!!busy||!running,onClick:()=>action(row.id,{kind:'steer'})},icon('IconSendOutlineRegular','➤')),h('button',{key:'remove',className:'dqs-action',disabled:!!busy,onClick:()=>action(row.id,{kind:'remove'})},icon('IconTrashOutlineRegular','×'))])); } return h('div',{className:'dqs-dock','data-queue-dock':'dsh-queue-steer'},h('div',{className:'dqs-panel'},h('button',{className:'dqs-header',onClick:()=>setOpen(!open)},icon('IconQueueOutlineRegular','☷'),h('span',{className:'dqs-count'},`Queue & Steering (${rows.length})`),h('span',null,open?'⌃':'⌄')),open && h(React.Fragment,null,h('div',{className:'dqs-tabs'},h('button',{className:`dqs-tab ${tab==='batch'?'active':''}`,onClick:()=>{setTab('batch');write(`${sessionId}:tab`,'batch')}},`List 1 · all at once (${sorted.filter((r)=>listOf(r)==='batch').length})`),h('button',{className:`dqs-tab ${tab==='sequential'?'active':''}`,onClick:()=>{setTab('sequential');write(`${sessionId}:tab`,'sequential')}},`List 2 · one at a time (${sorted.filter((r)=>listOf(r)==='sequential').length})`)),visible.length?h('ul',{className:'dqs-list'},visible.map((r)=>renderItem(r))):h('div',{className:'dqs-empty'},'No entries in this list')))); } return { inject: ['slots', 'conversation', 'sessions'], apply(ctx) { installCss(); ctx.slots.inject('settings.section', () => ctx.slots.register({name:'settings.section', id:'dsh-queue-steer', order:170, label:'Queue & Steering'}, SettingsPanel)); ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({name:'conversation.input.dock', id:'queue', priority:-1, order:20, inject:(sessionId)=>{ const actx = ctx.sessions.scope(sessionId); const conversation = actx.get('conversation'); return { updateQueue:(id,a)=>conversation.updateQueue(id,a), notify:(level,msg)=>conversation.input.for(actx).notify(level,msg) }; }}, QueueDock)); } }; } });