stashes 0.1.12 → 0.1.13
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/cli.js
CHANGED
|
@@ -52,7 +52,7 @@ var DEFAULT_DIRECTIVES = [
|
|
|
52
52
|
];
|
|
53
53
|
// ../server/dist/routes/api.js
|
|
54
54
|
import { Hono } from "hono";
|
|
55
|
-
import { join } from "path";
|
|
55
|
+
import { join, basename } from "path";
|
|
56
56
|
import { existsSync, readFileSync } from "fs";
|
|
57
57
|
var app = new Hono;
|
|
58
58
|
app.get("/health", (c) => c.json({ status: "ok", service: "stashes" }));
|
|
@@ -84,14 +84,54 @@ app.get("/projects/:id", (c) => {
|
|
|
84
84
|
if (!project)
|
|
85
85
|
return c.json({ error: "Project not found" }, 404);
|
|
86
86
|
const stashes = persistence.listStashes(project.id);
|
|
87
|
-
const
|
|
88
|
-
return c.json({ data: { ...project, stashes,
|
|
87
|
+
const chats = persistence.listChats(project.id);
|
|
88
|
+
return c.json({ data: { ...project, stashes, chats } });
|
|
89
89
|
});
|
|
90
90
|
app.delete("/projects/:id", (c) => {
|
|
91
91
|
const id = c.req.param("id");
|
|
92
92
|
getPersistence().deleteProject(id);
|
|
93
93
|
return c.json({ data: { deleted: id } });
|
|
94
94
|
});
|
|
95
|
+
app.get("/chats", (c) => {
|
|
96
|
+
const persistence = getPersistence();
|
|
97
|
+
const project = ensureProject(persistence);
|
|
98
|
+
const chats = persistence.listChats(project.id);
|
|
99
|
+
const stashes = persistence.listStashes(project.id);
|
|
100
|
+
return c.json({ data: { project, chats, stashCount: stashes.length } });
|
|
101
|
+
});
|
|
102
|
+
app.post("/chats", async (c) => {
|
|
103
|
+
const persistence = getPersistence();
|
|
104
|
+
const project = ensureProject(persistence);
|
|
105
|
+
const { title } = await c.req.json();
|
|
106
|
+
const chatCount = persistence.listChats(project.id).length;
|
|
107
|
+
const chat = {
|
|
108
|
+
id: `chat_${crypto.randomUUID().substring(0, 8)}`,
|
|
109
|
+
projectId: project.id,
|
|
110
|
+
title: title?.trim() || `Chat ${chatCount + 1}`,
|
|
111
|
+
createdAt: new Date().toISOString(),
|
|
112
|
+
updatedAt: new Date().toISOString()
|
|
113
|
+
};
|
|
114
|
+
persistence.saveChat(chat);
|
|
115
|
+
return c.json({ data: chat }, 201);
|
|
116
|
+
});
|
|
117
|
+
app.get("/chats/:chatId", (c) => {
|
|
118
|
+
const persistence = getPersistence();
|
|
119
|
+
const project = ensureProject(persistence);
|
|
120
|
+
const chatId = c.req.param("chatId");
|
|
121
|
+
const chat = persistence.getChat(project.id, chatId);
|
|
122
|
+
if (!chat)
|
|
123
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
124
|
+
const messages = persistence.getChatMessages(project.id, chatId);
|
|
125
|
+
const stashes = persistence.listStashes(project.id).filter((s) => s.originChatId === chatId);
|
|
126
|
+
return c.json({ data: { ...chat, messages, stashes } });
|
|
127
|
+
});
|
|
128
|
+
app.delete("/chats/:chatId", (c) => {
|
|
129
|
+
const persistence = getPersistence();
|
|
130
|
+
const project = ensureProject(persistence);
|
|
131
|
+
const chatId = c.req.param("chatId");
|
|
132
|
+
persistence.deleteChat(project.id, chatId);
|
|
133
|
+
return c.json({ data: { deleted: chatId } });
|
|
134
|
+
});
|
|
95
135
|
app.get("/screenshots/:filename", (c) => {
|
|
96
136
|
const filename = c.req.param("filename");
|
|
97
137
|
const filePath = join(serverState.projectPath, ".stashes", "screenshots", filename);
|
|
@@ -102,6 +142,20 @@ app.get("/screenshots/:filename", (c) => {
|
|
|
102
142
|
headers: { "content-type": "image/png", "cache-control": "no-cache" }
|
|
103
143
|
});
|
|
104
144
|
});
|
|
145
|
+
function ensureProject(persistence) {
|
|
146
|
+
const projects = persistence.listProjects();
|
|
147
|
+
if (projects.length > 0)
|
|
148
|
+
return projects[0];
|
|
149
|
+
const project = {
|
|
150
|
+
id: `proj_${crypto.randomUUID().substring(0, 8)}`,
|
|
151
|
+
name: basename(serverState.projectPath),
|
|
152
|
+
createdAt: new Date().toISOString(),
|
|
153
|
+
updatedAt: new Date().toISOString()
|
|
154
|
+
};
|
|
155
|
+
persistence.saveProject(project);
|
|
156
|
+
persistence.migrateOldChat(project.id);
|
|
157
|
+
return project;
|
|
158
|
+
}
|
|
105
159
|
var apiRoutes = app;
|
|
106
160
|
|
|
107
161
|
// ../core/dist/generation.js
|
|
@@ -411,7 +465,7 @@ class WorktreeManager {
|
|
|
411
465
|
}
|
|
412
466
|
|
|
413
467
|
// ../core/dist/persistence.js
|
|
414
|
-
import { readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync4, rmSync as rmSync2 } from "fs";
|
|
468
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync4, rmSync as rmSync2, readdirSync } from "fs";
|
|
415
469
|
import { join as join4, dirname } from "path";
|
|
416
470
|
var STASHES_DIR = ".stashes";
|
|
417
471
|
function ensureDir(dirPath) {
|
|
@@ -488,14 +542,64 @@ class PersistenceService {
|
|
|
488
542
|
const filePath = join4(this.basePath, "projects", projectId, "stashes.json");
|
|
489
543
|
writeJson(filePath, stashes);
|
|
490
544
|
}
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
545
|
+
listChats(projectId) {
|
|
546
|
+
const dir = join4(this.basePath, "projects", projectId, "chats");
|
|
547
|
+
if (!existsSync4(dir))
|
|
548
|
+
return [];
|
|
549
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
550
|
+
return files.map((f) => {
|
|
551
|
+
const data = readJson(join4(dir, f), null);
|
|
552
|
+
return data?.chat;
|
|
553
|
+
}).filter(Boolean).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
554
|
+
}
|
|
555
|
+
getChat(projectId, chatId) {
|
|
556
|
+
const filePath = join4(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
557
|
+
const data = readJson(filePath, null);
|
|
558
|
+
return data?.chat;
|
|
559
|
+
}
|
|
560
|
+
saveChat(chat) {
|
|
561
|
+
const filePath = join4(this.basePath, "projects", chat.projectId, "chats", `${chat.id}.json`);
|
|
562
|
+
const existing = readJson(filePath, { chat, messages: [] });
|
|
563
|
+
writeJson(filePath, { ...existing, chat });
|
|
564
|
+
}
|
|
565
|
+
deleteChat(projectId, chatId) {
|
|
566
|
+
const filePath = join4(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
567
|
+
if (existsSync4(filePath)) {
|
|
568
|
+
rmSync2(filePath);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
getChatMessages(projectId, chatId) {
|
|
572
|
+
const filePath = join4(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
573
|
+
const data = readJson(filePath, { messages: [] });
|
|
574
|
+
return data.messages;
|
|
575
|
+
}
|
|
576
|
+
saveChatMessage(projectId, chatId, message) {
|
|
577
|
+
const filePath = join4(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
578
|
+
const data = readJson(filePath, { chat: this.getChat(projectId, chatId), messages: [] });
|
|
579
|
+
writeJson(filePath, { ...data, messages: [...data.messages, message] });
|
|
580
|
+
}
|
|
581
|
+
migrateOldChat(projectId) {
|
|
582
|
+
const oldPath = join4(this.basePath, "projects", projectId, "chat.json");
|
|
583
|
+
if (!existsSync4(oldPath))
|
|
584
|
+
return null;
|
|
585
|
+
const messages = readJson(oldPath, []);
|
|
586
|
+
if (messages.length === 0) {
|
|
587
|
+
rmSync2(oldPath);
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
const chatId = `chat_${crypto.randomUUID().substring(0, 8)}`;
|
|
591
|
+
const chat = {
|
|
592
|
+
id: chatId,
|
|
593
|
+
projectId,
|
|
594
|
+
title: "Initial conversation",
|
|
595
|
+
createdAt: messages[0].createdAt,
|
|
596
|
+
updatedAt: messages[messages.length - 1].createdAt
|
|
597
|
+
};
|
|
598
|
+
const filePath = join4(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
599
|
+
writeJson(filePath, { chat, messages });
|
|
600
|
+
rmSync2(oldPath);
|
|
601
|
+
logger.info("persistence", `migrated old chat.json \u2192 ${chatId}`);
|
|
602
|
+
return chatId;
|
|
499
603
|
}
|
|
500
604
|
ensureGitignore(projectPath) {
|
|
501
605
|
const gitignorePath = join4(projectPath, ".gitignore");
|
|
@@ -721,7 +825,7 @@ async function allocatePort() {
|
|
|
721
825
|
throw new Error("No available ports in range 4010-4030");
|
|
722
826
|
}
|
|
723
827
|
async function generate(opts) {
|
|
724
|
-
const { projectPath, projectId, prompt, component, count = DEFAULT_STASH_COUNT, directives = DEFAULT_DIRECTIVES, onProgress } = opts;
|
|
828
|
+
const { projectPath, projectId, chatId, prompt, component, count = DEFAULT_STASH_COUNT, directives = DEFAULT_DIRECTIVES, onProgress } = opts;
|
|
725
829
|
const worktreeManager = new WorktreeManager(projectPath);
|
|
726
830
|
const persistence = new PersistenceService(projectPath);
|
|
727
831
|
const selectedDirectives = directives.slice(0, count);
|
|
@@ -743,6 +847,7 @@ async function generate(opts) {
|
|
|
743
847
|
id: stashId,
|
|
744
848
|
number: stashNumber,
|
|
745
849
|
projectId,
|
|
850
|
+
originChatId: chatId,
|
|
746
851
|
prompt,
|
|
747
852
|
componentPath: component?.filePath,
|
|
748
853
|
branch: worktree.branch,
|
|
@@ -844,7 +949,7 @@ async function allocatePort2() {
|
|
|
844
949
|
throw new Error("No available ports in range 4010-4030");
|
|
845
950
|
}
|
|
846
951
|
async function vary(opts) {
|
|
847
|
-
const { projectPath, sourceStashId, prompt, onProgress } = opts;
|
|
952
|
+
const { projectPath, sourceStashId, chatId, prompt, onProgress } = opts;
|
|
848
953
|
const persistence = new PersistenceService(projectPath);
|
|
849
954
|
const worktreeManager = new WorktreeManager(projectPath);
|
|
850
955
|
let sourceStash;
|
|
@@ -867,6 +972,7 @@ async function vary(opts) {
|
|
|
867
972
|
id: stashId,
|
|
868
973
|
number: stashNumber,
|
|
869
974
|
projectId,
|
|
975
|
+
originChatId: chatId,
|
|
870
976
|
prompt,
|
|
871
977
|
componentPath: sourceStash.componentPath,
|
|
872
978
|
branch: worktree.branch,
|
|
@@ -1224,7 +1330,7 @@ class StashService {
|
|
|
1224
1330
|
});
|
|
1225
1331
|
}
|
|
1226
1332
|
}
|
|
1227
|
-
async message(projectId, message, referenceStashIds, componentContext) {
|
|
1333
|
+
async message(projectId, chatId, message, referenceStashIds, componentContext) {
|
|
1228
1334
|
const component = componentContext ? { name: componentContext.name, filePath: this.selectedComponent?.filePath || "" } : this.selectedComponent;
|
|
1229
1335
|
let sourceCode = "";
|
|
1230
1336
|
const filePath = component?.filePath || "";
|
|
@@ -1322,7 +1428,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
1322
1428
|
}
|
|
1323
1429
|
await aiProcess.process.exited;
|
|
1324
1430
|
if (fullResponse) {
|
|
1325
|
-
this.persistence.saveChatMessage(projectId, {
|
|
1431
|
+
this.persistence.saveChatMessage(projectId, chatId, {
|
|
1326
1432
|
id: crypto.randomUUID(),
|
|
1327
1433
|
role: "assistant",
|
|
1328
1434
|
content: fullResponse,
|
|
@@ -1438,7 +1544,14 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
|
1438
1544
|
open(ws) {
|
|
1439
1545
|
clients.add(ws);
|
|
1440
1546
|
logger.info("ws", "client connected", { total: clients.size });
|
|
1441
|
-
|
|
1547
|
+
const project = ensureProject(persistence);
|
|
1548
|
+
ws.send(JSON.stringify({
|
|
1549
|
+
type: "server_ready",
|
|
1550
|
+
port: userDevPort,
|
|
1551
|
+
appProxyPort,
|
|
1552
|
+
projectId: project.id,
|
|
1553
|
+
projectName: project.name
|
|
1554
|
+
}));
|
|
1442
1555
|
},
|
|
1443
1556
|
async message(ws, message) {
|
|
1444
1557
|
const raw = typeof message === "string" ? message : new TextDecoder().decode(message);
|
|
@@ -1458,7 +1571,7 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
|
1458
1571
|
stashService.setSelectedComponent(event.component);
|
|
1459
1572
|
break;
|
|
1460
1573
|
case "message":
|
|
1461
|
-
persistence.saveChatMessage(event.projectId, {
|
|
1574
|
+
persistence.saveChatMessage(event.projectId, event.chatId, {
|
|
1462
1575
|
id: crypto.randomUUID(),
|
|
1463
1576
|
role: "user",
|
|
1464
1577
|
content: event.message,
|
|
@@ -1467,7 +1580,7 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
|
1467
1580
|
referenceStashIds: event.referenceStashIds,
|
|
1468
1581
|
componentContext: event.componentContext
|
|
1469
1582
|
});
|
|
1470
|
-
await stashService.message(event.projectId, event.message, event.referenceStashIds, event.componentContext);
|
|
1583
|
+
await stashService.message(event.projectId, event.chatId, event.message, event.referenceStashIds, event.componentContext);
|
|
1471
1584
|
break;
|
|
1472
1585
|
case "interact":
|
|
1473
1586
|
await stashService.switchPreview(event.stashId, event.sortedStashIds);
|
package/dist/mcp.js
CHANGED
|
@@ -347,7 +347,7 @@ class WorktreeManager {
|
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
// ../core/dist/persistence.js
|
|
350
|
-
import { readFileSync, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync3, rmSync as rmSync2 } from "fs";
|
|
350
|
+
import { readFileSync, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync3, rmSync as rmSync2, readdirSync } from "fs";
|
|
351
351
|
import { join as join3, dirname } from "path";
|
|
352
352
|
var STASHES_DIR = ".stashes";
|
|
353
353
|
function ensureDir(dirPath) {
|
|
@@ -424,14 +424,64 @@ class PersistenceService {
|
|
|
424
424
|
const filePath = join3(this.basePath, "projects", projectId, "stashes.json");
|
|
425
425
|
writeJson(filePath, stashes);
|
|
426
426
|
}
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
427
|
+
listChats(projectId) {
|
|
428
|
+
const dir = join3(this.basePath, "projects", projectId, "chats");
|
|
429
|
+
if (!existsSync3(dir))
|
|
430
|
+
return [];
|
|
431
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
432
|
+
return files.map((f) => {
|
|
433
|
+
const data = readJson(join3(dir, f), null);
|
|
434
|
+
return data?.chat;
|
|
435
|
+
}).filter(Boolean).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
436
|
+
}
|
|
437
|
+
getChat(projectId, chatId) {
|
|
438
|
+
const filePath = join3(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
439
|
+
const data = readJson(filePath, null);
|
|
440
|
+
return data?.chat;
|
|
441
|
+
}
|
|
442
|
+
saveChat(chat) {
|
|
443
|
+
const filePath = join3(this.basePath, "projects", chat.projectId, "chats", `${chat.id}.json`);
|
|
444
|
+
const existing = readJson(filePath, { chat, messages: [] });
|
|
445
|
+
writeJson(filePath, { ...existing, chat });
|
|
446
|
+
}
|
|
447
|
+
deleteChat(projectId, chatId) {
|
|
448
|
+
const filePath = join3(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
449
|
+
if (existsSync3(filePath)) {
|
|
450
|
+
rmSync2(filePath);
|
|
451
|
+
}
|
|
430
452
|
}
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
|
|
453
|
+
getChatMessages(projectId, chatId) {
|
|
454
|
+
const filePath = join3(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
455
|
+
const data = readJson(filePath, { messages: [] });
|
|
456
|
+
return data.messages;
|
|
457
|
+
}
|
|
458
|
+
saveChatMessage(projectId, chatId, message) {
|
|
459
|
+
const filePath = join3(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
460
|
+
const data = readJson(filePath, { chat: this.getChat(projectId, chatId), messages: [] });
|
|
461
|
+
writeJson(filePath, { ...data, messages: [...data.messages, message] });
|
|
462
|
+
}
|
|
463
|
+
migrateOldChat(projectId) {
|
|
464
|
+
const oldPath = join3(this.basePath, "projects", projectId, "chat.json");
|
|
465
|
+
if (!existsSync3(oldPath))
|
|
466
|
+
return null;
|
|
467
|
+
const messages = readJson(oldPath, []);
|
|
468
|
+
if (messages.length === 0) {
|
|
469
|
+
rmSync2(oldPath);
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
const chatId = `chat_${crypto.randomUUID().substring(0, 8)}`;
|
|
473
|
+
const chat = {
|
|
474
|
+
id: chatId,
|
|
475
|
+
projectId,
|
|
476
|
+
title: "Initial conversation",
|
|
477
|
+
createdAt: messages[0].createdAt,
|
|
478
|
+
updatedAt: messages[messages.length - 1].createdAt
|
|
479
|
+
};
|
|
480
|
+
const filePath = join3(this.basePath, "projects", projectId, "chats", `${chatId}.json`);
|
|
481
|
+
writeJson(filePath, { chat, messages });
|
|
482
|
+
rmSync2(oldPath);
|
|
483
|
+
logger.info("persistence", `migrated old chat.json \u2192 ${chatId}`);
|
|
484
|
+
return chatId;
|
|
435
485
|
}
|
|
436
486
|
ensureGitignore(projectPath) {
|
|
437
487
|
const gitignorePath = join3(projectPath, ".gitignore");
|
|
@@ -657,7 +707,7 @@ async function allocatePort() {
|
|
|
657
707
|
throw new Error("No available ports in range 4010-4030");
|
|
658
708
|
}
|
|
659
709
|
async function generate(opts) {
|
|
660
|
-
const { projectPath, projectId, prompt, component, count = DEFAULT_STASH_COUNT, directives = DEFAULT_DIRECTIVES, onProgress } = opts;
|
|
710
|
+
const { projectPath, projectId, chatId, prompt, component, count = DEFAULT_STASH_COUNT, directives = DEFAULT_DIRECTIVES, onProgress } = opts;
|
|
661
711
|
const worktreeManager = new WorktreeManager(projectPath);
|
|
662
712
|
const persistence = new PersistenceService(projectPath);
|
|
663
713
|
const selectedDirectives = directives.slice(0, count);
|
|
@@ -679,6 +729,7 @@ async function generate(opts) {
|
|
|
679
729
|
id: stashId,
|
|
680
730
|
number: stashNumber,
|
|
681
731
|
projectId,
|
|
732
|
+
originChatId: chatId,
|
|
682
733
|
prompt,
|
|
683
734
|
componentPath: component?.filePath,
|
|
684
735
|
branch: worktree.branch,
|
|
@@ -780,7 +831,7 @@ async function allocatePort2() {
|
|
|
780
831
|
throw new Error("No available ports in range 4010-4030");
|
|
781
832
|
}
|
|
782
833
|
async function vary(opts) {
|
|
783
|
-
const { projectPath, sourceStashId, prompt, onProgress } = opts;
|
|
834
|
+
const { projectPath, sourceStashId, chatId, prompt, onProgress } = opts;
|
|
784
835
|
const persistence = new PersistenceService(projectPath);
|
|
785
836
|
const worktreeManager = new WorktreeManager(projectPath);
|
|
786
837
|
let sourceStash;
|
|
@@ -803,6 +854,7 @@ async function vary(opts) {
|
|
|
803
854
|
id: stashId,
|
|
804
855
|
number: stashNumber,
|
|
805
856
|
projectId,
|
|
857
|
+
originChatId: chatId,
|
|
806
858
|
prompt,
|
|
807
859
|
componentPath: sourceStash.componentPath,
|
|
808
860
|
branch: worktree.branch,
|
|
@@ -1059,7 +1111,7 @@ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
|
1059
1111
|
|
|
1060
1112
|
// ../server/dist/routes/api.js
|
|
1061
1113
|
import { Hono } from "hono";
|
|
1062
|
-
import { join as join6 } from "path";
|
|
1114
|
+
import { join as join6, basename } from "path";
|
|
1063
1115
|
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
1064
1116
|
var app = new Hono;
|
|
1065
1117
|
app.get("/health", (c) => c.json({ status: "ok", service: "stashes" }));
|
|
@@ -1091,14 +1143,54 @@ app.get("/projects/:id", (c) => {
|
|
|
1091
1143
|
if (!project)
|
|
1092
1144
|
return c.json({ error: "Project not found" }, 404);
|
|
1093
1145
|
const stashes = persistence.listStashes(project.id);
|
|
1094
|
-
const
|
|
1095
|
-
return c.json({ data: { ...project, stashes,
|
|
1146
|
+
const chats = persistence.listChats(project.id);
|
|
1147
|
+
return c.json({ data: { ...project, stashes, chats } });
|
|
1096
1148
|
});
|
|
1097
1149
|
app.delete("/projects/:id", (c) => {
|
|
1098
1150
|
const id = c.req.param("id");
|
|
1099
1151
|
getPersistence().deleteProject(id);
|
|
1100
1152
|
return c.json({ data: { deleted: id } });
|
|
1101
1153
|
});
|
|
1154
|
+
app.get("/chats", (c) => {
|
|
1155
|
+
const persistence = getPersistence();
|
|
1156
|
+
const project = ensureProject(persistence);
|
|
1157
|
+
const chats = persistence.listChats(project.id);
|
|
1158
|
+
const stashes = persistence.listStashes(project.id);
|
|
1159
|
+
return c.json({ data: { project, chats, stashCount: stashes.length } });
|
|
1160
|
+
});
|
|
1161
|
+
app.post("/chats", async (c) => {
|
|
1162
|
+
const persistence = getPersistence();
|
|
1163
|
+
const project = ensureProject(persistence);
|
|
1164
|
+
const { title } = await c.req.json();
|
|
1165
|
+
const chatCount = persistence.listChats(project.id).length;
|
|
1166
|
+
const chat = {
|
|
1167
|
+
id: `chat_${crypto.randomUUID().substring(0, 8)}`,
|
|
1168
|
+
projectId: project.id,
|
|
1169
|
+
title: title?.trim() || `Chat ${chatCount + 1}`,
|
|
1170
|
+
createdAt: new Date().toISOString(),
|
|
1171
|
+
updatedAt: new Date().toISOString()
|
|
1172
|
+
};
|
|
1173
|
+
persistence.saveChat(chat);
|
|
1174
|
+
return c.json({ data: chat }, 201);
|
|
1175
|
+
});
|
|
1176
|
+
app.get("/chats/:chatId", (c) => {
|
|
1177
|
+
const persistence = getPersistence();
|
|
1178
|
+
const project = ensureProject(persistence);
|
|
1179
|
+
const chatId = c.req.param("chatId");
|
|
1180
|
+
const chat = persistence.getChat(project.id, chatId);
|
|
1181
|
+
if (!chat)
|
|
1182
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
1183
|
+
const messages = persistence.getChatMessages(project.id, chatId);
|
|
1184
|
+
const stashes = persistence.listStashes(project.id).filter((s) => s.originChatId === chatId);
|
|
1185
|
+
return c.json({ data: { ...chat, messages, stashes } });
|
|
1186
|
+
});
|
|
1187
|
+
app.delete("/chats/:chatId", (c) => {
|
|
1188
|
+
const persistence = getPersistence();
|
|
1189
|
+
const project = ensureProject(persistence);
|
|
1190
|
+
const chatId = c.req.param("chatId");
|
|
1191
|
+
persistence.deleteChat(project.id, chatId);
|
|
1192
|
+
return c.json({ data: { deleted: chatId } });
|
|
1193
|
+
});
|
|
1102
1194
|
app.get("/screenshots/:filename", (c) => {
|
|
1103
1195
|
const filename = c.req.param("filename");
|
|
1104
1196
|
const filePath = join6(serverState.projectPath, ".stashes", "screenshots", filename);
|
|
@@ -1109,6 +1201,20 @@ app.get("/screenshots/:filename", (c) => {
|
|
|
1109
1201
|
headers: { "content-type": "image/png", "cache-control": "no-cache" }
|
|
1110
1202
|
});
|
|
1111
1203
|
});
|
|
1204
|
+
function ensureProject(persistence) {
|
|
1205
|
+
const projects = persistence.listProjects();
|
|
1206
|
+
if (projects.length > 0)
|
|
1207
|
+
return projects[0];
|
|
1208
|
+
const project = {
|
|
1209
|
+
id: `proj_${crypto.randomUUID().substring(0, 8)}`,
|
|
1210
|
+
name: basename(serverState.projectPath),
|
|
1211
|
+
createdAt: new Date().toISOString(),
|
|
1212
|
+
updatedAt: new Date().toISOString()
|
|
1213
|
+
};
|
|
1214
|
+
persistence.saveProject(project);
|
|
1215
|
+
persistence.migrateOldChat(project.id);
|
|
1216
|
+
return project;
|
|
1217
|
+
}
|
|
1112
1218
|
var apiRoutes = app;
|
|
1113
1219
|
|
|
1114
1220
|
// ../server/dist/services/stash-service.js
|
|
@@ -1354,7 +1460,7 @@ class StashService {
|
|
|
1354
1460
|
});
|
|
1355
1461
|
}
|
|
1356
1462
|
}
|
|
1357
|
-
async message(projectId, message, referenceStashIds, componentContext) {
|
|
1463
|
+
async message(projectId, chatId, message, referenceStashIds, componentContext) {
|
|
1358
1464
|
const component = componentContext ? { name: componentContext.name, filePath: this.selectedComponent?.filePath || "" } : this.selectedComponent;
|
|
1359
1465
|
let sourceCode = "";
|
|
1360
1466
|
const filePath = component?.filePath || "";
|
|
@@ -1452,7 +1558,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
1452
1558
|
}
|
|
1453
1559
|
await aiProcess.process.exited;
|
|
1454
1560
|
if (fullResponse) {
|
|
1455
|
-
this.persistence.saveChatMessage(projectId, {
|
|
1561
|
+
this.persistence.saveChatMessage(projectId, chatId, {
|
|
1456
1562
|
id: crypto.randomUUID(),
|
|
1457
1563
|
role: "assistant",
|
|
1458
1564
|
content: fullResponse,
|
|
@@ -1568,7 +1674,14 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
|
1568
1674
|
open(ws) {
|
|
1569
1675
|
clients.add(ws);
|
|
1570
1676
|
logger.info("ws", "client connected", { total: clients.size });
|
|
1571
|
-
|
|
1677
|
+
const project = ensureProject(persistence);
|
|
1678
|
+
ws.send(JSON.stringify({
|
|
1679
|
+
type: "server_ready",
|
|
1680
|
+
port: userDevPort,
|
|
1681
|
+
appProxyPort,
|
|
1682
|
+
projectId: project.id,
|
|
1683
|
+
projectName: project.name
|
|
1684
|
+
}));
|
|
1572
1685
|
},
|
|
1573
1686
|
async message(ws, message) {
|
|
1574
1687
|
const raw = typeof message === "string" ? message : new TextDecoder().decode(message);
|
|
@@ -1588,7 +1701,7 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
|
1588
1701
|
stashService.setSelectedComponent(event.component);
|
|
1589
1702
|
break;
|
|
1590
1703
|
case "message":
|
|
1591
|
-
persistence.saveChatMessage(event.projectId, {
|
|
1704
|
+
persistence.saveChatMessage(event.projectId, event.chatId, {
|
|
1592
1705
|
id: crypto.randomUUID(),
|
|
1593
1706
|
role: "user",
|
|
1594
1707
|
content: event.message,
|
|
@@ -1597,7 +1710,7 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
|
1597
1710
|
referenceStashIds: event.referenceStashIds,
|
|
1598
1711
|
componentContext: event.componentContext
|
|
1599
1712
|
});
|
|
1600
|
-
await stashService.message(event.projectId, event.message, event.referenceStashIds, event.componentContext);
|
|
1713
|
+
await stashService.message(event.projectId, event.chatId, event.message, event.referenceStashIds, event.componentContext);
|
|
1601
1714
|
break;
|
|
1602
1715
|
case "interact":
|
|
1603
1716
|
await stashService.switchPreview(event.stashId, event.sortedStashIds);
|