anentrypoint-design 0.0.68 → 0.0.71
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/app-shell.css +2001 -0
- package/colors_and_type.css +625 -0
- package/dist/247420.app.js +4 -2
- package/dist/247420.css +354 -0
- package/dist/247420.js +33 -31
- package/package.json +7 -1
- package/src/components/community.js +165 -0
- package/src/components/freddie.js +112 -0
- package/src/components.js +13 -0
- package/src/index.js +1 -0
- package/src/web-components/freddie-chat.js +34 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.71",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
|
@@ -24,16 +24,22 @@
|
|
|
24
24
|
"./desktop/shell.js": "./src/desktop/shell.js",
|
|
25
25
|
"./desktop/freddie-dashboard.js": "./src/desktop/freddie-dashboard.js",
|
|
26
26
|
"./desktop/freddie-dashboard.css": "./src/desktop/freddie-dashboard.css",
|
|
27
|
+
"./colors_and_type.css": "./colors_and_type.css",
|
|
28
|
+
"./app-shell.css": "./app-shell.css",
|
|
27
29
|
"./page-html": {
|
|
28
30
|
"import": "./src/page-html.js",
|
|
29
31
|
"default": "./src/page-html.js"
|
|
30
32
|
},
|
|
31
33
|
"./src/page-html.js": "./src/page-html.js",
|
|
34
|
+
"./web-components/ds-chat.js": "./src/web-components/ds-chat.js",
|
|
35
|
+
"./web-components/freddie-chat.js": "./src/web-components/freddie-chat.js",
|
|
32
36
|
"./package.json": "./package.json"
|
|
33
37
|
},
|
|
34
38
|
"files": [
|
|
35
39
|
"dist",
|
|
36
40
|
"src",
|
|
41
|
+
"colors_and_type.css",
|
|
42
|
+
"app-shell.css",
|
|
37
43
|
"README.md"
|
|
38
44
|
],
|
|
39
45
|
"scripts": {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as webjsx from '../../vendor/webjsx/index.js';
|
|
2
|
+
const h = webjsx.createElement;
|
|
3
|
+
|
|
4
|
+
export function ServerIcon({ id, name, icon, active, badge, onClick } = {}) {
|
|
5
|
+
const initials = (name || '?').slice(0, 2).toUpperCase();
|
|
6
|
+
return h('div', { class: 'cm-server-icon' + (active ? ' active' : ''), onclick: onClick, title: name, 'data-id': id },
|
|
7
|
+
h('span', { class: 'cm-server-pill' }),
|
|
8
|
+
icon ? h('img', { src: icon, alt: name }) : h('span', {}, initials),
|
|
9
|
+
badge ? h('span', { class: 'cm-server-badge' }, badge > 99 ? '99+' : String(badge)) : null
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ServerRail({ servers = [], activeId, onSelect, onAdd } = {}) {
|
|
14
|
+
return h('div', { class: 'cm-server-rail' },
|
|
15
|
+
h('a', { class: 'cm-server-back', href: '../', title: 'Back' }, '◰'),
|
|
16
|
+
h('div', { class: 'cm-server-sep' }),
|
|
17
|
+
...servers.map(s => ServerIcon({ ...s, active: s.id === activeId, onClick: () => onSelect?.(s.id) })),
|
|
18
|
+
onAdd ? h('button', { class: 'cm-server-add', onclick: onAdd, title: 'Add server' }, '+') : null
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ChannelItem({ id, name, type = 'text', active, voiceActive, onClick, onContext } = {}) {
|
|
23
|
+
const icon = type === 'voice' ? '🔊' : type === 'forum' ? '◻' : '#';
|
|
24
|
+
return h('div', {
|
|
25
|
+
class: 'cm-channel-item' + (active ? ' active' : '') + (voiceActive ? ' voice-active' : ''),
|
|
26
|
+
'data-id': id,
|
|
27
|
+
onclick: onClick,
|
|
28
|
+
oncontextmenu: (e) => { e.preventDefault(); onContext?.(id, e.clientX, e.clientY); }
|
|
29
|
+
},
|
|
30
|
+
h('span', { class: 'cm-ch-icon' }, icon),
|
|
31
|
+
h('span', { class: 'cm-ch-name' }, name)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ChannelCategory({ id, name, channels = [], collapsed, activeId, onToggle, onAddChannel, onChannelClick, onChannelContext } = {}) {
|
|
36
|
+
return h('div', { class: 'cm-channel-category' },
|
|
37
|
+
h('div', {
|
|
38
|
+
class: 'cm-category-header' + (collapsed ? ' collapsed' : ''),
|
|
39
|
+
onclick: () => onToggle?.(id)
|
|
40
|
+
},
|
|
41
|
+
h('svg', { class: 'cm-cat-arrow', viewBox: '0 0 24 24' }, h('path', { d: 'M7 10l5 5 5-5z' })),
|
|
42
|
+
h('span', { class: 'cm-cat-name' }, name),
|
|
43
|
+
onAddChannel ? h('button', { class: 'cm-cat-add', onclick: (e) => { e.stopPropagation(); onAddChannel(id); }, title: 'Add channel' }, '+') : null
|
|
44
|
+
),
|
|
45
|
+
collapsed ? null : h('div', { class: 'cm-cat-channels' },
|
|
46
|
+
...channels.map(c => ChannelItem({
|
|
47
|
+
...c,
|
|
48
|
+
active: c.id === activeId,
|
|
49
|
+
onClick: () => onChannelClick?.(c),
|
|
50
|
+
onContext: onChannelContext
|
|
51
|
+
}))
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function VoiceUser({ identity, speaking, color } = {}) {
|
|
57
|
+
const initial = (identity || '?').slice(0, 1).toUpperCase();
|
|
58
|
+
return h('div', { class: 'cm-voice-user' + (speaking ? ' speaking' : '') },
|
|
59
|
+
h('div', { class: 'cm-voice-user-avatar', style: color ? `background:${color}` : '' }, initial),
|
|
60
|
+
h('span', { class: 'cm-voice-user-name' }, identity)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function UserPanel({ name, tag, color, muted, deafened, onMute, onDeafen, onSettings } = {}) {
|
|
65
|
+
const initial = (name || '?').slice(0, 1).toUpperCase();
|
|
66
|
+
return h('div', { class: 'cm-user-panel' },
|
|
67
|
+
h('div', { class: 'cm-user-avatar', style: color ? `background:${color}` : '' },
|
|
68
|
+
h('span', { class: 'cm-user-status-dot' }),
|
|
69
|
+
initial
|
|
70
|
+
),
|
|
71
|
+
h('div', { class: 'cm-user-info' },
|
|
72
|
+
h('div', { class: 'cm-user-name' }, name || 'You'),
|
|
73
|
+
tag ? h('div', { class: 'cm-user-tag' }, tag) : null
|
|
74
|
+
),
|
|
75
|
+
h('div', { class: 'cm-user-controls' },
|
|
76
|
+
h('button', { class: 'cm-user-btn' + (muted ? ' muted' : ''), onclick: onMute, title: muted ? 'Unmute' : 'Mute' }, muted ? '🔇' : '🎤'),
|
|
77
|
+
h('button', { class: 'cm-user-btn' + (deafened ? ' deafened' : ''), onclick: onDeafen, title: deafened ? 'Undeafen' : 'Deafen' }, deafened ? '🔕' : '🎧'),
|
|
78
|
+
h('button', { class: 'cm-user-btn', onclick: onSettings, title: 'Settings' }, '⚙')
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function ChannelSidebar({ serverName, channels = [], categories = [], activeId, collapsedCats = new Set(), onChannelClick, onCategoryToggle, onAddChannel, onChannelContext, userPanelProps } = {}) {
|
|
84
|
+
const uncategorized = channels.filter(c => !c.categoryId || !categories.find(cat => cat.id === c.categoryId));
|
|
85
|
+
const sorted = [...categories].sort((a, b) => (a.position || 0) - (b.position || 0));
|
|
86
|
+
|
|
87
|
+
return h('div', { class: 'cm-channel-sidebar' },
|
|
88
|
+
h('div', { class: 'cm-server-header' },
|
|
89
|
+
h('span', { class: 'cm-server-header-name' }, serverName || 'Server'),
|
|
90
|
+
),
|
|
91
|
+
h('div', { class: 'cm-channel-list' },
|
|
92
|
+
...sorted.map(cat => ChannelCategory({
|
|
93
|
+
id: cat.id,
|
|
94
|
+
name: cat.name,
|
|
95
|
+
channels: channels.filter(c => c.categoryId === cat.id).sort((a, b) => (a.position || 0) - (b.position || 0)),
|
|
96
|
+
collapsed: collapsedCats.has(cat.id),
|
|
97
|
+
activeId,
|
|
98
|
+
onToggle: onCategoryToggle,
|
|
99
|
+
onAddChannel,
|
|
100
|
+
onChannelClick,
|
|
101
|
+
onChannelContext
|
|
102
|
+
})),
|
|
103
|
+
uncategorized.length ? ChannelCategory({
|
|
104
|
+
id: 'uncategorized',
|
|
105
|
+
name: 'CHANNELS',
|
|
106
|
+
channels: uncategorized,
|
|
107
|
+
activeId,
|
|
108
|
+
onChannelClick,
|
|
109
|
+
onChannelContext
|
|
110
|
+
}) : null
|
|
111
|
+
),
|
|
112
|
+
userPanelProps ? UserPanel(userPanelProps) : null
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function MemberItem({ identity, name, color, status = 'online' } = {}) {
|
|
117
|
+
const initial = (name || identity || '?').slice(0, 1).toUpperCase();
|
|
118
|
+
return h('div', { class: 'cm-member-item' },
|
|
119
|
+
h('div', { class: 'cm-member-avatar', style: color ? `background:${color}` : '' },
|
|
120
|
+
h('span', { class: 'cm-member-status' + (status === 'online' ? ' online' : '') }),
|
|
121
|
+
initial
|
|
122
|
+
),
|
|
123
|
+
h('span', { class: 'cm-member-name' }, name || identity)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function MemberList({ categories = [], open } = {}) {
|
|
128
|
+
return h('div', { class: 'cm-member-list' + (open ? ' open' : '') },
|
|
129
|
+
...categories.flatMap(cat => [
|
|
130
|
+
h('div', { class: 'cm-member-category', key: cat.label }, `${cat.label} — ${cat.members.length}`),
|
|
131
|
+
...cat.members.map((m, i) => MemberItem({ ...m, key: m.identity || i }))
|
|
132
|
+
])
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function ChatHeader({ icon = '#', name, topic, toolbar = [] } = {}) {
|
|
137
|
+
return h('div', { class: 'cm-chat-header' },
|
|
138
|
+
h('span', { class: 'cm-chat-header-icon' }, icon),
|
|
139
|
+
h('span', { class: 'cm-chat-header-name' }, name),
|
|
140
|
+
topic ? h('span', { class: 'cm-chat-header-topic' }, topic) : null,
|
|
141
|
+
h('div', { class: 'cm-chat-header-toolbar' }, ...toolbar)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function VoiceStrip({ channelName, status, muted, deafened, onMute, onDeafen, onLeave, open } = {}) {
|
|
146
|
+
return h('div', { class: 'cm-voice-strip' + (open ? ' open' : '') },
|
|
147
|
+
h('div', { class: 'cm-vs-label' },
|
|
148
|
+
h('span', { class: 'cm-vs-channel' }, '🔊 ' + (channelName || 'voice')),
|
|
149
|
+
h('span', { class: 'cm-vs-status' }, status || 'connected')
|
|
150
|
+
),
|
|
151
|
+
h('button', { class: 'cm-vs-btn', onclick: onMute, title: 'Mute' }, muted ? '🔇' : '🎤'),
|
|
152
|
+
h('button', { class: 'cm-vs-btn', onclick: onDeafen, title: 'Deafen' }, deafened ? '🔕' : '🎧'),
|
|
153
|
+
h('button', { class: 'cm-vs-btn danger', onclick: onLeave, title: 'Leave' }, '✕')
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function CommunityShell({ serverRailProps, sidebarProps, children, memberListProps, voiceStripProps } = {}) {
|
|
158
|
+
return h('div', { class: 'cm-shell' },
|
|
159
|
+
serverRailProps ? ServerRail(serverRailProps) : null,
|
|
160
|
+
sidebarProps ? ChannelSidebar(sidebarProps) : null,
|
|
161
|
+
h('div', { class: 'cm-main' }, ...(Array.isArray(children) ? children : [children])),
|
|
162
|
+
memberListProps ? MemberList(memberListProps) : null,
|
|
163
|
+
voiceStripProps ? VoiceStrip(voiceStripProps) : null
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as webjsx from '../../vendor/webjsx/index.js';
|
|
2
|
+
export const h = webjsx.createElement;
|
|
3
|
+
import { Panel, Row, Hero, Receipt, Kpi, Table, Form } from './content.js';
|
|
4
|
+
import { Chip } from './shell.js';
|
|
5
|
+
import { EmptyState } from './files.js';
|
|
6
|
+
|
|
7
|
+
export const skillLabel = s => (s.name||'').replace(/^gm:/,'').replace(/-/g,' ');
|
|
8
|
+
export const getRecentPaths = () => { try { return JSON.parse(localStorage.getItem('fd_recent_cwds')||'[]'); } catch { return []; } };
|
|
9
|
+
export const saveRecentPath = p => { if (!p) return; const a = getRecentPaths().filter(x=>x!==p); a.unshift(p); localStorage.setItem('fd_recent_cwds', JSON.stringify(a.slice(0,5))); };
|
|
10
|
+
export function renderChatMessages(el, msgs) {
|
|
11
|
+
if (!el) return; el.innerHTML = '';
|
|
12
|
+
for (const m of msgs) {
|
|
13
|
+
const div = document.createElement('div');
|
|
14
|
+
div.className = 'fd-msg'+(m.role==='assistant'?' fd-msg-assistant':'');
|
|
15
|
+
if (m.role==='tool') { const det=document.createElement('details'); det.className='fd-tool-call'; const sum=document.createElement('summary'); sum.textContent=(m.name||'tool')+(m.argsSummary?': '+m.argsSummary:''); const pre=document.createElement('pre'); pre.className='fd-tool-body'; pre.textContent=m.content||''; det.appendChild(sum); det.appendChild(pre); div.appendChild(det); }
|
|
16
|
+
else { const pre=document.createElement('pre'); pre.className='fd-pre'; pre.textContent=m.content||''; div.appendChild(pre); }
|
|
17
|
+
el.appendChild(div);
|
|
18
|
+
}
|
|
19
|
+
el.scrollTop = el.scrollHeight;
|
|
20
|
+
}
|
|
21
|
+
export async function home(h0) {
|
|
22
|
+
const sessions=await h0.pi.sessions.list(); const health=h0.pi.health();
|
|
23
|
+
return [Hero({title:'freddie',body:'open js agent harness.',accent:h0.version||'web'}),Kpi({items:[[sessions.length,'sessions'],[h0.pi.tools.size,'tools'],[h0.pi.skills.size,'skills']]}),Panel({title:'quick start',children:Receipt({rows:[['open chat',"click 'chat' — set a working directory"],['pick skill','software dev, research, planning'],['set api key','keys tab → click chip'],['add cron','cron tab → form']]})}),Panel({title:'host',children:Receipt({rows:Object.entries(health).map(([k,v])=>[k,String(v)])})})];
|
|
24
|
+
}
|
|
25
|
+
export async function chat(h0) {
|
|
26
|
+
const skills=[...h0.pi.skills.values()]; const providers=await fetch('/api/providers').then(r=>r.json()).catch(()=>[]); const configured=providers.filter(p=>p.configured);
|
|
27
|
+
const cs=window.__fd_chatState=window.__fd_chatState||{cwd:'',skill:'',provider:'',model:'',messages:[],busy:false,sessionId:null};
|
|
28
|
+
if (!cs.cwd) cs.cwd=(getRecentPaths()[0]||'');
|
|
29
|
+
const root=document.getElementById('app'); const getMsgs=()=>root.querySelector('#fd-chat-msgs');
|
|
30
|
+
const newSession=()=>{ if(cs.busy)return; cs.messages=[]; cs.sessionId=null; renderChatMessages(getMsgs(),cs.messages); };
|
|
31
|
+
const parseSse=text=>{ const evs=[]; let ev=null,data=''; for(const line of text.split('\n')){ if(line.startsWith('event: '))ev=line.slice(7).trim(); else if(line.startsWith('data: '))data=line.slice(6).trim(); else if(line===''&&ev){try{evs.push({event:ev,data:JSON.parse(data)});}catch{}ev=null;data='';} } return evs; };
|
|
32
|
+
const sendChat=async ev=>{ ev.preventDefault(); if(cs.busy)return; const promptEl=ev.target.elements.prompt; const prompt=promptEl.value.trim(); if(!prompt)return;
|
|
33
|
+
cs.messages.push({role:'user',content:prompt}); promptEl.value=''; promptEl.style.height='auto'; cs.busy=true; saveRecentPath(cs.cwd); renderChatMessages(getMsgs(),cs.messages);
|
|
34
|
+
try { const body={prompt,cwd:cs.cwd||undefined,skill:cs.skill||undefined,provider:cs.provider||undefined,model:cs.model||undefined,sessionId:cs.sessionId||undefined};
|
|
35
|
+
const resp=await fetch('/api/chat',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(body)}); const text=await resp.text(); const events=parseSse(text); let ac='';
|
|
36
|
+
for(const{event,data}of events){ if(event==='start'&&data.sessionId)cs.sessionId=data.sessionId; if(event==='done'&&data.sessionId)cs.sessionId=data.sessionId;
|
|
37
|
+
if(event==='message'){ if(data.role==='assistant'){ const content=Array.isArray(data.content)?data.content:[{type:'text',text:String(data.content||'')}]; for(const block of content){ if(block.type==='text')ac+=block.text; if(block.type==='tool_use'){if(ac){cs.messages.push({role:'assistant',content:ac});ac='';}cs.messages.push({role:'tool',name:block.name,argsSummary:JSON.stringify(block.input||{}).slice(0,60),content:JSON.stringify(block.input||{},null,2)});} } }
|
|
38
|
+
else if(data.role==='tool'){const tc=Array.isArray(data.content)?data.content[0]:data;cs.messages.push({role:'tool',name:'result',argsSummary:'',content:String(tc?.content||tc?.text||JSON.stringify(tc))});} }
|
|
39
|
+
if(event==='done'&&data.result&&!ac)ac=data.result; if(event==='error')ac='error: '+(data.error||'unknown'); }
|
|
40
|
+
if(ac)cs.messages.push({role:'assistant',content:ac}); if(!events.length)cs.messages.push({role:'assistant',content:'(no response)'});
|
|
41
|
+
} catch(e){cs.messages.push({role:'assistant',content:'error: '+e.message});}
|
|
42
|
+
cs.busy=false; renderChatMessages(getMsgs(),cs.messages); };
|
|
43
|
+
const byCat=skills.reduce((a,s)=>{const c=s.category||'other';(a[c]=a[c]||[]).push(s);return a;},{});
|
|
44
|
+
setTimeout(()=>renderChatMessages(getMsgs(),cs.messages),50);
|
|
45
|
+
return [Panel({title:'chat',right:h('button',{class:'btn-primary',onclick:ev=>{ev.preventDefault();newSession();}},'+ new'),children:[
|
|
46
|
+
h('form',{class:'fd-chat-form',onsubmit:sendChat},h('label',{class:'fd-label'},'WORKING DIRECTORY'),h('input',{name:'cwd',type:'text',placeholder:'e.g. C:/dev/myproject',value:cs.cwd,oninput:ev=>{cs.cwd=ev.target.value;}}),
|
|
47
|
+
h('div',{class:'fd-row'},h('div',{class:'fd-col'},h('label',{class:'fd-label'},'SKILL'),h('select',{name:'skill',onchange:ev=>{cs.skill=ev.target.value;}},h('option',{value:''},'— no skill —'),...Object.entries(byCat).map(([cat,ss])=>h('optgroup',{label:cat},...ss.map(s=>h('option',{value:s.name,selected:cs.skill===s.name?'true':null},skillLabel(s))))))),
|
|
48
|
+
h('div',{class:'fd-col'},h('label',{class:'fd-label'},'PROVIDER'),h('select',{name:'provider',onchange:ev=>{cs.provider=ev.target.value;}},h('option',{value:''},configured.length?'— auto —':'— none configured —'),...configured.map(p=>h('option',{value:p.name,selected:cs.provider===p.name?'true':null},(p.available?'● ':'○ ')+p.name)))),
|
|
49
|
+
h('div',{class:'fd-col'},h('label',{class:'fd-label'},'MODEL'),h('input',{name:'model',type:'text',placeholder:'default',value:cs.model,oninput:ev=>{cs.model=ev.target.value;}}))),
|
|
50
|
+
h('div',{class:'fd-chat-send-row'},h('textarea',{name:'prompt',placeholder:'describe what you want…',rows:4,oninput:ev=>{ev.target.style.height='auto';ev.target.style.height=Math.min(ev.target.scrollHeight,240)+'px';}}),h('button',{type:'submit',class:'btn-primary',disabled:cs.busy?'true':null},cs.busy?'…':'send'))),
|
|
51
|
+
h('div',{id:'fd-chat-msgs',class:'fd-chat-thread'})]}),
|
|
52
|
+
configured.length===0?Panel({title:'no providers configured',children:Receipt({rows:[['set API key','keys tab → click chip'],['or use acptoapi','run acptoapi server on localhost:4800']]})}):Panel({title:'providers',children:h('div',{class:'fd-chips'},...providers.map(p=>Chip({tone:p.configured?(p.available?'ok':'warn'):'miss',children:p.name+(p.configured?(p.available?' ●':' ○'):'')})))})];
|
|
53
|
+
}
|
|
54
|
+
export async function sessions(h0) {
|
|
55
|
+
const list=await h0.pi.sessions.list();
|
|
56
|
+
const rows=list.map(s=>{const cont=h('button',{class:'btn-primary',onclick:async()=>{const msgs=await h0.pi.sessions.getMessages(s.id);const cs=window.__fd_chatState=window.__fd_chatState||{messages:[],busy:false,sessionId:null,cwd:'',skill:'',provider:'',model:''};cs.sessionId=s.id;cs.messages=msgs.map(m=>({role:m.role,content:String(m.content||'')}));if(s.cwd)cs.cwd=s.cwd;if(s.skill)cs.skill=s.skill;if(typeof window.__fd_nav==='function')window.__fd_nav('chat');}},'continue');return[(s.id||'').slice(0,8),s.title||'—',s.platform||'—',s.model||'—',s.cwd?s.cwd.slice(-30):'—',s.skill?skillLabel({name:s.skill}):'—',cont];});
|
|
57
|
+
return [Kpi({items:[[list.length,'sessions']]}),Panel({title:'sessions',count:list.length,children:list.length===0?EmptyState({text:'no sessions yet',glyph:'✉'}):Table({headers:['id','title','platform','model','cwd','skill',''],rows})})];
|
|
58
|
+
}
|
|
59
|
+
export async function projects(h0) {
|
|
60
|
+
const list=h0.pi.projects.list(); const active=h0.pi.projects.active();
|
|
61
|
+
const rows=list.map(p=>Row({key:p.name,code:p.name===active?.name?'●':'○',title:p.name+(p.name===active?.name?' (active)':''),meta:p.path,onClick:()=>{if(p.name!==active?.name)h0.pi.projects.setActive(p.name);}}));
|
|
62
|
+
return [Hero({title:'projects',body:'each project is its own ~/.freddie home.',accent:active?'active · '+active.name:'no active project'}),Kpi({items:[[list.length,'projects'],[active?.name||'—','active']]}),Panel({title:'add project',children:Form({fields:[{name:'name',placeholder:'name',required:true},{name:'path',placeholder:'/abs/path'}],submit:'add',onSubmit:ev=>{h0.pi.projects.create({name:ev.target.elements.name.value,path:ev.target.elements.path.value});}})}),Panel({title:'all projects',count:list.length,children:rows.length?rows:EmptyState({text:'no projects',glyph:'◆'})})];
|
|
63
|
+
}
|
|
64
|
+
export async function agents(h0) {
|
|
65
|
+
const a=typeof h0.pi.agents==='function'?await h0.pi.agents():{count:0,turns:0,active:null};
|
|
66
|
+
return [Kpi({items:[[a.count||0,'active'],[a.turns||0,'turns']]}),Panel({title:'agents',children:Receipt({rows:[['active session',a.active||'(none)'],['total turns',String(a.turns||0)]]})})];
|
|
67
|
+
}
|
|
68
|
+
export async function analytics(h0) {
|
|
69
|
+
const list=await h0.pi.sessions.list(); const tools=[...h0.pi.tools.values()];
|
|
70
|
+
const byPlat=list.reduce((a,s)=>{const k=s.platform||'?';a[k]=(a[k]||0)+1;return a;},{}); const byModel=list.reduce((a,s)=>{const k=s.model||'?';a[k]=(a[k]||0)+1;return a;},{});
|
|
71
|
+
return [Kpi({items:[[list.length,'sessions'],[tools.length,'tools']]}),Panel({title:'by platform',children:Object.keys(byPlat).length===0?EmptyState({text:'no data',glyph:'◉'}):Table({headers:['platform','count'],rows:Object.entries(byPlat).sort((a,b)=>b[1]-a[1])})}),Panel({title:'by model',children:Object.keys(byModel).length===0?EmptyState({text:'no data',glyph:'◎'}):Table({headers:['model','count'],rows:Object.entries(byModel).sort((a,b)=>b[1]-a[1])})})];
|
|
72
|
+
}
|
|
73
|
+
export async function models(h0) {
|
|
74
|
+
const cfg=typeof h0.pi.config?.load==='function'?await h0.pi.config.load():{};
|
|
75
|
+
const providers=await fetch('/api/providers').then(r=>r.json()).catch(()=>[]); const configured=providers.filter(p=>p.configured);
|
|
76
|
+
const probeState=window.__fd_probeState=window.__fd_probeState||{};
|
|
77
|
+
async function probeAll(){await Promise.allSettled(configured.map(async p=>{probeState[p.name]='loading';try{const r=await fetch('/api/providers/'+p.name+'/probe',{method:'POST'}).then(x=>x.json());probeState[p.name]=r.models||r.error||'?';}catch(e){probeState[p.name]='error: '+e.message;}}));if(typeof window.__fd_nav==='function')window.__fd_nav('models');}
|
|
78
|
+
const modelPanels=configured.map(p=>{const models=Array.isArray(probeState[p.name])?probeState[p.name]:p.models;const loading=probeState[p.name]==='loading';const children=loading?h('span',{},'probing…'):models&&models.length>0?Table({headers:['model id'],rows:models.map(m=>[m])}):h('span',{class:'fd-muted'},p.modelsError?'error: '+p.modelsError:'not probed — click "probe all"');return Panel({title:p.name+(p.available?' ●':' ○'),children});});
|
|
79
|
+
return [Kpi({items:[[configured.length,'configured'],[providers.filter(p=>p.available).length,'available']]}),Panel({title:'change active model',children:Form({fields:[{name:'provider',placeholder:'provider',value:cfg.agent?.provider||''},{name:'model',placeholder:'model id',value:cfg.agent?.model||''}],submit:'update',onSubmit:async ev=>{await h0.pi.config.saveValue('agent.provider',ev.target.elements.provider.value);await h0.pi.config.saveValue('agent.model',ev.target.elements.model.value);}})}),Panel({title:'providers',right:h('button',{class:'btn-primary',onclick:ev=>{ev.preventDefault();probeAll();}},'probe all'),children:h('div',{class:'fd-chips'},...providers.map(p=>Chip({tone:p.configured?(p.available?'ok':'warn'):'miss',children:p.name+(p.configured?(p.available?' ●':' ○'):' ·')})))}),...modelPanels];
|
|
80
|
+
}
|
|
81
|
+
export async function cron(h0) {
|
|
82
|
+
const list=await h0.pi.cron.list();
|
|
83
|
+
return [Kpi({items:[[list.length,'jobs']]}),Panel({title:'add job',children:Form({fields:[{name:'cron',placeholder:'* * * * *',required:true},{name:'prompt',placeholder:'prompt',required:true}],submit:'create',onSubmit:async ev=>{await h0.pi.cron.create({cron:ev.target.elements.cron.value,prompt:ev.target.elements.prompt.value});}})}),Panel({title:'jobs',count:list.length,children:list.length===0?EmptyState({text:'no cron jobs',glyph:'◷'}):Table({headers:['id','cron','prompt','enabled'],rows:list.map(j=>[j.id,j.cron,(j.prompt||'').slice(0,40),j.enabled?'yes':'no'])})})];
|
|
84
|
+
}
|
|
85
|
+
export async function skills(h0) {
|
|
86
|
+
const list=[...h0.pi.skills.values()]; const byCat=list.reduce((a,s)=>{(a[s.category||'other']=a[s.category||'other']||[]).push(s);return a;},{});
|
|
87
|
+
return [Kpi({items:[[list.length,'skills'],[Object.keys(byCat).length,'categories']]}),list.length===0?EmptyState({text:'no skills — add SKILL.md files to ~/.freddie/skills/',glyph:'◈'}):null,...Object.entries(byCat).map(([cat,ss])=>Panel({title:cat,count:ss.length,children:Table({headers:['name','description'],rows:ss.map(s=>[skillLabel(s),(s.description||'').slice(0,120)])})}))].filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
export async function config(h0) {
|
|
90
|
+
const cfg=typeof h0.pi.config?.load==='function'?await h0.pi.config.load():{};
|
|
91
|
+
const commands=typeof h0.pi.cli?.values==='function'?[...h0.pi.cli.values()]:[];
|
|
92
|
+
return [Kpi({items:[[commands.length,'commands'],[cfg._config_version||0,'config version']]}),Panel({title:'set config value',children:Form({fields:[{name:'key',placeholder:'dotted.key',required:true},{name:'value',placeholder:'value (json or string)',required:true}],submit:'save',onSubmit:async ev=>{let v=ev.target.elements.value.value;try{v=JSON.parse(v);}catch{}await h0.pi.config.saveValue(ev.target.elements.key.value,v);}})}),Panel({title:'commands',count:commands.length,children:Table({headers:['name','description'],rows:commands.map(c=>[c.name,c.description||''])})}),Panel({title:'active config',children:Receipt({rows:Object.entries(cfg).map(([k,v])=>[k,typeof v==='object'&&v!==null?JSON.stringify(v):String(v??'')])})})];
|
|
93
|
+
}
|
|
94
|
+
export async function env(h0) {
|
|
95
|
+
const list=typeof h0.pi.env?.list==='function'?h0.pi.env.list():[];
|
|
96
|
+
const setCount=list.filter(k=>k.set).length;
|
|
97
|
+
return [Kpi({items:[[setCount,'set'],[list.length-setCount,'missing'],[list.length,'total']]}),Panel({title:'environment variables',children:h('div',{class:'fd-chips'},...list.map(k=>h('span',{key:k.key,onclick:()=>{const v=prompt('set '+k.key+' (empty to unset):');if(v==null)return;if(typeof h0.pi.env.set==='function'){h0.pi.env.set(k.key,v);}},class:'fd-chip-wrap'},Chip({tone:k.set?'ok':'miss',children:k.key+(k.set?' ✓':' ·')}))))})];
|
|
98
|
+
}
|
|
99
|
+
export async function tools(h0) {
|
|
100
|
+
const list=[...h0.pi.tools.values()]; const bySet=list.reduce((a,t)=>{(a[t.toolset||'core']=a[t.toolset||'core']||[]).push(t);return a;},{});
|
|
101
|
+
return [Kpi({items:[[list.length,'tools'],[Object.keys(bySet).length,'toolsets']]}), ...Object.entries(bySet).map(([ts,items])=>Panel({title:'toolset · '+ts,count:items.length,children:items.map(t=>Row({key:t.name,code:'⚒',title:t.name,sub:(t.description||(t.schema&&t.schema.description)||'').slice(0,80)}))}))];
|
|
102
|
+
}
|
|
103
|
+
export async function batch(h0) {
|
|
104
|
+
const out=h('div',{id:'fd-batch-out'});
|
|
105
|
+
return [Panel({title:'run batch',children:Form({fields:[{name:'prompts',kind:'textarea',placeholder:'one prompt per line',rows:6},{name:'concurrency',type:'number',value:'4'}],submit:'run',onSubmit:async ev=>{const prompts=ev.target.elements.prompts.value.split('\n').map(s=>s.trim()).filter(Boolean);if(!prompts.length)return;const root=document.getElementById('app');const node=root.querySelector('#fd-batch-out');if(node)node.textContent='running…';try{const r=await h0.pi.batch.run({prompts,concurrency:Number(ev.target.elements.concurrency.value)||4});if(node){const results=Array.isArray(r)?r:(r&&typeof r==='object'?Object.entries(r).map(([k,v])=>({prompt:k,result:v})):[]);const tbl=document.createElement('table');const thead=tbl.createTHead();const hr=thead.insertRow();['#','prompt','result','status'].forEach(ht=>{const th=document.createElement('th');th.textContent=ht;hr.appendChild(th);});const tbody=tbl.createTBody();results.forEach((item,i)=>{const row=tbody.insertRow();[i+1,(item.prompt||'').slice(0,60),(item.result||item.error||'').slice(0,120),item.error?'error':'ok'].forEach(v=>{const td=row.insertCell();td.textContent=String(v);});});node.innerHTML='';node.appendChild(tbl);}}catch(e){if(node)node.textContent='error: '+(e.message||e);}}})}),Panel({title:'results',children:out})];
|
|
106
|
+
}
|
|
107
|
+
export async function gateway(h0) {
|
|
108
|
+
const platforms=typeof h0.pi.gateway?.platforms==='function'?h0.pi.gateway.platforms():[];
|
|
109
|
+
const active=platforms.filter(p=>p.enabled);
|
|
110
|
+
return [Kpi({items:[[platforms.length,'platforms'],[active.length,'active']]}),Panel({title:'platforms',count:platforms.length,right:active.length>0?Chip({tone:'ok',children:active.length+' active'}):Chip({tone:'miss',children:'none active'}),children:platforms.length===0?EmptyState({text:'no platforms registered',glyph:'⇌'}):platforms.map(p=>Row({key:p.name,code:p.enabled?'●':'○',title:p.name,sub:p.note||'',meta:p.enabled?'enabled':''}))})];
|
|
111
|
+
}
|
|
112
|
+
export const FREDDIE_PAGES = { home, chat, sessions, projects, agents, analytics, models, cron, skills, config, env, tools, batch, gateway };
|
package/src/components.js
CHANGED
|
@@ -31,3 +31,16 @@ export {
|
|
|
31
31
|
ConfirmDialog, PromptDialog,
|
|
32
32
|
FilePreviewMedia, FilePreviewCode, FilePreviewText, FileViewer
|
|
33
33
|
} from './components/files-modals.js';
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
ServerIcon, ServerRail,
|
|
37
|
+
ChannelItem, ChannelCategory,
|
|
38
|
+
VoiceUser, UserPanel, ChannelSidebar,
|
|
39
|
+
MemberItem, MemberList,
|
|
40
|
+
ChatHeader, VoiceStrip, CommunityShell
|
|
41
|
+
} from './components/community.js';
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
home, chat, sessions, projects, agents, analytics, models, cron, skills, config, env, tools, batch, gateway,
|
|
45
|
+
skillLabel, getRecentPaths, saveRecentPath, renderChatMessages, FREDDIE_PAGES
|
|
46
|
+
} from './components/freddie.js';
|
package/src/index.js
CHANGED
|
@@ -49,6 +49,7 @@ export {
|
|
|
49
49
|
};
|
|
50
50
|
export const h = webjsx.createElement;
|
|
51
51
|
export const applyDiff = webjsx.applyDiff;
|
|
52
|
+
export { FREDDIE_PAGES, home, chat, sessions, projects, agents, analytics, models, cron, skills, config, env, tools, batch, gateway, skillLabel, getRecentPaths, saveRecentPath, renderChatMessages } from './components.js';
|
|
52
53
|
export default {
|
|
53
54
|
webjsx, loadCss, scope, installStyles, mount, h, applyDiff,
|
|
54
55
|
registerDeckStage, getDeckStage, components, motion, debug, mountKit,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DsChat, registerChatElement } from './ds-chat.js';
|
|
2
|
+
import { register } from '../debug.js';
|
|
3
|
+
|
|
4
|
+
let _stats = { mounts: 0, sends: 0 };
|
|
5
|
+
|
|
6
|
+
class FreddieChat extends DsChat {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
this._title = 'freddie';
|
|
10
|
+
this._sub = '';
|
|
11
|
+
this._placeholder = 'message freddie · /tools · /tool name {json} · /run …';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
connectedCallback() {
|
|
15
|
+
_stats.mounts += 1;
|
|
16
|
+
super.connectedCallback();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let _registered = false;
|
|
21
|
+
export function registerFreddieChatElement() {
|
|
22
|
+
registerChatElement();
|
|
23
|
+
if (_registered) return;
|
|
24
|
+
if (!customElements.get('freddie-chat')) customElements.define('freddie-chat', FreddieChat);
|
|
25
|
+
_registered = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof window !== 'undefined' && typeof customElements !== 'undefined') {
|
|
29
|
+
registerFreddieChatElement();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
register('freddie-chat', () => ({ registered: _registered, ..._stats, instances: document.querySelectorAll('freddie-chat').length }));
|
|
33
|
+
|
|
34
|
+
export { FreddieChat };
|