Skip to content
Open
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
2 changes: 2 additions & 0 deletions backend/app/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
groups,
health,
knowledge,
meeting,
oidc,
openapi_responses,
quota,
Expand Down Expand Up @@ -78,4 +79,5 @@
)
api_router.include_router(rag.router, prefix="/rag", tags=["rag"])
api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
api_router.include_router(meeting.router, prefix="/meeting", tags=["meeting"])
api_router.include_router(k_router)
131 changes: 131 additions & 0 deletions backend/app/api/endpoints/meeting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# SPDX-FileCopyrightText: 2025 WeCode, Inc.
#
# SPDX-License-Identifier: Apache-2.0

"""
Meeting booking API endpoints.

This module provides endpoints for meeting booking operations.
Currently implements mock responses for development/testing.
In production, these endpoints can be configured to proxy to
an external meeting service.
"""

import asyncio
import time

from fastapi import APIRouter, Depends

from app.core.security import get_current_user
from app.models.user import User
from app.schemas.meeting import (
CancelMeetingResponse,
CreateMeetingRequest,
MeetingBookingResponse,
MeetingHealthResponse,
)

router = APIRouter()


def generate_meeting_id() -> str:
"""Generate a unique meeting ID."""
timestamp = int(time.time() * 1000)
return f"MTG-{timestamp:X}"


@router.post("/book", response_model=MeetingBookingResponse)
async def create_meeting(
request: CreateMeetingRequest,
current_user: User = Depends(get_current_user),
) -> MeetingBookingResponse:
"""
Create a new meeting booking.

Currently returns mock data for development.
In production, this endpoint can be configured to:
1. Call an external meeting service API
2. Store meeting data in database
3. Send calendar invitations

Args:
request: Meeting booking request data
current_user: Authenticated user making the request

Returns:
MeetingBookingResponse with booking result
"""
# Mock implementation for development
# TODO: Add support for external meeting service integration
return await mock_create_meeting(request, current_user)


async def mock_create_meeting(
request: CreateMeetingRequest,
current_user: User,
) -> MeetingBookingResponse:
"""
Mock implementation for meeting creation.
Simulates a successful booking with realistic delay.
"""
# Simulate network delay (300-500ms)
await asyncio.sleep(0.3 + (time.time() % 0.2))

# Generate mock meeting ID
meeting_id = generate_meeting_id()

# Build participant info for message
participant_count = len(request.participant_ids)
manual_count = (
len(request.manual_participants) if request.manual_participants else 0
)
total_participants = participant_count + manual_count

message = f'会议 "{request.title}" 已成功预约。'
if total_participants > 0:
message += f" 已邀请 {total_participants} 位参会人员。"

return MeetingBookingResponse(
success=True,
meeting_id=meeting_id,
message=message,
)


@router.delete("/{meeting_id}", response_model=CancelMeetingResponse)
async def cancel_meeting(
meeting_id: str,
current_user: User = Depends(get_current_user),
) -> CancelMeetingResponse:
"""
Cancel an existing meeting.

Args:
meeting_id: The ID of the meeting to cancel
current_user: Authenticated user making the request

Returns:
CancelMeetingResponse with cancellation result
"""
# Mock implementation
await asyncio.sleep(0.2)

return CancelMeetingResponse(
success=True,
message=f"会议 {meeting_id} 已成功取消。",
)


@router.get("/health", response_model=MeetingHealthResponse)
async def meeting_service_health() -> MeetingHealthResponse:
"""
Check meeting service health status.

Returns:
Health status of the meeting service
"""
return MeetingHealthResponse(
status="healthy",
mode="mock", # Will be "production" when external service is configured
message="Meeting service is running in mock mode",
)
51 changes: 51 additions & 0 deletions backend/app/schemas/meeting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# SPDX-FileCopyrightText: 2025 WeCode, Inc.
#
# SPDX-License-Identifier: Apache-2.0

"""
Meeting booking schemas for request/response validation.
"""

from typing import List, Optional

from pydantic import BaseModel, Field


class CreateMeetingRequest(BaseModel):
"""Request schema for creating a meeting booking."""

title: str = Field(..., description="Meeting title", min_length=1, max_length=200)
start_time: str = Field(..., description="Meeting start time in ISO format")
end_time: str = Field(..., description="Meeting end time in ISO format")
room_id: str = Field(..., description="Meeting room ID")
participant_ids: List[str] = Field(
default_factory=list, description="List of participant IDs"
)
manual_participants: Optional[List[str]] = Field(
default=None, description="Manually entered participants (name or email)"
)


class MeetingBookingResponse(BaseModel):
"""Response schema for meeting booking operations."""

success: bool = Field(..., description="Whether the operation was successful")
meeting_id: Optional[str] = Field(default=None, description="Created meeting ID")
error: Optional[str] = Field(default=None, description="Error message if failed")
message: Optional[str] = Field(default=None, description="Additional message")


class CancelMeetingResponse(BaseModel):
"""Response schema for meeting cancellation."""

