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

Refactoring, AI Actions, Compare tool #157

Merged
merged 44 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
54b1c65
todos
stoerr Oct 25, 2024
71df4ce
AITool interface
stoerr Oct 12, 2024
c3473b4
introduce GPTTool class
stoerr Oct 14, 2024
8dfb59c
add tools attribute to ChatCompletionRequest
stoerr Oct 14, 2024
54d8d76
co-developer adds comments to chat package
stoerr Oct 14, 2024
fe64323
add tool call to response
stoerr Oct 14, 2024
e3ffad5
tool messages in input
stoerr Oct 14, 2024
53cc6b6
implement tool request
stoerr Oct 15, 2024
a9c8263
start to parse tools response
stoerr Oct 15, 2024
b667f71
fixes
stoerr Oct 15, 2024
035dbaa
add unittest for full stream
stoerr Oct 28, 2024
8855b54
reading and merging tool calls
stoerr Oct 28, 2024
5a8f51b
add tool calls to message input
stoerr Oct 28, 2024
54d1294
add a (yet failing) archunit test that checks for API depencies on impl
stoerr Oct 29, 2024
d28eeba
refactoring: GPTCompletionCallback must not use impl objects
stoerr Oct 29, 2024
ca46174
Refactoring: move GPTChatCompletionTemplate out of impl package since…
stoerr Oct 29, 2024
29a3a34
remove / execute some FIXME comments
stoerr Oct 29, 2024
e02d224
comment for auto translation rules
stoerr Oct 29, 2024
7d66aa8
start implementing streamingChatCompletionWithToolCalls
stoerr Oct 29, 2024
677a451
more tool calling implementation
stoerr Oct 30, 2024
676021d
tools for getting page markdown and searching pages
stoerr Oct 30, 2024
177cc29
first working version of tools
stoerr Oct 31, 2024
4583d1f
make URLs clickable
stoerr Oct 31, 2024
1c9868f
fixes and improvements
stoerr Oct 31, 2024
7328e95
page templating workflow process
stoerr Oct 31, 2024
36ff05f
change format of search page to hopefully avoid url "completion" by t…
stoerr Oct 31, 2024
453c50f
import comparetool
stoerr Oct 31, 2024
07d46f4
mostly fix problem of garbled content links
stoerr Oct 31, 2024
f23eaf1
comparetool fix
stoerr Nov 1, 2024
e236ba8
fix autotranslation: force committing delayed translations
stoerr Nov 1, 2024
ae4cc20
translation tool: offer to make a copy
stoerr Nov 4, 2024
dfb34cc
spec for automatic translation migration
stoerr Nov 4, 2024
7a60d1a
show diff if translate copy is present
stoerr Nov 4, 2024
b5fb087
add temperature setting to ca translation config
stoerr Nov 4, 2024
63f2546
move fake translation into inner levels to be able to test more
stoerr Nov 5, 2024
ac121ec
mark pages where translation failed with ai_translationError to make …
stoerr Nov 5, 2024
9459fef
deprecate global translation instructions
stoerr Nov 8, 2024
98810a6
fix experiments ui resource types
stoerr Nov 8, 2024
5ba6919
merge tool idea
stoerr Nov 11, 2024
94ac727
ignore _aitranslate_bak in recursive translation
stoerr Nov 13, 2024
d4755b0
Fix code scanning alert no. 21: DOM text reinterpreted as HTML
stoerr Nov 13, 2024
678f89c
Fix code scanning alert no. 18: Client-side cross-site scripting
stoerr Nov 13, 2024
e0612d3
check integrity of dompurify
stoerr Nov 13, 2024
14c33e9
one more integrity check
stoerr Nov 13, 2024
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
9 changes: 9 additions & 0 deletions TODO.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# List of minor Todos

?? Default Translation Configuration in AEM?

https://wcm.io/caconfig/editor/usage.html translation config drop down

