mirror of
https://github.com/aljazceru/opencode.git
synced 2026-01-10 11:24:59 +01:00
wip: refactoring tui
This commit is contained in:
@@ -18,14 +18,17 @@ import (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
State map[string]any
|
||||
|
||||
CurrentSession *session.Session
|
||||
Logs interface{} // TODO: Define LogService interface when needed
|
||||
Logs any // TODO: Define LogService interface when needed
|
||||
Sessions SessionService
|
||||
Messages MessageService
|
||||
History interface{} // TODO: Define HistoryService interface when needed
|
||||
Permissions interface{} // TODO: Define PermissionService interface when needed
|
||||
History any // TODO: Define HistoryService interface when needed
|
||||
Permissions any // TODO: Define PermissionService interface when needed
|
||||
Status status.Service
|
||||
Client *client.Client
|
||||
Client *client.ClientWithResponses
|
||||
Events *client.Client
|
||||
|
||||
PrimaryAgent AgentService
|
||||
|
||||
@@ -36,9 +39,9 @@ type App struct {
|
||||
watcherCancelFuncs []context.CancelFunc
|
||||
cancelFuncsMutex sync.Mutex
|
||||
watcherWG sync.WaitGroup
|
||||
|
||||
|
||||
// UI state
|
||||
filepickerOpen bool
|
||||
filepickerOpen bool
|
||||
completionDialogOpen bool
|
||||
}
|
||||
|
||||
@@ -49,16 +52,22 @@ func New(ctx context.Context) (*App, error) {
|
||||
slog.Error("Failed to initialize status service", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// Initialize file utilities
|
||||
fileutil.Init()
|
||||
|
||||
// Create HTTP client
|
||||
httpClient, err := client.NewClient("http://localhost:16713")
|
||||
url := "http://localhost:16713"
|
||||
httpClient, err := client.NewClientWithResponses(url)
|
||||
if err != nil {
|
||||
slog.Error("Failed to create client", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
eventClient, err := client.NewClient(url)
|
||||
if err != nil {
|
||||
slog.Error("Failed to create event client", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create service bridges
|
||||
sessionBridge := NewSessionServiceBridge(httpClient)
|
||||
@@ -66,18 +75,20 @@ func New(ctx context.Context) (*App, error) {
|
||||
agentBridge := NewAgentServiceBridge(httpClient)
|
||||
|
||||
app := &App{
|
||||
State: make(map[string]any),
|
||||
Client: httpClient,
|
||||
Events: eventClient,
|
||||
CurrentSession: &session.Session{},
|
||||
Sessions: sessionBridge,
|
||||
Messages: messageBridge,
|
||||
PrimaryAgent: agentBridge,
|
||||
Status: status.GetService(),
|
||||
LSPClients: make(map[string]*lsp.Client),
|
||||
|
||||
|
||||
// TODO: These services need API endpoints:
|
||||
Logs: nil, // logging.GetService(),
|
||||
History: nil, // history.GetService(),
|
||||
Permissions: nil, // permission.GetService(),
|
||||
Logs: nil, // logging.GetService(),
|
||||
History: nil, // history.GetService(),
|
||||
Permissions: nil, // permission.GetService(),
|
||||
}
|
||||
|
||||
// Initialize theme based on configuration
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/sst/opencode/internal/message"
|
||||
"github.com/sst/opencode/pkg/client"
|
||||
)
|
||||
|
||||
// StorageWriteMsg is sent when a storage.write event is received
|
||||
type StorageWriteMsg struct {
|
||||
Key string
|
||||
Content interface{}
|
||||
}
|
||||
|
||||
// ProcessSSEEvent converts SSE events into TUI messages
|
||||
func ProcessSSEEvent(event interface{}) tea.Msg {
|
||||
switch e := event.(type) {
|
||||
case *client.EventStorageWrite:
|
||||
return StorageWriteMsg{
|
||||
Key: e.Key,
|
||||
Content: e.Content,
|
||||
}
|
||||
}
|
||||
|
||||
// Return the raw event if we don't have a specific handler
|
||||
return event
|
||||
}
|
||||
|
||||
// MessageFromStorage converts storage content to internal message format
|
||||
type MessageData struct {
|
||||
ID string `json:"id"`
|
||||
Role string `json:"role"`
|
||||
Parts []interface{} `json:"parts"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
// SessionInfoFromStorage converts storage content to session info
|
||||
type SessionInfoData struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ShareID *string `json:"shareID,omitempty"`
|
||||
Tokens struct {
|
||||
Input float32 `json:"input"`
|
||||
Output float32 `json:"output"`
|
||||
Reasoning float32 `json:"reasoning"`
|
||||
} `json:"tokens"`
|
||||
}
|
||||
|
||||
// ConvertStorageMessage converts a storage message to internal message format
|
||||
func ConvertStorageMessage(data interface{}, sessionID string) (*message.Message, error) {
|
||||
// Convert the interface{} to JSON then back to our struct
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var msgData MessageData
|
||||
if err := json.Unmarshal(jsonData, &msgData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert parts
|
||||
var parts []message.ContentPart
|
||||
for _, part := range msgData.Parts {
|
||||
partMap, ok := part.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
partType, ok := partMap["type"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch partType {
|
||||
case "text":
|
||||
if text, ok := partMap["text"].(string); ok {
|
||||
parts = append(parts, message.TextContent{Text: text})
|
||||
}
|
||||
case "tool-invocation":
|
||||
if toolInv, ok := partMap["toolInvocation"].(map[string]interface{}); ok {
|
||||
// Convert tool invocation to tool call
|
||||
toolCall := message.ToolCall{
|
||||
ID: toolInv["toolCallId"].(string),
|
||||
Name: toolInv["toolName"].(string),
|
||||
Type: "function",
|
||||
}
|
||||
|
||||
if args, ok := toolInv["args"]; ok {
|
||||
argsJSON, _ := json.Marshal(args)
|
||||
toolCall.Input = string(argsJSON)
|
||||
}
|
||||
|
||||
if state, ok := toolInv["state"].(string); ok {
|
||||
toolCall.Finished = state == "result"
|
||||
}
|
||||
|
||||
parts = append(parts, toolCall)
|
||||
|
||||
// If there's a result, add it as a tool result
|
||||
if result, ok := toolInv["result"]; ok && toolCall.Finished {
|
||||
resultStr := ""
|
||||
switch r := result.(type) {
|
||||
case string:
|
||||
resultStr = r
|
||||
default:
|
||||
resultJSON, _ := json.Marshal(r)
|
||||
resultStr = string(resultJSON)
|
||||
}
|
||||
|
||||
parts = append(parts, message.ToolResult{
|
||||
ToolCallID: toolCall.ID,
|
||||
Name: toolCall.Name,
|
||||
Content: resultStr,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert role
|
||||
var role message.MessageRole
|
||||
switch msgData.Role {
|
||||
case "user":
|
||||
role = message.User
|
||||
case "assistant":
|
||||
role = message.Assistant
|
||||
case "system":
|
||||
role = message.System
|
||||
default:
|
||||
role = message.MessageRole(msgData.Role)
|
||||
}
|
||||
|
||||
// Create message
|
||||
msg := &message.Message{
|
||||
ID: msgData.ID,
|
||||
Role: role,
|
||||
SessionID: sessionID,
|
||||
Parts: parts,
|
||||
CreatedAt: time.Now(), // TODO: Get from metadata
|
||||
UpdatedAt: time.Now(), // TODO: Get from metadata
|
||||
}
|
||||
|
||||
// Try to get timestamps from metadata
|
||||
if metadata, ok := msgData.Metadata["time"].(map[string]interface{}); ok {
|
||||
if created, ok := metadata["created"].(float64); ok {
|
||||
msg.CreatedAt = time.Unix(int64(created/1000), 0)
|
||||
}
|
||||
if completed, ok := metadata["completed"].(float64); ok {
|
||||
msg.UpdatedAt = time.Unix(int64(completed/1000), 0)
|
||||
}
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
@@ -14,11 +14,11 @@ import (
|
||||
|
||||
// SessionServiceBridge adapts the HTTP API to the old session.Service interface
|
||||
type SessionServiceBridge struct {
|
||||
client *client.Client
|
||||
client *client.ClientWithResponses
|
||||
}
|
||||
|
||||
// NewSessionServiceBridge creates a new session service bridge
|
||||
func NewSessionServiceBridge(client *client.Client) *SessionServiceBridge {
|
||||
func NewSessionServiceBridge(client *client.ClientWithResponses) *SessionServiceBridge {
|
||||
return &SessionServiceBridge{client: client}
|
||||
}
|
||||
|
||||
@@ -107,11 +107,11 @@ func (s *SessionServiceBridge) Delete(ctx context.Context, id string) error {
|
||||
|
||||
// AgentServiceBridge provides a minimal agent service that sends messages to the API
|
||||
type AgentServiceBridge struct {
|
||||
client *client.Client
|
||||
client *client.ClientWithResponses
|
||||
}
|
||||
|
||||
// NewAgentServiceBridge creates a new agent service bridge
|
||||
func NewAgentServiceBridge(client *client.Client) *AgentServiceBridge {
|
||||
func NewAgentServiceBridge(client *client.ClientWithResponses) *AgentServiceBridge {
|
||||
return &AgentServiceBridge{client: client}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func (a *AgentServiceBridge) Run(ctx context.Context, sessionID string, text str
|
||||
// return "", fmt.Errorf("attachments not supported yet")
|
||||
}
|
||||
|
||||
parts := interface{}([]map[string]interface{}{
|
||||
parts := any([]map[string]any{
|
||||
{
|
||||
"type": "text",
|
||||
"text": text,
|
||||
@@ -170,12 +170,12 @@ func (a *AgentServiceBridge) CompactSession(ctx context.Context, sessionID strin
|
||||
|
||||
// MessageServiceBridge provides a minimal message service that fetches from the API
|
||||
type MessageServiceBridge struct {
|
||||
client *client.Client
|
||||
client *client.ClientWithResponses
|
||||
broker *pubsub.Broker[message.Message]
|
||||
}
|
||||
|
||||
// NewMessageServiceBridge creates a new message service bridge
|
||||
func NewMessageServiceBridge(client *client.Client) *MessageServiceBridge {
|
||||
func NewMessageServiceBridge(client *client.ClientWithResponses) *MessageServiceBridge {
|
||||
return &MessageServiceBridge{
|
||||
client: client,
|
||||
broker: pubsub.NewBroker[message.Message](),
|
||||
@@ -198,7 +198,7 @@ func (m *MessageServiceBridge) List(ctx context.Context, sessionID string) ([]me
|
||||
defer resp.Body.Close()
|
||||
|
||||
// The API returns a different format, we'll need to adapt it
|
||||
var rawMessages interface{}
|
||||
var rawMessages any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&rawMessages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -247,4 +247,5 @@ func (m *MessageServiceBridge) ListAfter(ctx context.Context, sessionID string,
|
||||
// Subscribe subscribes to message events
|
||||
func (m *MessageServiceBridge) Subscribe(ctx context.Context) <-chan pubsub.Event[message.Message] {
|
||||
return m.broker.Subscribe(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user