feat: add project file system with CRUD, upload, inline editing, and video attachment support
- Add new `/projects/{slug}/files` endpoint group for per-project filesystem operations including directory and file CRUD, upload, and inline editing with public read and owner write access
- Extend attachment system to support video formats (webm, ogv, mov, m4v) with proper file icons and MIME types
- Implement configurable allowed file types via `allowed_file_types` site setting, replacing hardcoded `ALLOWED_UPLOAD_TYPES` with dynamic `allowed_extensions()` and `is_extension_allowed()` functions
- Add `delete_all_project_files()` call in `delete_content_item()` to clean up project files when a project is deleted
- Create database indexes on `project_files` table for `(project_uid, path)` and `(project_uid, parent_path)` to optimize file lookups
- Introduce `docs_prose.py` module with `render_prose()` function that renders Markdown content inside `data-render` divs using mistune, enabling dynamic prose rendering in documentation pages
- Enhance docs search with Markdown-aware text stripping (`_demarkdown()`) and improved HTML/script/style sanitization for better search indexing
- Update documentation API samples to reflect new attachment response fields (`is_image`, `is_video`, `mime_type`) and note video format support
- Update README to document the new project files endpoint and clarify AI gateway attribution for guest Devii sessions
This commit is contained in:
@@ -12,17 +12,25 @@ from devplacepy.config import INTERNAL_GATEWAY_URL, INTERNAL_MODEL
|
||||
DEFAULT_AI_URL = INTERNAL_GATEWAY_URL
|
||||
DEFAULT_AI_MODEL = INTERNAL_MODEL
|
||||
DEFAULT_BASE_URL = "http://127.0.0.1:10500"
|
||||
DEFAULT_TIMEOUT_SECONDS = 45.0
|
||||
DEFAULT_MAX_RESPONSE_CHARS = 12000
|
||||
CONTEXT_WINDOW_TOKENS = 1_048_576
|
||||
MAX_OUTPUT_TOKENS = 384_000
|
||||
SYSTEM_RESERVE_TOKENS = 64_000
|
||||
CHARS_PER_TOKEN = 3
|
||||
CONTEXT_INPUT_BUDGET_TOKENS = CONTEXT_WINDOW_TOKENS - MAX_OUTPUT_TOKENS - SYSTEM_RESERVE_TOKENS
|
||||
|
||||
MIN_TIMEOUT_SECONDS = 300.0
|
||||
DEFAULT_TIMEOUT_SECONDS = 300.0
|
||||
DEFAULT_MAX_RESPONSE_CHARS = 200_000
|
||||
DEFAULT_MAX_TOOL_ITERATIONS = 40
|
||||
DEFAULT_DELEGATE_MAX_ITERATIONS = 25
|
||||
DEFAULT_CONTEXT_COMPACT_THRESHOLD = 220_000
|
||||
DEFAULT_CONTEXT_COMPACT_THRESHOLD = CONTEXT_INPUT_BUDGET_TOKENS * CHARS_PER_TOKEN
|
||||
DEFAULT_CONTEXT_KEEP_TAIL = 12
|
||||
DEFAULT_RECALL_TOP_K = 3
|
||||
DEFAULT_FETCH_MAX_CHARS = 24_000
|
||||
DEFAULT_FETCH_TIMEOUT_SECONDS = 30.0
|
||||
DEFAULT_FETCH_MAX_BYTES = 5_000_000
|
||||
DEFAULT_FETCH_MAX_CHARS = 200_000
|
||||
DEFAULT_FETCH_TIMEOUT_SECONDS = 300.0
|
||||
DEFAULT_FETCH_MAX_BYTES = 8_000_000
|
||||
DEFAULT_RSEARCH_URL = "https://rsearch.app.molodetz.nl"
|
||||
DEFAULT_RSEARCH_TIMEOUT_SECONDS = 300.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -55,6 +63,8 @@ class Settings:
|
||||
allow_eval: bool
|
||||
rsearch_enabled: bool
|
||||
rsearch_url: str
|
||||
rsearch_timeout_seconds: float
|
||||
daily_limit_usd: float
|
||||
|
||||
|
||||
def load_settings() -> Settings:
|
||||
@@ -94,6 +104,10 @@ def load_settings() -> Settings:
|
||||
allow_eval=os.environ.get("DEVII_ALLOW_EVAL", "1").lower() in ("1", "true", "yes", "on"),
|
||||
rsearch_enabled=os.environ.get("DEVII_RSEARCH_ENABLED", "1").lower() in ("1", "true", "yes", "on"),
|
||||
rsearch_url=os.environ.get("DEVII_RSEARCH_URL", DEFAULT_RSEARCH_URL).rstrip("/"),
|
||||
rsearch_timeout_seconds=float(
|
||||
os.environ.get("DEVII_RSEARCH_TIMEOUT", DEFAULT_RSEARCH_TIMEOUT_SECONDS)
|
||||
),
|
||||
daily_limit_usd=0.0,
|
||||
)
|
||||
|
||||
|
||||
@@ -110,19 +124,24 @@ FIELD_PRICE_CACHE_HIT = "devii_price_cache_hit"
|
||||
FIELD_PRICE_CACHE_MISS = "devii_price_cache_miss"
|
||||
FIELD_PRICE_OUTPUT = "devii_price_output"
|
||||
FIELD_ALLOW_EVAL = "devii_allow_eval"
|
||||
FIELD_TIMEOUT = "devii_timeout"
|
||||
FIELD_FETCH_TIMEOUT = "devii_fetch_timeout"
|
||||
FIELD_RSEARCH_ENABLED = "devii_rsearch_enabled"
|
||||
FIELD_RSEARCH_URL = "devii_rsearch_url"
|
||||
FIELD_RSEARCH_TIMEOUT = "devii_rsearch_timeout"
|
||||
|
||||
LESSONS_DB_PATH = os.environ.get("DEVII_LESSONS_DB", "devii_lessons.db")
|
||||
|
||||
|
||||
def build_settings(config: dict, base_url: str, api_key: str) -> Settings:
|
||||
def build_settings(config: dict, base_url: str, api_key: str, owner_kind: str = "guest") -> Settings:
|
||||
configured_key = config[FIELD_AI_KEY] or str(uuid.uuid4())
|
||||
ai_key = api_key if (owner_kind == "user" and api_key) else configured_key
|
||||
return Settings(
|
||||
ai_url=config[FIELD_AI_URL] or DEFAULT_AI_URL,
|
||||
ai_key=config[FIELD_AI_KEY] or str(uuid.uuid4()),
|
||||
ai_key=ai_key,
|
||||
ai_model=config[FIELD_AI_MODEL] or DEFAULT_AI_MODEL,
|
||||
base_url=(base_url or DEFAULT_BASE_URL).rstrip("/"),
|
||||
timeout_seconds=DEFAULT_TIMEOUT_SECONDS,
|
||||
timeout_seconds=float(config.get(FIELD_TIMEOUT) or DEFAULT_TIMEOUT_SECONDS),
|
||||
max_response_chars=DEFAULT_MAX_RESPONSE_CHARS,
|
||||
max_tool_iterations=int(config[FIELD_MAX_ITERATIONS]),
|
||||
log_level="INFO",
|
||||
@@ -140,10 +159,15 @@ def build_settings(config: dict, base_url: str, api_key: str) -> Settings:
|
||||
login_email="",
|
||||
login_password="",
|
||||
fetch_max_chars=DEFAULT_FETCH_MAX_CHARS,
|
||||
fetch_timeout_seconds=DEFAULT_FETCH_TIMEOUT_SECONDS,
|
||||
fetch_timeout_seconds=float(config.get(FIELD_FETCH_TIMEOUT) or DEFAULT_FETCH_TIMEOUT_SECONDS),
|
||||
fetch_max_bytes=DEFAULT_FETCH_MAX_BYTES,
|
||||
fetch_allow_private=False,
|
||||
allow_eval=bool(config.get(FIELD_ALLOW_EVAL, True)),
|
||||
rsearch_enabled=bool(config.get(FIELD_RSEARCH_ENABLED, True)),
|
||||
rsearch_url=(config.get(FIELD_RSEARCH_URL) or DEFAULT_RSEARCH_URL).rstrip("/"),
|
||||
rsearch_timeout_seconds=float(config.get(FIELD_RSEARCH_TIMEOUT) or DEFAULT_RSEARCH_TIMEOUT_SECONDS),
|
||||
daily_limit_usd=float(
|
||||
(config.get(FIELD_GUEST_DAILY_USD, 0.05) if owner_kind == "guest"
|
||||
else config.get(FIELD_USER_DAILY_USD, 1.0)) or 0.0
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user