Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .mega-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ DISABLE_LINTERS:
- REPOSITORY_DEVSKIM
- REPOSITORY_TRIVY
- COPYPASTE_JSCPD
- PYTHON_PYRIGHT
- PYTHON_PYLINT

FILTER_REGEX_EXCLUDE: "(src/main/resources/static|script)"
34 changes: 34 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
version: "3.8"

services:
mock-suggestion:
build:
context: ./mock/suggestion
dockerfile: Dockerfile
container_name: mock-suggestion
ports:
- "5001:5000"
networks:
- cg-network

postgres:
image: postgres:latest
container_name: postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: commonground
ports:
- "5432:5432"
networks:
- cg-network
volumes:
- postgres_data:/var/lib/postgresql/data

networks:
cg-network:
driver: bridge

volumes:
postgres_data:
driver: local
14 changes: 14 additions & 0 deletions mock/suggestion/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3.9-slim
LABEL authors="yichen"

WORKDIR /app

COPY app.py .
COPY config.py .
COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 5000

CMD ["python", "app.py"]
93 changes: 93 additions & 0 deletions mock/suggestion/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
import json
from config import KEYWORDS, REPLACEMENTS

app = Flask(__name__)
CORS(app)


def highlight_text(text):
"""Scan text for predefined keywords and highlight them with <sugX> tags."""
highlighted_text = text
suggestions = []
counter = 1

for category, words in KEYWORDS.items():
for word in words:
if word in highlighted_text:
tag = f"<sug{counter}>{word}</sug{counter}>"
highlighted_text = highlighted_text.replace(word, tag, 1)
suggestions.append({
"message": f"{highlighted_text}",
"feedback":
f"This phrase falls under {category}. Consider rewording.",
"replacement":
REPLACEMENTS.get(word, f"Consider rewording '{word}'")
})
counter += 1

return highlighted_text, suggestions


@app.route('/api/mock-text-suggestion', methods=['POST'])
def mock_text_suggestion():
"""Mock API to analyze the original text input."""
data = request.json
text = data.get("text", "")

if not text:
return jsonify({"error": "No text provided"}), 400

highlighted_text, suggestions = highlight_text(text)

response = {
"text": text,
"suggestions": suggestions
}

return app.response_class(
response=json.dumps(response, ensure_ascii=False),
status=200,
mimetype="application/json"
)


@app.route('/api/mock-edited-text-suggestion', methods=['POST'])
def mock_edited_text_suggestion():
"""Mock API to analyze user-edited text."""
data = request.json
text = data.get("text", "")
edited_text = data.get("edited_text", "")
suggestions = data.get("suggestions", [])

if not text or not edited_text:
return jsonify({"error": "No text or edited_text provided"}), 400

# Mock the text-suggestion for edited highlight text
new_suggestions = []
for suggestion in suggestions:
if "<sug1>" in suggestion["message"]: # Only provide suggestion for `<sug1>`
new_suggestions.append({
"edited_message": suggestion["edited_message"].replace(
"來佔便宜",
"可能尋求更好的經濟機會"
),
"feedback": "‘佔便宜’ 可能帶有偏見性表述,建議以更客觀的方式描述難民的處境。",
"replacement": "部分難民可能尋求更好的經濟機會與生活條件"
})

response = {
"edited_text": edited_text,
"suggestions": new_suggestions
}

return app.response_class(
response=json.dumps(response, ensure_ascii=False),
status=200,
mimetype="application/json"
)


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False) # nosec B104
19 changes: 19 additions & 0 deletions mock/suggestion/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
KEYWORDS = {
"煽動性詞語": ["邪惡", "愚蠢", "可恥", "荒謬", "噁心", "醜陋"],
"極端化詞彙": ["所有", "沒有例外", "絕對", "從來沒有", "毫無疑問"],
"缺乏事實支持": ["明顯", "大家都知道", "鐵一般的事實"],
"人身攻擊": ["蠢蛋", "白痴", "低能", "垃圾", "腦殘", "智障"],
"偏見 & 刻板印象": ["某某派", "某某黨", "年輕人都", "女人就是", "男人就是"],
"引戰語言": ["你不懂啦", "這麼簡單的道理", "睜眼說瞎話", "你果然是"],
"缺乏邏輯推理": ["我猜測", "應該是這樣", "想都不用想", "這一定是"]
}