Generated with ... in conf pages
http://localhost:4502/editor.html/content/dam/wknd/en/magazine/arctic-surfing/aloha-spirits-in-northern-norway doesnt add page text or component text!!!

- default icon for ai.composum site
!!! Dictation in Chat repeat in history bar.
!! Empty prompt text
!! http://localhost:5502/editor.html/content/xxx/com/en/about-us/going-forward.html "Explore the world..." RTE
PDF and markdown as possible datatypes for the content creation dialog
Expand All @@ -13,6 +20,8 @@ Why are the teasers in
https://author-p43852-e197429.adobeaemcloud.com/editor.html/content/gfps/com/en/products-solutions/systems/primofit.html
not included into the excerpt.

? Add history in content creation dialog, current component, last text, ...

----------------

- Marker on page whenever a translation is in progress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.ValueMap;

class AITranslatePropertyWrapper {
public class AITranslatePropertyWrapper {

/**
* PageContent only property: saves the additional instructions the page was translated with.
Expand Down Expand Up @@ -62,6 +62,12 @@ class AITranslatePropertyWrapper {
*/
public static final String AI_MANUAL_CHANGE_SUFFIX = "_manualChange";

/**
* Attribute that is set on jcr:content of a page when the translation of a page failed, to make it easy to find such pages. Not set by {@link AITranslatePropertyWrapper}, but since all property names are defined here...
* Is set to the time at which the error occurred, to make it easy to find in the logs.
*/
public static final String AI_TRANSLATION_ERRORMARKER = "ai_translationError";

private final ModifiableValueMap targetValueMap;
private final String propertyName;
private final ValueMap sourceValueMap;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.composum.ai.aem.core.impl.autotranslate;

import static com.composum.ai.aem.core.impl.autotranslate.AITranslatePropertyWrapper.AI_TRANSLATION_ERRORMARKER;
import static com.composum.ai.backend.base.service.chat.impl.GPTTranslationServiceImpl.LASTID;
import static com.composum.ai.backend.base.service.chat.impl.GPTTranslationServiceImpl.MULTITRANSLATION_SEPARATOR_END;
import static com.composum.ai.backend.base.service.chat.impl.GPTTranslationServiceImpl.MULTITRANSLATION_SEPARATOR_START;
Expand Down Expand Up @@ -131,9 +132,23 @@ public Stats translateLiveCopy(@Nonnull Resource resource,
if (translationParameters.rules != null) {
allRules.addAll(translationParameters.rules);
}

if (autoTranslateCaConfig.rules() != null) {
allRules.addAll(Arrays.asList(autoTranslateCaConfig.rules()));
}
if (autoTranslateCaConfig.temperature() != null && !autoTranslateCaConfig.temperature().trim().isEmpty()) {
try {
double temperature = Double.parseDouble(autoTranslateCaConfig.temperature());
configuration = GPTConfiguration.ofTemperature(temperature).merge(configuration);
} catch (NumberFormatException e) {
LOG.error("Invalid temperature value {} for path {}", autoTranslateCaConfig.temperature(), resource.getPath());
}
}
if (autoTranslateCaConfig.preferHighIntelligenceModel()) {
configuration = GPTConfiguration.HIGH_INTELLIGENCE.merge(configuration);
} else if (autoTranslateCaConfig.preferStandardModel()) {
configuration = GPTConfiguration.STANDARD_INTELLIGENCE.merge(configuration);
}

// collect translation rules that apply
List<PropertyToTranslate> allTranslateableProperties = new ArrayList<>();
Expand Down Expand Up @@ -364,9 +379,6 @@ protected String remapPaths(String translatedValue, String blueprintPath, String
Pattern pattern = Pattern.compile("href=\"" +
Pattern.quote(blueprintPath) + "(/[^\"]*)\"");
String result = pattern.matcher(translatedValue).replaceAll("href=\"" + livecopyPath + "$1\"");
if (translatedValue.contains("href")) { // FIXME(hps,24/10/03) no checkin
LOG.trace("Remapping paths from {} to {} in {}", blueprintPath, livecopyPath, translatedValue);
}
return result;
}

Expand All @@ -392,6 +404,7 @@ protected void markAsAiTranslated(Resource resource, LiveRelationship liveRelati
liveRelationshipManager.cancelPropertyRelationship(resource.getResourceResolver(),
liveRelationship, targetWrapper.allGeneralKeys(), false);
}
valueMap.put(AI_TRANSLATION_ERRORMARKER, Boolean.FALSE); // reset error marker if there was one.
}

/**
Expand Down Expand Up @@ -581,10 +594,6 @@ protected boolean collectPropertiesToTranslate(
stats.translateableProperties++;
AITranslatePropertyWrapper targetWrapper = new AITranslatePropertyWrapper(sourceValueMap, targetValueMap, key);

if (StringUtils.contains(targetWrapper.getOriginalCopy(), "href")) { // FIXME(hps,24/10/03) no checkin
LOG.trace("Skipping {} in {} because it contains href", key, resource.getPath());
}

// we will translate except if the property is cancelled and we don't want to touch cancelled properties,
// or if we have a current translation.
boolean isCancelled = isCancelled(resource, key, relationship);
Expand Down Expand Up @@ -621,6 +630,10 @@ protected boolean collectPropertiesToTranslate(
propertyToTranslate.propertyName = key;
propertyToTranslate.isAlreadyCorrectlyTranslated = isAlreadyCorrectlyTranslated;
propertiesToTranslate.add(propertyToTranslate);

if (targetWrapper.getOriginal().contains("THROWUPRIGHTNOW49e43jwsdsg")) {
throw new IllegalStateException("THROWUPRIGHTNOW49e43jwsdsg requested for " + sourceResource.getPath());
}
}
for (Resource child : resource.getChildren()) {
if (!PATTERN_IGNORED_SUBNODE_NAMES.matcher(child.getName()).matches()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
// is also added to Sling-ContextAware-Configuration-Classes bnd header in pom.xml
public @interface AutoTranslateCaConfig {

@Property(label = "Additional Instructions", order = 1,
description = "Additional instructions for the automatic translation.")
@Property(label = "Additional Instructions (Deprecated)", order = 1,
description = "Additional instructions for the automatic translation. Deprecated, please use 'Rules for additional Instructions' instead - if you do not give a path regex nor a content pattern the instructions will be used everywhere.")
String additionalInstructions();

@Property(label = "Rules for additional Instructions", order = 2,
Expand Down Expand Up @@ -57,4 +57,16 @@
})
String includeExistingTranslationsInRetranslation();

@Property(label = "Optional Comment (for documentation, not used by AI)", order = 7,
description = "An optional comment about the configuration, for documentation purposes (not used by the translation).",
property = {
"widgetType=textarea",
"textareaRows=2"
})
String comment();

@Property(label = "Temperature", order = 8,
description = "Optional temperature setting that determines variability and creativity as a floating point between 0.0 and 1.0")
String temperature();

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@
import org.apache.sling.api.resource.LoginException;
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.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.composum.ai.backend.slingbase.AIConfigurationService;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.WCMException;
import com.day.cq.wcm.msm.api.LiveRelationshipManager;

@Model(adaptables = SlingHttpServletRequest.class)
public class AutoTranslateListModel {
Expand All @@ -35,6 +39,9 @@ public class AutoTranslateListModel {
@OSGiService
private AutoTranslateConfigService autoTranslateConfigService;

@OSGiService
private LiveRelationshipManager liveRelationshipManager;

@Self
private SlingHttpServletRequest request;

Expand All @@ -55,7 +62,7 @@ public boolean inProgress() {
return runs.stream().filter(run -> run.isInProgress()).findAny().isPresent();
}

public AutoTranslateService.TranslationRun createRun() throws LoginException, PersistenceException {
public AutoTranslateService.TranslationRun createRun() throws LoginException, PersistenceException, WCMException {
if (run == null) {
String path = request.getParameter("path");
if (path == null || path.isEmpty()) {
Expand All @@ -64,6 +71,7 @@ public AutoTranslateService.TranslationRun createRun() throws LoginException, Pe
path = path.replaceAll("_jcr_content", "jcr:content").replaceAll("\\.html$", "").trim();
boolean recursive = request.getParameter("recursive") != null;
boolean changed = request.getParameter("translateWhenChanged") != null;
boolean copyOriginalPage = request.getParameter("copyOriginalPage") != null;
String additionalInstructions = request.getParameter("additionalInstructions");
boolean debugaddinstructions = request.getParameter("debugaddinstructions") != null;
if (debugaddinstructions) {
Expand Down Expand Up @@ -93,11 +101,48 @@ public AutoTranslateService.TranslationRun createRun() throws LoginException, Pe
parms.translateWhenChanged = changed;
parms.additionalInstructions = additionalInstructions;
parms.breakInheritance = breakInheritance;
if (copyOriginalPage) {
copyOriginalPage(request, path);
}
run = autoTranslateService.startTranslation(request.getResourceResolver(), path, parms);
}
return run;
}

/**
* If parameter copyOriginalPage is set, we create a copy of the original page with this suffix
* before doing the translation.
*/
public static final String SUFFIX_TRANSLATECOPY = "_aitranslate_bak";

/**
* Make a copy of the original page for comparison purposes.
*/
protected void copyOriginalPage(SlingHttpServletRequest request, String path) throws WCMException, PersistenceException {
ResourceResolver resolver = request.getResourceResolver();
PageManager pageManager = resolver.adaptTo(PageManager.class);
Page originalPage = pageManager.getContainingPage(path);
path = originalPage.getPath();
if (originalPage != null) {
String newPath = path + SUFFIX_TRANSLATECOPY;
if (resolver.getResource(newPath) != null) {
resolver.delete(resolver.getResource(newPath));
}
Page copy = pageManager.copy(originalPage, newPath, null, true, true, false);
if (copy != null) {
liveRelationshipManager.endRelationship(copy.getContentResource(), true);
liveRelationshipManager.detach(copy.getContentResource(), true); // end doesn't seem to work
LOG.info("Created copy of {} at {}", originalPage.getPath(), newPath);
resolver.commit();
} else {
LOG.error("Failed to create copy of {} at {}", originalPage.getPath(), newPath);
throw new IllegalArgumentException("Failed to create copy of " + originalPage.getPageTitle() + " at " + newPath);
}
} else {
throw new IllegalArgumentException("No page exists at " + path);
}
}

public String rollback() throws WCMException, PersistenceException {
String path = request.getParameter("path");
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public @interface AutoTranslateRuleConfig {

@Property(label = "Path Regex", order = 1,
description = "A regular expression matching the absolute path to the page. " +
description = "A regular expression matching the absolute path to the page, incl. jcr:content. " +
"E.g. .*/home/products/.* will match all pages under .../home/products/. If empty every page will match" +
"if the content pattern condition is met.")
String pathRegex();
Expand All @@ -30,4 +30,12 @@
})
String additionalInstructions();

@Property(label = "Optional Comment (for documentation, not used by AI)", order = 4,
description = "An optional comment for the rule, for documentation purposes (not used by the translation).",
property = {
"widgetType=textarea",
"textareaRows=2"
})
String comment();

}
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ abstract class TranslationPage {
Pattern.compile("\\.(png|jpg|jpeg|gif|svg|mp3|mov|mp4)(/|$)", Pattern.CASE_INSENSITIVE);

public String pagePath;
public String translateCopyPagePath;

public String status;
public AutoPageTranslateService.Stats stats;

Expand All @@ -172,6 +174,17 @@ public String editorUrl() {
return "/editor.html" + pagePath + ".html";
}
}

/** If a translate copy is present, this would open a diff view. */
public String diffToCopyUrl() {
if (startsWith(pagePath, "/content/dam") || translateCopyPagePath == null) {
return null;
} else {
return "/mnt/overlay/wcm/core/content/sites/diffresources.html" + pagePath +
"?item=" + translateCopyPagePath + "&sideBySide";
}
}

}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.composum.ai.aem.core.impl.autotranslate;

