revengelibrary 0.1.5__tar.gz → 0.1.7__tar.gz
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.
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/MANIFEST.in +1 -1
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/PKG-INFO +2 -2
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/pyproject.toml +4 -3
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary/__init__.py +6 -1
- revengelibrary-0.1.7/revengelibrary/agents.py +77 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary/chat.py +8 -1
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary/cli.py +28 -4
- revengelibrary-0.1.7/revengelibrary/ide/app.js +311 -0
- revengelibrary-0.1.7/revengelibrary/ide/index.html +101 -0
- revengelibrary-0.1.7/revengelibrary/ide/styles.css +405 -0
- revengelibrary-0.1.7/revengelibrary/ide_server.py +480 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary.egg-info/PKG-INFO +2 -2
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary.egg-info/SOURCES.txt +6 -1
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary.egg-info/entry_points.txt +1 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/LICENSE +0 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/README.md +0 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary/memory_store.json +0 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary.egg-info/dependency_links.txt +0 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary.egg-info/requires.txt +0 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/revengelibrary.egg-info/top_level.txt +0 -0
- {revengelibrary-0.1.5 → revengelibrary-0.1.7}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: revengelibrary
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.1.7
|
|
4
|
+
Summary: Не нейросеть
|
|
5
5
|
Author: revengebibliotek contributors
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/example/revengelibrary
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "revengelibrary"
|
|
7
|
-
version = "0.1.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.1.7"
|
|
8
|
+
description = "Не нейросеть"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = { text = "MIT" }
|
|
@@ -27,6 +27,7 @@ Homepage = "https://github.com/example/revengelibrary"
|
|
|
27
27
|
|
|
28
28
|
[project.scripts]
|
|
29
29
|
revengelibrary = "revengelibrary.cli:main"
|
|
30
|
+
revengelibrary-ide = "revengelibrary.ide_server:main"
|
|
30
31
|
|
|
31
32
|
[tool.setuptools]
|
|
32
33
|
include-package-data = true
|
|
@@ -35,4 +36,4 @@ include-package-data = true
|
|
|
35
36
|
include = ["revengelibrary*"]
|
|
36
37
|
|
|
37
38
|
[tool.setuptools.package-data]
|
|
38
|
-
revengelibrary = ["memory_store.json"]
|
|
39
|
+
revengelibrary = ["memory_store.json", "ide/*.html", "ide/*.css", "ide/*.js"]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from .agents import DEFAULT_AGENT, AgentProfile, get_agent, list_agents
|
|
1
2
|
from .chat import (
|
|
2
3
|
APIError,
|
|
3
4
|
DEFAULT_MEMORY_FILE,
|
|
@@ -12,5 +13,9 @@ __all__ = [
|
|
|
12
13
|
"DEFAULT_OPENROUTER_API_KEY",
|
|
13
14
|
"DEFAULT_MODEL",
|
|
14
15
|
"DEFAULT_MEMORY_FILE",
|
|
16
|
+
"DEFAULT_AGENT",
|
|
17
|
+
"AgentProfile",
|
|
18
|
+
"list_agents",
|
|
19
|
+
"get_agent",
|
|
15
20
|
]
|
|
16
|
-
__version__ = "0.1.
|
|
21
|
+
__version__ = "0.1.7"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
DEFAULT_AGENT = "general"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class AgentProfile:
|
|
11
|
+
name: str
|
|
12
|
+
title: str
|
|
13
|
+
system_prompt: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
AGENT_PROFILES: dict[str, AgentProfile] = {
|
|
17
|
+
"general": AgentProfile(
|
|
18
|
+
name="general",
|
|
19
|
+
title="Универсальный ассистент",
|
|
20
|
+
system_prompt="You are a helpful assistant.",
|
|
21
|
+
),
|
|
22
|
+
"frontend": AgentProfile(
|
|
23
|
+
name="frontend",
|
|
24
|
+
title="Senior Frontend + Mobile Design",
|
|
25
|
+
system_prompt=(
|
|
26
|
+
"Ты senior frontend-разработчик с сильной экспертизой в дизайне "
|
|
27
|
+
"мобильных приложений. Пиши ответы как практический инженер: "
|
|
28
|
+
"mobile-first, доступность, понятная архитектура компонентов, "
|
|
29
|
+
"чистый UI/UX, адаптив под iOS/Android, хорошая типографика и "
|
|
30
|
+
"согласованная дизайн-система."
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
"backend": AgentProfile(
|
|
34
|
+
name="backend",
|
|
35
|
+
title="Backend Engineer",
|
|
36
|
+
system_prompt=(
|
|
37
|
+
"Ты senior backend-разработчик. Предлагай надежные API-контракты, "
|
|
38
|
+
"чистую архитектуру, контроль ошибок, безопасность и масштабируемость."
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
"qa": AgentProfile(
|
|
42
|
+
name="qa",
|
|
43
|
+
title="QA Engineer",
|
|
44
|
+
system_prompt=(
|
|
45
|
+
"Ты senior QA-инженер. Фокус: тест-кейсы, граничные условия, "
|
|
46
|
+
"регрессия, воспроизводимость багов и риски релиза."
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def list_agents() -> list[AgentProfile]:
|
|
53
|
+
return list(AGENT_PROFILES.values())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_agent(agent_name: str) -> AgentProfile:
|
|
57
|
+
name = (agent_name or DEFAULT_AGENT).strip().lower()
|
|
58
|
+
if name not in AGENT_PROFILES:
|
|
59
|
+
available = ", ".join(sorted(AGENT_PROFILES))
|
|
60
|
+
raise ValueError(f"Unknown agent '{agent_name}'. Available: {available}")
|
|
61
|
+
return AGENT_PROFILES[name]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_agent_memory_file(
|
|
65
|
+
base_memory_file: str | None,
|
|
66
|
+
agent_name: str,
|
|
67
|
+
) -> str | None:
|
|
68
|
+
if not base_memory_file:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
if agent_name == DEFAULT_AGENT:
|
|
72
|
+
return base_memory_file
|
|
73
|
+
|
|
74
|
+
path = Path(base_memory_file).expanduser()
|
|
75
|
+
suffix = path.suffix or ".json"
|
|
76
|
+
stem = path.stem if path.suffix else path.name
|
|
77
|
+
return str(path.with_name(f"{stem}__{agent_name}{suffix}"))
|
|
@@ -176,7 +176,7 @@ class FreeNeuroChatClient:
|
|
|
176
176
|
value = file_path if file_path is not None else self.memory_file
|
|
177
177
|
if not value:
|
|
178
178
|
return None
|
|
179
|
-
return
|
|
179
|
+
return _normalize_memory_path(value)
|
|
180
180
|
|
|
181
181
|
@staticmethod
|
|
182
182
|
def _normalize_messages(data: Any) -> list[dict[str, str]]:
|
|
@@ -205,3 +205,10 @@ class FreeNeuroChatClient:
|
|
|
205
205
|
return content
|
|
206
206
|
except (KeyError, TypeError, IndexError):
|
|
207
207
|
raise APIError(f"Unexpected API response: {data}") from None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _normalize_memory_path(value: str) -> Path:
|
|
211
|
+
path = Path(value).expanduser()
|
|
212
|
+
if path.suffix:
|
|
213
|
+
return path
|
|
214
|
+
return path.with_suffix(".json")
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
|
+
from .agents import DEFAULT_AGENT, build_agent_memory_file, get_agent, list_agents
|
|
6
7
|
from .chat import (
|
|
7
8
|
APIError,
|
|
8
9
|
DEFAULT_MEMORY_FILE,
|
|
@@ -29,8 +30,18 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
29
30
|
)
|
|
30
31
|
parser.add_argument(
|
|
31
32
|
"--system",
|
|
32
|
-
default=
|
|
33
|
-
help="System prompt.",
|
|
33
|
+
default=None,
|
|
34
|
+
help="System prompt override. If omitted, selected agent prompt is used.",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--agent",
|
|
38
|
+
default=os.getenv("REVENGELIBRARY_AGENT", DEFAULT_AGENT),
|
|
39
|
+
help="Agent role. Use --list-agents to view available roles.",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--list-agents",
|
|
43
|
+
action="store_true",
|
|
44
|
+
help="Show available agent roles and exit.",
|
|
34
45
|
)
|
|
35
46
|
parser.add_argument(
|
|
36
47
|
"--memory-file",
|
|
@@ -47,11 +58,24 @@ def main() -> int:
|
|
|
47
58
|
parser = _build_parser()
|
|
48
59
|
args = parser.parse_args()
|
|
49
60
|
|
|
61
|
+
if args.list_agents:
|
|
62
|
+
for profile in list_agents():
|
|
63
|
+
print(f"{profile.name}: {profile.title}")
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
agent = get_agent(args.agent)
|
|
68
|
+
except ValueError as exc:
|
|
69
|
+
parser.error(str(exc))
|
|
70
|
+
|
|
71
|
+
system_prompt = args.system if args.system else agent.system_prompt
|
|
72
|
+
memory_file = build_agent_memory_file(args.memory_file, agent.name)
|
|
73
|
+
|
|
50
74
|
client = FreeNeuroChatClient(
|
|
51
75
|
api_key=args.api_key or DEFAULT_OPENROUTER_API_KEY,
|
|
52
76
|
model=args.model,
|
|
53
|
-
system_prompt=
|
|
54
|
-
memory_file=
|
|
77
|
+
system_prompt=system_prompt,
|
|
78
|
+
memory_file=memory_file,
|
|
55
79
|
)
|
|
56
80
|
|
|
57
81
|
print(
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
const state = {
|
|
2
|
+
config: null,
|
|
3
|
+
files: [],
|
|
4
|
+
currentFile: null,
|
|
5
|
+
dirty: false,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const nodes = {
|
|
9
|
+
projectName: document.getElementById("projectName"),
|
|
10
|
+
filesCount: document.getElementById("filesCount"),
|
|
11
|
+
fileTree: document.getElementById("fileTree"),
|
|
12
|
+
currentFileTitle: document.getElementById("currentFileTitle"),
|
|
13
|
+
saveState: document.getElementById("saveState"),
|
|
14
|
+
editor: document.getElementById("editor"),
|
|
15
|
+
lineNumbers: document.getElementById("lineNumbers"),
|
|
16
|
+
terminal: document.getElementById("terminal"),
|
|
17
|
+
refreshTreeBtn: document.getElementById("refreshTreeBtn"),
|
|
18
|
+
newFileBtn: document.getElementById("newFileBtn"),
|
|
19
|
+
saveBtn: document.getElementById("saveBtn"),
|
|
20
|
+
formatBtn: document.getElementById("formatBtn"),
|
|
21
|
+
chatState: document.getElementById("chatState"),
|
|
22
|
+
chatLog: document.getElementById("chatLog"),
|
|
23
|
+
chatForm: document.getElementById("chatForm"),
|
|
24
|
+
chatInput: document.getElementById("chatInput"),
|
|
25
|
+
agentSelect: document.getElementById("agentSelect"),
|
|
26
|
+
modelInput: document.getElementById("modelInput"),
|
|
27
|
+
memoryInput: document.getElementById("memoryInput"),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function logLine(text, kind = "info") {
|
|
31
|
+
const stamp = new Date().toLocaleTimeString();
|
|
32
|
+
const row = `[${stamp}] [${kind.toUpperCase()}] ${text}`;
|
|
33
|
+
nodes.terminal.textContent += `${row}\n`;
|
|
34
|
+
nodes.terminal.scrollTop = nodes.terminal.scrollHeight;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fetchJSON(url, options = {}) {
|
|
38
|
+
const response = await fetch(url, {
|
|
39
|
+
headers: { "Content-Type": "application/json" },
|
|
40
|
+
...options,
|
|
41
|
+
});
|
|
42
|
+
const payload = await response.json().catch(() => ({}));
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new Error(payload.error || `HTTP ${response.status}`);
|
|
45
|
+
}
|
|
46
|
+
return payload;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setDirty(value) {
|
|
50
|
+
state.dirty = value;
|
|
51
|
+
nodes.saveState.textContent = value ? "Есть несохраненные изменения" : "Готово";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function updateLineNumbers() {
|
|
55
|
+
const lines = nodes.editor.value.split("\n").length;
|
|
56
|
+
let output = "";
|
|
57
|
+
for (let index = 1; index <= lines; index += 1) {
|
|
58
|
+
output += `${index}\n`;
|
|
59
|
+
}
|
|
60
|
+
nodes.lineNumbers.textContent = output;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function syncScroll() {
|
|
64
|
+
nodes.lineNumbers.scrollTop = nodes.editor.scrollTop;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function appendChatMessage(kind, text) {
|
|
68
|
+
const row = document.createElement("div");
|
|
69
|
+
row.className = `msg ${kind}`;
|
|
70
|
+
row.textContent = text;
|
|
71
|
+
nodes.chatLog.appendChild(row);
|
|
72
|
+
nodes.chatLog.scrollTop = nodes.chatLog.scrollHeight;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildTree(paths) {
|
|
76
|
+
const root = { dirs: new Map(), files: [] };
|
|
77
|
+
|
|
78
|
+
for (const rawPath of paths) {
|
|
79
|
+
const parts = rawPath.split("/");
|
|
80
|
+
let cursor = root;
|
|
81
|
+
for (let idx = 0; idx < parts.length; idx += 1) {
|
|
82
|
+
const part = parts[idx];
|
|
83
|
+
const isFile = idx === parts.length - 1;
|
|
84
|
+
if (isFile) {
|
|
85
|
+
cursor.files.push(rawPath);
|
|
86
|
+
} else {
|
|
87
|
+
if (!cursor.dirs.has(part)) {
|
|
88
|
+
cursor.dirs.set(part, { dirs: new Map(), files: [] });
|
|
89
|
+
}
|
|
90
|
+
cursor = cursor.dirs.get(part);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return root;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function renderTree() {
|
|
98
|
+
nodes.fileTree.innerHTML = "";
|
|
99
|
+
const tree = buildTree(state.files);
|
|
100
|
+
|
|
101
|
+
function renderNode(node, host, prefix = "") {
|
|
102
|
+
const dirNames = [...node.dirs.keys()].sort((a, b) => a.localeCompare(b));
|
|
103
|
+
for (const dirName of dirNames) {
|
|
104
|
+
const row = document.createElement("div");
|
|
105
|
+
row.className = "tree-row";
|
|
106
|
+
row.textContent = `${prefix}${dirName}/`;
|
|
107
|
+
row.title = `${prefix}${dirName}/`;
|
|
108
|
+
host.appendChild(row);
|
|
109
|
+
|
|
110
|
+
const group = document.createElement("div");
|
|
111
|
+
group.className = "tree-group";
|
|
112
|
+
host.appendChild(group);
|
|
113
|
+
renderNode(node.dirs.get(dirName), group, `${prefix}${dirName}/`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const fileNames = [...node.files].sort((a, b) => a.localeCompare(b));
|
|
117
|
+
for (const filePath of fileNames) {
|
|
118
|
+
const button = document.createElement("button");
|
|
119
|
+
button.type = "button";
|
|
120
|
+
button.className = "tree-row";
|
|
121
|
+
button.textContent = filePath.slice(prefix.length);
|
|
122
|
+
button.title = filePath;
|
|
123
|
+
if (state.currentFile === filePath) {
|
|
124
|
+
button.classList.add("active");
|
|
125
|
+
}
|
|
126
|
+
button.addEventListener("click", () => {
|
|
127
|
+
openFile(filePath).catch((error) => {
|
|
128
|
+
logLine(`Не удалось открыть ${filePath}: ${error.message}`, "error");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
host.appendChild(button);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
renderNode(tree, nodes.fileTree);
|
|
136
|
+
nodes.filesCount.textContent = `${state.files.length} files`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function loadFiles() {
|
|
140
|
+
const payload = await fetchJSON("/api/files");
|
|
141
|
+
state.files = payload.files || [];
|
|
142
|
+
renderTree();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function openFile(path) {
|
|
146
|
+
if (!path) return;
|
|
147
|
+
const payload = await fetchJSON(`/api/file?path=${encodeURIComponent(path)}`);
|
|
148
|
+
state.currentFile = payload.path;
|
|
149
|
+
nodes.currentFileTitle.textContent = payload.path;
|
|
150
|
+
nodes.editor.value = payload.content || "";
|
|
151
|
+
updateLineNumbers();
|
|
152
|
+
setDirty(false);
|
|
153
|
+
renderTree();
|
|
154
|
+
logLine(`Открыт файл: ${payload.path}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function saveFile() {
|
|
158
|
+
if (!state.currentFile) {
|
|
159
|
+
logLine("Сначала открой файл", "warn");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
await fetchJSON("/api/file", {
|
|
163
|
+
method: "POST",
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
path: state.currentFile,
|
|
166
|
+
content: nodes.editor.value,
|
|
167
|
+
}),
|
|
168
|
+
});
|
|
169
|
+
setDirty(false);
|
|
170
|
+
logLine(`Сохранено: ${state.currentFile}`, "ok");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function formatFile() {
|
|
174
|
+
if (!state.currentFile) {
|
|
175
|
+
logLine("Сначала открой файл", "warn");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const payload = await fetchJSON("/api/format", {
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
path: state.currentFile,
|
|
182
|
+
content: nodes.editor.value,
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
nodes.editor.value = payload.content || "";
|
|
186
|
+
updateLineNumbers();
|
|
187
|
+
setDirty(false);
|
|
188
|
+
logLine(`Форматирование завершено: ${state.currentFile}`, "ok");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function createFile() {
|
|
192
|
+
const path = prompt("Новый файл (например: src/main.py)");
|
|
193
|
+
if (!path) return;
|
|
194
|
+
|
|
195
|
+
await fetchJSON("/api/file", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
body: JSON.stringify({ path, content: "" }),
|
|
198
|
+
});
|
|
199
|
+
await loadFiles();
|
|
200
|
+
await openFile(path);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function sendChatMessage(event) {
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
const message = nodes.chatInput.value.trim();
|
|
206
|
+
if (!message) return;
|
|
207
|
+
|
|
208
|
+
const agent = nodes.agentSelect.value;
|
|
209
|
+
const model = nodes.modelInput.value.trim();
|
|
210
|
+
const memoryFile = nodes.memoryInput.value.trim();
|
|
211
|
+
|
|
212
|
+
appendChatMessage("user", message);
|
|
213
|
+
nodes.chatInput.value = "";
|
|
214
|
+
nodes.chatState.textContent = "Думаю...";
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const payload = await fetchJSON("/api/chat", {
|
|
218
|
+
method: "POST",
|
|
219
|
+
body: JSON.stringify({
|
|
220
|
+
message,
|
|
221
|
+
agent,
|
|
222
|
+
model,
|
|
223
|
+
memory_file: memoryFile || undefined,
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
appendChatMessage("ai", payload.reply || "Пустой ответ");
|
|
227
|
+
nodes.chatState.textContent = "Ожидание";
|
|
228
|
+
logLine(`Ответ от агента ${payload.agent}`, "ok");
|
|
229
|
+
} catch (error) {
|
|
230
|
+
appendChatMessage("system", `Ошибка: ${error.message}`);
|
|
231
|
+
nodes.chatState.textContent = "Ошибка";
|
|
232
|
+
logLine(`Ошибка чата: ${error.message}`, "error");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function bindEvents() {
|
|
237
|
+
nodes.editor.addEventListener("input", () => {
|
|
238
|
+
setDirty(true);
|
|
239
|
+
updateLineNumbers();
|
|
240
|
+
});
|
|
241
|
+
nodes.editor.addEventListener("scroll", syncScroll);
|
|
242
|
+
|
|
243
|
+
nodes.saveBtn.addEventListener("click", () => {
|
|
244
|
+
saveFile().catch((error) => logLine(`Save error: ${error.message}`, "error"));
|
|
245
|
+
});
|
|
246
|
+
nodes.formatBtn.addEventListener("click", () => {
|
|
247
|
+
formatFile().catch((error) => logLine(`Format error: ${error.message}`, "error"));
|
|
248
|
+
});
|
|
249
|
+
nodes.newFileBtn.addEventListener("click", () => {
|
|
250
|
+
createFile().catch((error) => logLine(`Create error: ${error.message}`, "error"));
|
|
251
|
+
});
|
|
252
|
+
nodes.refreshTreeBtn.addEventListener("click", () => {
|
|
253
|
+
loadFiles().catch((error) => logLine(`Refresh error: ${error.message}`, "error"));
|
|
254
|
+
});
|
|
255
|
+
nodes.chatForm.addEventListener("submit", sendChatMessage);
|
|
256
|
+
|
|
257
|
+
window.addEventListener("keydown", (event) => {
|
|
258
|
+
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "s") {
|
|
259
|
+
event.preventDefault();
|
|
260
|
+
saveFile().catch((error) => logLine(`Save error: ${error.message}`, "error"));
|
|
261
|
+
}
|
|
262
|
+
if (
|
|
263
|
+
(event.ctrlKey || event.metaKey) &&
|
|
264
|
+
event.shiftKey &&
|
|
265
|
+
event.key.toLowerCase() === "f"
|
|
266
|
+
) {
|
|
267
|
+
event.preventDefault();
|
|
268
|
+
formatFile().catch((error) =>
|
|
269
|
+
logLine(`Format error: ${error.message}`, "error")
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function initialize() {
|
|
276
|
+
bindEvents();
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
state.config = await fetchJSON("/api/config");
|
|
280
|
+
nodes.projectName.textContent = state.config.root_name || "project";
|
|
281
|
+
nodes.modelInput.value = state.config.default_model || "";
|
|
282
|
+
nodes.memoryInput.value = state.config.default_memory_file || "";
|
|
283
|
+
|
|
284
|
+
for (const agent of state.config.agents || []) {
|
|
285
|
+
const option = document.createElement("option");
|
|
286
|
+
option.value = agent.name;
|
|
287
|
+
option.textContent = `${agent.name} - ${agent.title}`;
|
|
288
|
+
if (agent.name === state.config.default_agent) {
|
|
289
|
+
option.selected = true;
|
|
290
|
+
}
|
|
291
|
+
nodes.agentSelect.appendChild(option);
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
logLine(`Config error: ${error.message}`, "error");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
await loadFiles();
|
|
299
|
+
if (state.files.length > 0) {
|
|
300
|
+
const preferred =
|
|
301
|
+
state.files.find((path) => path.toLowerCase() === "readme.md") || state.files[0];
|
|
302
|
+
await openFile(preferred);
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
logLine(`Init error: ${error.message}`, "error");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
appendChatMessage("system", "IDE готов. Выбери агента и отправь запрос.");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
initialize();
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ru">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>revengelibrary IDE</title>
|
|
7
|
+
<link rel="stylesheet" href="/app.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="ambient">
|
|
11
|
+
<span class="orb orb-a"></span>
|
|
12
|
+
<span class="orb orb-b"></span>
|
|
13
|
+
<span class="orb orb-c"></span>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="shell">
|
|
17
|
+
<header class="topbar">
|
|
18
|
+
<div class="brand">
|
|
19
|
+
<div class="brand-mark">RL</div>
|
|
20
|
+
<div class="brand-copy">
|
|
21
|
+
<strong>revengelibrary IDE</strong>
|
|
22
|
+
<span id="projectName">project</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="top-actions">
|
|
26
|
+
<button id="refreshTreeBtn" class="btn ghost">Обновить файлы</button>
|
|
27
|
+
<button id="newFileBtn" class="btn ghost">Новый файл</button>
|
|
28
|
+
<button id="saveBtn" class="btn primary">Сохранить</button>
|
|
29
|
+
<button id="formatBtn" class="btn accent">Форматировать</button>
|
|
30
|
+
</div>
|
|
31
|
+
</header>
|
|
32
|
+
|
|
33
|
+
<main class="workspace">
|
|
34
|
+
<aside class="panel files-panel">
|
|
35
|
+
<div class="panel-head">
|
|
36
|
+
<h2>Файлы</h2>
|
|
37
|
+
<small id="filesCount">0</small>
|
|
38
|
+
</div>
|
|
39
|
+
<div id="fileTree" class="tree"></div>
|
|
40
|
+
</aside>
|
|
41
|
+
|
|
42
|
+
<section class="panel editor-panel">
|
|
43
|
+
<div class="panel-head">
|
|
44
|
+
<h2 id="currentFileTitle">Файл не выбран</h2>
|
|
45
|
+
<small id="saveState">Готово</small>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="editor-shell">
|
|
48
|
+
<pre id="lineNumbers" class="line-numbers">1</pre>
|
|
49
|
+
<textarea
|
|
50
|
+
id="editor"
|
|
51
|
+
spellcheck="false"
|
|
52
|
+
placeholder="Открой файл слева или создай новый"
|
|
53
|
+
></textarea>
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
|
|
57
|
+
<aside class="panel assistant-panel">
|
|
58
|
+
<div class="panel-head">
|
|
59
|
+
<h2>AI-ассистент</h2>
|
|
60
|
+
<small id="chatState">Ожидание</small>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="controls">
|
|
64
|
+
<label>
|
|
65
|
+
Агент
|
|
66
|
+
<select id="agentSelect"></select>
|
|
67
|
+
</label>
|
|
68
|
+
<label>
|
|
69
|
+
Модель
|
|
70
|
+
<input id="modelInput" type="text" />
|
|
71
|
+
</label>
|
|
72
|
+
<label>
|
|
73
|
+
Memory
|
|
74
|
+
<input id="memoryInput" type="text" />
|
|
75
|
+
</label>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div id="chatLog" class="chat-log"></div>
|
|
79
|
+
|
|
80
|
+
<form id="chatForm" class="chat-form">
|
|
81
|
+
<textarea
|
|
82
|
+
id="chatInput"
|
|
83
|
+
rows="3"
|
|
84
|
+
placeholder="Спроси по коду, архитектуре или дизайну..."
|
|
85
|
+
></textarea>
|
|
86
|
+
<button type="submit" class="btn primary">Отправить</button>
|
|
87
|
+
</form>
|
|
88
|
+
</aside>
|
|
89
|
+
</main>
|
|
90
|
+
|
|
91
|
+
<section class="panel terminal-panel">
|
|
92
|
+
<div class="panel-head">
|
|
93
|
+
<h2>Журнал</h2>
|
|
94
|
+
</div>
|
|
95
|
+
<pre id="terminal"></pre>
|
|
96
|
+
</section>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<script src="/app.js"></script>
|
|
100
|
+
</body>
|
|
101
|
+
</html>
|