mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-12 15:55:03 -05:00
Compare commits
3 Commits
swiftyos/s
...
fix/copilo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5348d97437 | ||
|
|
6573d987ea | ||
|
|
ae8ce8b4ca |
@@ -12,7 +12,6 @@ from .base import BaseTool
|
||||
from .create_agent import CreateAgentTool
|
||||
from .customize_agent import CustomizeAgentTool
|
||||
from .edit_agent import EditAgentTool
|
||||
from .feature_requests import CreateFeatureRequestTool, SearchFeatureRequestsTool
|
||||
from .find_agent import FindAgentTool
|
||||
from .find_block import FindBlockTool
|
||||
from .find_library_agent import FindLibraryAgentTool
|
||||
@@ -46,9 +45,6 @@ TOOL_REGISTRY: dict[str, BaseTool] = {
|
||||
"view_agent_output": AgentOutputTool(),
|
||||
"search_docs": SearchDocsTool(),
|
||||
"get_doc_page": GetDocPageTool(),
|
||||
# Feature request tools
|
||||
"search_feature_requests": SearchFeatureRequestsTool(),
|
||||
"create_feature_request": CreateFeatureRequestTool(),
|
||||
# Workspace tools for CoPilot file operations
|
||||
"list_workspace_files": ListWorkspaceFilesTool(),
|
||||
"read_workspace_file": ReadWorkspaceFileTool(),
|
||||
|
||||
@@ -1,369 +0,0 @@
|
||||
"""Feature request tools - search and create feature requests via Linear."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pydantic import SecretStr
|
||||
|
||||
from backend.api.features.chat.model import ChatSession
|
||||
from backend.api.features.chat.tools.base import BaseTool
|
||||
from backend.api.features.chat.tools.models import (
|
||||
ErrorResponse,
|
||||
FeatureRequestCreatedResponse,
|
||||
FeatureRequestInfo,
|
||||
FeatureRequestSearchResponse,
|
||||
NoResultsResponse,
|
||||
ToolResponseBase,
|
||||
)
|
||||
from backend.blocks.linear._api import LinearClient
|
||||
from backend.data.model import APIKeyCredentials
|
||||
from backend.util.settings import Settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Target project and team IDs in our Linear workspace
|
||||
FEATURE_REQUEST_PROJECT_ID = "13f066f3-f639-4a67-aaa3-31483ebdf8cd"
|
||||
TEAM_ID = "557fd3d5-087e-43a9-83e3-476c8313ce49"
|
||||
|
||||
MAX_SEARCH_RESULTS = 10
|
||||
|
||||
# GraphQL queries/mutations
|
||||
SEARCH_ISSUES_QUERY = """
|
||||
query SearchFeatureRequests($term: String!, $filter: IssueFilter, $first: Int) {
|
||||
searchIssues(term: $term, filter: $filter, first: $first) {
|
||||
nodes {
|
||||
id
|
||||
identifier
|
||||
title
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
CUSTOMER_UPSERT_MUTATION = """
|
||||
mutation CustomerUpsert($input: CustomerUpsertInput!) {
|
||||
customerUpsert(input: $input) {
|
||||
success
|
||||
customer {
|
||||
id
|
||||
name
|
||||
externalIds
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
ISSUE_CREATE_MUTATION = """
|
||||
mutation IssueCreate($input: IssueCreateInput!) {
|
||||
issueCreate(input: $input) {
|
||||
success
|
||||
issue {
|
||||
id
|
||||
identifier
|
||||
title
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
CUSTOMER_NEED_CREATE_MUTATION = """
|
||||
mutation CustomerNeedCreate($input: CustomerNeedCreateInput!) {
|
||||
customerNeedCreate(input: $input) {
|
||||
success
|
||||
need {
|
||||
id
|
||||
body
|
||||
customer {
|
||||
id
|
||||
name
|
||||
}
|
||||
issue {
|
||||
id
|
||||
identifier
|
||||
title
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
_settings: Settings | None = None
|
||||
|
||||
|
||||
def _get_settings() -> Settings:
|
||||
global _settings
|
||||
if _settings is None:
|
||||
_settings = Settings()
|
||||
return _settings
|
||||
|
||||
|
||||
def _get_linear_client() -> LinearClient:
|
||||
"""Create a Linear client using the system API key from settings."""
|
||||
api_key = _get_settings().secrets.linear_api_key
|
||||
if not api_key:
|
||||
raise RuntimeError("LINEAR_API_KEY secret is not configured")
|
||||
credentials = APIKeyCredentials(
|
||||
id="system-linear",
|
||||
provider="linear",
|
||||
api_key=SecretStr(api_key),
|
||||
title="System Linear API Key",
|
||||
)
|
||||
return LinearClient(credentials=credentials)
|
||||
|
||||
|
||||
class SearchFeatureRequestsTool(BaseTool):
|
||||
"""Tool for searching existing feature requests in Linear."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "search_feature_requests"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return (
|
||||
"Search existing feature requests to check if a similar request "
|
||||
"already exists before creating a new one. Returns matching feature "
|
||||
"requests with their ID, title, and description."
|
||||
)
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search term to find matching feature requests.",
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
}
|
||||
|
||||
@property
|
||||
def requires_auth(self) -> bool:
|
||||
return True
|
||||
|
||||
async def _execute(
|
||||
self,
|
||||
user_id: str | None,
|
||||
session: ChatSession,
|
||||
**kwargs,
|
||||
) -> ToolResponseBase:
|
||||
query = kwargs.get("query", "").strip()
|
||||
session_id = session.session_id if session else None
|
||||
|
||||
if not query:
|
||||
return ErrorResponse(
|
||||
message="Please provide a search query.",
|
||||
error="Missing query parameter",
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
client = _get_linear_client()
|
||||
data = await client.query(
|
||||
SEARCH_ISSUES_QUERY,
|
||||
{
|
||||
"term": query,
|
||||
"filter": {
|
||||
"project": {"id": {"eq": FEATURE_REQUEST_PROJECT_ID}},
|
||||
},
|
||||
"first": MAX_SEARCH_RESULTS,
|
||||
},
|
||||
)
|
||||
|
||||
nodes = data.get("searchIssues", {}).get("nodes", [])
|
||||
|
||||
if not nodes:
|
||||
return NoResultsResponse(
|
||||
message=f"No feature requests found matching '{query}'.",
|
||||
suggestions=[
|
||||
"Try different keywords",
|
||||
"Use broader search terms",
|
||||
"You can create a new feature request if none exists",
|
||||
],
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
results = [
|
||||
FeatureRequestInfo(
|
||||
id=node["id"],
|
||||
identifier=node["identifier"],
|
||||
title=node["title"],
|
||||
description=node.get("description"),
|
||||
)
|
||||
for node in nodes
|
||||
]
|
||||
|
||||
return FeatureRequestSearchResponse(
|
||||
message=f"Found {len(results)} feature request(s) matching '{query}'.",
|
||||
results=results,
|
||||
count=len(results),
|
||||
query=query,
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
|
||||
class CreateFeatureRequestTool(BaseTool):
|
||||
"""Tool for creating feature requests (or adding needs to existing ones)."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "create_feature_request"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return (
|
||||
"Create a new feature request or add a customer need to an existing one. "
|
||||
"Always search first with search_feature_requests to avoid duplicates. "
|
||||
"If a matching request exists, pass its ID as existing_issue_id to add "
|
||||
"the user's need to it instead of creating a duplicate."
|
||||
)
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Title for the feature request.",
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Detailed description of what the user wants and why.",
|
||||
},
|
||||
"existing_issue_id": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"If adding a need to an existing feature request, "
|
||||
"provide its Linear issue ID (from search results). "
|
||||
"Omit to create a new feature request."
|
||||
),
|
||||
},
|
||||
},
|
||||
"required": ["title", "description"],
|
||||
}
|
||||
|
||||
@property
|
||||
def requires_auth(self) -> bool:
|
||||
return True
|
||||
|
||||
async def _find_or_create_customer(
|
||||
self, client: LinearClient, user_id: str
|
||||
) -> dict:
|
||||
"""Find existing customer by user_id or create a new one via upsert."""
|
||||
data = await client.mutate(
|
||||
CUSTOMER_UPSERT_MUTATION,
|
||||
{
|
||||
"input": {
|
||||
"name": user_id,
|
||||
"externalId": user_id,
|
||||
},
|
||||
},
|
||||
)
|
||||
result = data.get("customerUpsert", {})
|
||||
if not result.get("success"):
|
||||
raise RuntimeError(f"Failed to upsert customer: {data}")
|
||||
return result["customer"]
|
||||
|
||||
async def _execute(
|
||||
self,
|
||||
user_id: str | None,
|
||||
session: ChatSession,
|
||||
**kwargs,
|
||||
) -> ToolResponseBase:
|
||||
title = kwargs.get("title", "").strip()
|
||||
description = kwargs.get("description", "").strip()
|
||||
existing_issue_id = kwargs.get("existing_issue_id")
|
||||
session_id = session.session_id if session else None
|
||||
|
||||
if not title or not description:
|
||||
return ErrorResponse(
|
||||
message="Both title and description are required.",
|
||||
error="Missing required parameters",
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
if not user_id:
|
||||
return ErrorResponse(
|
||||
message="Authentication required to create feature requests.",
|
||||
error="Missing user_id",
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
client = _get_linear_client()
|
||||
|
||||
# Step 1: Find or create customer for this user
|
||||
customer = await self._find_or_create_customer(client, user_id)
|
||||
customer_id = customer["id"]
|
||||
customer_name = customer["name"]
|
||||
|
||||
# Step 2: Create or reuse issue
|
||||
if existing_issue_id:
|
||||
# Add need to existing issue - we still need the issue details for response
|
||||
is_new_issue = False
|
||||
issue_id = existing_issue_id
|
||||
else:
|
||||
# Create new issue in the feature requests project
|
||||
data = await client.mutate(
|
||||
ISSUE_CREATE_MUTATION,
|
||||
{
|
||||
"input": {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"teamId": TEAM_ID,
|
||||
"projectId": FEATURE_REQUEST_PROJECT_ID,
|
||||
},
|
||||
},
|
||||
)
|
||||
result = data.get("issueCreate", {})
|
||||
if not result.get("success"):
|
||||
return ErrorResponse(
|
||||
message="Failed to create feature request issue.",
|
||||
error=str(data),
|
||||
session_id=session_id,
|
||||
)
|
||||
issue = result["issue"]
|
||||
issue_id = issue["id"]
|
||||
is_new_issue = True
|
||||
|
||||
# Step 3: Create customer need on the issue
|
||||
data = await client.mutate(
|
||||
CUSTOMER_NEED_CREATE_MUTATION,
|
||||
{
|
||||
"input": {
|
||||
"customerId": customer_id,
|
||||
"issueId": issue_id,
|
||||
"body": description,
|
||||
"priority": 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
need_result = data.get("customerNeedCreate", {})
|
||||
if not need_result.get("success"):
|
||||
return ErrorResponse(
|
||||
message="Failed to attach customer need to the feature request.",
|
||||
error=str(data),
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
need = need_result["need"]
|
||||
issue_info = need["issue"]
|
||||
|
||||
return FeatureRequestCreatedResponse(
|
||||
message=(
|
||||
f"{'Created new feature request' if is_new_issue else 'Added your request to existing feature request'} "
|
||||
f"[{issue_info['identifier']}] {issue_info['title']}."
|
||||
),
|
||||
issue_id=issue_info["id"],
|
||||
issue_identifier=issue_info["identifier"],
|
||||
issue_title=issue_info["title"],
|
||||
issue_url=issue_info.get("url", ""),
|
||||
is_new_issue=is_new_issue,
|
||||
customer_name=customer_name,
|
||||
session_id=session_id,
|
||||
)
|
||||
@@ -40,9 +40,6 @@ class ResponseType(str, Enum):
|
||||
OPERATION_IN_PROGRESS = "operation_in_progress"
|
||||
# Input validation
|
||||
INPUT_VALIDATION_ERROR = "input_validation_error"
|
||||
# Feature request types
|
||||
FEATURE_REQUEST_SEARCH = "feature_request_search"
|
||||
FEATURE_REQUEST_CREATED = "feature_request_created"
|
||||
|
||||
|
||||
# Base response model
|
||||
@@ -424,34 +421,3 @@ class AsyncProcessingResponse(ToolResponseBase):
|
||||
status: str = "accepted" # Must be "accepted" for detection
|
||||
operation_id: str | None = None
|
||||
task_id: str | None = None
|
||||
|
||||
|
||||
# Feature request models
|
||||
class FeatureRequestInfo(BaseModel):
|
||||
"""Information about a feature request issue."""
|
||||
|
||||
id: str
|
||||
identifier: str
|
||||
title: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class FeatureRequestSearchResponse(ToolResponseBase):
|
||||
"""Response for search_feature_requests tool."""
|
||||
|
||||
type: ResponseType = ResponseType.FEATURE_REQUEST_SEARCH
|
||||
results: list[FeatureRequestInfo]
|
||||
count: int
|
||||
query: str
|
||||
|
||||
|
||||
class FeatureRequestCreatedResponse(ToolResponseBase):
|
||||
"""Response for create_feature_request tool."""
|
||||
|
||||
type: ResponseType = ResponseType.FEATURE_REQUEST_CREATED
|
||||
issue_id: str
|
||||
issue_identifier: str
|
||||
issue_title: str
|
||||
issue_url: str
|
||||
is_new_issue: bool # False if added to existing
|
||||
customer_name: str
|
||||
|
||||
@@ -658,9 +658,6 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
|
||||
mem0_api_key: str = Field(default="", description="Mem0 API key")
|
||||
elevenlabs_api_key: str = Field(default="", description="ElevenLabs API key")
|
||||
|
||||
linear_api_key: str = Field(
|
||||
default="", description="Linear API key for system-level operations"
|
||||
)
|
||||
linear_client_id: str = Field(default="", description="Linear client ID")
|
||||
linear_client_secret: str = Field(default="", description="Linear client secret")
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@ import { ToolUIPart, UIDataTypes, UIMessage, UITools } from "ai";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { CreateAgentTool } from "../../tools/CreateAgent/CreateAgent";
|
||||
import { EditAgentTool } from "../../tools/EditAgent/EditAgent";
|
||||
import {
|
||||
CreateFeatureRequestTool,
|
||||
SearchFeatureRequestsTool,
|
||||
} from "../../tools/FeatureRequests/FeatureRequests";
|
||||
import { FindAgentsTool } from "../../tools/FindAgents/FindAgents";
|
||||
import { FindBlocksTool } from "../../tools/FindBlocks/FindBlocks";
|
||||
import { RunAgentTool } from "../../tools/RunAgent/RunAgent";
|
||||
@@ -163,7 +159,7 @@ export const ChatMessagesContainer = ({
|
||||
|
||||
return (
|
||||
<Conversation className="min-h-0 flex-1">
|
||||
<ConversationContent className="flex flex-1 flex-col gap-6 px-3 py-6">
|
||||
<ConversationContent className="flex min-h-screen flex-1 flex-col gap-6 px-3 py-6">
|
||||
{isLoading && messages.length === 0 && (
|
||||
<div className="flex min-h-full flex-1 items-center justify-center">
|
||||
<LoadingSpinner className="text-neutral-600" />
|
||||
@@ -258,20 +254,6 @@ export const ChatMessagesContainer = ({
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-search_feature_requests":
|
||||
return (
|
||||
<SearchFeatureRequestsTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-create_feature_request":
|
||||
return (
|
||||
<CreateFeatureRequestTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ import { Text } from "@/components/atoms/Text/Text";
|
||||
import { CopilotChatActionsProvider } from "../components/CopilotChatActionsProvider/CopilotChatActionsProvider";
|
||||
import { CreateAgentTool } from "../tools/CreateAgent/CreateAgent";
|
||||
import { EditAgentTool } from "../tools/EditAgent/EditAgent";
|
||||
import {
|
||||
CreateFeatureRequestTool,
|
||||
SearchFeatureRequestsTool,
|
||||
} from "../tools/FeatureRequests/FeatureRequests";
|
||||
import { FindAgentsTool } from "../tools/FindAgents/FindAgents";
|
||||
import { FindBlocksTool } from "../tools/FindBlocks/FindBlocks";
|
||||
import { RunAgentTool } from "../tools/RunAgent/RunAgent";
|
||||
@@ -49,8 +45,6 @@ const SECTIONS = [
|
||||
"Tool: Create Agent",
|
||||
"Tool: Edit Agent",
|
||||
"Tool: View Agent Output",
|
||||
"Tool: Search Feature Requests",
|
||||
"Tool: Create Feature Request",
|
||||
"Full Conversation Example",
|
||||
] as const;
|
||||
|
||||
@@ -1427,235 +1421,6 @@ export default function StyleguidePage() {
|
||||
</SubSection>
|
||||
</Section>
|
||||
|
||||
{/* ============================================================= */}
|
||||
{/* SEARCH FEATURE REQUESTS */}
|
||||
{/* ============================================================= */}
|
||||
|
||||
<Section title="Tool: Search Feature Requests">
|
||||
<SubSection label="Input streaming">
|
||||
<SearchFeatureRequestsTool
|
||||
part={{
|
||||
type: "tool-search_feature_requests",
|
||||
toolCallId: uid(),
|
||||
state: "input-streaming",
|
||||
input: { query: "dark mode" },
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Input available">
|
||||
<SearchFeatureRequestsTool
|
||||
part={{
|
||||
type: "tool-search_feature_requests",
|
||||
toolCallId: uid(),
|
||||
state: "input-available",
|
||||
input: { query: "dark mode" },
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Output available (with results)">
|
||||
<SearchFeatureRequestsTool
|
||||
part={{
|
||||
type: "tool-search_feature_requests",
|
||||
toolCallId: uid(),
|
||||
state: "output-available",
|
||||
input: { query: "dark mode" },
|
||||
output: {
|
||||
type: "feature_request_search",
|
||||
message:
|
||||
'Found 2 feature request(s) matching "dark mode".',
|
||||
query: "dark mode",
|
||||
count: 2,
|
||||
results: [
|
||||
{
|
||||
id: "fr-001",
|
||||
identifier: "INT-42",
|
||||
title: "Add dark mode to the platform",
|
||||
description:
|
||||
"Users have requested a dark mode option for the builder and copilot interfaces to reduce eye strain during long sessions.",
|
||||
},
|
||||
{
|
||||
id: "fr-002",
|
||||
identifier: "INT-87",
|
||||
title: "Dark theme for agent output viewer",
|
||||
description:
|
||||
"Specifically requesting dark theme support for the agent output/execution viewer panel.",
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Output available (no results)">
|
||||
<SearchFeatureRequestsTool
|
||||
part={{
|
||||
type: "tool-search_feature_requests",
|
||||
toolCallId: uid(),
|
||||
state: "output-available",
|
||||
input: { query: "teleportation" },
|
||||
output: {
|
||||
type: "no_results",
|
||||
message:
|
||||
"No feature requests found matching 'teleportation'.",
|
||||
suggestions: [
|
||||
"Try different keywords",
|
||||
"Use broader search terms",
|
||||
"You can create a new feature request if none exists",
|
||||
],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Output available (error)">
|
||||
<SearchFeatureRequestsTool
|
||||
part={{
|
||||
type: "tool-search_feature_requests",
|
||||
toolCallId: uid(),
|
||||
state: "output-available",
|
||||
input: { query: "dark mode" },
|
||||
output: {
|
||||
type: "error",
|
||||
message: "Failed to search feature requests.",
|
||||
error: "LINEAR_API_KEY environment variable is not set",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Output error">
|
||||
<SearchFeatureRequestsTool
|
||||
part={{
|
||||
type: "tool-search_feature_requests",
|
||||
toolCallId: uid(),
|
||||
state: "output-error",
|
||||
input: { query: "dark mode" },
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
</Section>
|
||||
|
||||
{/* ============================================================= */}
|
||||
{/* CREATE FEATURE REQUEST */}
|
||||
{/* ============================================================= */}
|
||||
|
||||
<Section title="Tool: Create Feature Request">
|
||||
<SubSection label="Input streaming">
|
||||
<CreateFeatureRequestTool
|
||||
part={{
|
||||
type: "tool-create_feature_request",
|
||||
toolCallId: uid(),
|
||||
state: "input-streaming",
|
||||
input: {
|
||||
title: "Add dark mode",
|
||||
description: "I would love dark mode for the platform.",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Input available">
|
||||
<CreateFeatureRequestTool
|
||||
part={{
|
||||
type: "tool-create_feature_request",
|
||||
toolCallId: uid(),
|
||||
state: "input-available",
|
||||
input: {
|
||||
title: "Add dark mode",
|
||||
description: "I would love dark mode for the platform.",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Output available (new issue created)">
|
||||
<CreateFeatureRequestTool
|
||||
part={{
|
||||
type: "tool-create_feature_request",
|
||||
toolCallId: uid(),
|
||||
state: "output-available",
|
||||
input: {
|
||||
title: "Add dark mode",
|
||||
description: "I would love dark mode for the platform.",
|
||||
},
|
||||
output: {
|
||||
type: "feature_request_created",
|
||||
message:
|
||||
"Created new feature request [INT-105] Add dark mode.",
|
||||
issue_id: "issue-new-123",
|
||||
issue_identifier: "INT-105",
|
||||
issue_title: "Add dark mode",
|
||||
issue_url:
|
||||
"https://linear.app/autogpt/issue/INT-105/add-dark-mode",
|
||||
is_new_issue: true,
|
||||
customer_name: "user-abc-123",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Output available (added to existing issue)">
|
||||
<CreateFeatureRequestTool
|
||||
part={{
|
||||
type: "tool-create_feature_request",
|
||||
toolCallId: uid(),
|
||||
state: "output-available",
|
||||
input: {
|
||||
title: "Dark mode support",
|
||||
description:
|
||||
"Please add dark mode, it would help with long sessions.",
|
||||
existing_issue_id: "fr-001",
|
||||
},
|
||||
output: {
|
||||
type: "feature_request_created",
|
||||
message:
|
||||
"Added your request to existing feature request [INT-42] Add dark mode to the platform.",
|
||||
issue_id: "fr-001",
|
||||
issue_identifier: "INT-42",
|
||||
issue_title: "Add dark mode to the platform",
|
||||
issue_url:
|
||||
"https://linear.app/autogpt/issue/INT-42/add-dark-mode-to-the-platform",
|
||||
is_new_issue: false,
|
||||
customer_name: "user-xyz-789",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Output available (error)">
|
||||
<CreateFeatureRequestTool
|
||||
part={{
|
||||
type: "tool-create_feature_request",
|
||||
toolCallId: uid(),
|
||||
state: "output-available",
|
||||
input: {
|
||||
title: "Add dark mode",
|
||||
description: "I would love dark mode.",
|
||||
},
|
||||
output: {
|
||||
type: "error",
|
||||
message:
|
||||
"Failed to attach customer need to the feature request.",
|
||||
error: "Linear API request failed (500): Internal error",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
|
||||
<SubSection label="Output error">
|
||||
<CreateFeatureRequestTool
|
||||
part={{
|
||||
type: "tool-create_feature_request",
|
||||
toolCallId: uid(),
|
||||
state: "output-error",
|
||||
input: { title: "Add dark mode" },
|
||||
}}
|
||||
/>
|
||||
</SubSection>
|
||||
</Section>
|
||||
|
||||
{/* ============================================================= */}
|
||||
{/* FULL CONVERSATION EXAMPLE */}
|
||||
{/* ============================================================= */}
|
||||
|
||||
@@ -152,7 +152,7 @@ export function CreateAgentTool({ part }: Props) {
|
||||
<ToolAccordion {...getAccordionMeta(output)}>
|
||||
{isOperating && (
|
||||
<ContentGrid>
|
||||
<ProgressBar value={progress} className="max-w-[280px]" />
|
||||
<ProgressBar value={progress} />
|
||||
<ContentHint>
|
||||
This could take a few minutes, grab a coffee ☕
|
||||
</ContentHint>
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import {
|
||||
ContentBadge,
|
||||
ContentCard,
|
||||
ContentCardDescription,
|
||||
ContentCardHeader,
|
||||
ContentCardTitle,
|
||||
ContentGrid,
|
||||
ContentLink,
|
||||
ContentMessage,
|
||||
ContentSuggestionsList,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import {
|
||||
AccordionIcon,
|
||||
getAccordionTitle,
|
||||
getAnimationText,
|
||||
getFeatureRequestOutput,
|
||||
isCreatedOutput,
|
||||
isErrorOutput,
|
||||
isNoResultsOutput,
|
||||
isSearchResultsOutput,
|
||||
ToolIcon,
|
||||
type FeatureRequestToolType,
|
||||
} from "./helpers";
|
||||
|
||||
export interface FeatureRequestToolPart {
|
||||
type: FeatureRequestToolType;
|
||||
toolCallId: string;
|
||||
state: ToolUIPart["state"];
|
||||
input?: unknown;
|
||||
output?: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
part: FeatureRequestToolPart;
|
||||
}
|
||||
|
||||
function truncate(text: string, maxChars: number): string {
|
||||
const trimmed = text.trim();
|
||||
if (trimmed.length <= maxChars) return trimmed;
|
||||
return `${trimmed.slice(0, maxChars).trimEnd()}…`;
|
||||
}
|
||||
|
||||
export function SearchFeatureRequestsTool({ part }: Props) {
|
||||
const output = getFeatureRequestOutput(part);
|
||||
const text = getAnimationText(part);
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorOutput(output));
|
||||
|
||||
const normalized = useMemo(() => {
|
||||
if (!output) return null;
|
||||
return { title: getAccordionTitle(part.type, output) };
|
||||
}, [output, part.type]);
|
||||
|
||||
const isOutputAvailable = part.state === "output-available" && !!output;
|
||||
|
||||
const searchOutput =
|
||||
isOutputAvailable && output && isSearchResultsOutput(output)
|
||||
? output
|
||||
: null;
|
||||
const noResultsOutput =
|
||||
isOutputAvailable && output && isNoResultsOutput(output) ? output : null;
|
||||
const errorOutput =
|
||||
isOutputAvailable && output && isErrorOutput(output) ? output : null;
|
||||
|
||||
const hasExpandableContent =
|
||||
isOutputAvailable &&
|
||||
((!!searchOutput && searchOutput.count > 0) ||
|
||||
!!noResultsOutput ||
|
||||
!!errorOutput);
|
||||
|
||||
const accordionDescription =
|
||||
hasExpandableContent && searchOutput
|
||||
? `Found ${searchOutput.count} result${searchOutput.count === 1 ? "" : "s"} for "${searchOutput.query}"`
|
||||
: hasExpandableContent && (noResultsOutput || errorOutput)
|
||||
? ((noResultsOutput ?? errorOutput)?.message ?? null)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<ToolIcon
|
||||
toolType={part.type}
|
||||
isStreaming={isStreaming}
|
||||
isError={isError}
|
||||
/>
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasExpandableContent && normalized && (
|
||||
<ToolAccordion
|
||||
icon={<AccordionIcon toolType={part.type} />}
|
||||
title={normalized.title}
|
||||
description={accordionDescription}
|
||||
>
|
||||
{searchOutput && (
|
||||
<ContentGrid>
|
||||
{searchOutput.results.map((r) => (
|
||||
<ContentCard key={r.id}>
|
||||
<ContentCardHeader>
|
||||
<ContentCardTitle>
|
||||
{r.identifier} — {r.title}
|
||||
</ContentCardTitle>
|
||||
</ContentCardHeader>
|
||||
{r.description && (
|
||||
<ContentCardDescription>
|
||||
{truncate(r.description, 200)}
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
</ContentCard>
|
||||
))}
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{noResultsOutput && (
|
||||
<div>
|
||||
<ContentMessage>{noResultsOutput.message}</ContentMessage>
|
||||
{noResultsOutput.suggestions &&
|
||||
noResultsOutput.suggestions.length > 0 && (
|
||||
<ContentSuggestionsList items={noResultsOutput.suggestions} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errorOutput && (
|
||||
<div>
|
||||
<ContentMessage>{errorOutput.message}</ContentMessage>
|
||||
{errorOutput.error && (
|
||||
<ContentCardDescription>
|
||||
{errorOutput.error}
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreateFeatureRequestTool({ part }: Props) {
|
||||
const output = getFeatureRequestOutput(part);
|
||||
const text = getAnimationText(part);
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorOutput(output));
|
||||
|
||||
const normalized = useMemo(() => {
|
||||
if (!output) return null;
|
||||
return { title: getAccordionTitle(part.type, output) };
|
||||
}, [output, part.type]);
|
||||
|
||||
const isOutputAvailable = part.state === "output-available" && !!output;
|
||||
|
||||
const createdOutput =
|
||||
isOutputAvailable && output && isCreatedOutput(output) ? output : null;
|
||||
const errorOutput =
|
||||
isOutputAvailable && output && isErrorOutput(output) ? output : null;
|
||||
|
||||
const hasExpandableContent =
|
||||
isOutputAvailable && (!!createdOutput || !!errorOutput);
|
||||
|
||||
const accordionDescription =
|
||||
hasExpandableContent && createdOutput
|
||||
? `${createdOutput.issue_identifier} — ${createdOutput.issue_title}`
|
||||
: hasExpandableContent && errorOutput
|
||||
? errorOutput.message
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<ToolIcon
|
||||
toolType={part.type}
|
||||
isStreaming={isStreaming}
|
||||
isError={isError}
|
||||
/>
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasExpandableContent && normalized && (
|
||||
<ToolAccordion
|
||||
icon={<AccordionIcon toolType={part.type} />}
|
||||
title={normalized.title}
|
||||
description={accordionDescription}
|
||||
>
|
||||
{createdOutput && (
|
||||
<ContentCard>
|
||||
<ContentCardHeader
|
||||
action={
|
||||
createdOutput.issue_url ? (
|
||||
<ContentLink href={createdOutput.issue_url}>
|
||||
View
|
||||
</ContentLink>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<ContentCardTitle>
|
||||
{createdOutput.issue_identifier} — {createdOutput.issue_title}
|
||||
</ContentCardTitle>
|
||||
</ContentCardHeader>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<ContentBadge>
|
||||
{createdOutput.is_new_issue ? "New" : "Existing"}
|
||||
</ContentBadge>
|
||||
</div>
|
||||
<ContentMessage>{createdOutput.message}</ContentMessage>
|
||||
</ContentCard>
|
||||
)}
|
||||
|
||||
{errorOutput && (
|
||||
<div>
|
||||
<ContentMessage>{errorOutput.message}</ContentMessage>
|
||||
{errorOutput.error && (
|
||||
<ContentCardDescription>
|
||||
{errorOutput.error}
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
LightbulbIcon,
|
||||
MagnifyingGlassIcon,
|
||||
PlusCircleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Types (local until API client is regenerated) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
interface FeatureRequestInfo {
|
||||
id: string;
|
||||
identifier: string;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface FeatureRequestSearchResponse {
|
||||
type: "feature_request_search";
|
||||
message: string;
|
||||
results: FeatureRequestInfo[];
|
||||
count: number;
|
||||
query: string;
|
||||
}
|
||||
|
||||
export interface FeatureRequestCreatedResponse {
|
||||
type: "feature_request_created";
|
||||
message: string;
|
||||
issue_id: string;
|
||||
issue_identifier: string;
|
||||
issue_title: string;
|
||||
issue_url: string;
|
||||
is_new_issue: boolean;
|
||||
customer_name: string;
|
||||
}
|
||||
|
||||
interface NoResultsResponse {
|
||||
type: "no_results";
|
||||
message: string;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
interface ErrorResponse {
|
||||
type: "error";
|
||||
message: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type FeatureRequestOutput =
|
||||
| FeatureRequestSearchResponse
|
||||
| FeatureRequestCreatedResponse
|
||||
| NoResultsResponse
|
||||
| ErrorResponse;
|
||||
|
||||
export type FeatureRequestToolType =
|
||||
| "tool-search_feature_requests"
|
||||
| "tool-create_feature_request"
|
||||
| string;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Output parsing */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function parseOutput(output: unknown): FeatureRequestOutput | null {
|
||||
if (!output) return null;
|
||||
if (typeof output === "string") {
|
||||
const trimmed = output.trim();
|
||||
if (!trimmed) return null;
|
||||
try {
|
||||
return parseOutput(JSON.parse(trimmed) as unknown);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (typeof output === "object") {
|
||||
const type = (output as { type?: unknown }).type;
|
||||
if (
|
||||
type === "feature_request_search" ||
|
||||
type === "feature_request_created" ||
|
||||
type === "no_results" ||
|
||||
type === "error"
|
||||
) {
|
||||
return output as FeatureRequestOutput;
|
||||
}
|
||||
// Fallback structural checks
|
||||
if ("results" in output && "query" in output)
|
||||
return output as FeatureRequestSearchResponse;
|
||||
if ("issue_identifier" in output)
|
||||
return output as FeatureRequestCreatedResponse;
|
||||
if ("suggestions" in output && !("error" in output))
|
||||
return output as NoResultsResponse;
|
||||
if ("error" in output || "details" in output)
|
||||
return output as ErrorResponse;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getFeatureRequestOutput(
|
||||
part: unknown,
|
||||
): FeatureRequestOutput | null {
|
||||
if (!part || typeof part !== "object") return null;
|
||||
return parseOutput((part as { output?: unknown }).output);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Type guards */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function isSearchResultsOutput(
|
||||
output: FeatureRequestOutput,
|
||||
): output is FeatureRequestSearchResponse {
|
||||
return (
|
||||
output.type === "feature_request_search" ||
|
||||
("results" in output && "query" in output)
|
||||
);
|
||||
}
|
||||
|
||||
export function isCreatedOutput(
|
||||
output: FeatureRequestOutput,
|
||||
): output is FeatureRequestCreatedResponse {
|
||||
return (
|
||||
output.type === "feature_request_created" || "issue_identifier" in output
|
||||
);
|
||||
}
|
||||
|
||||
export function isNoResultsOutput(
|
||||
output: FeatureRequestOutput,
|
||||
): output is NoResultsResponse {
|
||||
return (
|
||||
output.type === "no_results" ||
|
||||
("suggestions" in output && !("error" in output))
|
||||
);
|
||||
}
|
||||
|
||||
export function isErrorOutput(
|
||||
output: FeatureRequestOutput,
|
||||
): output is ErrorResponse {
|
||||
return output.type === "error" || "error" in output;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Accordion metadata */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function getAccordionTitle(
|
||||
toolType: FeatureRequestToolType,
|
||||
output: FeatureRequestOutput,
|
||||
): string {
|
||||
if (toolType === "tool-search_feature_requests") {
|
||||
if (isSearchResultsOutput(output)) return "Feature requests";
|
||||
if (isNoResultsOutput(output)) return "No feature requests found";
|
||||
return "Feature request search error";
|
||||
}
|
||||
if (isCreatedOutput(output)) {
|
||||
return output.is_new_issue
|
||||
? "Feature request created"
|
||||
: "Added to feature request";
|
||||
}
|
||||
if (isErrorOutput(output)) return "Feature request error";
|
||||
return "Feature request";
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Animation text */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
interface AnimationPart {
|
||||
type: FeatureRequestToolType;
|
||||
state: ToolUIPart["state"];
|
||||
input?: unknown;
|
||||
output?: unknown;
|
||||
}
|
||||
|
||||
export function getAnimationText(part: AnimationPart): string {
|
||||
if (part.type === "tool-search_feature_requests") {
|
||||
const query = (part.input as { query?: string } | undefined)?.query?.trim();
|
||||
const queryText = query ? ` for "${query}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return `Searching feature requests${queryText}`;
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return `Searching feature requests${queryText}`;
|
||||
if (isSearchResultsOutput(output)) {
|
||||
return `Found ${output.count} feature request${output.count === 1 ? "" : "s"}${queryText}`;
|
||||
}
|
||||
if (isNoResultsOutput(output))
|
||||
return `No feature requests found${queryText}`;
|
||||
return `Error searching feature requests${queryText}`;
|
||||
}
|
||||
case "output-error":
|
||||
return `Error searching feature requests${queryText}`;
|
||||
default:
|
||||
return "Searching feature requests";
|
||||
}
|
||||
}
|
||||
|
||||
// create_feature_request
|
||||
const title = (part.input as { title?: string } | undefined)?.title?.trim();
|
||||
const titleText = title ? ` "${title}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return `Creating feature request${titleText}`;
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return `Creating feature request${titleText}`;
|
||||
if (isCreatedOutput(output)) {
|
||||
return output.is_new_issue
|
||||
? `Created ${output.issue_identifier}`
|
||||
: `Added to ${output.issue_identifier}`;
|
||||
}
|
||||
if (isErrorOutput(output)) return "Error creating feature request";
|
||||
return `Created feature request${titleText}`;
|
||||
}
|
||||
case "output-error":
|
||||
return "Error creating feature request";
|
||||
default:
|
||||
return "Creating feature request";
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Icons */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function ToolIcon({
|
||||
toolType,
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
toolType: FeatureRequestToolType;
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
const IconComponent =
|
||||
toolType === "tool-create_feature_request"
|
||||
? PlusCircleIcon
|
||||
: MagnifyingGlassIcon;
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon({
|
||||
toolType,
|
||||
}: {
|
||||
toolType: FeatureRequestToolType;
|
||||
}) {
|
||||
const IconComponent =
|
||||
toolType === "tool-create_feature_request"
|
||||
? CheckCircleIcon
|
||||
: LightbulbIcon;
|
||||
return <IconComponent size={32} weight="light" />;
|
||||
}
|
||||
@@ -10495,9 +10495,7 @@
|
||||
"operation_started",
|
||||
"operation_pending",
|
||||
"operation_in_progress",
|
||||
"input_validation_error",
|
||||
"feature_request_search",
|
||||
"feature_request_created"
|
||||
"input_validation_error"
|
||||
],
|
||||
"title": "ResponseType",
|
||||
"description": "Types of tool responses."
|
||||
|
||||
@@ -30,7 +30,6 @@ export function APIKeyCredentialsModal({
|
||||
const {
|
||||
form,
|
||||
isLoading,
|
||||
isSubmitting,
|
||||
supportsApiKey,
|
||||
providerName,
|
||||
schemaDescription,
|
||||
@@ -139,12 +138,7 @@ export function APIKeyCredentialsModal({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
className="min-w-68"
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Button type="submit" className="min-w-68">
|
||||
Add API Key
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
CredentialsMetaInput,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useState } from "react";
|
||||
import { useForm, type UseFormReturn } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -27,7 +26,6 @@ export function useAPIKeyCredentialsModal({
|
||||
}: Args): {
|
||||
form: UseFormReturn<APIKeyFormValues>;
|
||||
isLoading: boolean;
|
||||
isSubmitting: boolean;
|
||||
supportsApiKey: boolean;
|
||||
provider?: string;
|
||||
providerName?: string;
|
||||
@@ -35,7 +33,6 @@ export function useAPIKeyCredentialsModal({
|
||||
onSubmit: (values: APIKeyFormValues) => Promise<void>;
|
||||
} {
|
||||
const credentials = useCredentials(schema, siblingInputs);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const formSchema = z.object({
|
||||
apiKey: z.string().min(1, "API Key is required"),
|
||||
@@ -43,42 +40,48 @@ export function useAPIKeyCredentialsModal({
|
||||
expiresAt: z.string().optional(),
|
||||
});
|
||||
|
||||
function getDefaultExpirationDate(): string {
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
tomorrow.setHours(0, 0, 0, 0);
|
||||
const year = tomorrow.getFullYear();
|
||||
const month = String(tomorrow.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(tomorrow.getDate()).padStart(2, "0");
|
||||
const hours = String(tomorrow.getHours()).padStart(2, "0");
|
||||
const minutes = String(tomorrow.getMinutes()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
const form = useForm<APIKeyFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
apiKey: "",
|
||||
title: "",
|
||||
expiresAt: "",
|
||||
expiresAt: getDefaultExpirationDate(),
|
||||
},
|
||||
});
|
||||
|
||||
async function onSubmit(values: APIKeyFormValues) {
|
||||
if (!credentials || credentials.isLoading) return;
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const expiresAt = values.expiresAt
|
||||
? new Date(values.expiresAt).getTime() / 1000
|
||||
: undefined;
|
||||
const newCredentials = await credentials.createAPIKeyCredentials({
|
||||
api_key: values.apiKey,
|
||||
title: values.title,
|
||||
expires_at: expiresAt,
|
||||
});
|
||||
onCredentialsCreate({
|
||||
provider: credentials.provider,
|
||||
id: newCredentials.id,
|
||||
type: "api_key",
|
||||
title: newCredentials.title,
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
const expiresAt = values.expiresAt
|
||||
? new Date(values.expiresAt).getTime() / 1000
|
||||
: undefined;
|
||||
const newCredentials = await credentials.createAPIKeyCredentials({
|
||||
api_key: values.apiKey,
|
||||
title: values.title,
|
||||
expires_at: expiresAt,
|
||||
});
|
||||
onCredentialsCreate({
|
||||
provider: credentials.provider,
|
||||
id: newCredentials.id,
|
||||
type: "api_key",
|
||||
title: newCredentials.title,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
isLoading: !credentials || credentials.isLoading,
|
||||
isSubmitting,
|
||||
supportsApiKey: !!credentials?.supportsApiKey,
|
||||
provider: credentials?.provider,
|
||||
providerName:
|
||||
|
||||
Reference in New Issue
Block a user