|
|
|
|
@@ -31,6 +31,11 @@ class RequestInterpreter:
|
|
|
|
|
PLACEHOLDER_PROJECT_NAME_WORDS = {
|
|
|
|
|
'generated project', 'new project', 'project', 'temporary name', 'temp name', 'placeholder', 'untitled project',
|
|
|
|
|
}
|
|
|
|
|
ROUTING_STOPWORDS = REPO_NOISE_WORDS | GENERIC_PROJECT_NAME_WORDS | {
|
|
|
|
|
'about', 'after', 'again', 'appropriate', 'before', 'best', 'details', 'follow', 'following', 'implement',
|
|
|
|
|
'integration', 'instance', 'instances', 'later', 'make', 'now', 'primary', 'primarily', 'probably',
|
|
|
|
|
'remember', 'specific', 'suite', 'tearing', 'testing', 'through', 'used', 'using', 'workflow', 'workflows',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def __init__(self, ollama_url: str | None = None, model: str | None = None):
|
|
|
|
|
self.ollama_url = (ollama_url or settings.ollama_url).rstrip('/')
|
|
|
|
|
@@ -468,6 +473,7 @@ class RequestInterpreter:
|
|
|
|
|
projects = context.get('projects', [])
|
|
|
|
|
last_project_id = recent_history[0].get('project_id') if recent_history else None
|
|
|
|
|
last_issue = ((recent_history[0].get('related_issue') or {}).get('number') if recent_history else None)
|
|
|
|
|
last_project = next((project for project in projects if project.get('project_id') == last_project_id), None) if last_project_id else None
|
|
|
|
|
|
|
|
|
|
matched_project = None
|
|
|
|
|
for project in projects:
|
|
|
|
|
@@ -481,8 +487,24 @@ class RequestInterpreter:
|
|
|
|
|
break
|
|
|
|
|
if matched_project is None and not explicit_new:
|
|
|
|
|
follow_up_tokens = ['also', 'continue', 'for this project', 'for that project', 'work on this', 'work on that', 'fix that', 'add this']
|
|
|
|
|
if any(token in lowered for token in follow_up_tokens) and last_project_id:
|
|
|
|
|
matched_project = next((project for project in projects if project.get('project_id') == last_project_id), None)
|
|
|
|
|
leading_follow_up = lowered.startswith(('also', 'now', 'continue', 'remember', 'then'))
|
|
|
|
|
recent_overlap = 0
|
|
|
|
|
if last_project is not None:
|
|
|
|
|
recent_prompt_text = recent_history[0].get('prompt_text') or ''
|
|
|
|
|
project_reference_text = ' '.join(
|
|
|
|
|
part for part in [
|
|
|
|
|
last_project.get('name') or '',
|
|
|
|
|
last_project.get('description') or '',
|
|
|
|
|
((last_project.get('repository') or {}).get('name') or ''),
|
|
|
|
|
]
|
|
|
|
|
if part
|
|
|
|
|
)
|
|
|
|
|
recent_overlap = len(
|
|
|
|
|
self._routing_tokens(prompt_text)
|
|
|
|
|
& (self._routing_tokens(recent_prompt_text) | self._routing_tokens(project_reference_text))
|
|
|
|
|
)
|
|
|
|
|
if last_project_id and (leading_follow_up or any(token in lowered for token in follow_up_tokens) or recent_overlap >= 2):
|
|
|
|
|
matched_project = last_project
|
|
|
|
|
issue_number = referenced_issue
|
|
|
|
|
if issue_number is None and any(token in lowered for token in ['that issue', 'this issue', 'the issue']) and last_issue is not None:
|
|
|
|
|
issue_number = last_issue
|
|
|
|
|
@@ -497,6 +519,14 @@ class RequestInterpreter:
|
|
|
|
|
'reasoning_summary': 'Heuristic routing from chat history and project names.',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _routing_tokens(self, text: str) -> set[str]:
|
|
|
|
|
"""Extract meaningful tokens for heuristic continuation matching."""
|
|
|
|
|
cleaned = re.sub(r'[^a-z0-9]+', ' ', (text or '').lower())
|
|
|
|
|
return {
|
|
|
|
|
token for token in cleaned.split()
|
|
|
|
|
if len(token) >= 4 and token not in self.ROUTING_STOPWORDS
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _extract_issue_number(self, prompt_text: str) -> int | None:
|
|
|
|
|
match = re.search(r'(?:#|issue\s+)(\d+)', prompt_text, flags=re.IGNORECASE)
|
|
|
|
|
return int(match.group(1)) if match else None
|