@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.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/chat-view-bridge.d.ts +26 -0
- package/dist/chat-view-bridge.js +66 -0
- package/dist/chat-view-provider.d.ts +11 -0
- package/dist/chat-view-provider.js +50 -0
- package/dist/commands.d.ts +27 -0
- package/dist/commands.js +113 -0
- package/dist/create-extension.d.ts +33 -0
- package/dist/create-extension.js +92 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/install-browser.d.ts +33 -0
- package/dist/install-browser.js +39 -0
- package/dist/manifest.d.ts +13 -0
- package/dist/manifest.js +54 -0
- package/dist/protocol.d.ts +54 -0
- package/dist/protocol.js +1 -0
- package/dist/session-controller.d.ts +77 -0
- package/dist/session-controller.js +198 -0
- package/dist/ui-config.d.ts +38 -0
- package/dist/ui-config.js +33 -0
- package/dist/vscode-ui.d.ts +30 -0
- package/dist/vscode-ui.js +38 -0
- package/dist/webview/main.js +167 -0
- package/dist/webview/style.css +139 -0
- package/dist/webview-html.d.ts +9 -0
- package/dist/webview-html.js +32 -0
- package/package.json +37 -0
|
@@ -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, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, """);
|
|
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
|
+
}
|