claude-session-viewer 0.1.0 → 0.1.1
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/bin/cli.js +9 -2
- package/bin/dev.js +2 -1
- package/dist/client/assets/index-BRGpp7Nq.js +40 -0
- package/dist/client/assets/index-bRG2avxz.css +1 -0
- package/{index.html → dist/client/index.html} +2 -1
- package/dist/server/index.js +364 -0
- package/package.json +14 -3
- package/postcss.config.js +0 -6
- package/src/App.tsx +0 -174
- package/src/components/ProjectGroup.tsx +0 -132
- package/src/components/SessionDetail.tsx +0 -140
- package/src/components/SessionList.tsx +0 -45
- package/src/index.css +0 -55
- package/src/main.tsx +0 -10
- package/src/server/index.ts +0 -440
- package/tailwind.config.js +0 -11
- package/tsconfig.json +0 -31
- package/tsconfig.node.json +0 -10
- package/vite.config.ts +0 -32
package/src/server/index.ts
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
import Fastify from 'fastify'
|
|
2
|
-
import cors from '@fastify/cors'
|
|
3
|
-
import websocket from '@fastify/websocket'
|
|
4
|
-
import { homedir } from 'os'
|
|
5
|
-
import { join } from 'path'
|
|
6
|
-
import { readdir, readFile, stat } from 'fs/promises'
|
|
7
|
-
import chokidar from 'chokidar'
|
|
8
|
-
import getPort from 'get-port'
|
|
9
|
-
|
|
10
|
-
const CLAUDE_DIR = join(homedir(), '.claude')
|
|
11
|
-
const DEFAULT_PORT = 3000
|
|
12
|
-
|
|
13
|
-
const server = Fastify({
|
|
14
|
-
logger: true
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
// Plugins
|
|
18
|
-
await server.register(cors, {
|
|
19
|
-
origin: 'http://localhost:5173'
|
|
20
|
-
})
|
|
21
|
-
await server.register(websocket)
|
|
22
|
-
|
|
23
|
-
// Types
|
|
24
|
-
interface Session {
|
|
25
|
-
id: string
|
|
26
|
-
project: string
|
|
27
|
-
timestamp: string
|
|
28
|
-
messages: any[]
|
|
29
|
-
messageCount: number
|
|
30
|
-
title?: string
|
|
31
|
-
isAgent?: boolean
|
|
32
|
-
agentSessions?: Session[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface ProjectGroup {
|
|
36
|
-
name: string
|
|
37
|
-
displayName: string
|
|
38
|
-
sessionCount: number
|
|
39
|
-
lastActivity: string
|
|
40
|
-
sessions: Session[]
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Helper: Parse JSONL file
|
|
44
|
-
async function parseJsonl(filePath: string): Promise<any[]> {
|
|
45
|
-
const content = await readFile(filePath, 'utf-8')
|
|
46
|
-
return content
|
|
47
|
-
.split('\n')
|
|
48
|
-
.filter(line => line.trim())
|
|
49
|
-
.map(line => JSON.parse(line))
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Helper: Clean text by removing tags
|
|
53
|
-
function cleanText(text: string): string {
|
|
54
|
-
return text
|
|
55
|
-
.replace(/<ide_selection>[\s\S]*?<\/ide_selection>/g, ' ')
|
|
56
|
-
.replace(/<ide_opened_file>[\s\S]*?<\/ide_opened_file>/g, ' ')
|
|
57
|
-
.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, ' ')
|
|
58
|
-
.replace(/\s+/g, ' ')
|
|
59
|
-
.trim()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function extractFirstText(content: any): string | null {
|
|
63
|
-
if (Array.isArray(content)) {
|
|
64
|
-
for (const item of content) {
|
|
65
|
-
if (item.type === 'text' && item.text) {
|
|
66
|
-
const cleaned = cleanText(item.text)
|
|
67
|
-
if (cleaned) {
|
|
68
|
-
return cleaned
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return null
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (typeof content === 'string') {
|
|
76
|
-
const cleaned = cleanText(content)
|
|
77
|
-
return cleaned || null
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return null
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Helper: Extract title from session messages
|
|
84
|
-
function extractSessionTitle(messages: any[]): string {
|
|
85
|
-
// First, try to find queue-operation / enqueue message
|
|
86
|
-
for (const msg of messages) {
|
|
87
|
-
if (msg.type === 'queue-operation' && msg.operation === 'enqueue' && msg.content) {
|
|
88
|
-
const firstText = extractFirstText(msg.content)
|
|
89
|
-
if (firstText) {
|
|
90
|
-
return firstText.substring(0, 100).trim()
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Fallback: Find first user message with actual text content
|
|
96
|
-
for (const msg of messages) {
|
|
97
|
-
if (msg.type === 'user' && msg.message?.content) {
|
|
98
|
-
const firstText = extractFirstText(msg.message.content)
|
|
99
|
-
if (firstText) {
|
|
100
|
-
return firstText.substring(0, 100).trim()
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return 'Untitled Session'
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function getProjectNameFromPath(projectPath: string): string {
|
|
109
|
-
return projectPath.split('/').pop()?.replace(/-Users-hanyeol-Projects-/, '') || 'unknown'
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function getProjectDisplayName(projectName: string): string {
|
|
113
|
-
return projectName.replace(/-Users-hanyeol-Projects-/, '')
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function collectAgentDescriptions(messages: any[]): Map<string, string> {
|
|
117
|
-
const agentDescriptions = new Map<string, string>()
|
|
118
|
-
const toolUseDescriptions = new Map<string, string>()
|
|
119
|
-
const toolResultAgentIds = new Map<string, string>()
|
|
120
|
-
|
|
121
|
-
for (const msg of messages) {
|
|
122
|
-
if (msg.type === 'assistant' && msg.message?.content && Array.isArray(msg.message.content)) {
|
|
123
|
-
for (const item of msg.message.content) {
|
|
124
|
-
if (item.type === 'tool_use' && item.name === 'Task' && item.input?.description) {
|
|
125
|
-
toolUseDescriptions.set(item.id, item.input.description)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const agentId = msg.agentId || msg.toolUseResult?.agentId
|
|
131
|
-
if (agentId && msg.message?.content && Array.isArray(msg.message.content)) {
|
|
132
|
-
for (const item of msg.message.content) {
|
|
133
|
-
if (item.type === 'tool_result' && item.tool_use_id) {
|
|
134
|
-
toolResultAgentIds.set(item.tool_use_id, agentId)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
for (const [toolUseId, description] of toolUseDescriptions.entries()) {
|
|
141
|
-
const agentId = toolResultAgentIds.get(toolUseId)
|
|
142
|
-
if (agentId) {
|
|
143
|
-
agentDescriptions.set(`agent-${agentId}`, description)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return agentDescriptions
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function attachAgentSessionsFromMap(
|
|
151
|
-
session: Session,
|
|
152
|
-
agentDescriptions: Map<string, string>,
|
|
153
|
-
agentSessionsMap: Map<string, Session>
|
|
154
|
-
): void {
|
|
155
|
-
if (agentDescriptions.size === 0) return
|
|
156
|
-
|
|
157
|
-
session.agentSessions = []
|
|
158
|
-
for (const [agentSessionId, description] of agentDescriptions) {
|
|
159
|
-
const agentSession = agentSessionsMap.get(agentSessionId)
|
|
160
|
-
if (agentSession) {
|
|
161
|
-
agentSession.title = description
|
|
162
|
-
session.agentSessions.push(agentSession)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
session.agentSessions.sort((a, b) =>
|
|
166
|
-
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function loadAgentSessionsFromFiles(
|
|
171
|
-
projectPath: string,
|
|
172
|
-
projectName: string,
|
|
173
|
-
agentDescriptions: Map<string, string>
|
|
174
|
-
): Promise<Session[]> {
|
|
175
|
-
const agentSessions: Session[] = []
|
|
176
|
-
|
|
177
|
-
for (const [agentSessionId, description] of agentDescriptions) {
|
|
178
|
-
const agentFile = join(projectPath, `${agentSessionId}.jsonl`)
|
|
179
|
-
try {
|
|
180
|
-
const agentMessages = await parseJsonl(agentFile)
|
|
181
|
-
const agentFileStat = await stat(agentFile)
|
|
182
|
-
agentSessions.push({
|
|
183
|
-
id: agentSessionId,
|
|
184
|
-
project: projectName,
|
|
185
|
-
timestamp: agentFileStat.mtime.toISOString(),
|
|
186
|
-
messages: agentMessages,
|
|
187
|
-
messageCount: agentMessages.length,
|
|
188
|
-
title: description,
|
|
189
|
-
isAgent: true
|
|
190
|
-
})
|
|
191
|
-
} catch {
|
|
192
|
-
// Skip if agent file not found
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
agentSessions.sort((a, b) =>
|
|
197
|
-
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
198
|
-
)
|
|
199
|
-
return agentSessions
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function findAgentTitleFromParentMessages(messages: any[], agentId: string): string | null {
|
|
203
|
-
const agentDescriptions = collectAgentDescriptions(messages)
|
|
204
|
-
const description = agentDescriptions.get(`agent-${agentId}`)
|
|
205
|
-
return description || null
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Helper: Get all sessions from a project directory
|
|
209
|
-
async function getProjectSessions(projectPath: string): Promise<Session[]> {
|
|
210
|
-
const files = await readdir(projectPath)
|
|
211
|
-
const allSessions: Session[] = []
|
|
212
|
-
const agentSessionsMap = new Map<string, Session>()
|
|
213
|
-
|
|
214
|
-
// First pass: collect all sessions
|
|
215
|
-
for (const file of files) {
|
|
216
|
-
if (file.endsWith('.jsonl')) {
|
|
217
|
-
const filePath = join(projectPath, file)
|
|
218
|
-
const fileStat = await stat(filePath)
|
|
219
|
-
|
|
220
|
-
// Skip empty files
|
|
221
|
-
if (fileStat.size === 0) continue
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
const messages = await parseJsonl(filePath)
|
|
225
|
-
|
|
226
|
-
// Filter: Skip sessions with only 1 message that is assistant-only
|
|
227
|
-
if (messages.length === 1 && messages[0].type === 'assistant') {
|
|
228
|
-
continue
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Extract project name from path
|
|
232
|
-
const projectName = getProjectNameFromPath(projectPath)
|
|
233
|
-
|
|
234
|
-
// Extract session title
|
|
235
|
-
const title = extractSessionTitle(messages)
|
|
236
|
-
|
|
237
|
-
const sessionId = file.replace('.jsonl', '')
|
|
238
|
-
const isAgent = sessionId.startsWith('agent-')
|
|
239
|
-
|
|
240
|
-
const session: Session = {
|
|
241
|
-
id: sessionId,
|
|
242
|
-
project: projectName,
|
|
243
|
-
timestamp: fileStat.mtime.toISOString(),
|
|
244
|
-
messages,
|
|
245
|
-
messageCount: messages.length,
|
|
246
|
-
title,
|
|
247
|
-
isAgent
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (isAgent) {
|
|
251
|
-
agentSessionsMap.set(sessionId, session)
|
|
252
|
-
} else {
|
|
253
|
-
allSessions.push(session)
|
|
254
|
-
}
|
|
255
|
-
} catch (error) {
|
|
256
|
-
console.error(`Error parsing ${file}:`, error)
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Second pass: attach agent sessions to their parent sessions
|
|
262
|
-
for (const session of allSessions) {
|
|
263
|
-
const agentDescriptions = collectAgentDescriptions(session.messages)
|
|
264
|
-
attachAgentSessionsFromMap(session, agentDescriptions, agentSessionsMap)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return allSessions
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// API: Get all sessions grouped by project
|
|
271
|
-
server.get('/api/sessions', async (request, reply) => {
|
|
272
|
-
try {
|
|
273
|
-
const projectsDir = join(CLAUDE_DIR, 'projects')
|
|
274
|
-
const projects = await readdir(projectsDir)
|
|
275
|
-
|
|
276
|
-
const projectGroups: ProjectGroup[] = []
|
|
277
|
-
|
|
278
|
-
for (const project of projects) {
|
|
279
|
-
const projectPath = join(projectsDir, project)
|
|
280
|
-
const projectStat = await stat(projectPath)
|
|
281
|
-
|
|
282
|
-
if (projectStat.isDirectory()) {
|
|
283
|
-
const sessions = await getProjectSessions(projectPath)
|
|
284
|
-
|
|
285
|
-
if (sessions.length > 0) {
|
|
286
|
-
// Sort sessions by timestamp descending
|
|
287
|
-
sessions.sort((a, b) =>
|
|
288
|
-
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
const displayName = getProjectDisplayName(project)
|
|
292
|
-
|
|
293
|
-
projectGroups.push({
|
|
294
|
-
name: project,
|
|
295
|
-
displayName,
|
|
296
|
-
sessionCount: sessions.length,
|
|
297
|
-
lastActivity: sessions[0].timestamp, // Most recent session
|
|
298
|
-
sessions
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Sort project groups by last activity descending
|
|
305
|
-
projectGroups.sort((a, b) =>
|
|
306
|
-
new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime()
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
return { projects: projectGroups }
|
|
310
|
-
} catch (error) {
|
|
311
|
-
console.error('Error reading sessions:', error)
|
|
312
|
-
return { projects: [] }
|
|
313
|
-
}
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
// API: Get session by ID
|
|
317
|
-
server.get<{ Params: { id: string } }>('/api/sessions/:id', async (request, reply) => {
|
|
318
|
-
try {
|
|
319
|
-
const { id } = request.params
|
|
320
|
-
const projectsDir = join(CLAUDE_DIR, 'projects')
|
|
321
|
-
const projects = await readdir(projectsDir)
|
|
322
|
-
|
|
323
|
-
const isAgent = id.startsWith('agent-')
|
|
324
|
-
|
|
325
|
-
for (const project of projects) {
|
|
326
|
-
const projectPath = join(projectsDir, project)
|
|
327
|
-
const sessionFile = join(projectPath, `${id}.jsonl`)
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
const messages = await parseJsonl(sessionFile)
|
|
331
|
-
const fileStat = await stat(sessionFile)
|
|
332
|
-
const projectName = getProjectDisplayName(project)
|
|
333
|
-
let title = extractSessionTitle(messages)
|
|
334
|
-
|
|
335
|
-
// For agent sessions, try to find the description from parent session
|
|
336
|
-
if (isAgent) {
|
|
337
|
-
const agentId = id.replace('agent-', '')
|
|
338
|
-
const files = await readdir(projectPath)
|
|
339
|
-
for (const file of files) {
|
|
340
|
-
if (!file.startsWith('agent-') && file.endsWith('.jsonl')) {
|
|
341
|
-
try {
|
|
342
|
-
const parentMessages = await parseJsonl(join(projectPath, file))
|
|
343
|
-
const description = findAgentTitleFromParentMessages(parentMessages, agentId)
|
|
344
|
-
if (description) {
|
|
345
|
-
title = description
|
|
346
|
-
break
|
|
347
|
-
}
|
|
348
|
-
} catch {
|
|
349
|
-
continue
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// If this is a main session (not agent), attach agent sessions
|
|
356
|
-
let agentSessions: Session[] | undefined
|
|
357
|
-
if (!isAgent) {
|
|
358
|
-
const agentDescriptions = collectAgentDescriptions(messages)
|
|
359
|
-
if (agentDescriptions.size > 0) {
|
|
360
|
-
agentSessions = await loadAgentSessionsFromFiles(projectPath, projectName, agentDescriptions)
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return {
|
|
365
|
-
session: {
|
|
366
|
-
id,
|
|
367
|
-
project: projectName,
|
|
368
|
-
timestamp: fileStat.mtime.toISOString(),
|
|
369
|
-
messages,
|
|
370
|
-
messageCount: messages.length,
|
|
371
|
-
title,
|
|
372
|
-
isAgent,
|
|
373
|
-
agentSessions
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
} catch {
|
|
377
|
-
continue
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return reply.code(404).send({ error: 'Session not found' })
|
|
382
|
-
} catch (error) {
|
|
383
|
-
console.error('Error reading session:', error)
|
|
384
|
-
return reply.code(500).send({ error: 'Internal server error' })
|
|
385
|
-
}
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
// WebSocket: Watch for file changes
|
|
389
|
-
server.register(async function (fastify) {
|
|
390
|
-
fastify.get('/ws', { websocket: true }, (socket) => {
|
|
391
|
-
const projectsDir = join(CLAUDE_DIR, 'projects')
|
|
392
|
-
|
|
393
|
-
const watcher = chokidar.watch(projectsDir, {
|
|
394
|
-
ignoreInitial: true,
|
|
395
|
-
persistent: true
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
watcher.on('add', (path) => {
|
|
399
|
-
socket.send(JSON.stringify({ type: 'file-added', path }))
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
watcher.on('change', (path) => {
|
|
403
|
-
socket.send(JSON.stringify({ type: 'file-changed', path }))
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
watcher.on('unlink', (path) => {
|
|
407
|
-
socket.send(JSON.stringify({ type: 'file-deleted', path }))
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
socket.on('close', () => {
|
|
411
|
-
watcher.close()
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
socket.on('error', (err: Error) => {
|
|
415
|
-
console.error('WebSocket error:', err)
|
|
416
|
-
})
|
|
417
|
-
})
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
// Start server
|
|
421
|
-
const start = async () => {
|
|
422
|
-
try {
|
|
423
|
-
const envPort = process.env.PORT ? Number(process.env.PORT) : undefined
|
|
424
|
-
const port = Number.isFinite(envPort) ? envPort : await getPort({ port: DEFAULT_PORT })
|
|
425
|
-
|
|
426
|
-
await server.listen({ port })
|
|
427
|
-
|
|
428
|
-
if (port !== DEFAULT_PORT) {
|
|
429
|
-
console.log(`Port ${DEFAULT_PORT} is in use, using port ${port} instead`)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
console.log(`Server running on http://localhost:${port}`)
|
|
433
|
-
console.log(`Watching Claude directory: ${CLAUDE_DIR}`)
|
|
434
|
-
} catch (err) {
|
|
435
|
-
server.log.error(err)
|
|
436
|
-
process.exit(1)
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
start()
|
package/tailwind.config.js
DELETED
package/tsconfig.json
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"useDefineForClassFields": true,
|
|
5
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
|
|
9
|
-
/* Bundler mode */
|
|
10
|
-
"moduleResolution": "bundler",
|
|
11
|
-
"allowImportingTsExtensions": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"isolatedModules": true,
|
|
14
|
-
"noEmit": true,
|
|
15
|
-
"jsx": "react-jsx",
|
|
16
|
-
|
|
17
|
-
/* Linting */
|
|
18
|
-
"strict": true,
|
|
19
|
-
"noUnusedLocals": true,
|
|
20
|
-
"noUnusedParameters": true,
|
|
21
|
-
"noFallthroughCasesInSwitch": true,
|
|
22
|
-
|
|
23
|
-
/* Path aliases */
|
|
24
|
-
"baseUrl": ".",
|
|
25
|
-
"paths": {
|
|
26
|
-
"@/*": ["./src/*"]
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
"include": ["src"],
|
|
30
|
-
"references": [{ "path": "./tsconfig.node.json" }]
|
|
31
|
-
}
|
package/tsconfig.node.json
DELETED
package/vite.config.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import react from '@vitejs/plugin-react'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
|
|
5
|
-
const serverPort = Number(process.env.VITE_API_PORT) || 3000
|
|
6
|
-
|
|
7
|
-
// https://vitejs.dev/config/
|
|
8
|
-
export default defineConfig({
|
|
9
|
-
plugins: [react()],
|
|
10
|
-
resolve: {
|
|
11
|
-
alias: {
|
|
12
|
-
'@': path.resolve(__dirname, './src'),
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
server: {
|
|
16
|
-
port: 5173,
|
|
17
|
-
proxy: {
|
|
18
|
-
'/api': {
|
|
19
|
-
target: `http://localhost:${serverPort}`,
|
|
20
|
-
changeOrigin: true,
|
|
21
|
-
},
|
|
22
|
-
'/ws': {
|
|
23
|
-
target: `http://localhost:${serverPort}`,
|
|
24
|
-
ws: true,
|
|
25
|
-
changeOrigin: true,
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
define: {
|
|
30
|
-
'import.meta.env.VITE_API_PORT': JSON.stringify(serverPort.toString()),
|
|
31
|
-
},
|
|
32
|
-
})
|