anentrypoint-design 0.0.102 → 0.0.104
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 +464 -2005
- package/colors_and_type.css +148 -619
- package/dist/247420.css +592 -2748
- package/dist/247420.js +23 -62
- package/package.json +22 -23
- package/src/bootstrap.js +5 -18
- package/src/components/chat.js +15 -15
- package/src/components/community.js +9 -7
- package/src/components/content.js +71 -54
- package/src/components/files-modals.js +4 -2
- package/src/components/files.js +6 -14
- package/src/components/freddie/helpers.js +38 -77
- package/src/components/freddie.js +32 -16
- package/src/components/shell.js +22 -15
- package/src/components.js +7 -3
- package/src/debug.js +21 -30
- package/src/deck-stage.js +10 -6
- package/src/highlight.js +25 -11
- package/src/index.js +31 -7
- package/src/{desktop → kits/os}/freddie/helpers.js +1 -1
- package/src/{desktop → kits/os}/freddie/pages-chat.js +2 -2
- package/src/{desktop → kits/os}/freddie/pages-core.js +2 -2
- package/src/{desktop → kits/os}/freddie/pages-os.js +1 -1
- package/src/{desktop → kits/os}/freddie/pages-tools.js +2 -2
- package/src/{desktop → kits/os}/freddie-dashboard.js +2 -2
- package/src/markdown.js +27 -49
- package/src/motion.js +30 -17
- package/src/page-html.js +18 -116
- package/src/styles.js +14 -22
- package/src/web-components/ds-chat.js +26 -56
- package/dist/247420.app.js +0 -5
- package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTT0zRp8A.ttf +0 -0
- package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTT6jRp8A.ttf +0 -0
- package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTTBjNp8A.ttf +0 -0
- package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTTNDNp8A.ttf +0 -0
- package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTTnTRp8A.ttf +0 -0
- package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTTtDRp8A.ttf +0 -0
- package/dist/fonts/archivonarrow_v35_tss5ApVBdCYD5Q7hcxTE1ArZ0Zz8oY2KRmwvKhhv8laKpA.ttf +0 -0
- package/dist/fonts/archivonarrow_v35_tss5ApVBdCYD5Q7hcxTE1ArZ0Zz8oY2KRmwvKhhvHlGKpA.ttf +0 -0
- package/dist/fonts/archivonarrow_v35_tss5ApVBdCYD5Q7hcxTE1ArZ0Zz8oY2KRmwvKhhvLFGKpA.ttf +0 -0
- package/dist/fonts/archivonarrow_v35_tss5ApVBdCYD5Q7hcxTE1ArZ0Zz8oY2KRmwvKhhvy1aKpA.ttf +0 -0
- package/dist/fonts/jetbrainsmono_v24_tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8-qxjPQ.ttf +0 -0
- package/dist/fonts/jetbrainsmono_v24_tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8FqtjPQ.ttf +0 -0
- package/dist/fonts/jetbrainsmono_v24_tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8L6tjPQ.ttf +0 -0
- package/dist/fonts/jetbrainsmono_v24_tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxjPQ.ttf +0 -0
- package/dist/fonts/jetbrainsmono_v24_tDba2o-flEEny0FZhsfKu5WU4xD-IQ-PuZJJXxfpAO-LflOQ.ttf +0 -0
- package/dist/fonts/jetbrainsmono_v24_tDba2o-flEEny0FZhsfKu5WU4xD-IQ-PuZJJXxfpAO9seVOQ.ttf +0 -0
- package/dist/fonts/spacegrotesk_v22_V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj42Vksj.ttf +0 -0
- package/dist/fonts/spacegrotesk_v22_V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj4PVksj.ttf +0 -0
- package/dist/fonts/spacegrotesk_v22_V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj7aUUsj.ttf +0 -0
- package/dist/fonts/spacegrotesk_v22_V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj7oUUsj.ttf +0 -0
- package/src/app.js +0 -156
- package/src/components/freddie/pages-chains.js +0 -168
- package/src/components/freddie/pages-chat.js +0 -122
- package/src/components/freddie/pages-config-edit.js +0 -174
- package/src/components/freddie/pages-config.js +0 -193
- package/src/components/freddie/pages-core.js +0 -170
- package/src/components/freddie/pages-runtime.js +0 -69
- package/src/components/freddie/pages-voice.js +0 -109
- package/src/web-components/freddie-chat.js +0 -34
- /package/src/{desktop → kits/os}/about-app.js +0 -0
- /package/src/{desktop → kits/os}/app-panes.css +0 -0
- /package/src/{desktop → kits/os}/browser-app.js +0 -0
- /package/src/{desktop → kits/os}/files-app.js +0 -0
- /package/src/{desktop → kits/os}/freddie/routes.js +0 -0
- /package/src/{desktop → kits/os}/freddie-dashboard.css +0 -0
- /package/src/{desktop → kits/os}/icons.js +0 -0
- /package/src/{desktop → kits/os}/index.js +0 -0
- /package/src/{desktop → kits/os}/launcher.css +0 -0
- /package/src/{desktop → kits/os}/launcher.js +0 -0
- /package/src/{desktop → kits/os}/monitor-app.js +0 -0
- /package/src/{desktop → kits/os}/shell.js +0 -0
- /package/src/{desktop → kits/os}/terminal-app.js +0 -0
- /package/src/{desktop → kits/os}/theme.css +0 -0
- /package/src/{desktop → kits/os}/validate.css +0 -0
- /package/src/{desktop → kits/os}/validator-app.js +0 -0
- /package/src/{desktop → kits/os}/wm.css +0 -0
- /package/src/{desktop → kits/os}/wm.js +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/app.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
// Unified 247420 single-page app. Replaces the separate homepage,
|
|
2
|
-
// project_page, writing, and manifesto entry points with one WebJSX page
|
|
3
|
-
// that uses the design-system components and hash routing.
|
|
4
|
-
import { h, mount, installStyles, components as C, motion } from './index.js';
|
|
5
|
-
|
|
6
|
-
const data = {
|
|
7
|
-
nav: [['works', '#/works'], ['project', '#/project'], ['writing', '#/writing'], ['manifesto', '#/manifesto'], ['source ↗', 'https://github.com/AnEntrypoint']],
|
|
8
|
-
works: [
|
|
9
|
-
{ code: '001', title: 'gm', sub: 'state machine for coding agents', meta: '2025 · 3k★', body: 'a tiny deterministic state machine that lets llms code without losing their minds. it thinks so you don\'t have to (as much).', href: '#/project', source: 'https://github.com/AnEntrypoint/gm' },
|
|
10
|
-
{ code: '002', title: 'zellous', sub: 'production push-to-talk', meta: '2024 · live', body: 'hold the button. talk. someone on the other side hears you. opus codec, dynamic rooms, 50-message replay.', href: 'https://zellous.com', source: 'https://github.com/AnEntrypoint/zellous' },
|
|
11
|
-
{ code: '003', title: 'spoint', sub: 'spawnpoint', meta: '2024 · live', body: 'the directory for "where should we start?" one url, one room, everyone lands in the same place.', href: 'https://spoint.world', source: 'https://github.com/AnEntrypoint/spoint' },
|
|
12
|
-
{ code: '004', title: 'flatspace', sub: 'flat-file cms', meta: 'wip', body: 'still figuring out what to say about this one. come back tuesday.', href: 'https://github.com/AnEntrypoint/flatspace', source: 'https://github.com/AnEntrypoint/flatspace' },
|
|
13
|
-
{ code: '005', title: 'thebird', sub: '—', meta: 'wip', body: 'yes, the name is a reference. no, we won\'t tell you to what.', href: 'https://github.com/AnEntrypoint/thebird', source: 'https://github.com/AnEntrypoint/thebird' },
|
|
14
|
-
{ code: '006', title: 'mcp-repl', sub: 'repl for mcp', meta: '2024 · live', body: 'executenodejs, executedeno, executebash, astgrep_search.', href: 'https://github.com/AnEntrypoint/mcp-repl', source: 'https://github.com/AnEntrypoint/mcp-repl' },
|
|
15
|
-
{ code: '007', title: 'mutagen', sub: 'adaptogen server', meta: '2024 · live', body: 'everything to do with a dapp deg3n. read the source.', href: 'https://github.com/AnEntrypoint/mutagen', source: 'https://github.com/AnEntrypoint/mutagen' },
|
|
16
|
-
{ code: '008', title: 'techshaman', sub: 'member site', meta: 'ongoing', body: 'the official website for the techshaman.', href: 'https://github.com/AnEntrypoint/techshaman', source: 'https://github.com/AnEntrypoint/techshaman' }
|
|
17
|
-
],
|
|
18
|
-
posts: [
|
|
19
|
-
{ date: '2026.04.14', title: 'we were here first', tag: 'lore', href: 'https://247420.xyz' },
|
|
20
|
-
{ date: '2026.03.22', title: 'gm v0.4 postmortem, or: why state machines', tag: 'gm', href: 'https://github.com/AnEntrypoint/gm' },
|
|
21
|
-
{ date: '2026.02.09', title: 'push-to-talk is a protocol, not a feature', tag: 'zellous', href: 'https://github.com/AnEntrypoint/zellous' },
|
|
22
|
-
{ date: '2025.12.11', title: 'against the vibe-coded interface', tag: 'manifesto', href: '#/manifesto' },
|
|
23
|
-
{ date: '2025.10.03', title: 'notes on shipping weird', tag: 'notes', href: 'https://github.com/AnEntrypoint' }
|
|
24
|
-
],
|
|
25
|
-
manifesto: [
|
|
26
|
-
{ text: 'we are the creative department of the internet. always open (24/7). always a little bit high on possibility (420).' },
|
|
27
|
-
{ text: 'move fast. break things. document honestly. ship the rough draft. humor is load-bearing.' },
|
|
28
|
-
{ text: 'we will not tolerate simpleton design patterns, trifectas, gradients, or anything silly. nothing lame. we\'re internet natives and not easily pleased.', dim: true }
|
|
29
|
-
],
|
|
30
|
-
currentlyShipping: [
|
|
31
|
-
{ title: 'gm', sub: 'state machine v0.4.1', meta: 'live', live: true },
|
|
32
|
-
{ title: 'zellous', sub: 'push-to-talk', meta: 'live', live: true },
|
|
33
|
-
{ title: 'thebird', sub: '—', meta: 'wip', live: false }
|
|
34
|
-
],
|
|
35
|
-
project: {
|
|
36
|
-
name: 'gm', tagline: 'state machine for coding agents. it thinks, so you don\'t have to (as much).',
|
|
37
|
-
install: 'npx -y @anentrypoint/mcp-gm',
|
|
38
|
-
receipt: [['status', 'live · ships tuesdays'], ['stars', '3,124'], ['license', 'MIT'], ['lang', 'typescript · deno'], ['size', '2.1mb'], ['deps', '0 runtime'], ['authors', 'the collective'], ['first commit', '2024.09.03']],
|
|
39
|
-
changelog: [
|
|
40
|
-
{ date: '2026.04.20', ver: 'v0.4.1', msg: 'ship it. fixed the thing everyone complained about.' },
|
|
41
|
-
{ date: '2026.03.22', ver: 'v0.4.0', msg: 'new state machine runtime. broke everything on purpose.' },
|
|
42
|
-
{ date: '2026.02.09', ver: 'v0.3.7', msg: 'astgrep_search is now astgrep_enhanced_search.' },
|
|
43
|
-
{ date: '2025.12.11', ver: 'v0.3.0', msg: 'first public release. gm, world.' }
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const state = {
|
|
49
|
-
route: parseRoute(),
|
|
50
|
-
opened: -1,
|
|
51
|
-
copied: false
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
function parseRoute() {
|
|
55
|
-
const m = (location.hash || '#/').slice(1).split('/').filter(Boolean);
|
|
56
|
-
return { page: m[0] || 'works', id: m[1] || null };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
window.addEventListener('hashchange', () => {
|
|
60
|
-
state.route = parseRoute();
|
|
61
|
-
render();
|
|
62
|
-
requestAnimationFrame(() => {
|
|
63
|
-
motion.animateSelector('.app-main', 'fadeIn', { duration: 'var(--motion-base)' });
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
let render;
|
|
68
|
-
|
|
69
|
-
function navigate(name) {
|
|
70
|
-
location.hash = '#/' + name;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function topbar() {
|
|
74
|
-
return C.Topbar({
|
|
75
|
-
brand: '247420', leaf: 'an entrypoint',
|
|
76
|
-
items: data.nav,
|
|
77
|
-
active: state.route.page,
|
|
78
|
-
onNav: navigate
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function crumb() {
|
|
83
|
-
if (state.route.page === 'project') {
|
|
84
|
-
return C.Crumb({
|
|
85
|
-
trail: ['247420', 'gm'], leaf: 'overview',
|
|
86
|
-
right: [C.Chip({ tone: 'accent', children: '● live' }), C.Chip({ tone: 'dim', children: 'v0.4.1' })]
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return C.Crumb({ trail: ['247420'], leaf: state.route.page });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function status() {
|
|
93
|
-
return C.Status({
|
|
94
|
-
left: ['main', '• 8 works', '• 5 posts'],
|
|
95
|
-
right: ['probably emerging', h('a', { href: 'https://github.com/AnEntrypoint' }, 'source ↗')]
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function pageMain() {
|
|
100
|
-
const r = state.route.page;
|
|
101
|
-
if (r === 'project') {
|
|
102
|
-
return C.ProjectView({
|
|
103
|
-
project: data.project,
|
|
104
|
-
copied: state.copied,
|
|
105
|
-
onCopy: (cmd) => {
|
|
106
|
-
navigator.clipboard?.writeText(cmd);
|
|
107
|
-
state.copied = true;
|
|
108
|
-
render();
|
|
109
|
-
requestAnimationFrame(() => {
|
|
110
|
-
motion.animateSelector('.cli .copy', 'pulse', { duration: 'var(--motion-fast)' });
|
|
111
|
-
});
|
|
112
|
-
setTimeout(() => {
|
|
113
|
-
state.copied = false;
|
|
114
|
-
render();
|
|
115
|
-
}, 1200);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
if (r === 'writing') {
|
|
120
|
-
return [C.Section({ title: '// recent writing', children: C.WritingList({ posts: data.posts }) })];
|
|
121
|
-
}
|
|
122
|
-
if (r === 'manifesto') {
|
|
123
|
-
return [C.Section({ title: '// manifesto · rough draft', children: C.Manifesto({ paragraphs: data.manifesto }) })];
|
|
124
|
-
}
|
|
125
|
-
return C.HomeView({
|
|
126
|
-
state, onNav: navigate,
|
|
127
|
-
onToggleWork: (i) => {
|
|
128
|
-
state.opened = i;
|
|
129
|
-
render();
|
|
130
|
-
if (i >= 0) {
|
|
131
|
-
requestAnimationFrame(() => {
|
|
132
|
-
motion.animateSelector(`[data-work-index="${i}"]`, 'fadeInUp', { duration: 'var(--motion-fast)' });
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
works: data.works, posts: data.posts, manifesto: data.manifesto,
|
|
137
|
-
currentlyShipping: data.currentlyShipping
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function App() {
|
|
142
|
-
return C.AppShell({
|
|
143
|
-
topbar: topbar(),
|
|
144
|
-
crumb: crumb(),
|
|
145
|
-
main: pageMain(),
|
|
146
|
-
status: status(),
|
|
147
|
-
narrow: state.route.page === 'project'
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
await installStyles();
|
|
152
|
-
const root = document.getElementById('root');
|
|
153
|
-
render = mount(root, App);
|
|
154
|
-
requestAnimationFrame(() => {
|
|
155
|
-
motion.animateSelector('.app-main', 'fadeIn', { duration: 'var(--motion-base)' });
|
|
156
|
-
});
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
// Chain editor — talks to freddie's /api/acptoapi/* bridge (which proxies
|
|
2
|
-
// to acptoapi /v1/chains). Renders builtins read-only and runtime chains
|
|
3
|
-
// fully editable. Used by both freddie's dashboard and thebird via the
|
|
4
|
-
// FREDDIE_PAGES registry.
|
|
5
|
-
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
6
|
-
import { Panel, Hero, Kpi, Form } from '../content.js';
|
|
7
|
-
import { Chip } from '../shell.js';
|
|
8
|
-
import { EmptyState } from '../files.js';
|
|
9
|
-
const h = webjsx.createElement;
|
|
10
|
-
|
|
11
|
-
async function fetchJson(url, init) {
|
|
12
|
-
const r = await fetch(url, init);
|
|
13
|
-
const ct = r.headers.get('content-type') || '';
|
|
14
|
-
const body = ct.includes('json') ? await r.json() : await r.text();
|
|
15
|
-
if (!r.ok) throw new Error(typeof body === 'string' ? body : (body.error?.message || JSON.stringify(body)));
|
|
16
|
-
return body;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function loadChains() {
|
|
20
|
-
return await fetchJson('/api/acptoapi/chains');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function saveChain(name, links) {
|
|
24
|
-
return await fetchJson('/api/acptoapi/chains', {
|
|
25
|
-
method: 'POST',
|
|
26
|
-
headers: { 'content-type': 'application/json' },
|
|
27
|
-
body: JSON.stringify({ name, links }),
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function deleteChain(name) {
|
|
32
|
-
return await fetchJson('/api/acptoapi/chains/' + encodeURIComponent(name), { method: 'DELETE' });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function fetchProbe() {
|
|
36
|
-
try { return await fetchJson('/api/acptoapi/probe'); }
|
|
37
|
-
catch { return { results: [] }; }
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function chains(h0) {
|
|
41
|
-
// State stored on window to survive page-level re-renders
|
|
42
|
-
const state = window.__fd_chains = window.__fd_chains || {
|
|
43
|
-
loaded: false, error: null, chains: {}, builtin: [], runtime: [],
|
|
44
|
-
probe: [], editing: null, draft: { name: '', links: [] }, newLink: '',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
if (!state.loaded) {
|
|
48
|
-
try {
|
|
49
|
-
const cd = await loadChains();
|
|
50
|
-
state.chains = cd.chains || {};
|
|
51
|
-
state.builtin = cd.builtin || [];
|
|
52
|
-
state.runtime = cd.runtime || [];
|
|
53
|
-
const pl = await fetchProbe();
|
|
54
|
-
state.probe = pl.results || [];
|
|
55
|
-
state.loaded = true;
|
|
56
|
-
} catch (e) {
|
|
57
|
-
state.error = e.message;
|
|
58
|
-
state.loaded = true;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const nav = () => { if (typeof window.__fd_nav === 'function') window.__fd_nav('chains'); };
|
|
63
|
-
|
|
64
|
-
const onEdit = (name) => () => {
|
|
65
|
-
state.editing = name;
|
|
66
|
-
state.draft = { name, links: [...(state.chains[name] || [])] };
|
|
67
|
-
nav();
|
|
68
|
-
};
|
|
69
|
-
const onCancel = () => { state.editing = null; state.draft = { name: '', links: [] }; nav(); };
|
|
70
|
-
const onNew = () => { state.editing = '__new__'; state.draft = { name: '', links: [] }; nav(); };
|
|
71
|
-
const onAddLink = () => {
|
|
72
|
-
if (!state.newLink.trim()) return;
|
|
73
|
-
state.draft.links.push(state.newLink.trim());
|
|
74
|
-
state.newLink = '';
|
|
75
|
-
nav();
|
|
76
|
-
};
|
|
77
|
-
const onRemoveLink = (idx) => () => { state.draft.links.splice(idx, 1); nav(); };
|
|
78
|
-
const onSave = async () => {
|
|
79
|
-
try {
|
|
80
|
-
await saveChain(state.draft.name.trim(), state.draft.links);
|
|
81
|
-
state.loaded = false; state.editing = null;
|
|
82
|
-
nav();
|
|
83
|
-
} catch (e) { state.error = e.message; nav(); }
|
|
84
|
-
};
|
|
85
|
-
const onDelete = (name) => async () => {
|
|
86
|
-
if (state.builtin.includes(name)) return;
|
|
87
|
-
try { await deleteChain(name); state.loaded = false; nav(); }
|
|
88
|
-
catch (e) { state.error = e.message; nav(); }
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
if (state.error && !state.loaded) {
|
|
92
|
-
return [Hero({ title: 'chains', body: 'fallback chain editor', accent: 'error' }),
|
|
93
|
-
Panel({ title: 'error', children: h('span', { class: 'fd-muted' }, state.error) })];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (state.editing) {
|
|
97
|
-
const isNew = state.editing === '__new__';
|
|
98
|
-
const isBuiltin = !isNew && state.builtin.includes(state.editing);
|
|
99
|
-
return [
|
|
100
|
-
Hero({ title: isNew ? 'new chain' : 'edit ' + state.editing, body: 'links are tried top-to-bottom', accent: isBuiltin ? 'builtin read-only' : 'editable' }),
|
|
101
|
-
Panel({ title: 'name', children: isNew
|
|
102
|
-
? h('input', { class: 'fd-input', value: state.draft.name, oninput: e => { state.draft.name = e.target.value; } })
|
|
103
|
-
: h('code', {}, state.draft.name) }),
|
|
104
|
-
Panel({ title: 'links', count: state.draft.links.length,
|
|
105
|
-
children: state.draft.links.length === 0
|
|
106
|
-
? EmptyState({ text: 'no links yet — add one below', glyph: '↳' })
|
|
107
|
-
: h('div', { class: 'fd-list' }, ...state.draft.links.map((link, i) =>
|
|
108
|
-
h('div', { key: i, class: 'fd-list-row', 'data-cat': 'kit' },
|
|
109
|
-
h('span', { class: 'fd-list-code' }, String(i + 1).padStart(2, '0')),
|
|
110
|
-
h('div', { class: 'fd-list-main' }, h('div', { class: 'fd-list-title' }, link)),
|
|
111
|
-
isBuiltin ? null : h('button', { class: 'fd-btn fd-btn-mini', onclick: onRemoveLink(i) }, 'remove')
|
|
112
|
-
)
|
|
113
|
-
)) }),
|
|
114
|
-
isBuiltin ? null : Panel({ title: 'add link', children: h('div', { class: 'fd-form-row' },
|
|
115
|
-
h('input', { class: 'fd-input', placeholder: 'provider/model (e.g. groq/llama-3.3-70b-versatile)',
|
|
116
|
-
value: state.newLink, oninput: e => { state.newLink = e.target.value; } }),
|
|
117
|
-
h('button', { class: 'fd-btn', onclick: onAddLink }, 'add')
|
|
118
|
-
) }),
|
|
119
|
-
Panel({ title: 'actions', children: h('div', { class: 'fd-form-row' },
|
|
120
|
-
isBuiltin ? null : h('button', { class: 'fd-btn fd-btn-primary', onclick: onSave }, isNew ? 'create' : 'save'),
|
|
121
|
-
h('button', { class: 'fd-btn', onclick: onCancel }, 'cancel')
|
|
122
|
-
) }),
|
|
123
|
-
];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const allNames = Object.keys(state.chains).sort();
|
|
127
|
-
const builtinCount = state.builtin.length;
|
|
128
|
-
const runtimeCount = state.runtime.length;
|
|
129
|
-
const workingModels = state.probe.length;
|
|
130
|
-
|
|
131
|
-
return [
|
|
132
|
-
Hero({ title: 'chains', body: 'named fallback chains. callers send model=<name> to select one.', accent: allNames.length + ' total' }),
|
|
133
|
-
Kpi({ items: [[builtinCount, 'builtin'], [runtimeCount, 'runtime'], [workingModels, 'working models']] }),
|
|
134
|
-
Panel({ title: 'create', children: h('button', { class: 'fd-btn fd-btn-primary', onclick: onNew }, '+ new chain') }),
|
|
135
|
-
Panel({ title: 'chains', count: allNames.length,
|
|
136
|
-
children: allNames.length === 0
|
|
137
|
-
? EmptyState({ text: 'no chains configured', glyph: '⊞' })
|
|
138
|
-
: h('div', { class: 'fd-list' }, ...allNames.map(name => {
|
|
139
|
-
const links = state.chains[name] || [];
|
|
140
|
-
const isBuiltin = state.builtin.includes(name);
|
|
141
|
-
return h('div', { key: name, class: 'fd-list-row', 'data-cat': isBuiltin ? 'external' : 'kit' },
|
|
142
|
-
h('span', { class: 'fd-list-code' }, isBuiltin ? '◆' : '○'),
|
|
143
|
-
h('div', { class: 'fd-list-main' },
|
|
144
|
-
h('div', { class: 'fd-list-title' }, name),
|
|
145
|
-
h('div', { class: 'fd-list-sub' }, links.slice(0, 3).join(' → ') + (links.length > 3 ? ` (+${links.length - 3})` : '')),
|
|
146
|
-
),
|
|
147
|
-
h('div', { class: 'fd-list-meta' },
|
|
148
|
-
h('span', { class: 'fd-list-meta-mono' }, isBuiltin ? 'builtin' : 'runtime'),
|
|
149
|
-
h('button', { class: 'fd-btn fd-btn-mini', onclick: onEdit(name) }, isBuiltin ? 'view' : 'edit'),
|
|
150
|
-
isBuiltin ? null : h('button', { class: 'fd-btn fd-btn-mini', onclick: onDelete(name) }, 'delete'),
|
|
151
|
-
)
|
|
152
|
-
);
|
|
153
|
-
})) }),
|
|
154
|
-
Panel({ title: 'discovered models', count: workingModels,
|
|
155
|
-
right: workingModels > 0 ? Chip({ tone: 'ok', children: 'fresh' }) : Chip({ tone: 'miss', children: 'no probe' }),
|
|
156
|
-
children: workingModels === 0
|
|
157
|
-
? EmptyState({ text: 'run a probe to see working models', glyph: '✦' })
|
|
158
|
-
: h('div', { class: 'fd-list' }, ...state.probe.slice(0, 12).map(m =>
|
|
159
|
-
h('div', { key: m.provider + '/' + m.model, class: 'fd-list-row', 'data-cat': 'kit' },
|
|
160
|
-
h('span', { class: 'fd-list-code' }, m.provider.slice(0, 3)),
|
|
161
|
-
h('div', { class: 'fd-list-main' },
|
|
162
|
-
h('div', { class: 'fd-list-title' }, m.provider + '/' + m.model),
|
|
163
|
-
h('div', { class: 'fd-list-sub' }, m.ms + 'ms')
|
|
164
|
-
)
|
|
165
|
-
)
|
|
166
|
-
)) }),
|
|
167
|
-
];
|
|
168
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
2
|
-
import { Panel, Hero, Receipt } from '../content.js';
|
|
3
|
-
import { skillLabel, getRecentPaths, saveRecentPath, renderChatMessages } from './helpers.js';
|
|
4
|
-
const h = webjsx.createElement;
|
|
5
|
-
|
|
6
|
-
function parseSse(text) {
|
|
7
|
-
const evs = []; let ev = null, data = '';
|
|
8
|
-
for (const line of text.split('\n')) {
|
|
9
|
-
if (line.startsWith('event: ')) ev = line.slice(7).trim();
|
|
10
|
-
else if (line.startsWith('data: ')) data = line.slice(6).trim();
|
|
11
|
-
else if (line === '' && ev) { try { evs.push({ event: ev, data: JSON.parse(data) }); } catch {} ev = null; data = ''; }
|
|
12
|
-
}
|
|
13
|
-
return evs;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function chat(h0) {
|
|
17
|
-
const skills = [...h0.pi.skills.values()];
|
|
18
|
-
const providers = await fetch('/api/providers').then(r => r.json()).catch(() => []);
|
|
19
|
-
const configured = providers.filter(p => p.configured);
|
|
20
|
-
const v1Models = await fetch('/v1/models').then(r => r.json()).then(j => j.data || []).catch(() => []);
|
|
21
|
-
const cached = await fetch('/api/models/cached').then(r => r.json()).catch(() => ({}));
|
|
22
|
-
const sampler = await fetch('/api/models/sampler').then(r => r.json()).catch(() => ({ status: {} }));
|
|
23
|
-
(function() { const reg = window.__fd_modelPickers = window.__fd_modelPickers || {}; reg['chat.model'] = { key: 'chat.model', modelCount: v1Models.length, mountedAt: Date.now() }; window.__debug = window.__debug || {}; window.__debug.modelPickers = () => Object.values(window.__fd_modelPickers || {}); })();
|
|
24
|
-
const cs = window.__fd_chatState = window.__fd_chatState || { cwd: '', skill: '', provider: '', model: '', messages: [], busy: false, sessionId: null };
|
|
25
|
-
try { const last = localStorage.getItem('fd-chat-model'); if (last && !cs.model) cs.model = last } catch {}
|
|
26
|
-
if (!cs.cwd) cs.cwd = (getRecentPaths()[0] || '');
|
|
27
|
-
const root = document.getElementById('app');
|
|
28
|
-
const getMsgs = () => root.querySelector('#fd-chat-msgs');
|
|
29
|
-
const newSession = () => { if (cs.busy) return; cs.messages = []; cs.sessionId = null; renderChatMessages(getMsgs(), cs.messages); };
|
|
30
|
-
const sendChat = async ev => {
|
|
31
|
-
ev.preventDefault();
|
|
32
|
-
if (cs.busy) return;
|
|
33
|
-
const promptEl = ev.target.elements.prompt;
|
|
34
|
-
const prompt = promptEl.value.trim();
|
|
35
|
-
if (!prompt) return;
|
|
36
|
-
cs.messages.push({ role: 'user', content: prompt });
|
|
37
|
-
promptEl.value = ''; promptEl.style.height = 'auto';
|
|
38
|
-
cs.busy = true;
|
|
39
|
-
saveRecentPath(cs.cwd);
|
|
40
|
-
renderChatMessages(getMsgs(), cs.messages);
|
|
41
|
-
try {
|
|
42
|
-
const body = { prompt, cwd: cs.cwd || undefined, skill: cs.skill || undefined, provider: cs.provider || undefined, model: cs.model || undefined, sessionId: cs.sessionId || undefined };
|
|
43
|
-
const resp = await fetch('/api/chat', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
44
|
-
const text = await resp.text();
|
|
45
|
-
const events = parseSse(text);
|
|
46
|
-
let ac = '';
|
|
47
|
-
for (const { event, data } of events) {
|
|
48
|
-
if (event === 'start' && data.sessionId) cs.sessionId = data.sessionId;
|
|
49
|
-
if (event === 'done' && data.sessionId) cs.sessionId = data.sessionId;
|
|
50
|
-
if (event === 'message') {
|
|
51
|
-
if (data.role === 'assistant') {
|
|
52
|
-
const content = Array.isArray(data.content) ? data.content : [{ type: 'text', text: String(data.content || '') }];
|
|
53
|
-
for (const block of content) {
|
|
54
|
-
if (block.type === 'text') ac += block.text;
|
|
55
|
-
if (block.type === 'tool_use') {
|
|
56
|
-
if (ac) { cs.messages.push({ role: 'assistant', content: ac }); ac = ''; }
|
|
57
|
-
const argSummary = Object.keys(block.input || {}).slice(0, 3).map(k => k+'='+String(block.input[k]).slice(0,20)).join(' ');
|
|
58
|
-
cs.messages.push({ role: 'tool', name: block.name, argsSummary: argSummary, content: JSON.stringify(block.input || {}, null, 2) });
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} else if (data.role === 'tool') {
|
|
62
|
-
const tc = Array.isArray(data.content) ? data.content[0] : data;
|
|
63
|
-
cs.messages.push({ role: 'tool', name: 'result', argsSummary: '', content: String(tc?.content || tc?.text || JSON.stringify(tc)) });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (event === 'done' && data.result && !ac) ac = data.result;
|
|
67
|
-
if (event === 'error') ac = 'error: ' + (data.error || 'unknown');
|
|
68
|
-
}
|
|
69
|
-
if (ac) cs.messages.push({ role: 'assistant', content: ac });
|
|
70
|
-
if (!events.length) cs.messages.push({ role: 'assistant', content: '(no response)' });
|
|
71
|
-
} catch (e) { cs.messages.push({ role: 'assistant', content: 'error: '+e.message }); }
|
|
72
|
-
cs.busy = false;
|
|
73
|
-
renderChatMessages(getMsgs(), cs.messages);
|
|
74
|
-
};
|
|
75
|
-
const byCat = skills.reduce((a, s) => { const c = s.category || 'other'; (a[c] = a[c] || []).push(s); return a; }, {});
|
|
76
|
-
setTimeout(() => renderChatMessages(getMsgs(), cs.messages), 50);
|
|
77
|
-
return [
|
|
78
|
-
Hero({ title: 'chat', body: 'talk to the agent. pick a working dir, optional skill, optional provider.', accent: cs.sessionId ? 'session '+cs.sessionId.slice(0,8) : 'new session' }),
|
|
79
|
-
Panel({ title: 'chat', right: h('button', { class: 'btn-primary', onclick: ev => { ev.preventDefault(); newSession(); } }, '+ new'), children: [
|
|
80
|
-
h('form', { class: 'fd-chat-form', onsubmit: sendChat },
|
|
81
|
-
h('label', { class: 'fd-label' }, 'working directory'),
|
|
82
|
-
h('input', { name: 'cwd', type: 'text', placeholder: 'e.g. C:/dev/myproject', value: cs.cwd, oninput: ev => { cs.cwd = ev.target.value; } }),
|
|
83
|
-
h('div', { class: 'fd-row' },
|
|
84
|
-
h('div', { class: 'fd-col' },
|
|
85
|
-
h('label', { class: 'fd-label' }, 'skill'),
|
|
86
|
-
h('select', { name: 'skill', onchange: ev => { cs.skill = ev.target.value; } },
|
|
87
|
-
h('option', { value: '' }, '— no skill —'),
|
|
88
|
-
...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)))))
|
|
89
|
-
)
|
|
90
|
-
),
|
|
91
|
-
h('div', { class: 'fd-col' },
|
|
92
|
-
h('label', { class: 'fd-label' }, 'provider'),
|
|
93
|
-
h('select', { name: 'provider', onchange: ev => { cs.provider = ev.target.value; } },
|
|
94
|
-
h('option', { value: '' }, configured.length ? '— auto —' : '— none configured —'),
|
|
95
|
-
...configured.map(p => h('option', { value: p.name, selected: cs.provider === p.name ? 'true' : null }, (p.available ? '● ' : '○ ') + p.name))
|
|
96
|
-
)
|
|
97
|
-
),
|
|
98
|
-
h('div', { class: 'fd-col' },
|
|
99
|
-
h('label', { class: 'fd-label' }, 'model · ' + v1Models.length + ' available'),
|
|
100
|
-
(function() {
|
|
101
|
-
const samplerStatus = sampler && sampler.status ? sampler.status : {};
|
|
102
|
-
const isUnavail = id => { const s = samplerStatus[id]; return s && s.available === false; };
|
|
103
|
-
const grouped = v1Models.reduce((a, m) => { const idx = String(m.id || '').indexOf('/'); const g = idx > 0 ? m.id.slice(0, idx) : (m.owned_by || 'other'); (a[g] = a[g] || []).push(m); return a; }, {});
|
|
104
|
-
return h('select', { name: 'model', onchange: ev => { cs.model = ev.target.value; try { localStorage.setItem('fd-chat-model', cs.model) } catch {} } },
|
|
105
|
-
h('option', { value: '' }, '— auto —'),
|
|
106
|
-
...Object.entries(grouped).map(([g, ms]) => h('optgroup', { label: g + ' · ' + ms.length },
|
|
107
|
-
...ms.map(m => h('option', { value: m.id, selected: cs.model === m.id ? 'true' : null, disabled: isUnavail(m.id) ? 'true' : null }, m.id + (isUnavail(m.id) ? ' ✕' : ''))))));
|
|
108
|
-
})()
|
|
109
|
-
)
|
|
110
|
-
),
|
|
111
|
-
h('div', { class: 'fd-chat-send-row' },
|
|
112
|
-
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'; } }),
|
|
113
|
-
h('button', { type: 'submit', class: 'btn-primary', disabled: cs.busy ? 'true' : null }, cs.busy ? '…' : 'send')
|
|
114
|
-
)
|
|
115
|
-
),
|
|
116
|
-
h('div', { id: 'fd-chat-msgs', class: 'fd-chat-thread' })
|
|
117
|
-
] }),
|
|
118
|
-
configured.length === 0
|
|
119
|
-
? Panel({ title: 'no providers configured', children: Receipt({ rows: [['set API key', 'keys tab → click chip'], ['or use acptoapi', 'run acptoapi server on localhost:4800']] }) })
|
|
120
|
-
: null
|
|
121
|
-
];
|
|
122
|
-
}
|