fix: better repo name generation, refs NOISSUE

This commit is contained in:
2026-04-11 11:37:19 +02:00
parent b881ef635a
commit 29cf2aa6bd
2 changed files with 51 additions and 2 deletions

View File

@@ -18,6 +18,14 @@ except ImportError:
class RequestInterpreter: class RequestInterpreter:
"""Use Ollama to turn free-form text into a structured software request.""" """Use Ollama to turn free-form text into a structured software request."""
REQUEST_PREFIX_WORDS = {
'a', 'an', 'app', 'application', 'build', 'create', 'dashboard', 'develop', 'design', 'for', 'generate',
'internal', 'make', 'me', 'modern', 'need', 'new', 'our', 'platform', 'please', 'project', 'service',
'simple', 'site', 'start', 'system', 'the', 'tool', 'us', 'want', 'web', 'website', 'with',
}
REPO_NOISE_WORDS = REQUEST_PREFIX_WORDS | {'and', 'from', 'into', 'on', 'that', 'this', 'to'}
def __init__(self, ollama_url: str | None = None, model: str | None = None): def __init__(self, ollama_url: str | None = None, model: str | None = None):
self.ollama_url = (ollama_url or settings.ollama_url).rstrip('/') self.ollama_url = (ollama_url or settings.ollama_url).rstrip('/')
self.model = model or settings.OLLAMA_MODEL self.model = model or settings.OLLAMA_MODEL
@@ -301,6 +309,7 @@ class RequestInterpreter:
"""Normalize a candidate project name into a readable title.""" """Normalize a candidate project name into a readable title."""
cleaned = re.sub(r'[^A-Za-z0-9\s-]+', ' ', raw_name).strip(' -') cleaned = re.sub(r'[^A-Za-z0-9\s-]+', ' ', raw_name).strip(' -')
cleaned = re.sub(r'\s+', ' ', cleaned) cleaned = re.sub(r'\s+', ' ', cleaned)
cleaned = self._trim_request_prefix(cleaned)
special_upper = {'api', 'crm', 'erp', 'cms', 'hr', 'it', 'ui', 'qa'} special_upper = {'api', 'crm', 'erp', 'cms', 'hr', 'it', 'ui', 'qa'}
words = [] words = []
for word in cleaned.split()[:6]: for word in cleaned.split()[:6]:
@@ -308,14 +317,49 @@ class RequestInterpreter:
words.append(lowered.upper() if lowered in special_upper else lowered.capitalize()) words.append(lowered.upper() if lowered in special_upper else lowered.capitalize())
return ' '.join(words) or 'Generated Project' return ' '.join(words) or 'Generated Project'
def _trim_request_prefix(self, candidate: str) -> str:
"""Remove leading request phrasing from model-produced names and slugs."""
tokens = [token for token in re.split(r'[-\s]+', candidate or '') if token]
while tokens and tokens[0].lower() in self.REQUEST_PREFIX_WORDS:
tokens.pop(0)
trimmed = ' '.join(tokens).strip()
return trimmed or candidate.strip()
def _derive_repo_name(self, project_name: str) -> str: def _derive_repo_name(self, project_name: str) -> str:
"""Derive a repository slug from a human-readable project name.""" """Derive a repository slug from a human-readable project name."""
preferred = (project_name or 'project').strip().lower().replace(' ', '-') preferred_name = self._trim_request_prefix((project_name or 'project').strip())
preferred = preferred_name.lower().replace(' ', '-')
sanitized = ''.join(ch if ch.isalnum() or ch in {'-', '_'} else '-' for ch in preferred) sanitized = ''.join(ch if ch.isalnum() or ch in {'-', '_'} else '-' for ch in preferred)
while '--' in sanitized: while '--' in sanitized:
sanitized = sanitized.replace('--', '-') sanitized = sanitized.replace('--', '-')
return sanitized.strip('-') or 'project' return sanitized.strip('-') or 'project'
def _should_use_repo_name_candidate(self, candidate: str, project_name: str) -> bool:
"""Return whether a model-proposed repo slug is concise enough to trust directly."""
cleaned = self._trim_request_prefix(re.sub(r'[^A-Za-z0-9\s_-]+', ' ', candidate or '').strip())
if not cleaned:
return False
candidate_tokens = [token.lower() for token in re.split(r'[-\s_]+', cleaned) if token]
if not candidate_tokens:
return False
if len(candidate_tokens) > 6:
return False
noise_count = sum(1 for token in candidate_tokens if token in self.REPO_NOISE_WORDS)
if noise_count >= 2:
return False
if len('-'.join(candidate_tokens)) > 40:
return False
project_tokens = {
token.lower()
for token in re.split(r'[-\s_]+', project_name or '')
if token and token.lower() not in self.REPO_NOISE_WORDS
}
if project_tokens:
overlap = sum(1 for token in candidate_tokens if token in project_tokens)
if overlap == 0:
return False
return True
def _ensure_unique_repo_name(self, repo_name: str, reserved_names: set[str]) -> str: def _ensure_unique_repo_name(self, repo_name: str, reserved_names: set[str]) -> str:
"""Choose a repository slug that does not collide with tracked or remote repositories.""" """Choose a repository slug that does not collide with tracked or remote repositories."""
base_name = self._derive_repo_name(repo_name) base_name = self._derive_repo_name(repo_name)
@@ -329,7 +373,10 @@ class RequestInterpreter:
def _normalize_project_identity(self, payload: dict, fallback_name: str) -> tuple[str, str]: def _normalize_project_identity(self, payload: dict, fallback_name: str) -> tuple[str, str]:
"""Normalize model-proposed project and repository naming.""" """Normalize model-proposed project and repository naming."""
project_name = self._humanize_name(str(payload.get('project_name') or payload.get('name') or fallback_name)) project_name = self._humanize_name(str(payload.get('project_name') or payload.get('name') or fallback_name))
repo_name = self._derive_repo_name(str(payload.get('repo_name') or project_name)) repo_candidate = str(payload.get('repo_name') or '').strip()
repo_name = self._derive_repo_name(project_name)
if repo_candidate and self._should_use_repo_name_candidate(repo_candidate, project_name):
repo_name = self._derive_repo_name(repo_candidate)
return project_name, repo_name return project_name, repo_name
def _heuristic_fallback(self, prompt_text: str, context: dict | None = None) -> tuple[dict, dict]: def _heuristic_fallback(self, prompt_text: str, context: dict | None = None) -> tuple[dict, dict]:

