rdisipio commited on
Commit
68fb546
·
unverified ·
1 Parent(s): 73d2598

save info

Browse files
BASIC_RULES.md CHANGED
@@ -34,6 +34,7 @@ This document defines engineering constraints and implementation standards.
34
  - Original user text is sent to the backend for processing.
35
  - LLM calls are performed by the backend through OpenRouter.
36
  - Provide a mode/flow where no personal information or edit history is persisted.
 
37
 
38
  ## Design Constraints
39
  The product should align with Dieter Rams' 10 principles of design:
 
34
  - Original user text is sent to the backend for processing.
35
  - LLM calls are performed by the backend through OpenRouter.
36
  - Provide a mode/flow where no personal information or edit history is persisted.
37
+ - Default submission behavior should keep personal profile/history storage disabled unless explicitly enabled by the user.
38
 
39
  ## Design Constraints
40
  The product should align with Dieter Rams' 10 principles of design:
PROJECT_OVERVIEW.md CHANGED
@@ -22,7 +22,8 @@ Build an uncertainty-aware editorial workflow where users paste source text, rec
22
  - `factual error`
23
  - `cultural mismatch`
24
  12. User edits multiple sentences if needed.
25
- 13. User submits all edits.
 
26
 
27
  ## 3. Technical Architecture (Initial)
28
  - Frontend (React + Blueprint):
@@ -33,7 +34,8 @@ Build an uncertainty-aware editorial workflow where users paste source text, rec
33
  - Receive source paragraph and rewrite mode.
34
  - Orchestrate LLM rewrite via OpenRouter.
35
  - Compute and return uncertainty metadata per sentence.
36
- - Accept and process submitted user edits.
 
37
 
38
  ## 4. Runtime and Deployment Plan
39
  - Phase 1 (current): run locally on macOS for development and iteration.
@@ -57,6 +59,7 @@ Build an uncertainty-aware editorial workflow where users paste source text, rec
57
 
58
  ## 6. Privacy Requirement
59
  Users must be able to use the system without storing personal information, including edit history.
 
60
 
61
  ## 7. Open Decisions
62
  - Final UI control for threshold (toggle vs slider).
 
22
  - `factual error`
23
  - `cultural mismatch`
24
  12. User edits multiple sentences if needed.
25
+ 13. User chooses whether personal profile/history storage is enabled (default: disabled).
26
+ 14. User submits all edits to backend.
27
 
28
  ## 3. Technical Architecture (Initial)
29
  - Frontend (React + Blueprint):
 
34
  - Receive source paragraph and rewrite mode.
35
  - Orchestrate LLM rewrite via OpenRouter.
36
  - Compute and return uncertainty metadata per sentence.
37
+ - Accept submitted user edits via a dedicated API.
38
+ - Current stage: receive and acknowledge edits only (no downstream processing yet).
39
 
40
  ## 4. Runtime and Deployment Plan
41
  - Phase 1 (current): run locally on macOS for development and iteration.
 
59
 
60
  ## 6. Privacy Requirement
61
  Users must be able to use the system without storing personal information, including edit history.
62
+ The storage option defaults to no personal storage and is user-controlled at submission time.
63
 
64
  ## 7. Open Decisions
65
  - Final UI control for threshold (toggle vs slider).
backend/main.py CHANGED
@@ -65,6 +65,34 @@ class SummarizeResponse(BaseModel):
65
  sentences: list[SentenceUncertainty]
66
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  def _utc_iso_now() -> str:
69
  """Return the current UTC timestamp in ISO 8601 format."""
70
  return datetime.now(timezone.utc).isoformat()
@@ -150,3 +178,34 @@ def summarize(payload: SummarizeRequest) -> SummarizeResponse:
150
  )
151
 
152
  return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  sentences: list[SentenceUncertainty]
66
 
67
 
68
+ class EditorialChange(BaseModel):
69
+ """One user-proposed editorial change."""
70
+
71
+ sentence: str = Field(min_length=1)
72
+ correction: str
73
+ tag: Literal["editorial refinement", "factual error", "cultural mismatch"]
74
+ created_at: str
75
+
76
+
77
+ class EditorialChangesRequest(BaseModel):
78
+ """Payload for submitting staged editorial changes."""
79
+
80
+ source_text: str = Field(min_length=1)
81
+ style: RewriteStyle | None = None
82
+ summary: str = Field(min_length=1)
83
+ store_personal_data: bool = False
84
+ edits: list[EditorialChange]
85
+
86
+
87
+ class EditorialChangesResponse(BaseModel):
88
+ """Acknowledgement payload for editorial change submissions."""
89
+
90
+ status: Literal["accepted"]
91
+ received_at: str
92
+ edits_received: int
93
+ store_personal_data: bool
94
+
95
+
96
  def _utc_iso_now() -> str:
97
  """Return the current UTC timestamp in ISO 8601 format."""
98
  return datetime.now(timezone.utc).isoformat()
 
178
  )
179
 
180
  return response