success: bool = Field(..., description="Whether the cancellation was successful")
message: Optional[str] = Field(default=None, description="Status message")
error: Optional[str] = Field(default=None, description="Error message if failed")


class MeetingHealthResponse(BaseModel):
"""Response schema for meeting service health check."""

status: str = Field(..., description="Service status")
mode: str = Field(..., description="Service mode (mock or production)")
message: str = Field(..., description="Status message")
128 changes: 128 additions & 0 deletions backend/init_data/skills/meeting-booking/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# SPDX-FileCopyrightText: 2025 WeCode, Inc.
#
# SPDX-License-Identifier: Apache-2.0

---
description: "Intelligent Meeting Booking Assistant - Identifies meeting requirements, fetches available resources, and generates interactive booking forms"
displayName: "Meeting Booking"
version: "1.0.0"
author: "Wegent Team"
tags: ["meeting", "booking", "calendar", "scheduling"]
bindShells: ["Chat"]
provider:
module: provider
class: MeetingBookingToolProvider
tools:
- name: get_meeting_resources
provider: meeting-booking
config:
timeout: 30
dependencies:
- app.chat_shell.tools.pending_requests
---

# Meeting Booking Skill

When users express meeting-related requests (such as "schedule a meeting", "book a meeting room", "arrange a meeting"), use this Skill to help them complete the meeting booking process.

## Usage Flow

1. First, call `get_meeting_resources` tool to fetch available meeting rooms and potential participants
2. Based on user requirements and available resources, generate a meeting booking form (using the specified Markdown format below)
3. Wait for user to confirm the form - the frontend will automatically call the remote booking service
4. After receiving the booking result, provide feedback to the user

## Natural Language Time Parsing

Support parsing the following time expressions:

- "3pm" / "15:00" → 15:00
- "tomorrow at 10am" → Next day 10:00
- "day after tomorrow at 2:30pm" → Day after tomorrow 14:30
- "tonight at 8pm" → 20:00
- "30 minutes" / "1 hour" / "1.5 hours" → Meeting duration
- "下午3点" / "下午15点" → 15:00
- "明天上午10点" → Next day 10:00
- "后天下午2点半" → Day after tomorrow 14:30

## Meeting Booking Form Format

**IMPORTANT**: You MUST strictly follow this Markdown format for the form. The frontend relies on this exact format for parsing and rendering.

```markdown
## 📅 会议预约确认 (Meeting Booking Confirmation)

### 会议名称 (Meeting Title)
**Type**: text_input
**Value**: `{Auto-filled meeting title based on user description}`

### 会议时间 (Meeting Time)
**Type**: datetime_range
**Start**: `{Start time in ISO format, e.g., 2025-01-05T15:00:00}`
**End**: `{End time in ISO format, e.g., 2025-01-05T15:30:00}`
**Duration**: `{Duration in minutes, e.g., 30}`

### 会议地点 (Meeting Location)
**Type**: single_choice
**Options**:
- [✓] `{room_id}` - {Room Name} (Recommended)
- [ ] `{room_id}` - {Room Name}
...

### 参会人员 (Participants)
**Type**: multiple_choice
**Options**:
- [✓] `{user_id}` - {User Name} (Recommended)
- [ ] `{user_id}` - {User Name}
...
```

## Important Notes

- Room selection should prioritize rooms with appropriate capacity that are currently available
- Participants should be intelligently matched based on keywords mentioned by the user (department, project, name)
- If user doesn't specify duration, default to 30 minutes
- `[✓]` in the form indicates recommended default options
- Always output the form in the exact format shown above for proper frontend rendering
- After the user confirms the booking, you will receive the booking result. Respond with a friendly confirmation message.

## Example Interaction

**User**: Help me schedule a meeting about the AIGC platform, at 3pm, 30 minutes long

**Assistant** (after calling get_meeting_resources):
```markdown
## 📅 会议预约确认 (Meeting Booking Confirmation)

### 会议名称 (Meeting Title)
**Type**: text_input
**Value**: `AIGC平台讨论会议`

### 会议时间 (Meeting Time)
**Type**: datetime_range
**Start**: `2025-01-05T15:00:00`
**End**: `2025-01-05T15:30:00`
**Duration**: `30`

### 会议地点 (Meeting Location)
**Type**: single_choice
**Options**:
- [✓] `room_101` - 会议室A (101) (Recommended)
- [ ] `room_201` - 会议室B (201)
- [ ] `room_102` - 小会议室 (102)

### 参会人员 (Participants)
**Type**: multiple_choice
**Options**:
- [✓] `user_001` - 张三 (Recommended)
- [✓] `user_003` - 王五 (Recommended)
- [ ] `user_002` - 李四
- [ ] `user_004` - 赵六
```

## Error Handling

If the booking fails, you will receive an error message. In this case:
1. Explain to the user what went wrong
2. Suggest possible solutions (e.g., try a different time slot, different room)
3. Offer to help them try again with modified parameters
Loading