Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Frontend Architecture

The Kuse Cowork frontend is built with SolidJS, providing a reactive and performant user interface.

Technology Stack

Technology
Version
Purpose
SolidJS
1.8.x
Reactive UI framework
TypeScript
5.3.x
Type safety
Vite
5.4.x
Build tool
Tauri API
2.0.x
Backend communication

Project Structure

src/
├── App.tsx              # Main application component
├── index.tsx            # Entry point
├── index.css            # Global styles
├── components/          # UI components
│   ├── Chat.tsx         # Chat interface
│   ├── Chat.css
│   ├── AgentMain.tsx    # Agent view
│   ├── AgentMain.css
│   ├── Settings.tsx     # Settings panel
│   ├── Settings.css
│   ├── ModelSelector.tsx
│   ├── TaskPanel.tsx
│   ├── TaskSidebar.tsx
│   ├── MCPSettings.tsx
│   └── ...
├── stores/              # State management
│   ├── settings.ts      # Settings store
│   └── chat.ts          # Chat store
└── lib/                 # Utilities
    ├── tauri-api.ts     # Tauri bridge
    ├── ai-client.ts     # AI provider clients
    ├── mcp-api.ts       # MCP client
    └── claude.ts        # Claude utilities

Component Architecture

App Component

The root component that handles routing and layout:

const App: Component = () => {
  const [view, setView] = createSignal<'chat' | 'tasks'>('chat');

  return (
    <div class="app">
      <Sidebar view={view()} onViewChange={setView} />
      <main>
        <Switch>
          <Match when={view() === 'chat'}>
            <Chat />
          </Match>
          <Match when={view() === 'tasks'}>
            <AgentMain />
          </Match>
        </Switch>
      </main>
      <Show when={showSettings()}>
        <Settings />
      </Show>
    </div>
  );
};

Chat Component

Handles conversational interactions:

const Chat: Component = () => {
  const [messages, setMessages] = createSignal<Message[]>([]);
  const [input, setInput] = createSignal('');
  const [streaming, setStreaming] = createSignal(false);

  const sendMessage = async () => {
    const content = input();
    setInput('');

    await sendChatMessage(conversationId, content, (text) => {
      // Update streaming display
    });
  };

  return (
    <div class="chat">
      <MessageList messages={messages()} />
      <InputArea
        value={input()}
        onChange={setInput}
        onSubmit={sendMessage}
        disabled={streaming()}
      />
    </div>
  );
};

AgentMain Component

Manages task execution and progress display:

const AgentMain: Component = () => {
  const [task, setTask] = createSignal<Task | null>(null);
  const [events, setEvents] = createSignal<AgentEvent[]>([]);

  const runTask = async () => {
    await runTaskAgent(request, (event) => {
      setEvents(e => [...e, event]);
      // Handle different event types
    });
  };

  return (
    <div class="agent-main">
      <TaskSidebar onSelectTask={setTask} />
      <TaskPanel task={task()} events={events()} />
    </div>
  );
};

State Management

Settings Store

Global settings management:

// stores/settings.ts
import { createSignal } from "solid-js";

export interface Settings {
  apiKey: string;
  model: string;
  baseUrl: string;
  maxTokens: number;
  temperature?: number;
  providerKeys: Record<string, string>;
}

const [settings, setSettings] = createSignal<Settings>(DEFAULT_SETTINGS);

export function useSettings() {
  return {
    settings,
    updateSetting: async <K extends keyof Settings>(
      key: K,
      value: Settings[K]
    ) => {
      const newSettings = { ...settings(), [key]: value };
      setSettings(newSettings);
      await persistSettings(newSettings);
    },
  };
}

Chat Store

Message history and conversation state:

// stores/chat.ts
import { createStore } from "solid-js/store";

export interface Message {
  id: string;
  role: "user" | "assistant";
  content: string;
  timestamp: number;
}

const [messages, setMessages] = createStore<Message[]>([]);

export function useChat() {
  return {
    messages,
    addMessage: (msg: Message) => {
      setMessages(m => [...m, msg]);
    },
    clearMessages: () => {
      setMessages([]);
    },
  };
}

Tauri Integration

API Bridge

Communication with the Rust backend:

