Первый коммит

This commit is contained in:
2026-04-09 15:42:42 +03:00
commit c664209746
28 changed files with 2616 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>

After

Width:  |  Height:  |  Size: 261 B

+415
View File
@@ -0,0 +1,415 @@
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
color: var(--vscode-foreground);
background: var(--vscode-sideBar-background);
}
.chat-root {
display: flex;
flex-direction: column;
height: 100vh;
min-height: 200px;
overflow: hidden;
}
.chat-header {
flex-shrink: 0;
display: flex;
align-items: center;
align-content: flex-start;
flex-wrap: wrap;
gap: 8px;
min-height: 44px;
padding: 8px;
border-bottom: 1px solid var(--vscode-widget-border, rgba(128, 128, 128, 0.35));
background: var(--vscode-sideBar-background);
}
.header-controls {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
flex: 0 0 auto;
}
.process-select-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.process-select-label {
font-size: 0.78em;
opacity: 0.8;
}
.process-select-wrap select {
min-width: 120px;
font-family: inherit;
font-size: inherit;
padding: 4px 8px;
border: 1px solid var(--vscode-dropdown-border, var(--vscode-widget-border, rgba(128, 128, 128, 0.35)));
background: var(--vscode-dropdown-background, var(--vscode-input-background));
color: var(--vscode-dropdown-foreground, var(--vscode-input-foreground));
border-radius: 2px;
}
.process-select-wrap select:focus {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: 1px;
}
.header-session {
flex: 1 1 320px;
min-width: 220px;
display: flex;
align-items: flex-start;
gap: 6px;
padding: 4px 8px;
border: 1px solid var(--vscode-widget-border, rgba(128, 128, 128, 0.35));
border-radius: 4px;
background: var(--vscode-sideBar-background);
color: var(--vscode-descriptionForeground, var(--vscode-disabledForeground));
font-size: 0.88em;
line-height: 1.25;
cursor: pointer;
}
.header-session:hover {
border-color: var(--vscode-focusBorder, rgba(128, 128, 128, 0.6));
color: var(--vscode-foreground);
}
.header-session:focus {
outline: 1px solid var(--vscode-focusBorder, rgba(128, 128, 128, 0.6));
outline-offset: 1px;
}
.header-session.copied {
border-color: var(--vscode-testing-iconPassed, #73c991);
color: var(--vscode-testing-iconPassed, #73c991);
}
.header-session-label {
flex-shrink: 0;
font-weight: 600;
}
.header-session-value {
min-width: 0;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
font-family: var(--vscode-editor-font-family, var(--vscode-font-family));
}
.header-spacer {
flex: 999 1 auto;
}
.btn {
font-family: inherit;
font-size: var(--vscode-font-size);
padding: 4px 10px;
cursor: pointer;
border: 1px solid var(--vscode-button-border, transparent);
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border-radius: 2px;
}
.btn:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
.btn-primary {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.btn-primary:hover {
background: var(--vscode-button-hoverBackground);
}
.btn-icon {
min-width: 32px;
padding: 4px 8px;
font-size: 1.25rem;
line-height: 1;
}
.menu-wrap {
position: relative;
margin-left: auto;
}
.menu-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: 4px;
min-width: 200px;
padding: 4px 0;
background: var(--vscode-menu-background);
color: var(--vscode-menu-foreground);
border: 1px solid var(--vscode-menu-border);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.menu-item {
display: block;
width: 100%;
text-align: left;
padding: 6px 12px;
border: none;
background: transparent;
color: inherit;
font-family: inherit;
font-size: inherit;
cursor: pointer;
}
.menu-item:hover {
background: var(--vscode-menu-selectionBackground);
color: var(--vscode-menu-selectionForeground);
}
.chat-body {
flex: 1;
min-height: 0;
overflow: hidden;
}
.messages-pane {
height: 100%;
min-height: 0;
padding: 8px;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
}
.status-blocks {
display: flex;
flex-direction: column;
gap: 6px;
}
.status-block {
border: 1px solid var(--vscode-widget-border, transparent);
border-radius: 6px;
background: color-mix(in srgb, var(--vscode-sideBar-background) 88%, var(--vscode-editor-inactiveSelectionBackground) 12%);
color: color-mix(in srgb, var(--vscode-foreground) 62%, transparent);
}
.status-block-title {
padding: 6px 8px 4px;
font-size: 0.86em;
font-weight: 600;
opacity: 0.85;
border-bottom: 1px solid color-mix(in srgb, var(--vscode-widget-border, rgba(128, 128, 128, 0.35)) 70%, transparent);
}
.status-block-body {
max-height: 12.5em;
overflow-y: auto;
padding: 4px 8px 6px;
font-size: 0.84em;
line-height: 1.25;
white-space: pre-wrap;
word-break: break-word;
}
.status-block-line + .status-block-line {
margin-top: 2px;
}
.messages {
display: flex;
flex-direction: column;
gap: 8px;
overflow: visible;
}
.chat-footer {
flex-shrink: 0;
border-top: 1px solid var(--vscode-widget-border, rgba(128, 128, 128, 0.35));
background: var(--vscode-sideBar-background);
}
.input-pane {
display: flex;
flex-direction: column;
padding: 8px;
gap: 8px;
}
.input-pane textarea {
min-height: 72px;
max-height: 140px;
resize: vertical;
font-family: inherit;
font-size: inherit;
padding: 8px;
border: 1px solid var(--vscode-input-border, var(--vscode-widget-border));
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border-radius: 2px;
}
.input-pane textarea:focus {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: -1px;
}
.input-actions {
flex-shrink: 0;
display: flex;
justify-content: flex-end;
}
.task-status {
padding: 8px 10px;
border: 1px solid var(--vscode-widget-border, transparent);
border-radius: 6px;
background: var(--vscode-editor-inactiveSelectionBackground, rgba(128, 128, 128, 0.12));
}
.task-status-label,
.rag-label {
font-weight: 600;
margin-bottom: 2px;
}
.task-status-detail,
.rag-detail,
.rag-metrics {
font-size: 0.92em;
opacity: 0.88;
}
.task-progress {
margin-top: 8px;
height: 6px;
border-radius: 999px;
overflow: hidden;
background: var(--vscode-editorGroup-border, rgba(128, 128, 128, 0.15));
}
.task-progress-bar {
height: 100%;
width: 0%;
background: var(--vscode-progressBar-background, var(--vscode-button-background));
}
.rag-status {
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 10px;
border: 1px solid var(--vscode-widget-border, transparent);
border-radius: 6px;
background: color-mix(in srgb, var(--vscode-sideBar-background) 82%, var(--vscode-editor-inactiveSelectionBackground) 18%);
}
.rag-status-main {
display: flex;
gap: 8px;
align-items: flex-start;
}
.rag-dot {
width: 10px;
height: 10px;
border-radius: 999px;
margin-top: 4px;
background: var(--vscode-disabledForeground);
flex-shrink: 0;
}
.rag-dot.state-idle {
background: var(--vscode-disabledForeground);
}
.rag-dot.state-indexing {
background: var(--vscode-charts-yellow, #d7ba7d);
}
.rag-dot.state-ready {
background: var(--vscode-testing-iconPassed, #73c991);
}
.rag-dot.state-error {
background: var(--vscode-testing-iconFailed, #f14c4c);
}
.rag-text {
min-width: 0;
}
.msg {
max-width: 95%;
padding: 8px 10px;
border-radius: 6px;
white-space: pre-wrap;
word-break: break-word;
}
.msg-user {
align-self: flex-end;
background: var(--vscode-textBlockQuote-background);
border: 1px solid var(--vscode-widget-border, transparent);
}
.msg-assistant {
align-self: flex-start;
background: var(--vscode-editor-inactiveSelectionBackground, rgba(128, 128, 128, 0.15));
border: 1px solid var(--vscode-widget-border, transparent);
}
.msg-error {
align-self: stretch;
background: color-mix(in srgb, var(--vscode-inputValidation-errorBackground, rgba(241, 76, 76, 0.18)) 90%, transparent);
border: 1px solid var(--vscode-inputValidation-errorBorder, rgba(241, 76, 76, 0.7));
}
.msg-status {
align-self: stretch;
padding: 0;
border: none;
background: transparent;
color: var(--vscode-descriptionForeground, var(--vscode-disabledForeground));
font-size: 0.9em;
opacity: 0.9;
line-height: 1.12;
}
.msg-role {
font-size: 0.75em;
opacity: 0.8;
margin-bottom: 4px;
}
.msg-text,
.msg-markdown {
white-space: pre-wrap;
word-break: break-word;
}
.msg-markdown {
font-family: var(--vscode-editor-font-family, var(--vscode-font-family));
}
+76
View File
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src {{CSP}}; script-src {{CSP}};" />
<link href="{{CSS_URI}}" rel="stylesheet" />
</head>
<body>
<div class="chat-root">
<header class="chat-header">
<div class="header-controls">
<button type="button" class="btn" id="btn-clear" title="Новая сессия">Новая сессия</button>
<label class="process-select-wrap" for="process-version">
<span class="process-select-label">Процесс</span>
<select id="process-version" aria-label="Версия процесса агента">
<option value="v1">v1</option>
<option value="v2" selected>v2</option>
</select>
</label>
</div>
<div
class="header-session"
id="header-rag-session"
role="button"
tabindex="0"
title="Кликните, чтобы скопировать RAG session id"
aria-label="RAG session id"
>
<span class="header-session-label">RAG:</span>
<span class="header-session-value" id="header-rag-session-value"></span>
</div>
<div class="header-spacer"></div>
<div class="menu-wrap">
<button type="button" class="btn btn-icon" id="btn-menu" title="Меню" aria-haspopup="true" aria-expanded="false"></button>
<div class="menu-dropdown" id="menu-dropdown" hidden>
<button type="button" class="menu-item" data-action="settings">Настройки (заглушка)</button>
<button type="button" class="menu-item" data-action="about">О плагине (заглушка)</button>
</div>
</div>
</header>
<div class="chat-body">
<div class="messages-pane" id="feed-scroll">
<div class="status-blocks" id="status-blocks"></div>
<div class="messages" id="messages"></div>
</div>
</div>
<div class="chat-footer">
<div class="input-pane">
<div class="task-status" id="task-status" hidden>
<div class="task-status-label" id="task-status-label"></div>
<div class="task-status-detail" id="task-status-detail"></div>
<div class="task-progress" id="task-progress" hidden>
<div class="task-progress-bar" id="task-progress-bar"></div>
</div>
</div>
<textarea id="input" rows="3" placeholder="Сообщение…" aria-label="Текст сообщения"></textarea>
<div class="input-actions">
<button type="button" class="btn btn-primary" id="btn-send">Отправить</button>
</div>
<div class="rag-status" id="rag-status">
<div class="rag-status-main">
<span class="rag-dot" id="rag-dot"></span>
<div class="rag-text">
<div class="rag-label" id="rag-label">RAG не готов</div>
<div class="rag-detail" id="rag-detail">Ожидается индексация проекта.</div>
</div>
</div>
<div class="rag-metrics" id="rag-metrics"></div>
</div>
</div>
</div>
</div>
<script src="{{JS_URI}}"></script>
</body>
</html>
+238
View File
@@ -0,0 +1,238 @@
(function () {
const vscode = acquireVsCodeApi();
const feedScrollEl = document.getElementById("feed-scroll");
const headerRagSessionEl = document.getElementById("header-rag-session");
const headerRagSessionValueEl = document.getElementById("header-rag-session-value");
const processVersionEl = document.getElementById("process-version");
const statusBlocksEl = document.getElementById("status-blocks");
const messagesEl = document.getElementById("messages");
const inputEl = document.getElementById("input");
const btnSend = document.getElementById("btn-send");
const btnClear = document.getElementById("btn-clear");
const btnMenu = document.getElementById("btn-menu");
const menuDropdown = document.getElementById("menu-dropdown");
const taskStatusEl = document.getElementById("task-status");
const taskStatusLabelEl = document.getElementById("task-status-label");
const taskStatusDetailEl = document.getElementById("task-status-detail");
const taskProgressEl = document.getElementById("task-progress");
const taskProgressBarEl = document.getElementById("task-progress-bar");
const ragDotEl = document.getElementById("rag-dot");
const ragLabelEl = document.getElementById("rag-label");
const ragDetailEl = document.getElementById("rag-detail");
const ragMetricsEl = document.getElementById("rag-metrics");
let currentRagSessionId = "";
function renderStatusBlocks(items) {
statusBlocksEl.innerHTML = "";
(items || []).forEach(function (block) {
const wrap = document.createElement("div");
wrap.className = "status-block";
const title = document.createElement("div");
title.className = "status-block-title";
title.textContent = block.title || block.id || "Status";
wrap.appendChild(title);
const body = document.createElement("div");
body.className = "status-block-body";
(block.lines || []).forEach(function (line) {
const item = document.createElement("div");
item.className = "status-block-line";
item.textContent = line || "";
body.appendChild(item);
});
wrap.appendChild(body);
statusBlocksEl.appendChild(wrap);
});
}
function renderMessages(items) {
messagesEl.innerHTML = "";
(items || []).forEach(function (m) {
const div = document.createElement("div");
var roleClass = "msg-assistant";
if (m.role === "user") {
roleClass = "msg-user";
} else if (m.role === "status") {
roleClass = "msg-status";
} else if (m.role === "error") {
roleClass = "msg-error";
}
div.className = "msg " + roleClass;
const text = document.createElement("div");
text.className = m.kind === "markdown" ? "msg-markdown" : "msg-text";
text.textContent = m.text || "";
if (m.role !== "status") {
const role = document.createElement("div");
role.className = "msg-role";
role.textContent =
m.role === "user" ? "Вы" : m.role === "error" ? "Ошибка" : "Ответ";
div.appendChild(role);
}
div.appendChild(text);
messagesEl.appendChild(div);
});
}
function renderTaskStatus(status) {
var visible = Boolean(status && status.visible);
taskStatusEl.hidden = !visible;
if (!visible) {
taskProgressEl.hidden = true;
taskProgressBarEl.style.width = "0%";
return;
}
taskStatusLabelEl.textContent = status.label || "Статус";
taskStatusDetailEl.textContent = status.detail || "";
if (typeof status.progress === "number" && isFinite(status.progress)) {
taskProgressEl.hidden = false;
taskProgressBarEl.style.width = Math.max(0, Math.min(100, status.progress)) + "%";
} else {
taskProgressEl.hidden = true;
taskProgressBarEl.style.width = "0%";
}
}
function renderRagStatus(status) {
var state = (status && status.state) || "idle";
var sessionId = (status && status.ragSessionId) || "";
currentRagSessionId = sessionId;
ragDotEl.className = "rag-dot state-" + state;
ragLabelEl.textContent = (status && status.label) || "RAG не готов";
ragDetailEl.textContent =
(status && status.detail) || "Ожидается индексация проекта.";
if (headerRagSessionEl && headerRagSessionValueEl) {
headerRagSessionValueEl.textContent = sessionId || "—";
headerRagSessionEl.title = sessionId
? "Кликните, чтобы скопировать RAG session id"
: "RAG session is not created yet.";
}
var metrics = [];
if (status) {
metrics.push("indexed: " + (status.indexedFiles || 0));
metrics.push("failed: " + (status.failedFiles || 0));
metrics.push("cache hit: " + (status.cacheHitFiles || 0));
metrics.push("cache miss: " + (status.cacheMissFiles || 0));
}
ragMetricsEl.textContent = metrics.join(" | ");
}
function renderState(state) {
renderStatusBlocks(state.statusBlocks);
renderMessages(state.messages);
renderTaskStatus(state.taskStatus);
renderRagStatus(state.ragStatus);
if (processVersionEl && state && state.processVersion) {
processVersionEl.value = state.processVersion;
}
btnSend.disabled = false;
inputEl.disabled = false;
btnClear.disabled = Boolean(state.busy);
if (feedScrollEl) {
feedScrollEl.scrollTop = feedScrollEl.scrollHeight;
}
}
window.addEventListener("message", function (event) {
const msg = event.data;
if (msg && msg.type === "state") {
renderState(msg.payload || {});
}
});
function send() {
const text = (inputEl.value || "").trim();
if (!text) {
return;
}
vscode.postMessage({ type: "send", text: text });
inputEl.value = "";
}
btnSend.addEventListener("click", send);
inputEl.addEventListener("keydown", function (e) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
send();
}
});
btnClear.addEventListener("click", function () {
vscode.postMessage({ type: "clear" });
});
processVersionEl.addEventListener("change", function () {
vscode.postMessage({
type: "set-process-version",
value: processVersionEl.value || "v2",
});
});
async function copyRagSessionId() {
if (!currentRagSessionId) {
return;
}
try {
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
await navigator.clipboard.writeText(currentRagSessionId);
} else {
const helper = document.createElement("textarea");
helper.value = currentRagSessionId;
helper.style.position = "fixed";
helper.style.opacity = "0";
document.body.appendChild(helper);
helper.focus();
helper.select();
document.execCommand("copy");
document.body.removeChild(helper);
}
if (headerRagSessionEl) {
headerRagSessionEl.classList.add("copied");
window.setTimeout(function () {
headerRagSessionEl.classList.remove("copied");
}, 1200);
}
} catch (error) {
console.error("Failed to copy RAG session id", error);
}
}
headerRagSessionEl.addEventListener("click", function () {
void copyRagSessionId();
});
headerRagSessionEl.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
void copyRagSessionId();
}
});
btnMenu.addEventListener("click", function (e) {
e.stopPropagation();
const open = menuDropdown.hidden;
menuDropdown.hidden = !open;
btnMenu.setAttribute("aria-expanded", open ? "true" : "false");
});
document.addEventListener("click", function () {
if (!menuDropdown.hidden) {
menuDropdown.hidden = true;
btnMenu.setAttribute("aria-expanded", "false");
}
});
menuDropdown.addEventListener("click", function (e) {
e.stopPropagation();
const t = e.target;
if (t && t.classList && t.classList.contains("menu-item")) {
const action = t.getAttribute("data-action") || "";
vscode.postMessage({ type: "menu", action: action });
menuDropdown.hidden = true;
btnMenu.setAttribute("aria-expanded", "false");
}
});
vscode.postMessage({ type: "ready" });
})();