import static com.composum.ai.aem.core.impl.autotranslate.AITranslatePropertyWrapper.AI_TRANSLATION_ERRORMARKER;
import static org.apache.commons.lang3.StringUtils.startsWith;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
Expand All @@ -10,6 +14,7 @@
import javax.annotation.Nonnull;

import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
Expand Down Expand Up @@ -131,9 +136,10 @@ public TranslationRun startTranslation(
TranslationRunImpl run = new TranslationRunImpl();
run.id = "" + Math.abs(System.nanoTime());
run.rootPath = path;
run.translationParameters = translationParameters;
run.translationParameters = translationParameters.clone();
run.translationParameters.autoSave = true; // otherwise it'll be just rolled back
run.translatedPages = resources.stream()
.map(r -> new TranslationPageImpl(r.getPath()))
.map(r -> new TranslationPageImpl(r))
.collect(Collectors.toList());
run.waituntil = System.currentTimeMillis() + 1000; // when triggered during live copy creation.
run.status = TranslationStatus.QUEUED;
Expand All @@ -144,7 +150,7 @@ public TranslationRun startTranslation(
}

protected List<Resource> collectPages(Resource root, int maxDepth) {
if (maxDepth < 0) {
if (maxDepth < 0 || root.getName().endsWith(AutoTranslateListModel.SUFFIX_TRANSLATECOPY)) {
return Collections.emptyList();
}
if (root.getPath().contains("/jcr:content")) {
Expand Down Expand Up @@ -233,10 +239,21 @@ public void execute(ResourceResolver callResourceResolver) {
resourceResolver.revert();
resourceResolver.refresh();
Resource resource = resourceResolver.getResource(page.resourcePath);
if (resource != null) {
AutoPageTranslateService.Stats stats = pageTranslateService.translateLiveCopy(resource, translationParameters);
page.stats = stats;
page.status = stats.hasChanges() ? "done" : "unchanged";
try {
if (resource != null) {
AutoPageTranslateService.Stats stats = pageTranslateService.translateLiveCopy(resource, translationParameters);
page.stats = stats;
page.status = stats.hasChanges() ? "done" : "unchanged";
}
} catch (GPTException.GPTUserNotificationException e) {
throw e;
} catch (Exception e) {
resourceResolver.revert();
resourceResolver.refresh();
// mark translation as failed.
resource.adaptTo(ModifiableValueMap.class).put(AI_TRANSLATION_ERRORMARKER, Calendar.getInstance());
resourceResolver.commit();
throw e;
}
} catch (GPTException.GPTUserNotificationException e) {
page.status = "cancelled - user notification";
Expand Down Expand Up @@ -273,11 +290,15 @@ public void execute(ResourceResolver callResourceResolver) {
public static class TranslationPageImpl extends TranslationPage {
String resourcePath;

public TranslationPageImpl(String resourcePath) {
this.resourcePath = resourcePath;
public TranslationPageImpl(Resource resource) {
this.resourcePath = resource.getPath();
pagePath = ResourceUtil.getParent(resourcePath); // remove jcr:content
this.status = "queued";
Resource translateCopyResource = resource.getParent().getParent()
.getChild(resource.getParent().getName() + AutoTranslateListModel.SUFFIX_TRANSLATECOPY);
translateCopyPagePath = translateCopyResource != null ? translateCopyResource.getPath() : null;
}

}

}
Loading
Loading