Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AI Translation Merge Tool #159

Merged
merged 50 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
bfc4ab7
updating documentation
stoerr Jan 2, 2025
10271dd
Remove unused "translateWhenChanged" feature and _manualChange proper…
stoerr Jan 2, 2025
32bd7c7
Remove unused breakInheritance parameter - we never break inheritance…
stoerr Jan 2, 2025
af09e56
remove some debug logging statements
stoerr Jan 2, 2025
731538b
introduce properties ai_new_* into property wrapper
stoerr Jan 2, 2025
91aaaf8
for cancelled properties save originals and translations for merging
stoerr Jan 2, 2025
626c85d
first bit of merge UI
stoerr Jan 2, 2025
cac4771
collect properties
stoerr Jan 2, 2025
a7da877
refactoring
stoerr Jan 3, 2025
33a6049
use path
stoerr Jan 3, 2025
70116ff
reverse property name encoding
stoerr Jan 3, 2025
ff714e9
some tests
stoerr Jan 3, 2025
ff63ede
display several kinds of differences
stoerr Jan 3, 2025
6a8969b
only display diffs
stoerr Jan 3, 2025
acb9067
a little bit of rte text editor
stoerr Jan 3, 2025
cb7bd92
basic rte
stoerr Jan 3, 2025
51ceb0e
working RTE
stoerr Jan 3, 2025
9224929
move js and css out of list.html
stoerr Jan 3, 2025
aa94ff6
some setup to generate code for the merge app
stoerr Jan 3, 2025
e3aa3cb
continuing implementation
stoerr Jan 6, 2025
16b782a
next step
stoerr Jan 6, 2025
9be2508
continue
stoerr Jan 6, 2025
b0463c6
continue
stoerr Jan 6, 2025
a78256c
fixing
stoerr Jan 6, 2025
1e1ca41
styling
stoerr Jan 6, 2025
53d6aa8
partial servlet implementation
stoerr Jan 6, 2025
6d1cbad
partial merge implementation
stoerr Jan 6, 2025
b015c14
navigation buttons
stoerr Jan 7, 2025
fd05a88
save functionality
stoerr Jan 7, 2025
5c40bfe
implement intelligent merge
stoerr Jan 7, 2025
c9cc2ee
sticky bottom bar
stoerr Jan 7, 2025
70ad77c
implement column toggle buttons
stoerr Jan 7, 2025
404538f
implement footer bar buttons
stoerr Jan 7, 2025
0e61a17
operation "check" to find out whether a page has something to merge.
stoerr Jan 7, 2025
50f1f09
fix path matching in get page markdown ai tool
stoerr Jan 7, 2025
05a680b
modify page tool POC
stoerr Jan 7, 2025
fc7bc3a
more write adaptions
stoerr Jan 8, 2025
389588b
use high intelligence as default for content creation and raise stand…
stoerr Jan 8, 2025
20a2e1b
demoable
stoerr Jan 8, 2025
7d6812b
show component title and spinner
stoerr Jan 8, 2025
a1b3103
implement error message
stoerr Jan 8, 2025
64e65ed
test tooltip
stoerr Jan 8, 2025
f37498d
support textareas for plain text
stoerr Jan 8, 2025
34d860c
several fixes
stoerr Jan 8, 2025
876bff6
color diffs
stoerr Jan 8, 2025
e128c84
add tooltips
stoerr Jan 8, 2025
71025df
html redesign for separate action row
stoerr Jan 8, 2025
b3d8bbc
adapt javascript to row separation
stoerr Jan 8, 2025
0730af2
rework tooltips and intro text
stoerr Jan 9, 2025
815521a
remove unused aigen files
stoerr Jan 9, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package com.composum.ai.aem.core.impl;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import javax.servlet.Servlet;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.composum.ai.aem.core.impl.autotranslate.AutoTranslateMergeService;
import com.day.cq.wcm.api.WCMException;
import com.google.gson.Gson;

