Skip to content

Commit 4700553

Browse files
author
echoVic
committed
feat: Web UI improvements v0.0.13
- Real-time diff push via WebSocket during agent execution - Side-by-side diff view with Monaco DiffEditor - Hide unchanged regions with expandable sections - Collapsible sidebar and task detail panel - Dynamic editor height based on content - Remove unnecessary logs from terminal output - Add homepage screenshot to README
1 parent 41c1020 commit 4700553

18 files changed

Lines changed: 716 additions & 211 deletions

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
55
**一句话价值**:让任何 agent(你自己的或第三方 CLI)在隔离区里干活,并且**每一步可审计、可回滚、可恢复**
66

7+
![BAR Web UI](assets/homepage.png)
8+
79
## 为什么需要 BAR?
810

911
当你使用 Claude Code、Cursor、Copilot 等 AI Coding Agent 时:
@@ -124,12 +126,13 @@ bar ui -p 3000 # 指定端口
124126
bar ui --no-open # 不自动打开浏览器
125127
```
126128

127-
**主要功能 (v0.0.8)**
128-
- **现代化深色主题**:基于 Zinc 色系,Vercel/Linear 风格。
129-
- **Split View (分栏视图)**:左侧 Timeline,右侧 Monaco Editor Diff 视图。
130-
- **智能跳转**:首页自动跳转到当前活跃任务。
131-
- **实时 Diff**:选中步骤即可查看代码变更,支持语法高亮。
132-
- **WebSocket**:实时更新任务状态和日志。
129+
**主要功能 (v0.0.13)**
130+
- **实时 Diff 推送**:Agent 运行时实时查看代码变更,无需等待退出
131+
- **Side-by-Side Diff**:Monaco DiffEditor 并排对比视图,支持语法高亮
132+
- **隐藏未更改行**:自动折叠大段未更改代码,点击可展开
133+
- **可折叠面板**:左侧导航栏和任务详情面板均支持展开/收起
134+
- **现代化深色主题**:基于 Zinc 色系,Vercel/Linear 风格
135+
- **WebSocket 实时更新**:任务状态和日志实时同步
133136

134137
## 目录结构
135138

assets/homepage.png

430 KB
Loading

cmd/bar/ui.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"os/exec"
78
"os/signal"
9+
"runtime"
810
"syscall"
911

1012
"github.com/spf13/cobra"
@@ -65,11 +67,16 @@ func uiCmd() *cobra.Command {
6567
}
6668

6769
func openBrowser(url string) {
68-
// Try different commands to open browser
69-
for _, cmd := range []string{"open", "xdg-open", "start"} {
70-
if _, err := os.Stat("/usr/bin/" + cmd); err == nil || cmd == "start" {
71-
syscall.Exec("/usr/bin/"+cmd, []string{cmd, url}, os.Environ())
72-
return
73-
}
70+
var cmd *exec.Cmd
71+
switch runtime.GOOS {
72+
case "darwin":
73+
cmd = exec.Command("open", url)
74+
case "linux":
75+
cmd = exec.Command("xdg-open", url)
76+
case "windows":
77+
cmd = exec.Command("cmd", "/c", "start", url)
78+
default:
79+
return
7480
}
81+
cmd.Start()
7582
}

cmd/bar/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const (
1616
repoOwner = "echoVic"
1717
repoName = "blade-agent-runtime"
1818
installURL = "https://echovic.github.io/blade-agent-runtime/install.sh"
19-
currentVersion = "0.0.12"
19+
currentVersion = "0.0.13"
2020
)
2121

2222
func updateCmd() *cobra.Command {

cmd/bar/wrap.go

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ package main
22

33
import (
44
"fmt"
5+
"io"
56
"os"
67
"os/exec"
78
"os/signal"
89
"path/filepath"
910
"syscall"
1011
"time"
1112

13+
"github.com/creack/pty"
1214
"github.com/jaevor/go-nanoid"
1315
"github.com/spf13/cobra"
16+
"golang.org/x/term"
1417

1518
gitadapter "github.com/user/blade-agent-runtime/internal/adapters/git"
1619
"github.com/user/blade-agent-runtime/internal/core/ledger"
@@ -69,9 +72,6 @@ When the command exits, BAR records a step with the diff of all changes.`,
6972

