feat(ai): Add the Agents view#91
Conversation
Code Review SummaryThis PR adds the 'Agents' feature, allowing users to define and run markdown-based AI agents. It includes API service updates, navigation, and a comprehensive management view. 🚀 Key Improvements
💡 Minor Suggestions
|
| <div class="runtime-title"> | ||
| <Icon name="bot" :size="16" /> | ||
| <h3>{{ rt.name }}</h3> | ||
| <span class="scope-badge" :class="rt.scope"> |
There was a problem hiding this comment.
If rt.scope is 'deployment' but the deployment property is null or undefined, the badge will appear empty. Providing a fallback string ensures the UI remains descriptive.
| <span class="scope-badge" :class="rt.scope"> | |
| <span class="scope-badge" :class="rt.scope"> | |
| {{ rt.scope === "deployment" ? (rt.deployment || "deployment") : "system" }} | |
| </span> |
6853127 to
f405966
Compare
Deploying flatrun-ui with
|
| Latest commit: |
798fceb
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1d0f37cc.flatrun-ui.pages.dev |
| Branch Preview URL: | https://feat-agent-runtimes.flatrun-ui.pages.dev |
| <div class="agent-title"> | ||
| <Icon name="bot" :size="16" /> | ||
| <h3>{{ agent.name }}</h3> | ||
| <span class="scope-badge" :class="agent.scope"> |
There was a problem hiding this comment.
If an agent's scope is 'deployment' but the deployment property is missing or null, the badge will appear empty. Providing a fallback string (similar to other views in the system) ensures the UI remains descriptive.
| <span class="scope-badge" :class="agent.scope"> | |
| <span class="scope-badge" :class="agent.scope"> | |
| {{ agent.scope === "deployment" ? (agent.deployment || "deployment") : "system" }} | |
| </span> |
| </div> | ||
| <div class="header-actions"> | ||
| <button class="btn btn-icon" :disabled="loading" @click="fetchAgents"> | ||
| <i class="pi pi-refresh" :class="{ 'pi-spin': loading }" /> |
There was a problem hiding this comment.
For visual consistency across the application, consider using the project's custom Icon component instead of raw <i> tags with PrimeIcons classes where possible.
| <i class="pi pi-refresh" :class="{ 'pi-spin': loading }" /> | |
| <Icon name="refresh" :class="{ 'pi-spin': loading }" :size="16" /> |
f405966 to
3b2df84
Compare
| </div> | ||
|
|
||
| <div v-if="loading && agents.length === 0" class="loading-state"> | ||
| <i class="pi pi-spin pi-spinner" /> |
There was a problem hiding this comment.
For consistency with other components in the application, use the custom Icon component for the loading spinner.
| <i class="pi pi-spin pi-spinner" /> | |
| + <Icon name="spinner" class="pi-spin" :size="20" /> |
| const { data } = await agentsApi.run(agent.name); | ||
| // The run is a session; open the assistant on it so pending tool | ||
| // approvals and the transcript are handled by the existing chat. | ||
| assist.embedded = false; |
There was a problem hiding this comment.
It is generally better to perform the asynchronous data loading before making the assistant panel visible. This ensures that the UI doesn't display a stale or empty state if loadSession fails or takes time to complete.
| assist.embedded = false; | |
| + await assist.loadSession(data.id); | |
| + assist.subject = agent.name; | |
| + assist.embedded = false; | |
| + assist.visible = true; |
Agents get their own section in the navigation, aligned with the other main menu items. The view lists each agent with its scope; Run opens the assistant on the resulting session, so the transcript and any pending tool approvals are handled in the existing chat. Agents can now also be written in the panel: a New Agent editor with a starter template, edit and delete on each card, and validation errors surfaced on save so an invalid definition is never stored. Together with the assistant's ability to write agent files on request, an agent can be created by describing it in chat, by editing it in the panel, or by dropping a file in the agents directory.
3b2df84 to
798fceb
Compare
| const authStore = useAuthStore(); | ||
| const canWrite = computed(() => authStore.hasPermission("settings:write")); | ||
|
|
||
| const editorExtensions = [yaml(), oneDark]; |
There was a problem hiding this comment.
Since agents are markdown files with instructions, using the markdown extension for Codemirror would provide better syntax highlighting for the body than the yaml extension, which is only strictly correct for the frontmatter section.
| const editorExtensions = [yaml(), oneDark]; | |
| const editorExtensions = [markdown(), oneDark]; |
| }; | ||
|
|
||
| const saveAgent = async () => { | ||
| const name = editingName.value || nameInput.value.trim(); |
There was a problem hiding this comment.
Validating the agent name on the client side provides immediate feedback and prevents unnecessary API calls that would fail with a 400 error. It ensures the name matches the constraints mentioned in the input placeholder.
| const name = editingName.value || nameInput.value.trim(); | |
| const name = editingName.value || nameInput.value.trim(); | |
| const nameRegex = /^[a-zA-Z0-9._-]+$/; | |
| if (!name || !nameRegex.test(name)) { | |
| notifications.warning("Invalid name", "Name must only contain letters, digits, dots, dashes, and underscores"); | |
| return; | |
| } |
| assistant's tools; anything that changes state waits for approval. | ||
| `; | ||
|
|
||
| const openEditor = async (agent: AgentDefinition | null) => { |
There was a problem hiding this comment.
The modal only opens after the content is successfully fetched. On slower connections, the UI feels unresponsive between clicking 'Edit' and the modal appearing. It's better to open the modal immediately and show a loading state inside the editor.
| const openEditor = async (agent: AgentDefinition | null) => { | |
| const openEditor = async (agent: AgentDefinition | null) => { | |
| editingName.value = agent?.name ?? null; | |
| nameInput.value = ""; | |
| editorContent.value = editorTemplate; | |
| editorOpen.value = true; | |
| if (agent) { | |
| try { | |
| const { data } = await agentsApi.get(agent.name); | |
| editorContent.value = data.content; | |
| } catch (err: any) { | |
| notifications.error("Error", err.response?.data?.error || "Failed to load agent"); | |
| editorOpen.value = false; | |
| } | |
| } | |
| }; |
Panel surface for agents (flatrun/agent#189: markdown files run as AI sessions by the runtime). Agents get their own section in the navigation. The list shows each agent with its scope; Run opens the assistant on the resulting session, so the transcript and any pending tool approvals are handled by the existing chat rather than a new surface. The empty state shows where the files live and an example within what the tools can actually do.