View File

@@ -798,6 +798,7 @@ def get_llm_prompt_settings(db: DbSession):
@app.put('/llm/prompts/{prompt_key}') @app.put('/llm/prompts/{prompt_key}')
def update_llm_prompt_setting(prompt_key: str, request: LLMPromptSettingUpdateRequest, db: DbSession): def update_llm_prompt_setting(prompt_key: str, request: LLMPromptSettingUpdateRequest, db: DbSession):
"""Persist one editable LLM prompt override into the database.""" """Persist one editable LLM prompt override into the database."""
database_module.init_db()
result = DatabaseManager(db).save_llm_prompt_setting(prompt_key, request.value, actor='api') result = DatabaseManager(db).save_llm_prompt_setting(prompt_key, request.value, actor='api')
if result.get('status') == 'error': if result.get('status') == 'error':
raise HTTPException(status_code=400, detail=result.get('message', 'Prompt save failed')) raise HTTPException(status_code=400, detail=result.get('message', 'Prompt save failed'))
@@ -807,6 +808,7 @@ def update_llm_prompt_setting(prompt_key: str, request: LLMPromptSettingUpdateRe
@app.delete('/llm/prompts/{prompt_key}') @app.delete('/llm/prompts/{prompt_key}')
def reset_llm_prompt_setting(prompt_key: str, db: DbSession): def reset_llm_prompt_setting(prompt_key: str, db: DbSession):
"""Reset one editable LLM prompt override back to the environment/default value.""" """Reset one editable LLM prompt override back to the environment/default value."""
database_module.init_db()
result = DatabaseManager(db).reset_llm_prompt_setting(prompt_key, actor='api') result = DatabaseManager(db).reset_llm_prompt_setting(prompt_key, actor='api')
if result.get('status') == 'error': if result.get('status') == 'error':
raise HTTPException(status_code=400, detail=result.get('message', 'Prompt reset failed')) raise HTTPException(status_code=400, detail=result.get('message', 'Prompt reset failed'))