7073
childCmd := exec.Command(args[0], args[1:]...)
7174
childCmd.Dir = task.WorkspacePath
72-
childCmd.Stdin = os.Stdin
73-
childCmd.Stdout = os.Stdout
74-
childCmd.Stderr = os.Stderr
7575
childCmd.Env = append(os.Environ(),
7676
"BAR_ACTIVE=true",
7777
"BAR_TASK_ID="+task.ID,
@@ -81,21 +81,88 @@ When the command exits, BAR records a step with the diff of all changes.`,
8181
"BAR_REPO_ROOT="+task.RepoRoot,
8282
)
8383

84+
// Start command with PTY for interactive support
85+
ptmx, err := pty.Start(childCmd)
86+
if err != nil {
87+
app.Logger.Error("Failed to start command '%s': %v", args[0], err)
88+
if uiServer != nil {
89+
uiServer.Stop()
90+
}
91+
return fmt.Errorf("failed to start command: %w", err)
92+
}
93+
defer ptmx.Close()
94+
95+
// Handle terminal resize
96+
ch := make(chan os.Signal, 1)
97+
signal.Notify(ch, syscall.SIGWINCH)
98+
go func() {
99+
for range ch {
100+
if ws, err := pty.GetsizeFull(os.Stdin); err == nil {
101+
pty.Setsize(ptmx, ws)
102+
}
103+
}
104+
}()
105+
ch <- syscall.SIGWINCH // Initial resize
106+
107+
// Handle Ctrl+C - forward to child process
84108
sigChan := make(chan os.Signal, 1)
85109
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
86-
87110
go func() {
88-
sig := <-sigChan
89-
if childCmd.Process != nil {
90-
childCmd.Process.Signal(sig)
111+
for sig := range sigChan {
112+
if childCmd.Process != nil {
113+
childCmd.Process.Signal(sig)
114+
}
91115
}
92116
}()
93117

94118
app.Logger.Info("Starting wrapped command in %s", task.WorkspacePath)
95119
app.Logger.Info("Changes will be recorded when the command exits")
96120
app.Logger.Info("")
97121

98-
runErr := childCmd.Run()
122+
// Start live diff watcher if UI is enabled
123+
stopWatcher := make(chan struct{})
124+
if uiServer != nil {
125+
go func() {
126+
ticker := time.NewTicker(2 * time.Second)
127+
defer ticker.Stop()
128+
129+
var lastPatchLen int
130+
for {
131+
select {
132+
case <-stopWatcher:
133+
return
134+
case <-ticker.C:
135+
diffResult, err := app.DiffEngine.Generate(task.WorkspacePath, task.BaseRef)
136+
if err != nil {
137+
continue
138+
}
139+
140+
// Use patch length as simple change detector
141+
currentLen := len(diffResult.Patch)
142+
if currentLen != lastPatchLen {
143+
lastPatchLen = currentLen
144+
uiServer.BroadcastLiveDiff(task.ID, diffResult)
145+
}
146+
}
147+
}
148+
}()
149+
}
150+
151+
// Set stdin to raw mode for proper PTY interaction
152+
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
153+
if err == nil {
154+
defer term.Restore(int(os.Stdin.Fd()), oldState)
155+
}
156+
157+
// Copy stdin to PTY and PTY to stdout
158+
go func() { io.Copy(ptmx, os.Stdin) }()
159+
io.Copy(os.Stdout, ptmx)
160+
161+
// Stop the watcher
162+
close(stopWatcher)
163+
164+
// Wait for command to finish
165+
runErr := childCmd.Wait()
99166

100167
endTime := time.Now().UTC()
101168
duration := endTime.Sub(startTime)
@@ -165,7 +232,6 @@ When the command exits, BAR records a step with the diff of all changes.`,
165232
app.Logger.Info("Step %s recorded", stepID)
166233
app.Logger.Info("Files changed: %d (+%d, -%d)", diffResult.Files, diffResult.Additions, diffResult.Deletions)
167234

168-
// Stop UI server if running
169235
if uiServer != nil {
170236
uiServer.Stop()
171237
}

dist/bar_darwin_amd64.tar.gz

-196 Bytes
Binary file not shown.

dist/bar_darwin_arm64.tar.gz

-823 Bytes
Binary file not shown.

dist/bar_linux_amd64.tar.gz

-573 Bytes
Binary file not shown.

dist/bar_linux_arm64.tar.gz

8 KB
Binary file not shown.

go.mod

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ go 1.24.2
44

55
require (
66
github.com/go-git/go-git/v5 v5.16.4
7+
github.com/gorilla/websocket v1.5.3
78
github.com/jaevor/go-nanoid v1.4.0
89
github.com/spf13/cobra v1.10.2
910
github.com/spf13/viper v1.21.0
11+
golang.org/x/term v0.39.0
1012
gopkg.in/yaml.v3 v3.0.1
1113
)
1214

@@ -15,14 +17,14 @@ require (
1517
github.com/Microsoft/go-winio v0.6.2 // indirect
1618
github.com/ProtonMail/go-crypto v1.1.6 // indirect
1719
github.com/cloudflare/circl v1.6.1 // indirect
20+
github.com/creack/pty v1.1.24 // indirect
1821
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
1922
github.com/emirpasic/gods v1.18.1 // indirect
2023
github.com/fsnotify/fsnotify v1.9.0 // indirect
2124
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
2225
github.com/go-git/go-billy/v5 v5.6.2 // indirect
2326
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
2427
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
25-
github.com/gorilla/websocket v1.5.3 // indirect
2628
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2729
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
2830
github.com/kevinburke/ssh_config v1.2.0 // indirect
@@ -40,7 +42,7 @@ require (
4042
go.yaml.in/yaml/v3 v3.0.4 // indirect
4143
golang.org/x/crypto v0.37.0 // indirect
4244
golang.org/x/net v0.39.0 // indirect
43-
golang.org/x/sys v0.32.0 // indirect
45+
golang.org/x/sys v0.40.0 // indirect
4446
golang.org/x/text v0.28.0 // indirect
4547
gopkg.in/warnings.v0 v0.1.2 // indirect
4648
)

0 commit comments

Comments
 (0)