REPLACEMENTS = {
"愚蠢": "可能存在問題",
"所有": "許多",
"明顯": "根據數據顯示",
"垃圾": "缺乏邏輯",
"年輕人都": "某些年輕人可能傾向於",
"你不懂啦": "這點可能需要進一步討論",
"我猜測": "根據目前資訊,我推測"
}
2 changes: 2 additions & 0 deletions mock/suggestion/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask
flask-cors
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package tw.commonground.backend.service.suggestion;

import tw.commonground.backend.service.suggestion.dto.*;
import org.springframework.web.bind.annotation.*;
import tw.commonground.backend.shared.tracing.Traced;

@Traced
@RestController
@RequestMapping("/api")
public class SuggestionController {

private final SuggestionService suggestionService;

public SuggestionController(SuggestionService suggestionService) {
this.suggestionService = suggestionService;
}

@PostMapping("/text-suggestion")
public TextSuggestionResponse getTextSuggestions(@RequestBody TextSuggestionRequest request) {
return suggestionService.getTextSuggestions(request);
}

@PostMapping("/edited-text-suggestion")
public EditedTextSuggestionResponse getEditedTextSuggestions(@RequestBody EditedTextSuggestionRequest request) {
return suggestionService.getEditedTextSuggestions(request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package tw.commonground.backend.service.suggestion;

import org.springframework.beans.factory.annotation.Value;
import tw.commonground.backend.service.suggestion.dto.*;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class SuggestionService {

private final RestTemplate restTemplate = new RestTemplate();

@Value("${mockApiUrl}")
private String mockApiUrl;

public SuggestionService() { }


public TextSuggestionResponse getTextSuggestions(TextSuggestionRequest request) {
String url = mockApiUrl + "/api/mock-text-suggestion";

return makePostRequest(url, request, TextSuggestionResponse.class);
}


public EditedTextSuggestionResponse getEditedTextSuggestions(EditedTextSuggestionRequest request) {
String url = mockApiUrl + "/api/mock-edited-text-suggestion";

return makePostRequest(url, request, EditedTextSuggestionResponse.class);
}


private <T> T makePostRequest(String url, Object request, Class<T> responseType) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<Object> requestEntity = new HttpEntity<>(request, headers);
ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, responseType);

return response.getBody();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package tw.commonground.backend.service.suggestion.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class EditedTextRequestSuggestionItem {
private String message;

private String feedback;

private String replacement;

@JsonProperty("edited_message")
private String editedMessage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tw.commonground.backend.service.suggestion.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class EditedTextResponseSuggestionItem {
private String feedback;

private String replacement;

@JsonProperty("edited_message")
private String editedMessage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package tw.commonground.backend.service.suggestion.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.util.List;

@Getter
@Setter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class EditedTextSuggestionRequest {
private String text;

@JsonProperty("edited_text")
private String editedText;

private List<EditedTextRequestSuggestionItem> suggestions;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tw.commonground.backend.service.suggestion.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.util.List;

@Getter
@Setter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class EditedTextSuggestionResponse {
@JsonProperty("edited_text")
private String editedText;

private List<EditedTextResponseSuggestionItem> suggestions;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package tw.commonground.backend.service.suggestion.dto;

import lombok.*;

@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class TextResponseSuggestionItem {
private String message;

private String feedback;

private String replacement;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tw.commonground.backend.service.suggestion.dto;

import lombok.*;

@Getter
@Setter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class TextSuggestionRequest {
private String text;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package tw.commonground.backend.service.suggestion.dto;

import lombok.*;

import java.util.List;

@Getter
@Setter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class TextSuggestionResponse {
private String text;

private List<TextResponseSuggestionItem> suggestions;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package tw.commonground.backend.service.suggestion.dto;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package tw.commonground.backend.service.suggestion;