mirror of
https://github.com/langchain-ai/openwork.git
synced 2026-07-01 20:37:03 -04:00
fix: minor tweaks and type improvements
This commit is contained in:
@@ -23,7 +23,7 @@ let globalWorkspacePath: string | null = null
|
||||
* Set the workspace path for filesystem synchronization.
|
||||
* When set, files created by the agent will be synced to this directory.
|
||||
*/
|
||||
export function setWorkspacePath(path: string | null) {
|
||||
export function setWorkspacePath(path: string | null): void {
|
||||
globalWorkspacePath = path
|
||||
console.log('[Runtime] Workspace path set to:', path)
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export async function createAgentRuntime(options: CreateAgentRuntimeOptions = {}
|
||||
const model = getModelInstance(modelId)
|
||||
console.log('[Runtime] Model instance created:', typeof model)
|
||||
|
||||
const saver = await getCheckpointer()
|
||||
const checkpointer = await getCheckpointer()
|
||||
console.log('[Runtime] Checkpointer ready')
|
||||
|
||||
// Use provided workspace path, fall back to global, or null for no sync
|
||||
@@ -107,18 +107,19 @@ export async function createAgentRuntime(options: CreateAgentRuntimeOptions = {}
|
||||
|
||||
// Using type assertion to work around version compatibility issues
|
||||
// between @langchain packages and deepagentsjs types
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const agent = createDeepAgent({
|
||||
model: model as any,
|
||||
checkpointer: saver as any,
|
||||
model,
|
||||
checkpointer,
|
||||
// Use SyncedStateBackend to enable bidirectional disk sync
|
||||
backend: createSyncedBackendFactory(syncPath) as any
|
||||
backend: createSyncedBackendFactory(syncPath)
|
||||
})
|
||||
|
||||
console.log('[Runtime] Deep agent created with', syncPath ? 'disk sync' : 'state-only storage')
|
||||
return agent
|
||||
}
|
||||
|
||||
export type DeepAgent = ReturnType<typeof createDeepAgent>
|
||||
|
||||
// Clean up resources
|
||||
export async function closeRuntime(): Promise<void> {
|
||||
if (checkpointer) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// @ts-ignore this is a workaround to avoid type errors in the main process
|
||||
import type { createAgentRuntime } from './runtime'
|
||||
|
||||
export type DeepAgent = Awaited<ReturnType<typeof createAgentRuntime>>
|
||||
Vendored
+2
@@ -36,7 +36,9 @@ interface CustomAPI {
|
||||
}
|
||||
models: {
|
||||
list: () => Promise<ModelConfig[]>
|
||||
listProviders: () => Promise<Provider[]>
|
||||
getDefault: () => Promise<string>
|
||||
deleteApiKey: (provider: string) => Promise<void>
|
||||
setDefault: (modelId: string) => Promise<void>
|
||||
setApiKey: (provider: string, apiKey: string) => Promise<void>
|
||||
getApiKey: (provider: string) => Promise<string | null>
|
||||
|
||||
@@ -152,7 +152,9 @@ const api = {
|
||||
select: (threadId?: string): Promise<string | null> => {
|
||||
return ipcRenderer.invoke('workspace:select', threadId)
|
||||
},
|
||||
syncToDisk: (threadId: string): Promise<{
|
||||
syncToDisk: (
|
||||
threadId: string
|
||||
): Promise<{
|
||||
success: boolean
|
||||
synced?: string[]
|
||||
errors?: string[]
|
||||
|
||||
+29
-19
@@ -18,29 +18,35 @@ function App(): React.JSX.Element {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [leftWidth, setLeftWidth] = useState(LEFT_DEFAULT)
|
||||
const [rightWidth, setRightWidth] = useState(RIGHT_DEFAULT)
|
||||
|
||||
|
||||
// Track drag start widths
|
||||
const dragStartWidths = useRef<{ left: number; right: number } | null>(null)
|
||||
|
||||
const handleLeftResize = useCallback((totalDelta: number) => {
|
||||
if (!dragStartWidths.current) {
|
||||
dragStartWidths.current = { left: leftWidth, right: rightWidth }
|
||||
}
|
||||
const newWidth = dragStartWidths.current.left + totalDelta
|
||||
setLeftWidth(Math.min(LEFT_MAX, Math.max(LEFT_MIN, newWidth)))
|
||||
}, [leftWidth, rightWidth])
|
||||
const handleLeftResize = useCallback(
|
||||
(totalDelta: number) => {
|
||||
if (!dragStartWidths.current) {
|
||||
dragStartWidths.current = { left: leftWidth, right: rightWidth }
|
||||
}
|
||||
const newWidth = dragStartWidths.current.left + totalDelta
|
||||
setLeftWidth(Math.min(LEFT_MAX, Math.max(LEFT_MIN, newWidth)))
|
||||
},
|
||||
[leftWidth, rightWidth]
|
||||
)
|
||||
|
||||
const handleRightResize = useCallback((totalDelta: number) => {
|
||||
if (!dragStartWidths.current) {
|
||||
dragStartWidths.current = { left: leftWidth, right: rightWidth }
|
||||
}
|
||||
const newWidth = dragStartWidths.current.right - totalDelta
|
||||
setRightWidth(Math.min(RIGHT_MAX, Math.max(RIGHT_MIN, newWidth)))
|
||||
}, [leftWidth, rightWidth])
|
||||
const handleRightResize = useCallback(
|
||||
(totalDelta: number) => {
|
||||
if (!dragStartWidths.current) {
|
||||
dragStartWidths.current = { left: leftWidth, right: rightWidth }
|
||||
}
|
||||
const newWidth = dragStartWidths.current.right - totalDelta
|
||||
setRightWidth(Math.min(RIGHT_MAX, Math.max(RIGHT_MIN, newWidth)))
|
||||
},
|
||||
[leftWidth, rightWidth]
|
||||
)
|
||||
|
||||
// Reset drag start on mouse up
|
||||
useEffect(() => {
|
||||
const handleMouseUp = () => {
|
||||
const handleMouseUp = (): void => {
|
||||
dragStartWidths.current = null
|
||||
}
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
@@ -78,11 +84,15 @@ function App(): React.JSX.Element {
|
||||
{/* Draggable titlebar region with app badge */}
|
||||
<div className="h-8 w-full shrink-0 app-drag-region bg-sidebar relative">
|
||||
<div className="absolute top-[14px] left-[76px] flex items-center gap-1.5 px-2 py-0.5 rounded-sm bg-primary/10 border border-primary/30 leading-none">
|
||||
<span className="text-[10px] font-semibold uppercase tracking-[0.1em] text-primary leading-none">OPENWORK</span>
|
||||
<span className="text-[9px] text-primary/70 font-mono leading-none">{__APP_VERSION__}</span>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-widest text-primary leading-none">
|
||||
OPENWORK
|
||||
</span>
|
||||
<span className="text-[9px] text-primary/70 font-mono leading-none">
|
||||
{__APP_VERSION__}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Left Sidebar - Thread List */}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { WorkspacePicker } from './WorkspacePicker'
|
||||
import { ApprovalDialog } from '@/components/hitl/ApprovalDialog'
|
||||
import { ElectronIPCTransport } from '@/lib/electron-transport'
|
||||
import type { Message } from '@/types'
|
||||
import type { DeepAgent } from '../../../../main/agent/types'
|
||||
|
||||
interface ChatContainerProps {
|
||||
threadId: string
|
||||
@@ -158,7 +159,7 @@ export function ChatContainer({ threadId }: ChatContainerProps): React.JSX.Eleme
|
||||
)
|
||||
|
||||
// Use the useStream hook with our custom transport
|
||||
const stream = useStream({
|
||||
const stream = useStream<DeepAgent>({
|
||||
transport,
|
||||
threadId,
|
||||
messagesKey: 'messages',
|
||||
@@ -169,7 +170,6 @@ export function ChatContainer({ threadId }: ChatContainerProps): React.JSX.Eleme
|
||||
console.error('[ChatContainer] Stream error:', error)
|
||||
}
|
||||
})
|
||||
console.log('[ChatContainer] Stream:', stream.messages)
|
||||
|
||||
// Refresh threads when loading state changes from true to false (stream completed)
|
||||
const prevLoadingRef = useRef(false)
|
||||
|
||||
@@ -33,15 +33,15 @@ const STATUS_CONFIG = {
|
||||
}
|
||||
}
|
||||
|
||||
export function TodoPanel() {
|
||||
export function TodoPanel(): React.JSX.Element {
|
||||
const { todos } = useAppStore()
|
||||
const [completedExpanded, setCompletedExpanded] = useState(false)
|
||||
|
||||
// Group todos by status
|
||||
const inProgress = todos.filter(t => t.status === 'in_progress')
|
||||
const pending = todos.filter(t => t.status === 'pending')
|
||||
const completed = todos.filter(t => t.status === 'completed')
|
||||
const cancelled = todos.filter(t => t.status === 'cancelled')
|
||||
const inProgress = todos.filter((t) => t.status === 'in_progress')
|
||||
const pending = todos.filter((t) => t.status === 'pending')
|
||||
const completed = todos.filter((t) => t.status === 'completed')
|
||||
const cancelled = todos.filter((t) => t.status === 'cancelled')
|
||||
|
||||
// Completed section includes both completed and cancelled
|
||||
const doneItems = [...completed, ...cancelled]
|
||||
@@ -59,10 +59,12 @@ export function TodoPanel() {
|
||||
<div className="p-4 border-b border-border">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-section-header">PROGRESS</span>
|
||||
<span className="text-data text-sm">{done}/{total}</span>
|
||||
<span className="text-data text-sm">
|
||||
{done}/{total}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-1.5 rounded-full bg-background overflow-hidden">
|
||||
<div
|
||||
<div
|
||||
className="h-full bg-status-nominal transition-all duration-300"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
@@ -73,9 +75,7 @@ export function TodoPanel() {
|
||||
<ScrollArea className="flex-1 min-h-0">
|
||||
<div className="p-4 space-y-2">
|
||||
{!hasAnyTodos ? (
|
||||
<div className="text-center text-sm text-muted-foreground py-8">
|
||||
No tasks yet
|
||||
</div>
|
||||
<div className="text-center text-sm text-muted-foreground py-8">No tasks yet</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Completed/Cancelled Section (Collapsible) */}
|
||||
@@ -129,22 +129,26 @@ export function TodoPanel() {
|
||||
)
|
||||
}
|
||||
|
||||
function TodoItem({ todo }: { todo: Todo }) {
|
||||
function TodoItem({ todo }: { todo: Todo }): React.JSX.Element {
|
||||
const config = STATUS_CONFIG[todo.status]
|
||||
const Icon = config.icon
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
"flex items-start gap-3 rounded-sm border border-border p-3 transition-colors",
|
||||
todo.status === 'completed' && "opacity-60",
|
||||
todo.status === 'cancelled' && "opacity-40"
|
||||
)}>
|
||||
<Icon className={cn("size-4 shrink-0 mt-0.5", config.color)} />
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-start gap-3 rounded-sm border border-border p-3 transition-colors',
|
||||
todo.status === 'completed' && 'opacity-60',
|
||||
todo.status === 'cancelled' && 'opacity-40'
|
||||
)}
|
||||
>
|
||||
<Icon className={cn('size-4 shrink-0 mt-0.5', config.color)} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className={cn(
|
||||
"text-sm",
|
||||
(todo.status === 'completed' || todo.status === 'cancelled') && "line-through"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
'text-sm',
|
||||
(todo.status === 'completed' || todo.status === 'cancelled') && 'line-through'
|
||||
)}
|
||||
>
|
||||
{todo.content}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { create } from 'zustand'
|
||||
import type { Thread, Message, Todo, ModelConfig, Provider, HITLRequest, FileInfo, Subagent } from '@/types'
|
||||
import type {
|
||||
Thread,
|
||||
Message,
|
||||
Todo,
|
||||
ModelConfig,
|
||||
Provider,
|
||||
HITLRequest,
|
||||
FileInfo,
|
||||
Subagent
|
||||
} from '@/types'
|
||||
|
||||
interface AppState {
|
||||
// Threads
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@
|
||||
"src/renderer/src/**/*",
|
||||
"src/renderer/src/**/*.tsx",
|
||||
"src/preload/*.d.ts",
|
||||
"src/types.ts"
|
||||
"src/types.ts",
|
||||
"src/main/agent/types.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
|
||||
Reference in New Issue
Block a user