181
+
182
+
183
+ @backend.post("/api/editorial-changes", response_model=EditorialChangesResponse)
184
+ def submit_editorial_changes(payload: EditorialChangesRequest) -> EditorialChangesResponse:
185
+ """Receive staged editorial changes without applying business processing."""
186
+ received_at = _utc_iso_now()
187
+ logger.info(
188
+ (
189
+ "Editorial changes received | style=%s edits=%s "
190
+ "store_personal_data=%s summary_length=%s source_length=%s"
191
+ ),
192
+ payload.style,
193
+ len(payload.edits),
194
+ payload.store_personal_data,
195
+ len(payload.summary),
196
+ len(payload.source_text),
197
+ )
198
+
199
+ response = EditorialChangesResponse(
200
+ status="accepted",
201
+ received_at=received_at,
202
+ edits_received=len(payload.edits),
203
+ store_personal_data=payload.store_personal_data,
204
+ )
205
+ logger.info(
206
+ "Editorial changes accepted | edits_received=%s store_personal_data=%s received_at=%s",
207
+ response.edits_received,
208
+ response.store_personal_data,
209
+ response.received_at,
210
+ )
211
+ return response
frontend/src/App.jsx CHANGED
@@ -13,6 +13,9 @@ export function App() {
13
  const [sentences, setSentences] = useState([]);
14
  const [editorialCards, setEditorialCards] = useState([]);
15
  const [isLoading, setIsLoading] = useState(false);
 
 
 
16
  const [errorMessage, setErrorMessage] = useState("");
17
 
18
  const handleGenerate = async (style) => {
@@ -24,6 +27,7 @@ export function App() {
24
 
25
  setIsLoading(true);
26
  setErrorMessage("");
 
27
  setSelectedStyle(style);
28
 
29
  try {
@@ -95,6 +99,51 @@ export function App() {
95
  );
96
  };
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  return (
99
  <main className="app-shell">
100
  <Card className="panel" elevation={1}>
@@ -191,6 +240,23 @@ export function App() {
191
  </div>
192
  </Card>
193
  ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  </section>
195
  ) : null}
196
  </Card>
 
13
  const [sentences, setSentences] = useState([]);
14
  const [editorialCards, setEditorialCards] = useState([]);
15
  const [isLoading, setIsLoading] = useState(false);
16
+ const [isSubmittingChanges, setIsSubmittingChanges] = useState(false);
17
+ const [storePersonalData, setStorePersonalData] = useState(false);
18
+ const [submitMessage, setSubmitMessage] = useState("");
19
  const [errorMessage, setErrorMessage] = useState("");
20
 
21
  const handleGenerate = async (style) => {
 
27
 
28
  setIsLoading(true);
29
  setErrorMessage("");
30
+ setSubmitMessage("");
31
  setSelectedStyle(style);
32
 
33
  try {
 
99
  );
100
  };
101
 
102
+ const handleSubmitChanges = async () => {
103
+ if (editorialCards.length === 0) {
104
+ setErrorMessage("No editorial changes to submit.");
105
+ return;
106
+ }
107
+
108
+ setIsSubmittingChanges(true);
109
+ setErrorMessage("");
110
+ setSubmitMessage("");
111
+
112
+ try {
113
+ const response = await fetch(`${API_BASE_URL}/api/editorial-changes`, {
114
+ method: "POST",
115
+ headers: {
116
+ "Content-Type": "application/json"
117
+ },
118
+ body: JSON.stringify({
119
+ source_text: sourceText,
120
+ style: selectedStyle || null,
121
+ summary: generatedSummary,
122
+ store_personal_data: storePersonalData,
123
+ edits: editorialCards.map((card) => ({
124
+ sentence: card.sentence,
125
+ correction: card.correction,
126
+ tag: card.tag,
127
+ created_at: card.createdAt
128
+ }))
129
+ })
130
+ });
131
+
132
+ if (!response.ok) {
133
+ throw new Error(`Submit failed with status ${response.status}.`);
134
+ }
135
+
136
+ const data = await response.json();
137
+ setSubmitMessage(
138
+ `Changes submitted (${data.edits_received} edits, personal storage: ${data.store_personal_data ? "enabled" : "disabled"}).`
139
+ );
140
+ } catch (error) {
141
+ setErrorMessage(error instanceof Error ? error.message : "Unknown error.");
142
+ } finally {
143
+ setIsSubmittingChanges(false);
144
+ }
145
+ };
146
+
147
  return (
148
  <main className="app-shell">
149
  <Card className="panel" elevation={1}>
 
240
  </div>
241
  </Card>
242
  ))}
243
+ <div className="submit-controls">
244
+ <label className="privacy-checkbox">
245
+ <input
246
+ type="checkbox"
247
+ checked={storePersonalData}
248
+ onChange={(event) => setStorePersonalData(event.target.checked)}
249
+ />
250
+ Store personal information in profile/history
251
+ </label>
252
+ <Button
253
+ intent="success"
254
+ text="Submit Changes"
255
+ loading={isSubmittingChanges}
256
+ onClick={handleSubmitChanges}
257
+ />
258
+ </div>
259
+ {submitMessage ? <p className="success-text">{submitMessage}</p> : null}
260
  </section>
261
  ) : null}
262
  </Card>
frontend/src/styles.css CHANGED
@@ -108,3 +108,23 @@ body {
108
  gap: 8px;
109
  margin-top: 10px;
110
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  gap: 8px;
109
  margin-top: 10px;
110
  }
111
+
112
+ .submit-controls {
113
+ display: flex;
114
+ flex-wrap: wrap;
115
+ align-items: center;
116
+ justify-content: space-between;
117
+ gap: 12px;
118
+ margin-top: 8px;
119
+ }
120
+
121
+ .privacy-checkbox {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 8px;
125
+ }
126
+
127
+ .success-text {
128
+ color: #0a6640;
129
+ margin: 0;
130
+ }