/**
* Servlet with functionality for the AI Translation Merge tool.
* The operations are distinguished by parameter 'operation'. There are:
* <ul>
* <li>save: save a translation</li>
* <li>check: check if a resource has unmerged translations</li>
* <li>merge: merge translations</li>
* </ul>
*/
@Component(service = Servlet.class,
property = {
Constants.SERVICE_DESCRIPTION + "=Composum AI Translation Merge Servlet",
ServletResolverConstants.SLING_SERVLET_PATHS + "=/bin/cpm/ai/aitranslationmerge",
ServletResolverConstants.SLING_SERVLET_METHODS + "=" + HttpConstants.METHOD_POST
})
public class AemAITranslationMergeServlet extends SlingAllMethodsServlet {

private static final Logger LOG = LoggerFactory.getLogger(AemAITranslationMergeServlet.class);

public static final Gson gson = new Gson();

@Reference
private AutoTranslateMergeService mergeService;

@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
String operation = request.getParameter("operation");
if ("save".equals(operation)) {
handleSave(request, response);
} else if ("check".equals(operation)) {
handleCheck(request, response);
} else if ("merge".equals(operation)) {
handleMerge(request, response);
} else {
response.sendError(SlingHttpServletResponse.SC_BAD_REQUEST, "Invalid operation");
}
}

protected void handleCheck(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
String path = request.getParameter("path");
if (StringUtils.isBlank(path)) {
response.sendError(SlingHttpServletResponse.SC_BAD_REQUEST, "Missing parameter path");
return;
}
ResourceResolver resolver = request.getResourceResolver();
Resource resource = resolver.getResource(path);
if (resource == null) {
response.sendError(SlingHttpServletResponse.SC_NOT_FOUND, "Resource not found: " + path);
return;
}
List<AutoTranslateMergeService.AutoTranslateProperty> props = mergeService.getProperties(resource);
boolean hasUnmerged = props != null && !props.isEmpty();
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().println(gson.toJson(Collections.singletonMap("mergeable", hasUnmerged)));
}

protected void handleSave(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
String path = request.getParameter("path");
String propertyName = request.getParameter("propertyName");
String body = request.getParameter("body");

if (StringUtils.isBlank(path) || StringUtils.isBlank(propertyName) || StringUtils.isBlank(body)) {
response.sendError(SlingHttpServletResponse.SC_BAD_REQUEST, "Missing parameters");
return;
}

ResourceResolver resolver = request.getResourceResolver();
Resource resource = resolver.getResource(path);
if (resource == null) {
response.sendError(SlingHttpServletResponse.SC_NOT_FOUND, "Resource not found: " + path);
return;
}

try {
mergeService.saveTranslation(resource, propertyName, body, true);
resolver.commit();
response.setStatus(SlingHttpServletResponse.SC_OK);
} catch (PersistenceException | WCMException | IllegalArgumentException e) {
LOG.error("Error saving property", e);
response.sendError(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error saving property " + propertyName + " on resource " + path);
}
}

protected void handleMerge(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
MergeRequest mergeRequest = gson.fromJson(request.getReader(), MergeRequest.class);
// if mergeRequest or any of it's properties are null, complain
if (mergeRequest == null || mergeRequest.path == null || mergeRequest.propertyName == null || mergeRequest.originalSource == null || mergeRequest.newSource == null || mergeRequest.newTranslation == null || mergeRequest.currentText == null) {
LOG.error("Invalid merge request: {}", mergeRequest);
response.sendError(SlingHttpServletResponse.SC_BAD_REQUEST, "Missing parameters");
return;
}

ResourceResolver resolver = request.getResourceResolver();
Resource resource = resolver.getResource(mergeRequest.path);
if (resource == null) {
response.sendError(SlingHttpServletResponse.SC_NOT_FOUND, "Resource not found: " + mergeRequest.path);
return;
}

LOG.info("Merging text for path: {}, propertyName: {}, OS: {}, NS: {}, NT: {}, C: {}",
mergeRequest.path, mergeRequest.propertyName, mergeRequest.originalSource, mergeRequest.newSource, mergeRequest.newTranslation, mergeRequest.currentText);

String mergedText = mergeService.intelligentMerge(mergeRequest.language,
resource, mergeRequest.originalSource, mergeRequest.newSource, mergeRequest.newTranslation, mergeRequest.currentText);

response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
response.getWriter().println(mergedText);
}

private static class MergeRequest {
String path;
String propertyName;
String originalSource;
String newSource;
String newTranslation;
String currentText;
String language;


@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
if (path != null) {
builder.append("path", path);
}
if (propertyName != null) {
builder.append("propertyName", propertyName);
}
if (originalSource != null) {
builder.append("originalSource", originalSource);
}
if (newSource != null) {
builder.append("newSource", newSource);
}
if (newTranslation != null) {
builder.append("newTranslation", newTranslation);
}
if (currentText != null) {
builder.append("currentText", currentText);
}
if (language != null) {
builder.append("targetLanguage", language);
}
return builder.toString();
}
}

}
Loading
Loading