Add SaaS AI engine (#5907)

This commit is contained in:
James Brunton
2026-03-16 11:01:50 +00:00
committed by GitHub
parent cddc8e6df0
commit c58a6092ec
182 changed files with 29961 additions and 3 deletions
+225
View File
@@ -0,0 +1,225 @@
from . import tool_models
from .api.requests import (
CreateSessionRequest,
DetectTypeRequest,
FillFieldsRequest,
GenerateAllSectionsRequest,
GenerateSectionRequest,
IntentCheckRequest,
PdfAnswerRequest,
RepromptRequest,
UpdateDraftRequest,
UpdateOutlineRequest,
UpdateTemplateRequest,
)
from .api.responses import (
CreateSessionResponse,
DetectTypeResponse,
DocTypeClassification,
ErrorDetailResponse,
FillFieldsResponse,
GenerateAllSectionsResponse,
GenerateErrorResponse,
GenerateSectionResponse,
GenerateSuccessResponse,
HealthResponse,
HtmlResponse,
IntentCheckResponse,
LLMDraftSectionsResponse,
LLMFieldValuesResponse,
MissingQuestionsResponse,
NeedsInfoResponse,
OutlineResponse,
OutlineSection,
PdfAnswer,
PdfAnswerResponse,
PdfEditorUploadResponse,
SectionContent,
SuccessResponse,
UploadAssetResponse,
VersionEntry,
VersionsResponse,
)
from .base import ApiModel
from .briefs import Action, BriefModel, DocumentType, IntentClassification
from .chat import (
ChatInfoRequest,
ChatInfoResponse,
ChatMessage,
ChatRouteRequest,
ChatRouteResponse,
ChatTitleContext,
ChatTitleExample,
CreateIntentHint,
EditIntentHint,
InferredTool,
InferToolsRequest,
InferToolsResponse,
InterpretParameterRequest,
InterpretParameterResponse,
SmartFolderIntentHint,
)
from .common import Constraint, DraftSection, ErrorCode, FieldValue, PdfPreflight, UploadedFileInfo
from .editing.api import (
EditMessageRequest,
EditMessageResponse,
EditResultFile,
EditSessionResponse,
EditToolCall,
FrontendExecutionPlan,
FrontendExecutionStep,
)
from .editing.confirmation import ConfirmationAction, ConfirmationAnswer, ConfirmationIntent
from .editing.decisions import AskUserMessage, DefaultsDecision, IntentDecision
from .editing.operations import IncompatibleChainError, OperationRef
from .file_processing import (
ClarificationDecision,
EditToolSelection,
FollowupIntent,
JsonValue,
PendingRequirement,
ToolParameter,
)
from .java import AISession, JavaCreateSessionResponse, JavaUpdateSessionRequest
from .llm import LLMGenerateAllSectionsResponse, LLMGeneratedSection
from .pdf_editor import (
Color,
Document,
DocumentElt,
FontElt,
ImageElt,
InfoElt,
Metadata,
PageElt,
TextElt,
)
from .pdf_generator import (
CreateStreamPhase,
CreateStreamRequest,
PreviewTemplateHtmlRequest,
RenderPreviewRequest,
ThemePayload,
)
from .smart_folders import (
AvailableTool,
SmartFolderAutomation,
SmartFolderConfig,
SmartFolderCreateRequest,
SmartFolderCreateResponse,
SmartFolderOperation,
)
from .tool_models import OperationId
__all__ = [
"AISession",
"Action",
"ApiModel",
"AskUserMessage",
"AvailableTool",
"BriefModel",
"ChatInfoRequest",
"ChatInfoResponse",
"ChatMessage",
"ChatRouteRequest",
"ChatRouteResponse",
"ChatTitleContext",
"ChatTitleExample",
"ClarificationDecision",
"Color",
"ConfirmationAction",
"ConfirmationAnswer",
"ConfirmationIntent",
"Constraint",
"CreateIntentHint",
"CreateSessionRequest",
"CreateSessionResponse",
"CreateStreamPhase",
"CreateStreamRequest",
"PreviewTemplateHtmlRequest",
"RenderPreviewRequest",
"ThemePayload",
"DefaultsDecision",
"DetectTypeRequest",
"DetectTypeResponse",
"DocTypeClassification",
"Document",
"DocumentElt",
"DocumentType",
"DraftSection",
"EditIntentHint",
"EditMessageRequest",
"EditMessageResponse",
"EditResultFile",
"EditSessionResponse",
"EditToolCall",
"EditToolSelection",
"ErrorCode",
"ErrorDetailResponse",
"FieldValue",
"FrontendExecutionPlan",
"FrontendExecutionStep",
"FillFieldsRequest",
"FillFieldsResponse",
"FollowupIntent",
"FontElt",
"GenerateAllSectionsRequest",
"GenerateAllSectionsResponse",
"GenerateErrorResponse",
"GenerateSectionRequest",
"GenerateSectionResponse",
"GenerateSuccessResponse",
"HealthResponse",
"HtmlResponse",
"ImageElt",
"IncompatibleChainError",
"InfoElt",
"IntentCheckRequest",
"IntentCheckResponse",
"IntentClassification",
"IntentDecision",
"InferredTool",
"InferToolsRequest",
"InferToolsResponse",
"InterpretParameterRequest",
"InterpretParameterResponse",
"JavaCreateSessionResponse",
"JavaUpdateSessionRequest",
"JsonValue",
"LLMFieldValuesResponse",
"LLMGenerateAllSectionsResponse",
"LLMGeneratedSection",
"LLMDraftSectionsResponse",
"Metadata",
"MissingQuestionsResponse",
"NeedsInfoResponse",
"OperationId",
"OperationRef",
"OutlineResponse",
"OutlineSection",
"PdfPreflight",
"PageElt",
"PdfAnswer",
"PdfAnswerRequest",
"PdfAnswerResponse",
"PdfEditorUploadResponse",
"PendingRequirement",
"RepromptRequest",
"SectionContent",
"SmartFolderAutomation",
"SmartFolderConfig",
"SmartFolderCreateRequest",
"SmartFolderCreateResponse",
"SmartFolderIntentHint",
"SmartFolderOperation",
"SuccessResponse",
"TextElt",
"ToolParameter",
"UpdateDraftRequest",
"UpdateOutlineRequest",
"UpdateTemplateRequest",
"UploadedFileInfo",
"UploadAssetResponse",
"VersionEntry",
"VersionsResponse",
"tool_models",
]
+77
View File
@@ -0,0 +1,77 @@
from .requests import (
CreateSessionRequest,
DetectTypeRequest,
FillFieldsRequest,
GenerateAllSectionsRequest,
GenerateSectionRequest,
IntentCheckRequest,
PdfAnswerRequest,
RepromptRequest,
UpdateDraftRequest,
UpdateOutlineRequest,
UpdateTemplateRequest,
)
from .responses import (
CreateSessionResponse,
DetectTypeResponse,
DocTypeClassification,
ErrorDetailResponse,
FillFieldsResponse,
GenerateAllSectionsResponse,
GenerateErrorResponse,
GenerateSectionResponse,
GenerateSuccessResponse,
HealthResponse,
IntentCheckResponse,
LLMDraftSectionsResponse,
LLMFieldValuesResponse,
MissingQuestionsResponse,
NeedsInfoResponse,
OutlineResponse,
PdfAnswer,
PdfAnswerResponse,
PdfEditorUploadResponse,
SectionContent,
SuccessResponse,
UploadAssetResponse,
VersionEntry,
VersionsResponse,
)
__all__ = [
"CreateSessionRequest",
"DetectTypeRequest",
"FillFieldsRequest",
"GenerateAllSectionsRequest",
"GenerateSectionRequest",
"IntentCheckRequest",
"PdfAnswerRequest",
"RepromptRequest",
"UpdateDraftRequest",
"UpdateOutlineRequest",
"UpdateTemplateRequest",
"CreateSessionResponse",
"DetectTypeResponse",
"DocTypeClassification",
"ErrorDetailResponse",
"FillFieldsResponse",
"GenerateAllSectionsResponse",
"GenerateErrorResponse",
"GenerateSectionResponse",
"GenerateSuccessResponse",
"HealthResponse",
"IntentCheckResponse",
"LLMFieldValuesResponse",
"LLMDraftSectionsResponse",
"MissingQuestionsResponse",
"NeedsInfoResponse",
"OutlineResponse",
"PdfAnswer",
"PdfAnswerResponse",
"PdfEditorUploadResponse",
"SectionContent",
"SuccessResponse",
"UploadAssetResponse",
"VersionEntry",
"VersionsResponse",
]
+72
View File
@@ -0,0 +1,72 @@
from __future__ import annotations
from pydantic import Field
from ..base import ApiModel
from ..chat import ChatMessage
from ..common import Constraint, DraftSection
class IntentCheckRequest(ApiModel):
prompt: str = ""
conversation_history: list[ChatMessage] = Field(default_factory=list)
current_pdf_url: str | None = None
class DetectTypeRequest(ApiModel):
prompt: str = ""
explicit_type: str | None = None
class PdfAnswerRequest(ApiModel):
pdf_url: str | None = None
question: str | None = None
class CreateSessionRequest(ApiModel):
prompt: str = ""
doc_type: str = ""
template_id: str = ""
class UpdateOutlineRequest(ApiModel):
outline_text: str = ""
constraints: Constraint | None = None
outline_filename: str | None = None
class UpdateDraftRequest(ApiModel):
draft_sections: list[DraftSection] = Field(default_factory=list)
class UpdateTemplateRequest(ApiModel):
doc_type: str | None = None
template_id: str | None = None
class RepromptRequest(ApiModel):
prompt: str = ""
class FillFieldsRequest(ApiModel):
fields: list[dict[str, str]] = Field(default_factory=list)
extra_prompt: str = ""
class GenerateSectionRequest(ApiModel):
session_id: str | None = None
section_label: str = ""
section_index: int = 0
custom_prompt: str = ""
document_prompt: str = ""
doc_type: str = "document"
existing_sections: list[DraftSection] = Field(default_factory=list)
class GenerateAllSectionsRequest(ApiModel):
session_id: str | None = None
document_prompt: str = ""
doc_type: str = "document"
sections: list[DraftSection] = Field(default_factory=list)
only_indices: list[int] | None = None
additional_prompt: str = ""
+155
View File
@@ -0,0 +1,155 @@
from __future__ import annotations
from pydantic import Field
from ..base import ApiModel
from ..common import DraftSection, FieldValue
class OutlineSection(ApiModel):
"""A single section in the document outline with pre-filled content."""
label: str = Field(description="Section title/header")
value: str = Field(description="Pre-filled content extracted from user's prompt")
class OutlineResponse(ApiModel):
"""Structured outline with detected document type and sections."""
doc_type: str = Field(description="Detected document type")
sections: list[OutlineSection]
outline_filename: str | None = None
class DocTypeClassification(ApiModel):
doc_type: str
class SectionContent(ApiModel):
content: str
class HtmlResponse(ApiModel):
html: str
class PdfAnswer(ApiModel):
answer: str
evidence: list[str] = Field(default_factory=list)
class MissingQuestionsResponse(ApiModel):
message: str
class LLMFieldValuesResponse(ApiModel):
fields: list[FieldValue]
class LLMDraftSectionsResponse(ApiModel):
sections: list[DraftSection]
class IntentCheckResponse(ApiModel):
wants_pdf: bool | None = None
has_enough_info: bool | None = None
document_type: str | None = None
missing_fields: list[str] | None = None
doc_type: str | None = None
has_pdf: bool | None = None
reason: str | None = None
class DetectTypeResponse(ApiModel):
doc_type: str | None = None
confidence: str | None = None
method: str | None = None
error: str | None = None
class PdfAnswerResponse(ApiModel):
answer: str | None = None
mode: str | None = None
error: str | None = None
class NeedsInfoResponse(ApiModel):
needs_info: bool | None = None
message: str | None = None
missing: list[str] | None = None
collected: dict[str, str] | None = None
doc_type: str | None = None
class VersionEntry(ApiModel):
id: str
prompt: str
doc_type: str
pdf_url: str
created_at: str
template_used: bool
edit_mode: bool
class GenerateSuccessResponse(ApiModel):
pdf_url: str
doc_type: str
message: str
version: VersionEntry
template_used: bool
editing_existing: bool
class GenerateErrorResponse(ApiModel):
error: str
editing_existing: bool
class ErrorDetailResponse(ApiModel):
error: str
detail: str
class CreateSessionResponse(ApiModel):
session_id: str
doc_type: str
detection_method: str
template_html: str | None = None # Selected .html template, or None
class SuccessResponse(ApiModel):
success: bool
class FillFieldsResponse(ApiModel):
fields: list[dict[str, str]]
class GenerateSectionResponse(ApiModel):
content: str | None = None
section_index: int | None = None
error: str | None = None
class GenerateAllSectionsResponse(ApiModel):
sections: list[DraftSection] | None = None
incomplete_section_indices: list[int] | None = None
error: str | None = None
class VersionsResponse(ApiModel):
versions: list[VersionEntry]
class UploadAssetResponse(ApiModel):
asset_id: str
asset_url: str
class PdfEditorUploadResponse(ApiModel):
pdf_url: str
class HealthResponse(ApiModel):
status: str
engine: str
+13
View File
@@ -0,0 +1,13 @@
from __future__ import annotations
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
class ApiModel(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel,
extra="forbid",
validate_by_name=True,
validate_by_alias=True,
)
+54
View File
@@ -0,0 +1,54 @@
from __future__ import annotations
from enum import StrEnum
from .base import ApiModel
class DocumentType(StrEnum):
academic = "academic"
agenda = "agenda"
brochure = "brochure"
business_card = "business_card"
case_study = "case_study"
checklist = "checklist"
contract = "contract"
creative = "creative"
datasheet = "datasheet"
document = "document"
flyer = "flyer"
invoice = "invoice"
letter = "letter"
manual = "manual"
menu = "menu"
minutes = "minutes"
newsletter = "newsletter"
one_pager = "one_pager"
poster = "poster"
presentation = "presentation"
press_release = "press_release"
proposal = "proposal"
recipe = "recipe"
report = "report"
resume = "resume"
timeline = "timeline"
whitepaper = "whitepaper"
class Action(StrEnum):
new = "new"
edit = "edit"
question = "question"
class BriefModel(ApiModel):
pass
class IntentClassification(BriefModel):
doc_type: DocumentType
action: Action
wants_pdf: bool
has_enough_info: bool
missing_fields: list[str]
notes: str
+107
View File
@@ -0,0 +1,107 @@
from __future__ import annotations
from typing import Literal
from pydantic import Field, model_validator
from .base import ApiModel
class ChatInfoRequest(ApiModel):
message: str = ""
history: list[ChatMessage] = Field(default_factory=list)
class ChatInfoResponse(ApiModel):
assistant_message: str = ""
class InterpretParameterRequest(ApiModel):
tool_id: str
tool_name: str
question: str
user_response: str
class InterpretParameterResponse(ApiModel):
type: Literal["value", "confused", "cancel", "default"]
extracted_value: str | None = None
help_message: str
class ChatMessage(ApiModel):
role: Literal["system", "assistant", "user"]
content: str | list[str | dict]
class ChatTitleExample(ApiModel):
user: str
title: str
class ChatTitleContext(ApiModel):
current_title: str | None = None
max_length: int | None = None
style_hint: str | None = None
examples: list[ChatTitleExample] = Field(default_factory=list)
class EditIntentHint(ApiModel):
mode: Literal["command", "info", "document_question", "html_edit", "ambiguous"]
requires_file_context: bool = False
class CreateIntentHint(ApiModel):
action: Literal["start", "generate_pdf", "regenerate_outline"] = "start"
doc_type: str | None = None # Detected document type when action="start"
template_tex: str | None = None # Selected .tex from default_templates, or None if unknown
class SmartFolderIntentHint(ApiModel):
action: Literal["configure", "create"]
class ChatRouteRequest(ApiModel):
message: str = ""
history: list[ChatMessage] = Field(default_factory=list)
has_files: bool = False
has_editable_html: bool = False
has_create_session: bool = False
has_edit_session: bool = False
last_route: Literal["edit", "create", "none"] = "none"
request_title: bool = False
title_context: ChatTitleContext | None = None
class InferToolsRequest(ApiModel):
message: str
available_tools: list[str]
class InferredTool(ApiModel):
tool_id: str
confidence: Literal["high", "medium", "low"]
class InferToolsResponse(ApiModel):
tools: list[InferredTool]
reason: str
class ChatRouteResponse(ApiModel):
intent: Literal["create", "edit", "smart_folder"]
create_intent: CreateIntentHint | None = None
edit_intent: EditIntentHint | None = None
smart_folder_intent: SmartFolderIntentHint | None = None
reason: str = ""
suggested_title: str | None = None
@model_validator(mode="after")
def _require_matching_intent(self) -> ChatRouteResponse:
if self.intent == "create" and self.create_intent is None:
raise ValueError("create_intent is required when intent='create'")
elif self.intent == "edit" and self.edit_intent is None:
raise ValueError("edit_intent is required when intent='edit'")
elif self.intent == "smart_folder" and self.smart_folder_intent is None:
raise ValueError("smart_folder_intent is required when intent='smart_folder'")
return self
+39
View File
@@ -0,0 +1,39 @@
from __future__ import annotations
from enum import StrEnum
from .base import ApiModel
class ErrorCode(StrEnum):
"""Error codes for special handling."""
INSUFFICIENT_CREDITS = "INSUFFICIENT_CREDITS"
class Constraint(ApiModel):
tone: str | None = None
audience: str | None = None
page_count: int | None = None
class UploadedFileInfo(ApiModel):
name: str | None = None
type: str | None = None
class PdfPreflight(ApiModel):
file_size_mb: float | None = None
is_encrypted: bool | None = None
page_count: int | None = None
has_text_layer: bool | None = None
class DraftSection(ApiModel):
label: str
value: str
class FieldValue(ApiModel):
label: str
value: str
+30
View File
@@ -0,0 +1,30 @@
from .api import (
EditMessageRequest,
EditMessageResponse,
EditResultFile,
EditSessionResponse,
EditToolCall,
FrontendExecutionPlan,
FrontendExecutionStep,
)
from .confirmation import ConfirmationAction, ConfirmationAnswer, ConfirmationIntent
from .decisions import AskUserMessage, DefaultsDecision, IntentDecision
from .operations import IncompatibleChainError, OperationRef
__all__ = [
"AskUserMessage",
"ConfirmationAction",
"ConfirmationAnswer",
"ConfirmationIntent",
"DefaultsDecision",
"EditMessageRequest",
"EditMessageResponse",
"EditResultFile",
"EditSessionResponse",
"EditToolCall",
"FrontendExecutionPlan",
"FrontendExecutionStep",
"IncompatibleChainError",
"IntentDecision",
"OperationRef",
]
+61
View File
@@ -0,0 +1,61 @@
from __future__ import annotations
from typing import Any, Literal
from pydantic import Field
from ..base import ApiModel
from ..chat import EditIntentHint
from ..common import ErrorCode
from ..tool_models import OperationId, ParamToolModel
class EditSessionResponse(ApiModel):
session_id: str
file_name: str
file_type: str | None = None
class EditMessageRequest(ApiModel):
message: str = ""
action: str | None = None
edit_intent: EditIntentHint | None = None
class EditToolCall(ApiModel):
operation_id: OperationId
parameters: ParamToolModel
class EditResultFile(ApiModel):
url: str
name: str | None = None
class FrontendExecutionStep(ApiModel):
operation_id: OperationId
parameters: ParamToolModel
class FrontendExecutionPlan(ApiModel):
mode: Literal["single", "pipeline"]
steps: list[FrontendExecutionStep] = Field(default_factory=list)
pipeline_name: str | None = None
class EditMessageResponse(ApiModel):
assistant_message: str
tool_calls: list[EditToolCall] = Field(default_factory=list)
execute_on_frontend: bool = False
frontend_plan: FrontendExecutionPlan | None = None
result_file_url: str | None = None
result_file_name: str | None = None
result_files: list[EditResultFile] | None = None
result_json: dict[str, Any] | None = None
result_text: str | None = None
result_bool: bool | None = None
needs_more_info: bool = False
missing_parameters: list[str] = Field(default_factory=list)
confirmation_required: bool = False
warning: str | None = None
error: ErrorCode | None = None
+23
View File
@@ -0,0 +1,23 @@
from __future__ import annotations
from typing import Literal
from pydantic import Field
from ..base import ApiModel
ConfirmationAction = Literal["confirm", "cancel", "modify", "new_request", "question"]
class ConfirmationAnswer(ApiModel):
message: str
class ConfirmationIntent(ApiModel):
"""
Intent classification during confirmation phase (AWAITING_CONFIRM state).
CRITICAL: Never ignore user messages during confirmation.
"""
action: ConfirmationAction = Field(..., description="confirm, cancel, modify, new_request, or question")
modification_description: str | None = Field(None, description="If action=modify, what change the user wants")
+18
View File
@@ -0,0 +1,18 @@
from __future__ import annotations
from typing import Literal
from ..base import ApiModel
class DefaultsDecision(ApiModel):
use_defaults: bool
class AskUserMessage(ApiModel):
message: str
class IntentDecision(ApiModel):
mode: Literal["command", "info", "document_question", "ambiguous"]
requires_file_context: bool = False
+18
View File
@@ -0,0 +1,18 @@
from __future__ import annotations
from ..base import ApiModel
from ..tool_models import OperationId
class OperationRef(ApiModel):
"""Reference to an operation with enough info for frontend tool lookup."""
operation_id: OperationId
class IncompatibleChainError(ApiModel):
"""Validation error data for incompatible operation chains."""
type: str # Always "incompatible_chain"
current_operation: OperationRef
next_operation: OperationRef
+40
View File
@@ -0,0 +1,40 @@
from __future__ import annotations
from typing import Any, Literal
from pydantic import Field
from .base import ApiModel
from .tool_models import OperationId, ParamToolModel
type JsonValue = dict[str, Any] | list[Any] | str | int | float | bool | None
class ToolParameter(ApiModel):
name: str
value: JsonValue
class EditToolSelection(ApiModel):
action: Literal["call_tool", "ask_user", "no_tool"]
operation_ids: list[OperationId] = Field(
default_factory=list,
description="Operation IDs to run in order. Use multiple items for compound requests.",
)
response_message: str | None = None
class ClarificationDecision(ApiModel):
action: str = Field(..., description="proceed or ask_user")
question: str | None = None
missing_parameters: list[str] = Field(default_factory=list)
class FollowupIntent(ApiModel):
mode: str = Field(..., description="fill_missing, new_request, or info")
class PendingRequirement(ApiModel):
operation_id: OperationId
parameters: ParamToolModel | None = None
missing: list[str] = Field(default_factory=list)
+40
View File
@@ -0,0 +1,40 @@
from __future__ import annotations
from .base import ApiModel
from .common import Constraint, DraftSection
class JavaCreateSessionResponse(ApiModel):
session_id: str
class JavaUpdateSessionRequest(ApiModel):
outline_text: str | None = None
outline_filename: str | None = None
outline_approved: bool | None = None
outline_constraints: Constraint | None = None
draft_sections: list[DraftSection] | None = None
polished_html: str | None = None
pdf_url: str | None = None
doc_type: str | None = None
template_id: str | None = None
status: str | None = None
class AISession(ApiModel):
session_id: str | None = None
user_id: str | None = None
prompt_latest: str | None = None
prompt_initial: str | None = None
doc_type: str | None = None
template_id: str | None = None
outline_text: str | None = None
outline_filename: str | None = None
outline_approved: bool | None = None
outline_constraints: Constraint | None = None
draft_sections: list[DraftSection] | None = None
polished_html: str | None = None
pdf_url: str | None = None
created_at: str | None = None
updated_at: str | None = None
status: str | None = None
+12
View File
@@ -0,0 +1,12 @@
from __future__ import annotations
from .base import ApiModel
class LLMGeneratedSection(ApiModel):
index: int
value: str
class LLMGenerateAllSectionsResponse(ApiModel):
sections: list[LLMGeneratedSection]
+89
View File
@@ -0,0 +1,89 @@
from __future__ import annotations
from .base import ApiModel
class Color(ApiModel):
color_space: str
components: list[float]
class TextElt(ApiModel):
id: str
text: str
font_id: str | None = None
font_size: float | None = None
x: float
y: float
width: float
height: float
text_matrix: list[float]
fill_color: Color | None = None
class ImageElt(ApiModel):
id: str
object_name: str | None = None
x: float
y: float
width: float
height: float
left: float
top: float
bottom: float
right: float
image_data: str | None = None
image_format: str | None = None
class InfoElt(ApiModel):
anchors: list[float]
boundaries: list[float]
observed_left: float
observed_right: float
y_min: float
y_max: float
offset: float = 0.0
page_width: float | None = None
class PageElt(ApiModel):
width: float
height: float
page_number: float
text_elements: list[TextElt]
image_elements: list[ImageElt]
class Metadata(ApiModel):
number_of_pages: int
class FontElt(ApiModel):
id: str | None
uid: str | None
base_name: str | None = None
embedded: bool
program: None
program_format: None = None
web_program: None = None
web_program_format: None = None
pdf_program: None = None
pdf_program_format: None = None
ascent: float
descent: float
units_per_em: float
standard14_name: None = None
color: str | None
font_descriptor_flags: int | None = None
class DocumentElt(ApiModel):
metadata: Metadata
fonts: list[FontElt]
pages: list[PageElt]
lazy_images: bool
class Document(ApiModel):
document: DocumentElt
+47
View File
@@ -0,0 +1,47 @@
from __future__ import annotations
from enum import StrEnum
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
from .base import ApiModel
class ThemePayload(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel,
extra="allow", # CSS var overrides (--theme-primary etc.) are dynamic
validate_by_name=True,
validate_by_alias=True,
)
logo_base64: str | None = None
def css_overrides(self) -> dict[str, str] | None:
extras = self.model_extra or {}
return {k: str(v) for k, v in extras.items()} if extras else None
class CreateStreamPhase(StrEnum):
OUTLINE = "outline"
DRAFT = "draft"
POLISH = "polish"
REVISE = "revise"
class CreateStreamRequest(ApiModel):
phase: CreateStreamPhase = CreateStreamPhase.OUTLINE
theme: ThemePayload | None = None # theme dict sent in POST body
additional_instructions: str = ""
# Optional HTML revision inputs (used when phase=REVISE).
base_html: str | None = None
class PreviewTemplateHtmlRequest(ApiModel):
template: str = ""
class RenderPreviewRequest(ApiModel):
template: str = ""
theme: ThemePayload | None = None
+41
View File
@@ -0,0 +1,41 @@
from __future__ import annotations
from pydantic import Field
from .base import ApiModel
from .chat import ChatMessage
class AvailableTool(ApiModel):
id: str
name: str
class SmartFolderOperation(ApiModel):
operation: str
parameters: str = Field(default="{}")
class SmartFolderAutomation(ApiModel):
name: str
description: str | None = None
operations: list[SmartFolderOperation] = Field(default_factory=list)
class SmartFolderConfig(ApiModel):
name: str
description: str
automation: SmartFolderAutomation
icon: str
accent_color: str
class SmartFolderCreateRequest(ApiModel):
message: str = ""
history: list[ChatMessage] = Field(default_factory=list)
available_tools: list[AvailableTool] = Field(default_factory=list)
class SmartFolderCreateResponse(ApiModel):
assistant_message: str
smart_folder_config: SmartFolderConfig | None = None
+439
View File
@@ -0,0 +1,439 @@
# AUTO-GENERATED FILE. DO NOT EDIT.
# Generated by scripts/generate_tool_models.py from frontend TypeScript sources.
from __future__ import annotations
from enum import StrEnum
from typing import Any, Literal
from models.base import ApiModel
class AddAttachmentsParams(ApiModel):
attachments: list[dict[str, Any]] = []
class AdjustContrastParams(ApiModel):
blue: float = 100
brightness: float = 100
contrast: float = 100
green: float = 100
red: float = 100
saturation: float = 100
class AutoRenameParams(ApiModel):
use_first_text_as_fallback: bool = False
class AutomateParams(ApiModel):
pass
class BookletImpositionParams(ApiModel):
add_border: bool = False
add_gutter: bool = False
double_sided: bool = True
duplex_pass: Literal["BOTH", "FIRST", "SECOND"] = "BOTH"
flip_on_short_edge: bool = False
gutter_size: float = 12
pages_per_sheet: float = 2
spine_location: Literal["LEFT", "RIGHT"] = "LEFT"
class CertSignParams(ApiModel):
cert_file: dict[str, Any] | None = None
cert_type: Literal["", "PEM", "PKCS12", "PFX", "JKS"] = ""
jks_file: dict[str, Any] | None = None
location: str = ""
name: str = ""
p12_file: dict[str, Any] | None = None
page_number: float = 1
password: str = ""
private_key_file: dict[str, Any] | None = None
reason: str = ""
show_logo: bool = True
show_signature: bool = False
sign_mode: Literal["MANUAL", "AUTO"] = "MANUAL"
class ChangeMetadataParams(ApiModel):
author: str = ""
creation_date: str | None = None
creator: str = ""
custom_metadata: list[dict[str, str]] = []
delete_all: bool = False
keywords: str = ""
modification_date: str | None = None
producer: str = ""
subject: str = ""
title: str = ""
trapped: Literal["True", "False", "Unknown"] | None = None
class ChangePermissionsParams(ApiModel):
prevent_assembly: bool = False
prevent_extract_content: bool = False
prevent_extract_for_accessibility: bool = False
prevent_fill_in_form: bool = False
prevent_modify: bool = False
prevent_modify_annotations: bool = False
prevent_printing: bool = False
prevent_printing_faithful: bool = False
class AddPasswordParams(ApiModel):
key_length: float = 128
owner_password: str = ""
password: str = ""
permissions: ChangePermissionsParams = ChangePermissionsParams.model_validate(
{
"preventAssembly": False,
"preventExtractContent": False,
"preventExtractForAccessibility": False,
"preventFillInForm": False,
"preventModify": False,
"preventModifyAnnotations": False,
"preventPrinting": False,
"preventPrintingFaithful": False,
}
)
class CompressParams(ApiModel):
compression_level: float = 5
compression_method: Literal["quality", "filesize"] = "quality"
expected_size: str = ""
file_size_unit: Literal["KB", "MB"] = "MB"
file_size_value: str = ""
grayscale: bool = False
class ConvertParams(ApiModel):
cbz_options: dict[str, bool] = {"optimizeForEbook": False}
cbz_output_options: dict[str, float] = {"dpi": 150}
email_options: dict[str, Any] = {
"includeAttachments": True,
"maxAttachmentSizeMB": 10,
"downloadHtml": False,
"includeAllRecipients": False,
}
from_extension: str = ""
html_options: dict[str, float] = {"zoomLevel": 1}
image_options: dict[str, Any] = {
"colorType": "color",
"dpi": 300,
"singleOrMultiple": "multiple",
"fitOption": "maintainAspectRatio",
"autoRotate": True,
"combineImages": True,
}
is_smart_detection: bool = False
pdfa_options: dict[str, str] = {"outputFormat": "pdfa-1"}
smart_detection_type: Literal["mixed", "images", "web", "none"] = "none"
to_extension: str = ""
class CropParams(ApiModel):
crop_area: dict[str, float] = {"x": 0, "y": 0, "width": 595, "height": 842}
class EditTableOfContentsParams(ApiModel):
bookmarks: list[dict[str, Any]] = []
replace_existing: bool = True
class ExtractImagesParams(ApiModel):
allow_duplicates: bool = False
format: Literal["png", "jpg", "gif"] = "png"
class ExtractPagesParams(ApiModel):
page_numbers: str = ""
class FlattenParams(ApiModel):
flatten_only_forms: bool = False
class MergeParams(ApiModel):
generate_table_of_contents: bool = False
remove_digital_signature: bool = False
class OcrParams(ApiModel):
additional_options: list[str] = []
languages: list[str] = []
ocr_render_type: str = "hocr"
ocr_type: str = "skip-text"
class OverlayPdfsParams(ApiModel):
counts: list[float] = []
overlay_files: list[dict[str, Any]] = []
overlay_mode: Literal["SequentialOverlay", "InterleavedOverlay", "FixedRepeatOverlay"] = "SequentialOverlay"
overlay_position: Literal[0, 1] = 0
class PageLayoutParams(ApiModel):
add_border: bool = False
pages_per_sheet: float = 4
class PdfToSinglePageParams(ApiModel):
pass
class RedactParams(ApiModel):
convert_pdfto_image: bool = True
custom_padding: float = 0.1
mode: Literal["automatic", "manual"] = "automatic"
redact_color: str = "#000000"
use_regex: bool = False
whole_word_search: bool = False
words_to_redact: list[str] = []
class RemoveAnnotationsParams(ApiModel):
pass
class RemoveBlanksParams(ApiModel):
include_blank_pages: bool = False
threshold: float = 10
white_percent: float = 99.9
class RemoveCertSignParams(ApiModel):
pass
class RemoveImageParams(ApiModel):
pass
class RemovePagesParams(ApiModel):
page_numbers: str = ""
class RemovePasswordParams(ApiModel):
password: str = ""
class ReorganizePagesParams(ApiModel):
custom_mode: str | None = None
page_numbers: str | None = None
class RepairParams(ApiModel):
pass
class ReplaceColorParams(ApiModel):
back_ground_color: str = "#ffffff"
high_contrast_color_combination: Literal[
"WHITE_TEXT_ON_BLACK", "BLACK_TEXT_ON_WHITE", "YELLOW_TEXT_ON_BLACK", "GREEN_TEXT_ON_BLACK"
] = "WHITE_TEXT_ON_BLACK"
replace_and_invert_option: Literal[
"HIGH_CONTRAST_COLOR", "CUSTOM_COLOR", "FULL_INVERSION", "COLOR_SPACE_CONVERSION"
] = "HIGH_CONTRAST_COLOR"
text_color: str = "#000000"
class RotateParams(ApiModel):
angle: float = 0
class SanitizeParams(ApiModel):
remove_embedded_files: bool = True
remove_fonts: bool = False
remove_java_script: bool = True
remove_links: bool = False
remove_metadata: bool = False
remove_xmpmetadata: bool = False
class ScalePagesParams(ApiModel):
page_size: Literal["KEEP", "A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL"] | None = None
scale_factor: float = 1
class ScannerImageSplitParams(ApiModel):
angle_threshold: float = 10
border_size: float = 1
min_area: float = 10000
min_contour_area: float = 500
tolerance: float = 30
class SignParams(ApiModel):
font_family: str = "Helvetica"
font_size: float = 16
location: str = "Digital"
reason: str = "Document signing"
signature_data: str | None = None
signature_position: dict[str, float] | None = None
signature_type: Literal["text", "image", "canvas"] = "canvas"
signer_name: str = ""
text_color: str = "#000000"
class SplitParams(ApiModel):
allow_duplicates: bool = False
bookmark_level: str = "1"
duplex_mode: bool = False
h_div: str = "2"
include_metadata: bool = False
merge: bool = False
method: Literal[
"", "byPages", "bySections", "bySize", "byPageCount", "byDocCount", "byChapters", "byPageDivider"
] = ""
pages: str = ""
split_value: str = ""
v_div: str = "2"
class UnlockPdfformsParams(ApiModel):
pass
class WatermarkParams(ApiModel):
alphabet: str = "roman"
convert_pdfto_image: bool = False
custom_color: str = "#d3d3d3"
font_size: float = 12
height_spacer: float = 50
opacity: float = 50
rotation: float = 0
watermark_image: dict[str, Any] | None = None
watermark_text: str = ""
watermark_type: Literal["text", "image"] | None = None
width_spacer: float = 50
type ParamToolModel = (
AddAttachmentsParams
| AddPasswordParams
| AdjustContrastParams
| AutoRenameParams
| AutomateParams
| BookletImpositionParams
| CertSignParams
| ChangeMetadataParams
| ChangePermissionsParams
| CompressParams
| ConvertParams
| CropParams
| EditTableOfContentsParams
| ExtractImagesParams
| ExtractPagesParams
| FlattenParams
| MergeParams
| OcrParams
| OverlayPdfsParams
| PageLayoutParams
| PdfToSinglePageParams
| RedactParams
| RemoveAnnotationsParams
| RemoveBlanksParams
| RemoveCertSignParams
| RemoveImageParams
| RemovePagesParams
| RemovePasswordParams
| ReorganizePagesParams
| RepairParams
| ReplaceColorParams
| RotateParams
| SanitizeParams
| ScalePagesParams
| ScannerImageSplitParams
| SignParams
| SplitParams
| UnlockPdfformsParams
| WatermarkParams
)
type ParamToolModelType = type[ParamToolModel]
class OperationId(StrEnum):
ADD_ATTACHMENTS = "addAttachments"
ADD_PASSWORD = "addPassword"
ADJUST_CONTRAST = "adjustContrast"
AUTO_RENAME = "autoRename"
AUTOMATE = "automate"
BOOKLET_IMPOSITION = "bookletImposition"
CERT_SIGN = "certSign"
CHANGE_METADATA = "changeMetadata"
CHANGE_PERMISSIONS = "changePermissions"
COMPRESS = "compress"
CONVERT = "convert"
CROP = "crop"
EDIT_TABLE_OF_CONTENTS = "editTableOfContents"
EXTRACT_IMAGES = "extractImages"
EXTRACT_PAGES = "extractPages"
FLATTEN = "flatten"
MERGE = "merge"
OCR = "ocr"
OVERLAY_PDFS = "overlayPdfs"
PAGE_LAYOUT = "pageLayout"
PDF_TO_SINGLE_PAGE = "pdfToSinglePage"
REDACT = "redact"
REMOVE_ANNOTATIONS = "removeAnnotations"
REMOVE_BLANKS = "removeBlanks"
REMOVE_CERT_SIGN = "removeCertSign"
REMOVE_IMAGE = "removeImage"
REMOVE_PAGES = "removePages"
REMOVE_PASSWORD = "removePassword"
REORGANIZE_PAGES = "reorganizePages"
REPAIR = "repair"
REPLACE_COLOR = "replaceColor"
ROTATE = "rotate"
SANITIZE = "sanitize"
SCALE_PAGES = "scalePages"
SCANNER_IMAGE_SPLIT = "scannerImageSplit"
SIGN = "sign"
SPLIT = "split"
UNLOCK_PDFFORMS = "unlockPDFForms"
WATERMARK = "watermark"
OPERATIONS: dict[OperationId, ParamToolModelType] = {
OperationId.ADD_ATTACHMENTS: AddAttachmentsParams,
OperationId.ADD_PASSWORD: AddPasswordParams,
OperationId.ADJUST_CONTRAST: AdjustContrastParams,
OperationId.AUTO_RENAME: AutoRenameParams,
OperationId.AUTOMATE: AutomateParams,
OperationId.BOOKLET_IMPOSITION: BookletImpositionParams,
OperationId.CERT_SIGN: CertSignParams,
OperationId.CHANGE_METADATA: ChangeMetadataParams,
OperationId.CHANGE_PERMISSIONS: ChangePermissionsParams,
OperationId.COMPRESS: CompressParams,
OperationId.CONVERT: ConvertParams,
OperationId.CROP: CropParams,
OperationId.EDIT_TABLE_OF_CONTENTS: EditTableOfContentsParams,
OperationId.EXTRACT_IMAGES: ExtractImagesParams,
OperationId.EXTRACT_PAGES: ExtractPagesParams,
OperationId.FLATTEN: FlattenParams,
OperationId.MERGE: MergeParams,
OperationId.OCR: OcrParams,
OperationId.OVERLAY_PDFS: OverlayPdfsParams,
OperationId.PAGE_LAYOUT: PageLayoutParams,
OperationId.PDF_TO_SINGLE_PAGE: PdfToSinglePageParams,
OperationId.REDACT: RedactParams,
OperationId.REMOVE_ANNOTATIONS: RemoveAnnotationsParams,
OperationId.REMOVE_BLANKS: RemoveBlanksParams,
OperationId.REMOVE_CERT_SIGN: RemoveCertSignParams,
OperationId.REMOVE_IMAGE: RemoveImageParams,
OperationId.REMOVE_PAGES: RemovePagesParams,
OperationId.REMOVE_PASSWORD: RemovePasswordParams,
OperationId.REORGANIZE_PAGES: ReorganizePagesParams,
OperationId.REPAIR: RepairParams,
OperationId.REPLACE_COLOR: ReplaceColorParams,
OperationId.ROTATE: RotateParams,
OperationId.SANITIZE: SanitizeParams,
OperationId.SCALE_PAGES: ScalePagesParams,
OperationId.SCANNER_IMAGE_SPLIT: ScannerImageSplitParams,
OperationId.SIGN: SignParams,
OperationId.SPLIT: SplitParams,
OperationId.UNLOCK_PDFFORMS: UnlockPdfformsParams,
OperationId.WATERMARK: WatermarkParams,
}