// lib/tauri-api.ts
import { invoke } from "@tauri-apps/api/core";
import { listen, UnlistenFn } from "@tauri-apps/api/event";

export async function sendChatMessage(
  conversationId: string,
  content: string,
  onStream: (text: string) => void
): Promise<string> {
  let unlisten: UnlistenFn | undefined;

  try {
    unlisten = await listen<StreamPayload>("chat-stream", (event) => {
      onStream(event.payload.text);
    });

    return await invoke<string>("send_chat_message", {
      conversationId,
      content,
    });
  } finally {
    unlisten?.();
  }
}

Event Handling

Real-time updates from backend:

// Listening for agent events
const unlisten = await listen<AgentEvent>("agent-event", (event) => {
  switch (event.payload.type) {
    case "text":
      updateContent(event.payload.content);
      break;
    case "tool_start":
      showToolExecution(event.payload.tool);
      break;
    case "tool_end":
      hideToolExecution();
      break;
    case "done":
      completeTask();
      break;
  }
});

Styling

CSS Organization

Each component has its own CSS file:

/* Chat.css */
.chat {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
}

.chat-input {
  border-top: 1px solid var(--border-color);
  padding: 1rem;
}

Theme Variables

Global CSS variables for theming:

:root {
  --primary-color: #6366f1;
  --background-color: #ffffff;
  --text-color: #1f2937;
  --border-color: #e5e7eb;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background-color: #1f2937;
    --text-color: #f9fafb;
    --border-color: #374151;
  }
}

Web Fallback

For development without Tauri:

// lib/tauri-api.ts
export function isTauri(): boolean {
  return typeof window !== "undefined" &&
    ("__TAURI__" in window || "__TAURI_INTERNALS__" in window);
}

export async function getSettings(): Promise<Settings> {
  if (!isTauri()) {
    // Fallback to localStorage
    const stored = localStorage.getItem("kuse-cowork-settings");
    return stored ? JSON.parse(stored) : DEFAULT_SETTINGS;
  }
  return invoke<Settings>("get_settings");
}

AI Client (Web Mode)

Direct AI provider access for web development:

// lib/ai-client.ts
class AnthropicProvider implements AIProvider {
  async sendMessage(
    messages: AIMessage[],
    settings: Settings,
    onStream?: (text: string) => void
  ): Promise<string> {
    const response = await fetch(`${settings.baseUrl}/v1/messages`, {
      method: "POST",
      headers: {
        "x-api-key": settings.apiKey,
        "anthropic-version": "2023-06-01",
      },
      body: JSON.stringify({
        model: settings.model,
        messages,
        stream: !!onStream,
      }),
    });

    if (onStream) {
      return this.handleStreamResponse(response, onStream);
    }
    return this.handleResponse(response);
  }
}

Performance Optimizations

Reactive Updates

SolidJS fine-grained reactivity:

// Only updates when specific signal changes
<Show when={loading()}>
  <Spinner />
</Show>

// Efficient list rendering
<For each={messages()}>
  {(message) => <MessageItem message={message} />}
</For>

Memoization

const filteredModels = createMemo(() => {
  return AVAILABLE_MODELS.filter(m =>
    m.provider === selectedProvider()
  );
});

Lazy Loading

const Settings = lazy(() => import("./components/Settings"));

Build Configuration

Vite Config

// vite.config.ts
export default defineConfig({
  plugins: [solidPlugin()],
  server: {
    port: 1420,
    strictPort: true,
  },
  build: {
    target: "esnext",
    minify: "esbuild",
  },
});

TypeScript Config

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "jsx": "preserve",
    "jsxImportSource": "solid-js",
    "strict": true
  }
}

Testing

Component Testing

import { render, screen } from "@solidjs/testing-library";
import { Chat } from "./Chat";

test("renders chat input", () => {
  render(() => <Chat />);
  expect(screen.getByRole("textbox")).toBeInTheDocument();
});

Integration Testing

test("sends message to backend", async () => {
  const mockInvoke = vi.fn().mockResolvedValue("response");
  vi.mock("@tauri-apps/api/core", () => ({ invoke: mockInvoke }));

  // Test message sending
});

Next Steps