anentrypoint-design 0.0.413 → 0.0.415
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/dist/247420.js +63 -13
- package/dist/app-shell.css +40 -594
- package/dist/colors_and_type.css +607 -47
- package/dist/index.html +227 -277
- package/dist/preview/buttons.html +39 -28
- package/dist/preview/colors-core.html +18 -8
- package/dist/preview/colors-lore.html +26 -17
- package/dist/preview/colors-semantic.html +24 -18
- package/dist/preview/dateline.html +6 -5
- package/dist/preview/dropzone.html +4 -3
- package/dist/preview/file-grid.html +10 -9
- package/dist/preview/file-row.html +14 -13
- package/dist/preview/file-toolbar.html +4 -3
- package/dist/preview/file-viewer.html +4 -3
- package/dist/preview/header.html +26 -24
- package/dist/preview/icons-unicode.html +8 -5
- package/dist/preview/index-row.html +17 -9
- package/dist/preview/inputs.html +4 -3
- package/dist/preview/manifesto.html +34 -17
- package/dist/preview/motion-default.js +77 -57
- package/dist/preview/rules.html +4 -3
- package/dist/preview/spacing.html +13 -12
- package/dist/preview/stamps-lore.html +5 -4
- package/dist/preview/stamps.html +4 -3
- package/dist/preview/theme-ink.html +7 -6
- package/dist/preview/type-display.html +14 -5
- package/dist/preview/type-mono.html +6 -5
- package/dist/preview/type-prose.html +12 -10
- package/dist/preview/type-scale.html +4 -3
- package/dist/preview/wordmarks.html +10 -7
- package/dist/site/content/globals/site.yaml +5 -3
- package/dist/site/content/pages/freddie.yaml +2 -2
- package/dist/site/content/pages/home.yaml +55 -55
- package/dist/site/theme.mjs +96 -348
- package/dist/slides/index.html +13 -13
- package/dist/src/bootstrap.js +10 -1
- package/dist/src/components/chat.js +43 -195
- package/dist/src/components/community.js +18 -166
- package/dist/src/components/content.js +30 -210
- package/dist/src/components/files-modals.js +14 -106
- package/dist/src/components/files.js +15 -116
- package/dist/src/components/freddie/helpers.js +53 -8
- package/dist/src/components/freddie.js +35 -25
- package/dist/src/components/shell.js +16 -114
- package/dist/src/components/theme-toggle.js +69 -25
- package/dist/src/components.js +92 -13
- package/dist/src/deck-stage.js +6 -4
- package/dist/src/highlight.js +51 -7
- package/dist/src/index.js +58 -12
- package/dist/src/kits/os/about-app.js +2 -3
- package/dist/src/kits/os/app-panes.css +46 -70
- package/dist/src/kits/os/browser-app.js +28 -1
- package/dist/src/kits/os/files-app.js +7 -0
- package/dist/src/kits/os/freddie/pages-chat.js +90 -75
- package/dist/src/kits/os/freddie/pages-core.js +9 -9
- package/dist/src/kits/os/freddie/pages-os.js +8 -8
- package/dist/src/kits/os/freddie/pages-tools.js +14 -14
- package/dist/src/kits/os/freddie/routes.js +21 -19
- package/dist/src/kits/os/freddie-dashboard.css +42 -21
- package/dist/src/kits/os/freddie-dashboard.js +6 -6
- package/dist/src/kits/os/icons.js +38 -16
- package/dist/src/kits/os/index.js +11 -0
- package/dist/src/kits/os/launcher.css +26 -10
- package/dist/src/kits/os/launcher.js +3 -0
- package/dist/src/kits/os/shell.js +72 -108
- package/dist/src/kits/os/terminal-app.js +1 -1
- package/dist/src/kits/os/theme.css +1330 -150
- package/dist/src/kits/os/wm.css +68 -15
- package/dist/src/kits/os/wm.js +93 -17
- package/dist/src/markdown.js +121 -12
- package/dist/src/motion.js +18 -2
- package/dist/src/page-html.js +62 -134
- package/dist/src/theme.js +106 -13
- package/dist/src/web-components/ds-chat.js +80 -9
- package/dist/ui_kits/aicat/app.js +54 -6
- package/dist/ui_kits/aicat/index.html +2 -0
- package/dist/ui_kits/blog/index.html +33 -17
- package/dist/ui_kits/chat/app.js +90 -22
- package/dist/ui_kits/chat/index.html +2 -0
- package/dist/ui_kits/community/app.js +86 -24
- package/dist/ui_kits/community/index.html +1 -0
- package/dist/ui_kits/dashboard/app.js +123 -34
- package/dist/ui_kits/dashboard/index.html +1 -0
- package/dist/ui_kits/docs/index.html +43 -33
- package/dist/ui_kits/error_404/app.js +78 -23
- package/dist/ui_kits/file_browser/README.md +1 -1
- package/dist/ui_kits/file_browser/app.js +45 -10
- package/dist/ui_kits/file_browser/index.html +1 -0
- package/dist/ui_kits/gallery/app.js +130 -51
- package/dist/ui_kits/homepage/app.js +156 -138
- package/dist/ui_kits/project_page/app.js +142 -125
- package/dist/ui_kits/project_page/index.html +2 -1
- package/dist/ui_kits/search/app.js +52 -15
- package/dist/ui_kits/settings/app.js +166 -39
- package/dist/ui_kits/signin/app.js +175 -49
- package/dist/ui_kits/signin/index.html +1 -0
- package/dist/ui_kits/slide_deck/app.js +83 -49
- package/dist/ui_kits/system_primer/app.js +57 -40
- package/dist/ui_kits/terminal/app.js +160 -43
- package/package.json +1 -1
- package/src/page-html/client-script.js +40 -11
- package/src/page-html/page-styles.js +22 -1
- package/src/page-html.js +10 -0
package/dist/src/index.js
CHANGED
|
@@ -5,15 +5,28 @@
|
|
|
5
5
|
import * as webjsx from '../vendor/webjsx/index.js';
|
|
6
6
|
import { loadCss, scope } from './styles.js';
|
|
7
7
|
import { registerDeckStage, getDeckStage } from './deck-stage.js';
|
|
8
|
+
import { Router, createRouter } from './router.js';
|
|
8
9
|
import * as components from './components.js';
|
|
9
10
|
import * as motion from './motion.js';
|
|
10
11
|
import * as debug from './debug.js';
|
|
11
|
-
import { renderMarkdown, ensureReady as ensureMarkdownReady } from './markdown.js';
|
|
12
|
-
import {
|
|
12
|
+
import { renderMarkdown, ensureReady as ensureMarkdownReady, sanitizeHtml, isDegraded as isMarkdownDegraded, configureMarkdownCdn, getMarkdownCdnConfig } from './markdown.js';
|
|
13
|
+
import { escapeHtml, escapeJson } from './html-escape.js';
|
|
14
|
+
import { uid, shortUid } from './uid.js';
|
|
15
|
+
import { ensurePrism, highlightAllUnder, configurePrismCdn, getPrismCdnConfig } from './highlight.js';
|
|
16
|
+
import { ensureMermaid, renderMermaid, renderMermaidBlocksUnder, configureMermaidCdn, getMermaidCdnConfig } from './mermaid.js';
|
|
17
|
+
import { ensureKatex, renderMathBlocksUnder, configureKatexCdn, getKatexCdnConfig } from './math.js';
|
|
13
18
|
import { renderPageHtml } from './page-html.js';
|
|
19
|
+
import { HeroFromPageData } from './components/content.js';
|
|
20
|
+
import { ThemeToggle } from './components/theme-toggle.js';
|
|
14
21
|
import { mountKit } from './bootstrap.js';
|
|
15
22
|
import * as theme from './theme.js';
|
|
23
|
+
import { t, registerLocale, getLocale, setLocale, availableLocales } from './i18n.js';
|
|
16
24
|
import { registerChatElement, DsChat } from './web-components/ds-chat.js';
|
|
25
|
+
import { registerFreddieChatElement, FreddieChat } from './web-components/freddie-chat.js';
|
|
26
|
+
import { formatTime, formatDateTime, formatNumber, formatRelativeTime } from './locale.js';
|
|
27
|
+
import { queueMessage, listQueued, flushQueue, watchReconnect, isOnline } from './idb-outbox.js';
|
|
28
|
+
import { createVirtualizer, measureRef } from './virtual-scroll.js';
|
|
29
|
+
import { applyMotion, getMotion, isMotionReduced, onMotionChange, initMotion } from './motion-toggle.js';
|
|
17
30
|
|
|
18
31
|
let _installed = false;
|
|
19
32
|
export async function installStyles(target) {
|
|
@@ -32,9 +45,18 @@ export async function installStyles(target) {
|
|
|
32
45
|
}
|
|
33
46
|
}
|
|
34
47
|
|
|
48
|
+
// Tracks nodes already mounted via mount() so a second mount() call onto the
|
|
49
|
+
// same DOM node fails loud instead of silently layering a second render loop
|
|
50
|
+
// (double applyDiff/animateTree on one root corrupts webjsx's diff state).
|
|
51
|
+
const _mountedRoots = new WeakSet();
|
|
52
|
+
|
|
35
53
|
export function mount(rootEl, viewFn, { autoScope = true } = {}) {
|
|
36
|
-
if (!rootEl) throw new Error('mount: rootEl required');
|
|
54
|
+
if (!rootEl) throw new Error('mount: rootEl required (received ' + (rootEl === null ? 'null' : typeof rootEl) + ')');
|
|
37
55
|
if (typeof viewFn !== 'function') throw new Error('mount: viewFn required');
|
|
56
|
+
if (_mountedRoots.has(rootEl)) {
|
|
57
|
+
throw new Error('mount: this element is already mounted — call the returned render() to re-render, do not mount() the same root twice');
|
|
58
|
+
}
|
|
59
|
+
_mountedRoots.add(rootEl);
|
|
38
60
|
if (autoScope && rootEl.classList && !rootEl.classList.contains(scope.slice(1))) {
|
|
39
61
|
const cls = scope.slice(1);
|
|
40
62
|
const inheritedFromAncestor = rootEl.closest && rootEl.closest('.' + cls);
|
|
@@ -51,36 +73,60 @@ export function mount(rootEl, viewFn, { autoScope = true } = {}) {
|
|
|
51
73
|
return render;
|
|
52
74
|
}
|
|
53
75
|
|
|
54
|
-
// Side-effect: register <ds-chat> as soon as the SDK loads in a browser.
|
|
76
|
+
// Side-effect: register <ds-chat> + <freddie-chat> as soon as the SDK loads in a browser.
|
|
55
77
|
if (typeof window !== 'undefined' && typeof customElements !== 'undefined') {
|
|
56
78
|
registerChatElement();
|
|
79
|
+
registerFreddieChatElement();
|
|
57
80
|
}
|
|
58
81
|
|
|
59
82
|
export {
|
|
60
83
|
webjsx, loadCss, scope,
|
|
61
84
|
registerDeckStage, getDeckStage,
|
|
85
|
+
Router, createRouter,
|
|
62
86
|
components, motion, debug, mountKit,
|
|
63
|
-
renderMarkdown, ensureMarkdownReady,
|
|
64
|
-
|
|
87
|
+
renderMarkdown, ensureMarkdownReady, sanitizeHtml, isMarkdownDegraded,
|
|
88
|
+
configureMarkdownCdn, getMarkdownCdnConfig,
|
|
89
|
+
ensurePrism, highlightAllUnder, configurePrismCdn, getPrismCdnConfig,
|
|
90
|
+
ensureMermaid, renderMermaid, renderMermaidBlocksUnder, configureMermaidCdn, getMermaidCdnConfig,
|
|
91
|
+
ensureKatex, renderMathBlocksUnder, configureKatexCdn, getKatexCdnConfig,
|
|
65
92
|
registerChatElement, DsChat,
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
registerFreddieChatElement, FreddieChat,
|
|
94
|
+
renderPageHtml, HeroFromPageData,
|
|
95
|
+
escapeHtml, escapeJson,
|
|
96
|
+
uid, shortUid,
|
|
97
|
+
theme, ThemeToggle,
|
|
98
|
+
t, registerLocale, getLocale, setLocale, availableLocales,
|
|
99
|
+
formatTime, formatDateTime, formatNumber, formatRelativeTime,
|
|
100
|
+
queueMessage, listQueued, flushQueue, watchReconnect, isOnline,
|
|
101
|
+
createVirtualizer, measureRef,
|
|
102
|
+
applyMotion, getMotion, isMotionReduced, onMotionChange, initMotion
|
|
68
103
|
};
|
|
69
|
-
export { applyTheme, getTheme, resolvedTheme, onThemeChange, initTheme
|
|
104
|
+
export { applyTheme, getTheme, resolvedTheme, onThemeChange, initTheme,
|
|
105
|
+
applyAccent, getAccent, applyDensity, getDensity } from './theme.js';
|
|
106
|
+
export { extractAtQuery, buildEntriesFromFiles, filterFileEntries, buildAtInsertText,
|
|
107
|
+
buildAtMentionText, buildFileAtMentionsText } from './file-mention.js';
|
|
70
108
|
export const h = webjsx.createElement;
|
|
71
109
|
export const applyDiff = webjsx.applyDiff;
|
|
72
110
|
|
|
111
|
+
// spoint kit paint surfaces (loading screen, HUD, editor chrome).
|
|
112
|
+
export { renderLoadingScreen } from './kits/spoint/loading-screen.js';
|
|
113
|
+
export { renderGameHud, Crosshair, AmmoCounter, HealthBar, BoostIndicator } from './kits/spoint/game-hud.js';
|
|
114
|
+
export { renderHostJoinLobby } from './kits/spoint/host-join-lobby.js';
|
|
115
|
+
|
|
73
116
|
// Re-export freddie helpers so consumers can `import { FREDDIE_PAGES } from
|
|
74
117
|
// 'anentrypoint-design'` directly.
|
|
75
118
|
export {
|
|
76
119
|
FREDDIE_PAGES,
|
|
77
120
|
home, chat, voice, sessions, projects, agents, analytics,
|
|
78
121
|
models, cron, skills, config, env, tools, batch, gateway, chains,
|
|
79
|
-
skillLabel, getRecentPaths, saveRecentPath, renderChatMessages
|
|
122
|
+
skillLabel, getRecentPaths, saveRecentPath, renderChatMessages,
|
|
123
|
+
fmtBytes, fmtFileSize, fmtTime, fmtAgo, fmtDuration
|
|
80
124
|
} from './components.js';
|
|
81
125
|
|
|
82
126
|
export default {
|
|
83
127
|
webjsx, loadCss, scope, installStyles, mount, h, applyDiff,
|
|
84
|
-
registerDeckStage, getDeckStage,
|
|
85
|
-
|
|
128
|
+
registerDeckStage, getDeckStage, Router, createRouter,
|
|
129
|
+
components, motion, debug, mountKit,
|
|
130
|
+
renderMarkdown, ensurePrism, registerChatElement, renderPageHtml, HeroFromPageData,
|
|
131
|
+
escapeHtml, escapeJson
|
|
86
132
|
};
|
|
@@ -13,10 +13,9 @@ export function renderAboutApp(opts = {}) {
|
|
|
13
13
|
'libsql via sql.js · freddie host',
|
|
14
14
|
'responsive: phone / tablet / desktop',
|
|
15
15
|
],
|
|
16
|
-
footer = '
|
|
16
|
+
footer = 'click apps menu for more.',
|
|
17
17
|
links = [
|
|
18
18
|
{ href: 'https://github.com/AnEntrypoint/thebird', text: 'source' },
|
|
19
|
-
{ href: './validate.html', text: 'validate' },
|
|
20
19
|
],
|
|
21
20
|
} = opts;
|
|
22
21
|
|
|
@@ -35,7 +34,7 @@ export function renderAboutApp(opts = {}) {
|
|
|
35
34
|
ul.appendChild(li);
|
|
36
35
|
}
|
|
37
36
|
const foot = document.createElement('p');
|
|
38
|
-
foot.
|
|
37
|
+
foot.textContent = footer;
|
|
39
38
|
const meta = document.createElement('p');
|
|
40
39
|
meta.className = 'meta';
|
|
41
40
|
links.forEach((l, i) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* App-pane chrome for kit components rendered inside .wm-win bodies.
|
|
2
2
|
Bible tokens only — no hardcoded color, font, or radius. Focus = inset rail; no shadows, no gradients. */
|
|
3
3
|
|
|
4
|
-
/*
|
|
4
|
+
/* --- terminal-app --- */
|
|
5
5
|
.terminal-app {
|
|
6
6
|
display: flex;
|
|
7
7
|
flex-direction: column;
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
display: flex;
|
|
15
15
|
align-items: center;
|
|
16
16
|
justify-content: space-between;
|
|
17
|
-
|
|
17
|
+
gap: var(--space-2);
|
|
18
|
+
padding: var(--space-1) var(--space-2);
|
|
18
19
|
background: var(--os-bg-2);
|
|
19
20
|
font-family: var(--os-mono);
|
|
20
21
|
font-size: 11px;
|
|
@@ -22,16 +23,32 @@
|
|
|
22
23
|
text-transform: lowercase;
|
|
23
24
|
flex: 0 0 auto;
|
|
24
25
|
}
|
|
25
|
-
.terminal-app-title {
|
|
26
|
-
|
|
26
|
+
.terminal-app-title {
|
|
27
|
+
font-family: var(--os-mono);
|
|
28
|
+
min-width: 0;
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
text-overflow: ellipsis;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
}
|
|
33
|
+
.terminal-app-status {
|
|
34
|
+
font-family: var(--os-mono);
|
|
35
|
+
color: var(--os-fg-3);
|
|
36
|
+
flex: 0 0 auto;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
text-overflow: ellipsis;
|
|
39
|
+
white-space: nowrap;
|
|
40
|
+
}
|
|
27
41
|
.terminal-app-slot {
|
|
28
42
|
flex: 1 1 auto;
|
|
43
|
+
min-width: 0;
|
|
29
44
|
min-height: 0;
|
|
30
45
|
overflow: hidden;
|
|
31
46
|
background: var(--os-bg-0);
|
|
47
|
+
position: relative;
|
|
32
48
|
}
|
|
49
|
+
.terminal-app-slot > * { width: 100%; height: 100%; }
|
|
33
50
|
|
|
34
|
-
/*
|
|
51
|
+
/* --- browser-app --- */
|
|
35
52
|
.browser-app {
|
|
36
53
|
display: flex;
|
|
37
54
|
flex-direction: column;
|
|
@@ -43,15 +60,15 @@
|
|
|
43
60
|
.browser-app-bar {
|
|
44
61
|
display: flex;
|
|
45
62
|
align-items: center;
|
|
46
|
-
gap:
|
|
47
|
-
padding:
|
|
63
|
+
gap: var(--space-1);
|
|
64
|
+
padding: var(--space-1) var(--space-2);
|
|
48
65
|
background: var(--os-bg-2);
|
|
49
66
|
flex: 0 0 auto;
|
|
50
67
|
}
|
|
51
68
|
.browser-app-btn {
|
|
52
69
|
height: 28px;
|
|
53
70
|
min-width: 28px;
|
|
54
|
-
padding: 0
|
|
71
|
+
padding: 0 var(--space-2);
|
|
55
72
|
background: var(--os-bg-3);
|
|
56
73
|
color: var(--os-fg);
|
|
57
74
|
border: none;
|
|
@@ -61,10 +78,15 @@
|
|
|
61
78
|
outline: none;
|
|
62
79
|
}
|
|
63
80
|
.browser-app-btn:hover { background: var(--os-accent-soft); }
|
|
81
|
+
.browser-app-btn:focus-visible { box-shadow: inset 0 0 0 1px var(--os-accent); }
|
|
82
|
+
/* Inert back/forward when there's no history in that direction. */
|
|
83
|
+
.browser-app-btn:disabled { opacity: 0.4; cursor: default; pointer-events: none; }
|
|
84
|
+
.browser-app-btn:disabled:hover { background: var(--os-bg-3); }
|
|
64
85
|
.browser-app-url {
|
|
65
86
|
flex: 1 1 auto;
|
|
87
|
+
min-width: 0;
|
|
66
88
|
height: 28px;
|
|
67
|
-
padding: 0
|
|
89
|
+
padding: 0 var(--space-2);
|
|
68
90
|
background: var(--os-bg-0);
|
|
69
91
|
color: var(--os-fg);
|
|
70
92
|
border: none;
|
|
@@ -75,78 +97,32 @@
|
|
|
75
97
|
.browser-app-url:focus { box-shadow: inset 4px 0 0 var(--os-accent); }
|
|
76
98
|
.browser-app-slot {
|
|
77
99
|
flex: 1 1 auto;
|
|
100
|
+
min-width: 0;
|
|
78
101
|
min-height: 0;
|
|
79
102
|
overflow: hidden;
|
|
80
103
|
background: var(--os-bg-0);
|
|
81
|
-
|
|
104
|
+
position: relative;
|
|
82
105
|
}
|
|
83
|
-
.browser-app-slot > * {
|
|
106
|
+
.browser-app-slot > * { width: 100%; height: 100%; }
|
|
84
107
|
.browser-app-status {
|
|
85
108
|
flex: 0 0 auto;
|
|
86
|
-
padding:
|
|
109
|
+
padding: var(--space-1) var(--space-2);
|
|
87
110
|
background: var(--os-bg-2);
|
|
88
111
|
font: 11px var(--os-mono);
|
|
89
112
|
color: var(--os-fg-3);
|
|
90
113
|
text-transform: lowercase;
|
|
91
114
|
min-height: 18px;
|
|
92
115
|
}
|
|
116
|
+
/* Loading: a thin indeterminate bar under the toolbar. Error: red status row. */
|
|
117
|
+
.browser-app-loading .browser-app-bar { box-shadow: inset 0 -2px 0 var(--os-accent); }
|
|
118
|
+
.browser-app[data-error] .browser-app-status { color: var(--os-danger, #c0392b); }
|
|
93
119
|
|
|
94
|
-
/*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
.validator-app-head {
|
|
104
|
-
display: flex;
|
|
105
|
-
align-items: center;
|
|
106
|
-
gap: 10px;
|
|
107
|
-
padding: 6px 10px;
|
|
108
|
-
background: var(--os-bg-2);
|
|
109
|
-
flex: 0 0 auto;
|
|
110
|
-
}
|
|
111
|
-
.validator-app-banner {
|
|
112
|
-
padding: 2px 10px;
|
|
113
|
-
border-radius: 999px;
|
|
114
|
-
font: 11px var(--os-mono);
|
|
115
|
-
text-transform: lowercase;
|
|
116
|
-
background: var(--os-bg-3);
|
|
117
|
-
color: var(--os-fg-2);
|
|
118
|
-
}
|
|
119
|
-
.validator-app-banner[data-state="pass"] { background: var(--os-accent-soft); color: var(--os-fg); }
|
|
120
|
-
.validator-app-banner[data-state="fail"] { background: var(--os-red); color: var(--os-bg-0); }
|
|
121
|
-
.validator-app-counts {
|
|
122
|
-
font: 11px var(--os-mono);
|
|
123
|
-
color: var(--os-fg-2);
|
|
124
|
-
}
|
|
125
|
-
.validator-app-rerun {
|
|
126
|
-
margin-left: auto;
|
|
127
|
-
height: 24px;
|
|
128
|
-
padding: 0 10px;
|
|
129
|
-
background: var(--os-bg-3);
|
|
130
|
-
color: var(--os-fg);
|
|
131
|
-
border: none;
|
|
132
|
-
border-radius: var(--os-radius-sm);
|
|
133
|
-
font: 11px var(--os-mono);
|
|
134
|
-
text-transform: lowercase;
|
|
135
|
-
cursor: pointer;
|
|
136
|
-
outline: none;
|
|
137
|
-
}
|
|
138
|
-
.validator-app-rerun:hover { background: var(--os-accent-soft); }
|
|
139
|
-
.validator-app-slot {
|
|
140
|
-
flex: 1 1 auto;
|
|
141
|
-
min-height: 0;
|
|
142
|
-
overflow: hidden;
|
|
143
|
-
display: flex;
|
|
144
|
-
background: var(--os-bg-0);
|
|
145
|
-
}
|
|
146
|
-
.validator-app-slot > * { flex: 1 1 auto; min-width: 0; min-height: 0; }
|
|
147
|
-
.validator-app-frame {
|
|
148
|
-
width: 100%;
|
|
149
|
-
height: 100%;
|
|
150
|
-
border: none;
|
|
151
|
-
background: var(--os-bg-0);
|
|
120
|
+
/* Touch tap-target floor for the browser pane toolbar. The back/forward/reload
|
|
121
|
+
* buttons and the URL field are 28px by default (no @media anywhere), below the
|
|
122
|
+
* 44px touch minimum on tablets and phones. Lift them on coarse pointers and
|
|
123
|
+
* grow the bar so the controls fit with breathing room. */
|
|
124
|
+
@media (hover: none), (pointer: coarse) {
|
|
125
|
+
.browser-app-bar { min-height: 52px; padding: 6px var(--space-2); }
|
|
126
|
+
.browser-app-btn { min-height: var(--os-tap, 44px); height: var(--os-tap, 44px); min-width: var(--os-tap, 44px); }
|
|
127
|
+
.browser-app-url { height: var(--os-tap, 44px); }
|
|
152
128
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Browser-pane paint surface — URL bar + iframe slot + status row. Consumer owns iframe.
|
|
2
2
|
// renderBrowserPane({initialUrl, callbacks: {onNavigate, onReload, onBack, onForward}})
|
|
3
|
-
// -> {node, slot, setUrl, setStatus, dispose}.
|
|
3
|
+
// -> {node, slot, setUrl, setStatus, setNav, setLoading, setError, dispose}.
|
|
4
4
|
// slot is the container the consumer should append its iframe to.
|
|
5
|
+
// setNav({canBack,canForward}) disables the back/forward buttons when history
|
|
6
|
+
// has no entry in that direction; setLoading(bool) toggles a loading state on
|
|
7
|
+
// the bar; setError(msg|null) surfaces a load failure in the status row.
|
|
5
8
|
|
|
6
9
|
export function renderBrowserPane(opts = {}) {
|
|
7
10
|
const { initialUrl = 'about:blank', callbacks = {} } = opts;
|
|
@@ -21,8 +24,14 @@ export function renderBrowserPane(opts = {}) {
|
|
|
21
24
|
return b;
|
|
22
25
|
};
|
|
23
26
|
const backBtn = mkBtn('<', 'back');
|
|
27
|
+
backBtn.setAttribute('aria-label', 'Back');
|
|
24
28
|
const fwdBtn = mkBtn('>', 'forward');
|
|
29
|
+
fwdBtn.setAttribute('aria-label', 'Forward');
|
|
25
30
|
const reloadBtn = mkBtn('reload', 'reload');
|
|
31
|
+
reloadBtn.setAttribute('aria-label', 'Reload');
|
|
32
|
+
// Start with no history in either direction until the consumer says otherwise.
|
|
33
|
+
backBtn.disabled = true;
|
|
34
|
+
fwdBtn.disabled = true;
|
|
26
35
|
|
|
27
36
|
const urlInput = document.createElement('input');
|
|
28
37
|
urlInput.type = 'text';
|
|
@@ -44,6 +53,8 @@ export function renderBrowserPane(opts = {}) {
|
|
|
44
53
|
|
|
45
54
|
const status = document.createElement('div');
|
|
46
55
|
status.className = 'browser-app-status';
|
|
56
|
+
status.setAttribute('role', 'status');
|
|
57
|
+
status.setAttribute('aria-live', 'polite');
|
|
47
58
|
status.textContent = '';
|
|
48
59
|
|
|
49
60
|
node.append(bar, slot, status);
|
|
@@ -53,6 +64,22 @@ export function renderBrowserPane(opts = {}) {
|
|
|
53
64
|
get slot() { return slot; },
|
|
54
65
|
setUrl(u) { urlInput.value = u; },
|
|
55
66
|
setStatus(s) { status.textContent = s; },
|
|
67
|
+
// Reflect history availability so disabled buttons read as inert.
|
|
68
|
+
setNav({ canBack = false, canForward = false } = {}) {
|
|
69
|
+
backBtn.disabled = !canBack;
|
|
70
|
+
fwdBtn.disabled = !canForward;
|
|
71
|
+
},
|
|
72
|
+
// Loading: tint the bar + announce; the consumer flips it off on load/error.
|
|
73
|
+
setLoading(on) {
|
|
74
|
+
node.classList.toggle('browser-app-loading', !!on);
|
|
75
|
+
if (on) { node.removeAttribute('data-error'); status.textContent = 'loading...'; }
|
|
76
|
+
},
|
|
77
|
+
// Error: persistent failure surfaced in the live status row.
|
|
78
|
+
setError(msg) {
|
|
79
|
+
node.classList.remove('browser-app-loading');
|
|
80
|
+
if (msg) { node.setAttribute('data-error', '1'); status.textContent = msg; }
|
|
81
|
+
else { node.removeAttribute('data-error'); }
|
|
82
|
+
},
|
|
56
83
|
dispose() {},
|
|
57
84
|
};
|
|
58
85
|
}
|
|
@@ -10,8 +10,11 @@ export function renderFilesApp(opts = {}) {
|
|
|
10
10
|
node.dataset.component = 'files-app';
|
|
11
11
|
|
|
12
12
|
let preview = null;
|
|
13
|
+
let selected = null;
|
|
13
14
|
async function refresh() {
|
|
14
15
|
const items = await list();
|
|
16
|
+
preview = null;
|
|
17
|
+
selected = null;
|
|
15
18
|
node.innerHTML = '';
|
|
16
19
|
const head = document.createElement('div');
|
|
17
20
|
head.className = 'head';
|
|
@@ -21,7 +24,11 @@ export function renderFilesApp(opts = {}) {
|
|
|
21
24
|
const row = document.createElement('div');
|
|
22
25
|
row.className = 'row';
|
|
23
26
|
row.textContent = p;
|
|
27
|
+
row.title = p;
|
|
24
28
|
row.addEventListener('click', async () => {
|
|
29
|
+
if (selected) selected.classList.remove('selected');
|
|
30
|
+
selected = row;
|
|
31
|
+
row.classList.add('selected');
|
|
25
32
|
const body = await readFile(p);
|
|
26
33
|
if (preview) preview.remove();
|
|
27
34
|
preview = document.createElement('pre');
|
|
@@ -1,143 +1,158 @@
|
|
|
1
|
-
// Chat page —
|
|
1
|
+
// Chat page — dashboard surface for the agent. Renders via the kit's
|
|
2
|
+
// Chat + ChatComposer primitives so the dashboard tab and the OS chat panel
|
|
3
|
+
// share the same bubble / tool-call / empty-state chrome. The bespoke
|
|
4
|
+
// cwd/skill/provider/model selectors live in a collapsible config strip
|
|
5
|
+
// above the thread (mirror of the OS panel cc-strip pattern).
|
|
6
|
+
//
|
|
7
|
+
// Wire-format translation lives in ./chat-protocol.js; provider discovery and
|
|
8
|
+
// the three send strategies (server SSE / in-page agent runtime / direct
|
|
9
|
+
// gateway completion) plus the event->state fold live in ./chat-transport.js.
|
|
10
|
+
// This module owns only the page's own state, DOM wiring, and config strip.
|
|
11
|
+
|
|
2
12
|
import * as webjsx from '../../../../vendor/webjsx/index.js';
|
|
3
13
|
import * as components from '../../../components.js';
|
|
4
|
-
import { getRecentPaths, saveRecentPath, skillLabel
|
|
14
|
+
import { getRecentPaths, saveRecentPath, skillLabel } from '../../../components/freddie/helpers.js';
|
|
15
|
+
import { toKitMessage } from './chat-protocol.js';
|
|
16
|
+
import { loadProviders, fetchChatEvents, applyChatEvents } from './chat-transport.js';
|
|
5
17
|
|
|
6
18
|
const h = webjsx.createElement;
|
|
7
|
-
const { Panel, Receipt, Chip } = components;
|
|
8
|
-
|
|
9
|
-
function parseSseEvents(text) {
|
|
10
|
-
const events = [];
|
|
11
|
-
let curEvent = null, curData = '';
|
|
12
|
-
for (const line of text.split('\n')) {
|
|
13
|
-
if (line.startsWith('event: ')) { curEvent = line.slice(7).trim(); }
|
|
14
|
-
else if (line.startsWith('data: ')) { curData = line.slice(6).trim(); }
|
|
15
|
-
else if (line === '' && curEvent) {
|
|
16
|
-
try { events.push({ event: curEvent, data: JSON.parse(curData) }); } catch {}
|
|
17
|
-
curEvent = null; curData = '';
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return events;
|
|
21
|
-
}
|
|
19
|
+
const { Panel, Receipt, Chip, Icon } = components;
|
|
22
20
|
|
|
23
21
|
export function makeChatPage(ctx) {
|
|
24
22
|
return async function chat(h0) {
|
|
25
23
|
const root = ctx.root;
|
|
26
24
|
const skills = [...h0.pi.skills.values()];
|
|
27
|
-
const providers = await
|
|
25
|
+
const providers = await loadProviders();
|
|
28
26
|
const configuredProviders = providers.filter(p => p.configured);
|
|
29
27
|
|
|
30
28
|
const chatState = window.__fd_chatState = window.__fd_chatState || {
|
|
31
|
-
cwd: '', skill: '', provider: '', model: '', messages: [], busy: false, sessionId: null,
|
|
29
|
+
cwd: '', skill: '', provider: '', model: '', messages: [], busy: false, sessionId: null, draft: '', abort: null,
|
|
32
30
|
};
|
|
33
31
|
if (!chatState.cwd) chatState.cwd = (getRecentPaths()[0] || '');
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
// Find the chat container in the live DOM (set on the rendered <ds-chat>).
|
|
34
|
+
const getChatHost = () => root.querySelector('ds-chat.fd-dashboard-chat');
|
|
35
|
+
const syncMessages = () => {
|
|
36
|
+
const host = getChatHost();
|
|
37
|
+
if (host) host.messages = chatState.messages.map(toKitMessage);
|
|
38
|
+
};
|
|
36
39
|
|
|
37
40
|
const newSession = () => {
|
|
38
41
|
if (chatState.busy) return;
|
|
39
42
|
chatState.messages = [];
|
|
40
43
|
chatState.sessionId = null;
|
|
41
|
-
|
|
44
|
+
syncMessages();
|
|
42
45
|
};
|
|
43
46
|
|
|
44
|
-
const
|
|
45
|
-
|
|
47
|
+
const cancelInFlight = () => {
|
|
48
|
+
if (chatState.abort) { try { chatState.abort.abort(); } catch { /* swallow: the in-flight request may already be settled, abort() is a no-op then */ } chatState.abort = null; }
|
|
49
|
+
chatState.busy = false;
|
|
50
|
+
syncMessages();
|
|
51
|
+
renderPage();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const sendChat = async (prompt) => {
|
|
46
55
|
if (chatState.busy) return;
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
chatState.messages.push({ role: 'user', content: prompt });
|
|
51
|
-
promptEl.value = '';
|
|
52
|
-
promptEl.style.height = 'auto';
|
|
56
|
+
const trimmed = String(prompt || '').trim();
|
|
57
|
+
if (!trimmed) return;
|
|
58
|
+
chatState.messages.push({ role: 'user', content: trimmed });
|
|
53
59
|
chatState.busy = true;
|
|
60
|
+
chatState.progress = 'agent thinking…';
|
|
61
|
+
chatState.abort = new AbortController();
|
|
54
62
|
saveRecentPath(chatState.cwd);
|
|
55
|
-
|
|
63
|
+
syncMessages();
|
|
64
|
+
renderPage();
|
|
56
65
|
try {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const text = await resp.text();
|
|
60
|
-
const events = parseSseEvents(text);
|
|
61
|
-
let assistantContent = '';
|
|
62
|
-
for (const { event, data } of events) {
|
|
63
|
-
if (event === 'start' && data.sessionId) chatState.sessionId = data.sessionId;
|
|
64
|
-
if (event === 'done' && data.sessionId) chatState.sessionId = data.sessionId;
|
|
65
|
-
if (event === 'message') {
|
|
66
|
-
const role = data.role;
|
|
67
|
-
if (role === 'assistant') {
|
|
68
|
-
const content = Array.isArray(data.content) ? data.content : [{ type: 'text', text: String(data.content || '') }];
|
|
69
|
-
for (const block of content) {
|
|
70
|
-
if (block.type === 'text') assistantContent += block.text;
|
|
71
|
-
if (block.type === 'tool_use') {
|
|
72
|
-
if (assistantContent) { chatState.messages.push({ role: 'assistant', content: assistantContent }); assistantContent = ''; }
|
|
73
|
-
const argsSummary = JSON.stringify(block.input || {}).slice(0, 60);
|
|
74
|
-
chatState.messages.push({ role: 'tool', name: block.name, argsSummary, content: JSON.stringify(block.input || {}, null, 2) });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
} else if (role === 'tool') {
|
|
78
|
-
const tc = Array.isArray(data.content) ? data.content[0] : data;
|
|
79
|
-
chatState.messages.push({ role: 'tool', name: 'result', argsSummary: '', content: String(tc?.content || tc?.text || JSON.stringify(tc)) });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (event === 'done' && data.result) { if (!assistantContent) assistantContent = data.result; }
|
|
83
|
-
if (event === 'error') assistantContent = 'error: ' + (data.error || 'unknown');
|
|
84
|
-
}
|
|
66
|
+
const events = await fetchChatEvents(trimmed, chatState, renderPage);
|
|
67
|
+
const assistantContent = applyChatEvents(events, chatState, syncMessages);
|
|
85
68
|
if (assistantContent) chatState.messages.push({ role: 'assistant', content: assistantContent });
|
|
86
69
|
if (!events.length) chatState.messages.push({ role: 'assistant', content: '(no response)' });
|
|
87
70
|
} catch (e) {
|
|
88
|
-
chatState.messages.push({ role: 'assistant', content: '
|
|
71
|
+
if (e.name === 'AbortError') chatState.messages.push({ role: 'assistant', content: '[cancelled]' });
|
|
72
|
+
else chatState.messages.push({ role: 'assistant', content: 'error: ' + e.message });
|
|
89
73
|
}
|
|
74
|
+
chatState.abort = null;
|
|
90
75
|
chatState.busy = false;
|
|
91
|
-
|
|
76
|
+
chatState.progress = '';
|
|
77
|
+
syncMessages();
|
|
78
|
+
renderPage();
|
|
92
79
|
};
|
|
93
80
|
|
|
94
81
|
const recentPaths = getRecentPaths();
|
|
95
82
|
const datalistId = 'fd-cwd-list';
|
|
96
83
|
const byCat = skills.reduce((a, s) => { const c = s.category || 'other'; (a[c] = a[c] || []).push(s); return a; }, {});
|
|
97
84
|
|
|
98
|
-
|
|
85
|
+
const renderPage = () => {
|
|
86
|
+
const host = getChatHost();
|
|
87
|
+
if (host) {
|
|
88
|
+
host.busy = chatState.busy;
|
|
89
|
+
host.placeholder = chatState.busy
|
|
90
|
+
? (chatState.progress || 'agent working…')
|
|
91
|
+
: 'describe what you want to do in the working directory…';
|
|
92
|
+
}
|
|
93
|
+
// Refresh disabled state on header buttons.
|
|
94
|
+
const newBtn = root.querySelector('.fd-chat-new');
|
|
95
|
+
if (newBtn) newBtn.disabled = !!chatState.busy;
|
|
96
|
+
const cancelBtn = root.querySelector('.fd-chat-cancel');
|
|
97
|
+
if (cancelBtn) cancelBtn.style.display = chatState.busy ? '' : 'none';
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// After mount, seed messages onto the ds-chat element + wire the send event.
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
const host = getChatHost();
|
|
103
|
+
if (host && !host._fdBound) {
|
|
104
|
+
host._fdBound = true;
|
|
105
|
+
host.addEventListener('send', (e) => { sendChat(e.detail && e.detail.text); });
|
|
106
|
+
host.messages = chatState.messages.map(toKitMessage);
|
|
107
|
+
host.placeholder = 'describe what you want to do in the working directory…';
|
|
108
|
+
host.sub = chatState.sessionId ? ('session ' + chatState.sessionId.slice(0, 8)) : 'agent';
|
|
109
|
+
}
|
|
110
|
+
renderPage();
|
|
111
|
+
}, 50);
|
|
99
112
|
|
|
100
113
|
const selSkill = h('select', { name: 'skill', onchange: (ev) => { chatState.skill = ev.target.value; } },
|
|
101
|
-
h('option', { value: '' }, '
|
|
114
|
+
h('option', { value: '' }, 'no skill'),
|
|
102
115
|
...Object.entries(byCat).map(([cat, ss]) =>
|
|
103
116
|
h('optgroup', { label: cat },
|
|
104
117
|
...ss.map(s => h('option', { value: s.name, selected: chatState.skill === s.name ? 'true' : null, title: s.description || s.name }, skillLabel(s)))
|
|
105
118
|
)));
|
|
106
119
|
|
|
107
120
|
const selProv = h('select', { name: 'provider', onchange: (ev) => { chatState.provider = ev.target.value; } },
|
|
108
|
-
h('option', { value: '' }, configuredProviders.length ? '
|
|
109
|
-
...configuredProviders.map(p => h('option', { value: p.name, selected: chatState.provider === p.name ? 'true' : null }, (p.available ? '
|
|
121
|
+
h('option', { value: '' }, configuredProviders.length ? 'auto' : 'no providers configured'),
|
|
122
|
+
...configuredProviders.map(p => h('option', { value: p.name, selected: chatState.provider === p.name ? 'true' : null }, (p.available ? '(on) ' : '(off) ') + p.name)));
|
|
110
123
|
|
|
111
124
|
return [
|
|
112
125
|
Panel({
|
|
113
126
|
title: 'chat',
|
|
114
|
-
right: h('
|
|
127
|
+
right: h('div', { class: 'fd-chat-actions' },
|
|
128
|
+
h('button', { class: 'btn-secondary fd-btn-mini fd-chat-cancel', style: chatState.busy ? '' : 'display:none', onclick: (ev) => { ev.preventDefault(); cancelInFlight(); } }, 'cancel'),
|
|
129
|
+
h('button', { class: 'btn-primary fd-btn-mini fd-chat-new', onclick: (ev) => { ev.preventDefault(); newSession(); }, disabled: chatState.busy ? 'true' : null }, 'new session')
|
|
130
|
+
),
|
|
115
131
|
children: [
|
|
116
132
|
h('datalist', { id: datalistId }, ...recentPaths.map(p => h('option', { value: p }))),
|
|
117
|
-
h('
|
|
118
|
-
h('div', { class: 'fd-chat-field' },
|
|
133
|
+
h('div', { class: 'fd-chat-config' },
|
|
134
|
+
h('div', { class: 'fd-chat-field fd-chat-field-grow' },
|
|
119
135
|
h('label', {}, 'working directory'),
|
|
120
136
|
h('input', { name: 'cwd', type: 'text', placeholder: 'e.g. C:/dev/myproject or /home/user/project', value: chatState.cwd, list: datalistId, oninput: (ev) => { chatState.cwd = ev.target.value; } })),
|
|
121
137
|
h('div', { class: 'fd-chat-row' },
|
|
122
138
|
h('div', { class: 'fd-chat-field fd-chat-field-grow' }, h('label', {}, 'skill'), selSkill),
|
|
123
139
|
h('div', { class: 'fd-chat-field fd-chat-field-grow' }, h('label', {}, 'provider'), selProv),
|
|
124
140
|
h('div', { class: 'fd-chat-field fd-chat-field-grow' }, h('label', {}, 'model (optional)'),
|
|
125
|
-
h('input', { name: 'model', type: 'text', placeholder: configuredProviders.find(p => p.name === chatState.provider)?.defaultModel || 'default', value: chatState.model, oninput: (ev) => { chatState.model = ev.target.value; } }))),
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
h('div', { id: 'fd-chat-msgs', class: 'fd-chatlog' }),
|
|
141
|
+
h('input', { name: 'model', type: 'text', placeholder: configuredProviders.find(p => p.name === chatState.provider)?.defaultModel || 'default', value: chatState.model, oninput: (ev) => { chatState.model = ev.target.value; } })))),
|
|
142
|
+
// Live chat surface — the kit's ds-chat web component handles
|
|
143
|
+
// layout, scroll, empty state, bubble chrome, tool-call cards,
|
|
144
|
+
// composer focus rings, send/cancel button swap, etc.
|
|
145
|
+
h('ds-chat', { class: 'fd-dashboard-chat ds-247420', title: 'chat', placeholder: 'describe what you want to do in the working directory…' }),
|
|
131
146
|
],
|
|
132
147
|
}),
|
|
133
148
|
configuredProviders.length === 0
|
|
134
149
|
? Panel({ title: 'no providers configured', children: Receipt({ rows: [
|
|
135
150
|
['set API key', 'go to keys tab, click a provider chip to set its key'],
|
|
136
151
|
['then reload', 'refresh this page to see providers here'],
|
|
137
|
-
['or use
|
|
152
|
+
['or use a gateway', 'run a gateway server on localhost:4800 for local LLMs'],
|
|
138
153
|
] }) })
|
|
139
154
|
: Panel({ title: 'configured providers', children: h('div', { class: 'fd-chip-wrap' },
|
|
140
|
-
...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.
|
|
155
|
+
...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.configured ? [p.name, ' ', p.available ? Icon('circle-dot') : Icon('circle')] : p.name }))) }),
|
|
141
156
|
];
|
|
142
157
|
};
|
|
143
158
|
}
|