feat: move dashboard to apps/ so we can share ws proto
This commit is contained in:
Generated
+12
@@ -12,6 +12,7 @@
|
||||
"@assistant-ui/react-streamdown": "^0.1.11",
|
||||
"@audiowave/react": "^0.6.2",
|
||||
"@chenglou/pretext": "^0.0.6",
|
||||
"@hermes/shared": "file:../shared",
|
||||
"@nanostores/react": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@streamdown/code": "^1.1.1",
|
||||
@@ -64,6 +65,13 @@
|
||||
"wait-on": "^9.0.5"
|
||||
}
|
||||
},
|
||||
"../shared": {
|
||||
"name": "@hermes/shared",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@antfu/install-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
|
||||
@@ -1696,6 +1704,10 @@
|
||||
"@hapi/hoek": "^11.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@hermes/shared": {
|
||||
"resolved": "../shared",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"preview": "vite preview --host 127.0.0.1 --port 4174"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hermes/shared": "file:../shared",
|
||||
"@assistant-ui/react": "^0.12.28",
|
||||
"@assistant-ui/react-streamdown": "^0.1.11",
|
||||
"@audiowave/react": "^0.6.2",
|
||||
|
||||
@@ -260,7 +260,7 @@ export function ChatView({
|
||||
<div className="min-w-0 flex-1">
|
||||
{title && (
|
||||
<SessionActionsMenu
|
||||
align="end"
|
||||
align="start"
|
||||
onDelete={selectedSessionId ? onDeleteSelectedSession : undefined}
|
||||
onPin={selectedSessionId ? onToggleSelectedPin : undefined}
|
||||
pinned={selectedIsPinned}
|
||||
|
||||
@@ -51,7 +51,9 @@ export function AppShell({
|
||||
// column for a closed sidebar pushes/clips the actual chat surface.
|
||||
const displayedSidebarWidth = sidebarOpen ? sidebarWidth : 0
|
||||
const titlebarControls = titlebarControlsPosition(connection?.windowButtonPosition)
|
||||
const titlebarContentInset = titlebarControls.left + TITLEBAR_HEIGHT + Math.round(TITLEBAR_HEIGHT / 2)
|
||||
const titlebarContentInset = sidebarOpen
|
||||
? 0
|
||||
: titlebarControls.left + TITLEBAR_HEIGHT + Math.round(TITLEBAR_HEIGHT / 2)
|
||||
const showRightRail = rightRailOpen && (inspectorOpen || Boolean(previewTarget))
|
||||
|
||||
// Right rail yields to chat min-width before the chat column starts crushing the composer.
|
||||
|
||||
@@ -16,7 +16,7 @@ export const titlebarButtonClass =
|
||||
'h-[var(--titlebar-control-height)] w-[var(--titlebar-control-size)] rounded-md text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
|
||||
export const titlebarHeaderBaseClass =
|
||||
'relative z-3 flex h-(--titlebar-height) shrink-0 items-center justify-center gap-3 bg-background/70 px-[max(0.75rem,var(--titlebar-content-inset,0px))] backdrop-blur-sm'
|
||||
'relative z-3 flex h-(--titlebar-height) shrink-0 items-center justify-start gap-3 bg-background/70 px-[max(0.75rem,var(--titlebar-content-inset,0px))] backdrop-blur-sm'
|
||||
|
||||
export const titlebarHeaderShadowClass =
|
||||
"shadow-header after:pointer-events-none after:absolute after:left-0 after:right-0 after:top-full after:h-10 after:bg-linear-to-b after:from-background after:via-background/80 after:to-transparent after:content-['']"
|
||||
|
||||
+9
-135
@@ -14,6 +14,7 @@ import type {
|
||||
SkillInfo,
|
||||
ToolsetInfo
|
||||
} from '@/types/hermes'
|
||||
import { JsonRpcGatewayClient } from '@hermes/shared'
|
||||
|
||||
export type {
|
||||
AudioSpeakResponse,
|
||||
@@ -41,143 +42,16 @@ export type {
|
||||
ToolsetInfo
|
||||
} from '@/types/hermes'
|
||||
|
||||
type PendingCall = {
|
||||
resolve: (value: unknown) => void
|
||||
reject: (error: Error) => void
|
||||
}
|
||||
|
||||
export class HermesGateway {
|
||||
private socket: WebSocket | null = null
|
||||
private nextId = 1
|
||||
private pending = new Map<number, PendingCall>()
|
||||
private eventHandlers = new Set<(event: RpcEvent) => void>()
|
||||
private stateHandlers = new Set<(state: string) => void>()
|
||||
|
||||
async connect(wsUrl: string): Promise<void> {
|
||||
if (this.socket?.readyState === WebSocket.OPEN) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setState('connecting')
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const ws = new WebSocket(wsUrl)
|
||||
this.socket = ws
|
||||
|
||||
ws.addEventListener('open', () => {
|
||||
this.setState('open')
|
||||
resolve()
|
||||
})
|
||||
|
||||
ws.addEventListener('error', () => {
|
||||
this.setState('error')
|
||||
reject(new Error('Could not connect to Hermes gateway'))
|
||||
})
|
||||
|
||||
ws.addEventListener('close', () => {
|
||||
this.setState('closed')
|
||||
|
||||
for (const call of this.pending.values()) {
|
||||
call.reject(new Error('Hermes gateway connection closed'))
|
||||
}
|
||||
|
||||
this.pending.clear()
|
||||
})
|
||||
|
||||
ws.addEventListener('message', message => {
|
||||
this.handleMessage(message.data)
|
||||
})
|
||||
export class HermesGateway extends JsonRpcGatewayClient {
|
||||
constructor() {
|
||||
super({
|
||||
closedErrorMessage: 'Hermes gateway connection closed',
|
||||
connectErrorMessage: 'Could not connect to Hermes gateway',
|
||||
createRequestId: nextId => nextId,
|
||||
notConnectedErrorMessage: 'Hermes gateway is not connected',
|
||||
requestTimeoutMs: 0
|
||||
})
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.socket?.close()
|
||||
this.socket = null
|
||||
}
|
||||
|
||||
onEvent(handler: (event: RpcEvent) => void): () => void {
|
||||
this.eventHandlers.add(handler)
|
||||
|
||||
return () => this.eventHandlers.delete(handler)
|
||||
}
|
||||
|
||||
onState(handler: (state: string) => void): () => void {
|
||||
this.stateHandlers.add(handler)
|
||||
|
||||
return () => this.stateHandlers.delete(handler)
|
||||
}
|
||||
|
||||
request<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
|
||||
const socket = this.socket
|
||||
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||
return Promise.reject(new Error('Hermes gateway is not connected'))
|
||||
}
|
||||
|
||||
const id = this.nextId++
|
||||
|
||||
const payload = JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
method,
|
||||
params
|
||||
})
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.pending.set(id, {
|
||||
resolve: value => resolve(value as T),
|
||||
reject
|
||||
})
|
||||
socket.send(payload)
|
||||
})
|
||||
}
|
||||
|
||||
private handleMessage(raw: unknown): void {
|
||||
const text = typeof raw === 'string' ? raw : String(raw)
|
||||
|
||||
let frame: {
|
||||
id?: number
|
||||
result?: unknown
|
||||
error?: { message?: string }
|
||||
method?: string
|
||||
params?: RpcEvent
|
||||
}
|
||||
|
||||
try {
|
||||
frame = JSON.parse(text)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof frame.id === 'number') {
|
||||
const call = this.pending.get(frame.id)
|
||||
|
||||
if (!call) {
|
||||
return
|
||||
}
|
||||
|
||||
this.pending.delete(frame.id)
|
||||
|
||||
if (frame.error) {
|
||||
call.reject(new Error(frame.error.message || 'Hermes RPC failed'))
|
||||
} else {
|
||||
call.resolve(frame.result)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (frame.method === 'event' && frame.params) {
|
||||
for (const handler of this.eventHandlers) {
|
||||
handler(frame.params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setState(state: string): void {
|
||||
for (const handler of this.stateHandlers) {
|
||||
handler(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function listSessions(limit = 40, minMessages = 0): Promise<PaginatedSessions> {
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": ["./src/*"],
|
||||
"@hermes/shared": ["../shared/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "../shared/src"],
|
||||
"references": []
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
'@hermes/shared': path.resolve(__dirname, '../shared/src')
|
||||
}
|
||||
},
|
||||
server: {
|
||||
|
||||
Reference in New Issue
Block a user