Skip to content

Commit 4a9ec76

Browse files
authored
Merge pull request #182 from mongodb-developer/interactive_journal
Updates to dev productivity app
2 parents ebd87bd + e0bffe9 commit 4a9ec76

File tree

7 files changed

+37
-144
lines changed

7 files changed

+37
-144
lines changed

apps/project-assistant/backend/app/routers/helpers.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,21 @@ def image_to_base64(image_path: Path) -> dict:
155155

156156

157157
def save_assistant_message(
158-
db, project_id: str, project_title: str, content: str, msg_date: datetime
158+
db,
159+
project_id: str,
160+
project_title: str,
161+
content: str,
162+
version: int,
163+
msg_date: datetime,
159164
) -> None:
160165
"""Save an assistant response message."""
161166
db.messages.insert_one(
162167
{
163168
"project_id": project_id,
164169
"project_title": project_title,
170+
"user_id": USER_ID,
165171
"role": "assistant",
172+
"version": version,
166173
"content": content,
167174
"created_at": msg_date,
168175
}

apps/project-assistant/backend/app/routers/routes.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import logging
32
from datetime import datetime
43
from typing import Optional
@@ -59,21 +58,20 @@ def send_message(
5958

6059
def stream_and_save():
6160
response_content = ""
62-
for chunk in generate_response(conversation, memories=memories):
63-
yield json.dumps(chunk) + "\n"
64-
if chunk["type"] == "response":
65-
response_content += chunk["content"]
61+
for text in generate_response(conversation, memories=memories):
62+
yield text
63+
response_content += text
6664

6765
# Save messages to DB after streaming completes
6866
if content:
6967
save_user_message(db, project_id, project_title, content, version, msg_date)
7068
for path in image_paths:
7169
save_user_message(db, project_id, project_title, path, version, msg_date)
7270
save_assistant_message(
73-
db, project_id, project_title, response_content, msg_date
71+
db, project_id, project_title, response_content, version, msg_date
7472
)
7573

76-
return StreamingResponse(stream_and_save(), media_type="application/x-ndjson")
74+
return StreamingResponse(stream_and_save(), media_type="text/plain")
7775

7876

7977
@router.get("/search")

apps/project-assistant/backend/app/services/anthropic.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def extract_memories(user_message: str) -> list[dict]:
3232
response = client.beta.messages.parse(
3333
model=ANTHROPIC_MODEL,
3434
max_tokens=2000,
35-
temperature=1,
35+
temperature=0,
3636
betas=["structured-outputs-2025-11-13"],
3737
system=MEMORY_EXTRACTION_PROMPT,
3838
messages=[{"role": "user", "content": user_message}],
@@ -50,7 +50,7 @@ def extract_memories(user_message: str) -> list[dict]:
5050

5151

5252
def generate_response(messages: list[dict], memories: Optional[list[str]] = None):
53-
"""Generate a response with extended thinking."""
53+
"""Generate a streaming response."""
5454
logger.info(
5555
f"Generating response using {ANTHROPIC_MODEL} with {len(memories) if memories else 0} memories"
5656
)
@@ -62,18 +62,9 @@ def generate_response(messages: list[dict], memories: Optional[list[str]] = None
6262

6363
with client.messages.stream(
6464
model=ANTHROPIC_MODEL,
65-
max_tokens=16000,
66-
temperature=1,
67-
thinking={
68-
"type": "enabled",
69-
"budget_tokens": 8000,
70-
},
65+
max_tokens=4096,
66+
temperature=0,
7167
system=system_prompt,
7268
messages=messages,
7369
) as stream:
74-
for event in stream:
75-
if event.type == "content_block_delta":
76-
if hasattr(event.delta, "thinking"):
77-
yield {"type": "thinking", "content": event.delta.thinking}
78-
elif hasattr(event.delta, "text"):
79-
yield {"type": "response", "content": event.delta.text}
70+
yield from stream.text_stream

apps/project-assistant/backend/app/services/prompts.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,24 @@
2323
]"""
2424

2525

26-
SYSTEM_PROMPT = """You are an AI-powered developer productivity assistant.
26+
SYSTEM_PROMPT = """You are an AI-powered project-planning assistant.
2727
Your role is to help developers plan and break down projects into actionable steps.
2828
29-
IMPORTANT: When context about the user's preferences or past decisions is provided, reference them naturally to maintain consistency.
29+
IMPORTANT: When context about the user's preferences or past decisions is provided, actively reference them in your response.
3030
3131
Guidelines:
32-
- Help break down projects into smaller, manageable tasks
32+
- Break down projects into smaller, manageable tasks
3333
- Ask clarifying questions about requirements, tech stack, and constraints
3434
- Suggest best practices and potential approaches
35-
- Identify dependencies and potential blockers early
36-
- Reference past preferences (e.g., "You typically prefer TypeScript - should we use that here?")
35+
- Actively reference past preferences
3736
- Keep responses concise and actionable
3837
39-
Formatting:
40-
- Use plain text and bullet points only - no headers or titles
41-
- Keep it conversational and direct
42-
- Use numbered lists for sequential steps
38+
STRICT Formatting Rules:
39+
- NO headers, titles, or section labels
40+
- NO markdown formatting (no **, #, etc.)
41+
- Use simple bullet points with dashes (-)
42+
- Use numbered lists (1. 2. 3.) only for sequential steps
43+
- Write in plain conversational text
44+
- Keep paragraphs short
4345
44-
Remember: You're a planning assistant. Help developers think through their projects systematically."""
46+
Remember, your goal is to assist developers in planning their projects effectively while respecting their established preferences and decisions."""

apps/project-assistant/frontend/src/App.css

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -821,61 +821,6 @@
821821
display: block;
822822
}
823823

824-
/* Thinking section */
825-
.thinking-section {
826-
margin-bottom: 12px;
827-
}
828-
829-
.thinking-toggle {
830-
display: flex;
831-
align-items: center;
832-
gap: 6px;
833-
padding: 8px 12px;
834-
background: var(--mongodb-off-white);
835-
border: 1px solid var(--mongodb-gray-light);
836-
border-radius: 4px;
837-
font-family: inherit;
838-
font-size: 13px;
839-
color: var(--mongodb-slate);
840-
cursor: pointer;
841-
transition: all 0.15s ease;
842-
}
843-
844-
.thinking-toggle:hover {
845-
background: var(--mongodb-white);
846-
border-color: var(--mongodb-dark-green);
847-
color: var(--mongodb-slate);
848-
}
849-
850-
.thinking-toggle.expanded {
851-
border-bottom-left-radius: 0;
852-
border-bottom-right-radius: 0;
853-
border-bottom-color: transparent;
854-
}
855-
856-
.thinking-icon {
857-
font-size: 10px;
858-
transition: transform 0.15s ease;
859-
}
860-
861-
.thinking-label {
862-
font-weight: 500;
863-
}
864-
865-
.thinking-content {
866-
padding: 12px 16px;
867-
background: var(--mongodb-off-white);
868-
border: 1px solid var(--mongodb-gray-light);
869-
border-top: none;
870-
border-radius: 0 0 4px 4px;
871-
font-size: 13px;
872-
line-height: 1.6;
873-
color: var(--mongodb-slate);
874-
white-space: pre-wrap;
875-
max-height: 300px;
876-
overflow-y: auto;
877-
}
878-
879824
/* Date picker modal */
880825
.date-picker-overlay {
881826
position: fixed;

apps/project-assistant/frontend/src/App.jsx

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -118,43 +118,21 @@ function App() {
118118
body: formData
119119
})
120120

121-
// Read newline-delimited JSON stream
121+
// Read text stream
122122
const reader = res.body.getReader()
123123
const decoder = new TextDecoder()
124-
let buffer = ''
125-
let thinkingContent = ''
126124
let responseContent = ''
127125

128126
while (true) {
129127
const { done, value } = await reader.read()
130128
if (done) break
131129

132-
buffer += decoder.decode(value, { stream: true })
133-
134-
// Process complete JSON lines
135-
const lines = buffer.split('\n')
136-
buffer = lines.pop() // Keep incomplete line in buffer
137-
138-
for (const line of lines) {
139-
if (!line.trim()) continue
140-
try {
141-
const chunk = JSON.parse(line)
142-
if (chunk.type === 'thinking') {
143-
thinkingContent += chunk.content
144-
} else if (chunk.type === 'response') {
145-
responseContent += chunk.content
146-
}
147-
148-
// Update message with current state
149-
setMessages(prev => prev.map(msg =>
150-
msg._id === aiMessageId
151-
? { ...msg, thinking: thinkingContent, content: responseContent }
152-
: msg
153-
))
154-
} catch (e) {
155-
console.error('Failed to parse chunk:', e)
156-
}
157-
}
130+
responseContent += decoder.decode(value, { stream: true })
131+
setMessages(prev => prev.map(msg =>
132+
msg._id === aiMessageId
133+
? { ...msg, content: responseContent }
134+
: msg
135+
))
158136
}
159137
}
160138

apps/project-assistant/frontend/src/components/Entry.jsx

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,9 @@ function Entry({ messages, onSendMessage, hasActiveProject, activeProject, proje
88
const [searchResults, setSearchResults] = useState(null)
99
const [isSearching, setIsSearching] = useState(false)
1010
const [saveStatus, setSaveStatus] = useState(null)
11-
const [expandedThinking, setExpandedThinking] = useState({})
1211
const messagesEndRef = useRef(null)
1312
const fileInputRef = useRef(null)
1413

15-
const toggleThinking = (msgId) => {
16-
setExpandedThinking(prev => ({
17-
...prev,
18-
[msgId]: !prev[msgId]
19-
}))
20-
}
21-
2214
const handleSaveProject = async () => {
2315
setSaveStatus('saving')
2416
try {
@@ -179,26 +171,6 @@ function Entry({ messages, onSendMessage, hasActiveProject, activeProject, proje
179171
<div className="message-label">
180172
{msg.role === 'user' ? 'You' : 'Assistant'}
181173
</div>
182-
{msg.thinking && (
183-
<div className="thinking-section">
184-
<button
185-
className={`thinking-toggle ${expandedThinking[msg._id] ? 'expanded' : ''}`}
186-
onClick={() => toggleThinking(msg._id)}
187-
>
188-
<span className="thinking-icon">
189-
{expandedThinking[msg._id] ? '▼' : '▶'}
190-
</span>
191-
<span className="thinking-label">
192-
{expandedThinking[msg._id] ? 'Thinking' : 'Show thinking'}
193-
</span>
194-
</button>
195-
{expandedThinking[msg._id] && (
196-
<div className="thinking-content">
197-
{msg.thinking}
198-
</div>
199-
)}
200-
</div>
201-
)}
202174
{msg.content && (
203175
<div className="message-text">
204176
<ReactMarkdown>{msg.content}</ReactMarkdown>

0 commit comments

Comments
 (0)