Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 46 additions & 20 deletions app/Http/Controllers/HomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Features\Desktop\MvpShell;
use App\Models\InstanceConnection;
use App\Models\SurrealWorkspace;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Surreal\SurrealRuntimeManager;
Expand All @@ -18,6 +19,8 @@

class HomeController extends Controller
{
private const FAVORITES_ENABLED = false;

/**
* @return array{
* slug: string,
Expand All @@ -35,19 +38,20 @@ class HomeController extends Controller
* messages: array<int, array{speaker: string, role: string, body: string, meta: string, tone: string}>
* }
*/
private function activeWorkspaceState(InstanceConnection $activeConnection, bool $localReady): array
private function activeWorkspaceState(InstanceConnection $activeConnection, Workspace $workspace, bool $localReady): array
{
if ($activeConnection->kind === InstanceConnection::KIND_SERVER) {
return [
'slug' => 'remote-instance',
'label' => $activeConnection->name,
'meta' => $this->connectionMeta($activeConnection),
'prefix' => $this->connectionPrefix($activeConnection),
'summary' => sprintf(
'A connected server workspace for shared orchestration, worker presence, and linked team context on %s.',
'slug' => $workspace->slug,
'label' => $workspace->name,
'meta' => $activeConnection->name,
'prefix' => strtoupper(substr($workspace->name, 0, 1)),
'summary' => $workspace->summary ?: sprintf(
'%s is the active workspace on %s for shared orchestration, worker presence, and linked team context.',
$workspace->name,
$activeConnection->name,
),
'room' => '# relay-ops',
'room' => '# general',
'roomStatus' => 'remote',
'participants' => [
['label' => 'You', 'meta' => 'Human'],
Expand Down Expand Up @@ -106,14 +110,14 @@ private function activeWorkspaceState(InstanceConnection $activeConnection, bool
}

return [
'slug' => 'current-instance',
'label' => $activeConnection->name,
'meta' => $this->connectionMeta($activeConnection),
'prefix' => $this->connectionPrefix($activeConnection),
'summary' => $localReady
? 'The embedded Surreal runtime is available and the primary workspace is ready.'
: 'A primary workspace on this instance for conversations, tasks, artifacts, and decisions.',
'room' => '# design-room',
'slug' => $workspace->slug,
'label' => $workspace->name,
'meta' => $activeConnection->name,
'prefix' => strtoupper(substr($workspace->name, 0, 1)),
'summary' => $workspace->summary ?: ($localReady
? sprintf('The embedded Surreal runtime is available and %s is ready.', $workspace->name)
: sprintf('%s is a workspace on this instance for conversations, tasks, artifacts, and decisions.', $workspace->name)),
'room' => '# general',
'roomStatus' => $localReady ? 'ready' : 'draft',
'participants' => [
['label' => 'You', 'meta' => 'Human'],
Expand Down Expand Up @@ -231,17 +235,35 @@ private function connectionLinks(EloquentCollection $connections, InstanceConnec
->all();
}

/**
* @param EloquentCollection<int, Workspace> $workspaces
* @return array<int, array{id: int, label: string, prefix: string, active: bool, tone: string}>
*/
private function workspaceLinks(EloquentCollection $workspaces, Workspace $activeWorkspace): array
{
return $workspaces
->map(fn (Workspace $workspace): array => [
'id' => (int) $workspace->getKey(),
'label' => $workspace->name,
'prefix' => strtoupper(substr($workspace->name, 0, 1)),
'active' => (int) $workspace->getKey() === (int) $activeWorkspace->getKey(),
'tone' => 'room',
])
->values()
->all();
}

/**
* @param array{
* room: string,
* label: string,
* participants: array<int, array{label: string, meta: string}>
* } $workspace
* @return array<int, array{label: string, active?: bool, prefix: string, tone: string}>
*/
private function favoriteLinks(array $workspace, string $viewerName): array
{
$favorites = [
['label' => $workspace['room'], 'active' => true, 'prefix' => '#', 'tone' => 'room'],
['label' => $workspace['label'], 'active' => true, 'prefix' => $workspace['prefix'], 'tone' => 'room'],
['label' => $viewerName, 'prefix' => substr($viewerName, 0, 1), 'tone' => 'human'],
];

Expand Down Expand Up @@ -465,7 +487,7 @@ public function __invoke(

try {
if ($runtimeManager->ensureReady()) {
Workspace::desktopPreview();
SurrealWorkspace::desktopPreview();
$localReady = true;
}
} catch (Throwable $exception) {
Expand All @@ -488,12 +510,16 @@ public function __invoke(
}

$connections = $connectionManager->connectionsFor($request->user());
$activeWorkspace = $this->activeWorkspaceState($activeConnection, $localReady);
$workspaces = $connectionManager->workspacesFor($activeConnection);
$activeWorkspaceModel = $connectionManager->activeWorkspaceFor($activeConnection, $workspaces);
$activeWorkspace = $this->activeWorkspaceState($activeConnection, $activeWorkspaceModel, $localReady);

return view('welcome', [
'mvpShellEnabled' => $mvpShellEnabled,
'activeConnection' => $activeConnection,
'connectionLinks' => $this->connectionLinks($connections, $activeConnection),
'favoritesEnabled' => self::FAVORITES_ENABLED,
'workspaceLinks' => $this->workspaceLinks($workspaces, $activeWorkspaceModel),
'activeWorkspace' => $activeWorkspace,
'favoriteLinks' => $this->favoriteLinks($activeWorkspace, $viewerName),
'roomLinks' => $this->roomLinks($activeConnection, $activeWorkspace['room']),
Expand Down
55 changes: 55 additions & 0 deletions app/Http/Controllers/WorkspaceController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreWorkspaceRequest;
use App\Models\InstanceConnection;
use App\Models\Workspace;
use App\Support\Connections\InstanceConnectionManager;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class WorkspaceController extends Controller
{
public function store(
StoreWorkspaceRequest $request,
InstanceConnectionManager $connectionManager,
): RedirectResponse {
$activeConnection = $connectionManager->activeConnectionFor(
$request->user(),
$request->root(),
$request->session(),
);

if ($activeConnection->kind === InstanceConnection::KIND_SERVER && ! $activeConnection->is_authenticated) {
return to_route('connections.connect', $activeConnection);
}

$connectionManager->createWorkspace($activeConnection, [
'name' => $request->validated('workspace_name'),
]);

return to_route('home');
}

public function activate(
Request $request,
Workspace $workspace,
InstanceConnectionManager $connectionManager,
): RedirectResponse {
if ((int) $workspace->instanceConnection->user_id !== (int) $request->user()->getKey()) {
abort(404);
}

$connectionManager->activateWorkspace($workspace, $request->session());

if (
$workspace->instanceConnection->kind === InstanceConnection::KIND_SERVER
&& ! $workspace->instanceConnection->is_authenticated
) {
return to_route('connections.connect', $workspace->instanceConnection);
}

return to_route('home');
}
}
35 changes: 35 additions & 0 deletions app/Http/Requests/StoreWorkspaceRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;

class StoreWorkspaceRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user() !== null;
}

/**
* @return array<string, ValidationRule|array<int, ValidationRule|string>|string>
*/
public function rules(): array
{
return [
'workspace_name' => ['required', 'string', 'max:255', 'regex:/\\S/'],
];
}

/**
* @return array<string, string>
*/
public function messages(): array
{
return [
'workspace_name.required' => 'Enter a workspace name to create it on this connection.',
'workspace_name.regex' => 'Workspace names cannot be blank.',
];
}
}
18 changes: 18 additions & 0 deletions app/Models/InstanceConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

#[Fillable([
'user_id',
'name',
'kind',
'base_url',
'active_workspace_id',
'session_context',
'last_authenticated_at',
'last_used_at',
Expand Down Expand Up @@ -47,6 +49,22 @@ public function user(): BelongsTo
return $this->belongsTo(User::class);
}

/**
* @return BelongsTo<Workspace, $this>
*/
public function activeWorkspace(): BelongsTo
{
return $this->belongsTo(Workspace::class, 'active_workspace_id');
}

/**
* @return HasMany<Workspace, $this>
*/
public function workspaces(): HasMany
{
return $this->hasMany(Workspace::class);
}

protected function summary(): Attribute
{
return Attribute::make(
Expand Down
27 changes: 27 additions & 0 deletions app/Models/SurrealWorkspace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;

#[Fillable(['id', 'name', 'summary', 'status'])]
class SurrealWorkspace extends SurrealModel
{
protected $table = 'workspace_previews';

public static function desktopPreview(): self
{
$workspace = static::find('desktop-preview');

if ($workspace !== null) {
return $workspace;
}

return static::create([
'id' => 'desktop-preview',
'name' => 'Desktop Preview Workspace',
'summary' => 'A Surreal-backed workspace record created to prove the first Katra persistence layer.',
'status' => 'active',
]);
}
}
31 changes: 15 additions & 16 deletions app/Models/Workspace.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@

namespace App\Models;

use Database\Factories\WorkspaceFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

#[Fillable(['id', 'name', 'summary', 'status'])]
class Workspace extends SurrealModel
#[Fillable(['instance_connection_id', 'name', 'slug', 'summary'])]
class Workspace extends Model
{
protected $table = 'workspaces';
/** @use HasFactory<WorkspaceFactory> */
use HasFactory;

public static function desktopPreview(): self
{
$workspace = static::find('desktop-preview');

if ($workspace !== null) {
return $workspace;
}
protected $table = 'connection_workspaces';

return static::create([
'id' => 'desktop-preview',
'name' => 'Desktop Preview Workspace',
'summary' => 'A Surreal-backed workspace record created to prove the first Katra persistence layer.',
'status' => 'active',
]);
/**
* @return BelongsTo<InstanceConnection, $this>
*/
public function instanceConnection(): BelongsTo
{
return $this->belongsTo(InstanceConnection::class);
}
}
Loading
Loading