@chatbridge/vscode 0.7.0

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.
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ (() => {
3
+ // src/webview/main.ts
4
+ var vscode = acquireVsCodeApi();
5
+ var history = document.getElementById("history");
6
+ var status = document.getElementById("status");
7
+ var attachments = document.getElementById("attachments");
8
+ var form = document.getElementById("composer");
9
+ var input = document.getElementById("input");
10
+ var sendButton = document.getElementById("send");
11
+ var welcome = document.getElementById("welcome");
12
+ var welcomeText = document.getElementById("welcome-text");
13
+ var banner = document.getElementById("banner");
14
+ var footer = document.getElementById("footer");
15
+ var config = {};
16
+ function applyConfig(c) {
17
+ config = c;
18
+ if (c.sendButton?.background) {
19
+ sendButton.style.setProperty("--cb-send-bg", c.sendButton.background);
20
+ }
21
+ if (c.sendButton?.foreground) {
22
+ sendButton.style.setProperty("--cb-send-fg", c.sendButton.foreground);
23
+ }
24
+ if (c.userMessage?.borderColor) {
25
+ document.documentElement.style.setProperty(
26
+ "--cb-user-border",
27
+ c.userMessage.borderColor
28
+ );
29
+ }
30
+ welcomeText.textContent = c.welcome ?? "";
31
+ if (c.bannerUri) {
32
+ banner.src = c.bannerUri;
33
+ banner.hidden = false;
34
+ } else {
35
+ banner.hidden = true;
36
+ }
37
+ footer.textContent = c.footer ?? "";
38
+ footer.hidden = !c.footer;
39
+ }
40
+ function el(tag, className, text) {
41
+ const e = document.createElement(tag);
42
+ e.className = className;
43
+ if (text !== void 0) e.textContent = text;
44
+ return e;
45
+ }
46
+ function button(label, onClick) {
47
+ const b = el("button", "action", label);
48
+ b.setAttribute("type", "button");
49
+ b.addEventListener("click", onClick);
50
+ return b;
51
+ }
52
+ function formatSize(bytes) {
53
+ if (bytes < 1024) return `${bytes} B`;
54
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
55
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
56
+ }
57
+ function renderMessage(m) {
58
+ const box = el("div", `message ${m.role}`);
59
+ if (m.role === "separator") {
60
+ box.textContent = `\u2014 ${m.text} \u2014`;
61
+ return box;
62
+ }
63
+ box.appendChild(el("div", "text", m.text));
64
+ for (const a of m.attachments ?? []) {
65
+ box.appendChild(
66
+ el("div", "attachment", `\u{1F4CE} ${a.path} (${formatSize(a.bytes)})`)
67
+ );
68
+ }
69
+ return box;
70
+ }
71
+ function renderStatus(s) {
72
+ status.replaceChildren();
73
+ status.hidden = false;
74
+ if (s.status === "busy" || s.status === "opening") {
75
+ status.appendChild(el("span", "spinner"));
76
+ status.appendChild(
77
+ el(
78
+ "span",
79
+ "progress-text",
80
+ s.status === "opening" ? "Opening browser..." : "Waiting..."
81
+ )
82
+ );
83
+ return;
84
+ }
85
+ if (s.status === "dead") {
86
+ const auth = s.lastError === "AUTH_REQUIRED" || s.lastError === "AUTH_EXPIRED";
87
+ status.appendChild(
88
+ el(
89
+ "span",
90
+ "progress-text",
91
+ auth ? "Not logged in." : "The chat stopped."
92
+ )
93
+ );
94
+ if (auth) {
95
+ status.appendChild(
96
+ button(
97
+ "Log in",
98
+ () => vscode.postMessage({ type: "command", name: "login" })
99
+ )
100
+ );
101
+ }
102
+ status.appendChild(
103
+ button(
104
+ "New chat",
105
+ () => vscode.postMessage({ type: "command", name: "newChat" })
106
+ )
107
+ );
108
+ return;
109
+ }
110
+ status.hidden = true;
111
+ }
112
+ function renderAttachments(s) {
113
+ attachments.replaceChildren();
114
+ s.pendingAttachments.forEach((a, index) => {
115
+ const chip = el("span", "chip", `\u{1F4CE} ${a.path} (${formatSize(a.bytes)})`);
116
+ const x = button(
117
+ "\xD7",
118
+ () => vscode.postMessage({ type: "removeAttachment", index })
119
+ );
120
+ x.className = "chip-remove";
121
+ chip.appendChild(x);
122
+ attachments.appendChild(chip);
123
+ });
124
+ }
125
+ function render(s) {
126
+ history.replaceChildren(...s.messages.map(renderMessage));
127
+ history.scrollTop = history.scrollHeight;
128
+ renderStatus(s);
129
+ renderAttachments(s);
130
+ welcome.hidden = s.messages.length > 0 || !config.welcome && !config.bannerUri;
131
+ history.hidden = !welcome.hidden;
132
+ const locked = s.status === "busy" || s.status === "opening";
133
+ input.disabled = locked;
134
+ sendButton.disabled = locked;
135
+ if (!locked) input.focus();
136
+ }
137
+ function submit() {
138
+ const text = input.value;
139
+ if (text.trim() === "" && attachments.childElementCount === 0) return;
140
+ vscode.postMessage({ type: "send", text });
141
+ input.value = "";
142
+ }
143
+ form.addEventListener("submit", (e) => {
144
+ e.preventDefault();
145
+ submit();
146
+ });
147
+ input.addEventListener("keydown", (e) => {
148
+ if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
149
+ e.preventDefault();
150
+ submit();
151
+ }
152
+ });
153
+ window.addEventListener("message", (event) => {
154
+ const m = event.data;
155
+ if (m.type === "state") {
156
+ const { type: _type, ...state } = m;
157
+ render(state);
158
+ } else if (m.type === "config") {
159
+ const { type: _type, ...rest } = m;
160
+ applyConfig(rest);
161
+ } else if (m.type === "progress") {
162
+ const t = status.querySelector(".progress-text");
163
+ if (t) t.textContent = m.text;
164
+ }
165
+ });
166
+ vscode.postMessage({ type: "ready" });
167
+ })();
@@ -0,0 +1,139 @@
1
+ body {
2
+ margin: 0;
3
+ display: flex;
4
+ flex-direction: column;
5
+ height: 100vh;
6
+ font-family: var(--vscode-font-family);
7
+ font-size: var(--vscode-font-size);
8
+ color: var(--vscode-foreground);
9
+ background: var(--vscode-sideBar-background);
10
+ }
11
+ #history {
12
+ flex: 1;
13
+ overflow-y: auto;
14
+ padding: 8px;
15
+ }
16
+ .message {
17
+ margin-bottom: 10px;
18
+ padding: 6px 8px;
19
+ border-radius: 4px;
20
+ white-space: pre-wrap;
21
+ word-break: break-word;
22
+ }
23
+ .message.user {
24
+ background: var(--vscode-input-background);
25
+ border: 1px solid var(--cb-user-border, var(--vscode-focusBorder));
26
+ }
27
+ .message.assistant {
28
+ background: var(--vscode-editor-background);
29
+ }
30
+ .message.error {
31
+ color: var(--vscode-errorForeground);
32
+ border-left: 3px solid var(--vscode-errorForeground);
33
+ }
34
+ .message.separator {
35
+ text-align: center;
36
+ opacity: 0.6;
37
+ }
38
+ .attachment {
39
+ opacity: 0.7;
40
+ font-size: 90%;
41
+ margin-top: 4px;
42
+ }
43
+ #status {
44
+ padding: 4px 8px;
45
+ display: flex;
46
+ gap: 8px;
47
+ align-items: center;
48
+ opacity: 0.85;
49
+ }
50
+ .spinner {
51
+ width: 10px;
52
+ height: 10px;
53
+ border: 2px solid var(--vscode-progressBar-background);
54
+ border-top-color: transparent;
55
+ border-radius: 50%;
56
+ animation: spin 0.8s linear infinite;
57
+ }
58
+ @keyframes spin {
59
+ to {
60
+ transform: rotate(360deg);
61
+ }
62
+ }
63
+ #attachments {
64
+ padding: 0 8px;
65
+ display: flex;
66
+ flex-wrap: wrap;
67
+ gap: 4px;
68
+ }
69
+ .chip {
70
+ background: var(--vscode-badge-background);
71
+ color: var(--vscode-badge-foreground);
72
+ border-radius: 10px;
73
+ padding: 2px 8px;
74
+ font-size: 90%;
75
+ }
76
+ .chip-remove,
77
+ .action {
78
+ margin-left: 6px;
79
+ background: var(--vscode-button-background);
80
+ color: var(--vscode-button-foreground);
81
+ border: none;
82
+ border-radius: 3px;
83
+ padding: 2px 8px;
84
+ cursor: pointer;
85
+ }
86
+ #composer {
87
+ display: flex;
88
+ gap: 6px;
89
+ padding: 8px;
90
+ border-top: 1px solid var(--vscode-panel-border);
91
+ }
92
+ #input {
93
+ flex: 1;
94
+ resize: none;
95
+ background: var(--vscode-input-background);
96
+ color: var(--vscode-input-foreground);
97
+ border: 1px solid var(--vscode-input-border, transparent);
98
+ font-family: inherit;
99
+ }
100
+ #welcome {
101
+ flex: 1;
102
+ display: flex;
103
+ flex-direction: column;
104
+ justify-content: center;
105
+ align-items: center;
106
+ padding: 16px 12px;
107
+ text-align: center;
108
+ opacity: 0.7;
109
+ }
110
+ /* `display: flex` above would otherwise beat the `hidden` attribute. */
111
+ #welcome[hidden] {
112
+ display: none;
113
+ }
114
+ #banner {
115
+ max-width: 100%;
116
+ height: auto;
117
+ }
118
+ #welcome-text {
119
+ margin: 8px 0 0;
120
+ white-space: pre-line;
121
+ }
122
+ #footer {
123
+ padding: 0 8px 8px;
124
+ text-align: center;
125
+ font-size: 90%;
126
+ opacity: 0.7;
127
+ }
128
+ #send {
129
+ background: var(--cb-send-bg, var(--vscode-button-background));
130
+ color: var(--cb-send-fg, var(--vscode-button-foreground));
131
+ border: none;
132
+ border-radius: 3px;
133
+ padding: 0 12px;
134
+ cursor: pointer;
135
+ }
136
+ #send:disabled,
137
+ #input:disabled {
138
+ opacity: 0.5;
139
+ }
@@ -0,0 +1,9 @@
1
+ export interface HtmlInputs {
2
+ cspSource: string;
3
+ nonce: string;
4
+ scriptUri: string;
5
+ styleUri: string;
6
+ title: string;
7
+ }
8
+ /** The whole document; no inline script or style, no external origin. */
9
+ export declare function buildHtml(i: HtmlInputs): string;
@@ -0,0 +1,32 @@
1
+ function escapeHtml(s) {
2
+ return s
3
+ .replace(/&/g, "&amp;")
4
+ .replace(/</g, "&lt;")
5
+ .replace(/>/g, "&gt;")
6
+ .replace(/"/g, "&quot;");
7
+ }
8
+ /** The whole document; no inline script or style, no external origin. */
9
+ export function buildHtml(i) {
10
+ return `<!DOCTYPE html>
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="UTF-8">
14
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${i.cspSource}; script-src 'nonce-${i.nonce}'; style-src ${i.cspSource};">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
+ <link rel="stylesheet" href="${i.styleUri}">
17
+ <title>${escapeHtml(i.title)}</title>
18
+ </head>
19
+ <body>
20
+ <div id="welcome" hidden><img id="banner" alt="" hidden><p id="welcome-text"></p></div>
21
+ <main id="history" aria-live="polite"></main>
22
+ <div id="status" hidden></div>
23
+ <div id="attachments"></div>
24
+ <form id="composer">
25
+ <textarea id="input" rows="3" placeholder="Message (Enter to send, Shift+Enter for a newline)"></textarea>
26
+ <button id="send" type="submit">Send</button>
27
+ </form>
28
+ <footer id="footer" hidden></footer>
29
+ <script nonce="${i.nonce}" src="${i.scriptUri}"></script>
30
+ </body>
31
+ </html>`;
32
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@chatbridge/vscode",
3
+ "version": "0.7.0",
4
+ "description": "VSCode extension factory for chatbridge: a sidebar chat view on top of ChatSession",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/7milch/chatbridge-cli.git",
9
+ "directory": "packages/vscode"
10
+ },
11
+ "keywords": ["chatbridge", "vscode", "playwright", "chat"],
12
+ "type": "module",
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": ["dist", "README.md", "LICENSE"],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "check:webview": "tsc -p tsconfig.webview.json",
28
+ "build:webview": "bun run check:webview && esbuild src/webview/main.ts --bundle --format=iife --target=es2022 --outfile=dist/webview/main.js && mkdir -p dist/webview && cp src/webview/style.css dist/webview/style.css"
29
+ },
30
+ "dependencies": {
31
+ "@chatbridge/core": "0.7.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/vscode": "1.138.0",
35
+ "esbuild": "0.25.12"
36
+ }
37
+ }