Skip to content

Commit bfcc268

Browse files
authored
Merge pull request #381 from weaviate/v6-iteration1-feedback
v6: Implement feedback from Iteration I - NearText search + GroupBy - New vectorizers: 1. `text2vec-contextionary` 2. `text2vec-weaviate` 3. `multi2vec-clip` 4. `img2vec-neural` - BLOB data type + NearImage search
2 parents 8649708 + b71fbd7 commit bfcc268

28 files changed

Lines changed: 860 additions & 173 deletions

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@
349349
<configuration>
350350
<sources>
351351
<source>${project.basedir}/src/it/java</source>
352+
<source>${project.basedir}/src/it/resources</source>
352353
</sources>
353354
</configuration>
354355
</execution>

src/it/java/io/weaviate/containers/Container.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
public class Container {
1717
public static final Weaviate WEAVIATE = Weaviate.createDefault();
1818
public static final Contextionary CONTEXTIONARY = Contextionary.createDefault();
19+
public static final Img2VecNeural IMG2VEC_NEURAL = Img2VecNeural.createDefault();
1920

2021
static {
2122
startAll();
@@ -39,21 +40,23 @@ static void stopAll() {
3940
WEAVIATE.stop();
4041
}
4142

42-
public static Group compose(Weaviate weaviate, GenericContainer<?>... containers) {
43-
return new Group(weaviate, containers);
43+
public static ContainerGroup compose(Weaviate weaviate, GenericContainer<?>... containers) {
44+
return new ContainerGroup(weaviate, containers);
4445
}
4546

4647
public static TestRule asTestRule(Startable container) {
4748
return new PerTestSuite(container);
4849
};
4950

50-
public static class Group implements Startable {
51+
public static class ContainerGroup implements Startable {
5152
private final Weaviate weaviate;
5253
private final List<GenericContainer<?>> containers;
5354

54-
private Group(Weaviate weaviate, GenericContainer<?>... containers) {
55+
private ContainerGroup(Weaviate weaviate, GenericContainer<?>... containers) {
5556
this.weaviate = weaviate;
5657
this.containers = Arrays.asList(containers);
58+
59+
weaviate.dependsOn(containers);
5760
setSharedNetwork();
5861
}
5962

@@ -63,8 +66,7 @@ public WeaviateClient getClient() {
6366

6467
@Override
6568
public void start() {
66-
containers.forEach(GenericContainer::start);
67-
weaviate.start();
69+
weaviate.start(); // testcontainers will resolve dependencies
6870
}
6971

7072
@Override

src/it/java/io/weaviate/containers/Contextionary.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public Contextionary build() {
3535
.withEnv("EXTENSIONS_STORAGE_ORIGIN", "http://weaviate:8080")
3636
.withEnv("NEIGHBOR_OCCURRENCE_IGNORE_PERCENTILE", "5")
3737
.withEnv("ENABLE_COMPOUND_SPLITTING", "'false'");
38-
container.withCreateContainerCmdModifier(cmd -> cmd.withHostName("contextionary"));
38+
container.withCreateContainerCmdModifier(cmd -> cmd.withHostName(HOST_NAME));
3939
return container;
4040
}
4141
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.weaviate.containers;
2+
3+
import org.testcontainers.containers.GenericContainer;
4+
5+
public class Img2VecNeural extends GenericContainer<Img2VecNeural> {
6+
public static final String DOCKER_IMAGE = "cr.weaviate.io/semitechnologies/img2vec-pytorch";
7+
public static final String VERSION = "resnet50";
8+
9+
public static final String MODULE = "img2vec-neural";
10+
public static final String HOST_NAME = MODULE;
11+
public static final String URL = HOST_NAME + ":8080";
12+
13+
static Img2VecNeural createDefault() {
14+
return new Builder().build();
15+
}
16+
17+
static Img2VecNeural.Builder custom() {
18+
return new Builder();
19+
}
20+
21+
public static class Builder {
22+
private String versionTag;
23+
24+
public Builder() {
25+
this.versionTag = VERSION;
26+
}
27+
28+
public Img2VecNeural build() {
29+
var container = new Img2VecNeural(DOCKER_IMAGE + ":" + versionTag);
30+
container.withCreateContainerCmdModifier(cmd -> cmd.withHostName(HOST_NAME));
31+
return container;
32+
}
33+
}
34+
35+
public Img2VecNeural(String image) {
36+
super(image);
37+
}
38+
}

src/it/java/io/weaviate/containers/Weaviate.java

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package io.weaviate.containers;
22

33
import java.io.IOException;
4+
import java.util.Arrays;
5+
import java.util.HashMap;
46
import java.util.HashSet;
7+
import java.util.Map;
58
import java.util.Set;
69

710
import org.testcontainers.weaviate.WeaviateContainer;
@@ -10,7 +13,7 @@
1013
import io.weaviate.client6.WeaviateClient;
1114

1215
public class Weaviate extends WeaviateContainer {
13-
private static WeaviateClient clientInstance;
16+
private WeaviateClient clientInstance;
1417

1518
public static final String VERSION = "1.29.0";
1619
public static final String DOCKER_IMAGE = "semitechnologies/weaviate";
@@ -42,14 +45,14 @@ public static Weaviate.Builder custom() {
4245

4346
public static class Builder {
4447
private String versionTag;
45-
private Set<String> enableModules;
48+
private Set<String> enableModules = new HashSet<>();
4649
private String defaultVectorizerModule;
47-
private String contextionaryUrl;
4850
private boolean telemetry;
4951

52+
private Map<String, String> environment = new HashMap<>();
53+
5054
public Builder() {
5155
this.versionTag = VERSION;
52-
this.enableModules = new HashSet<>();
5356
this.telemetry = false;
5457
}
5558

@@ -58,43 +61,46 @@ public Builder withVersion(String version) {
5861
return this;
5962
}
6063

61-
public Builder addModule(String module) {
62-
enableModules.add(module);
64+
public Builder addModules(String... modules) {
65+
enableModules.addAll(Arrays.asList(modules));
6366
return this;
6467
}
6568

6669
public Builder withDefaultVectorizer(String module) {
67-
addModule(module);
68-
defaultVectorizerModule = module;
70+
addModules(module);
71+
environment.put("DEFAULT_VECTORIZER_MODULE", module);
6972
return this;
7073
}
7174

7275
public Builder withContextionaryUrl(String url) {
73-
contextionaryUrl = url;
76+
addModules(Contextionary.MODULE);
77+
environment.put("CONTEXTIONARY_URL", url);
78+
return this;
79+
}
80+
81+
public Builder withImageInference(String url, String module) {
82+
addModules(module);
83+
environment.put("IMAGE_INFERENCE_API", "http://" + url);
7484
return this;
7585
}
7686

77-
public Builder enableTelemetry() {
78-
telemetry = true;
87+
public Builder enableTelemetry(boolean enable) {
88+
telemetry = enable;
7989
return this;
8090
}
8191

8292
public Weaviate build() {
8393
var c = new Weaviate(DOCKER_IMAGE + ":" + versionTag);
8494

8595
if (!enableModules.isEmpty()) {
96+
c.withEnv("ENABLE_API_BASED_MODULES", "'true'");
8697
c.withEnv("ENABLE_MODULES", String.join(",", enableModules));
8798
}
88-
if (defaultVectorizerModule != null) {
89-
c.withEnv("DEFAULT_VECTORIZER_MODULE", defaultVectorizerModule);
90-
}
91-
if (contextionaryUrl != null) {
92-
c.withEnv("CONTEXTIONARY_URL", contextionaryUrl);
93-
}
9499
if (!telemetry) {
95100
c.withEnv("DISABLE_TELEMETRY", "true");
96101
}
97102

103+
environment.forEach((name, value) -> c.withEnv(name, value));
98104
c.withCreateContainerCmdModifier(cmd -> cmd.withHostName("weaviate"));
99105
return c;
100106
}

src/it/java/io/weaviate/integration/CollectionsITest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ public void testCrossReferences() throws IOException {
6060

6161
// Assert: Things --ownedBy-> Owners
6262
Assertions.assertThat(things.config.get())
63-
// Assertions.assertThat(client.collections.getConfig(nsOwners))
6463
.as("after create Things").get()
6564
.satisfies(c -> {
6665
Assertions.assertThat(c.references())

src/it/java/io/weaviate/integration/DataITest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.weaviate.client6.v1.collections.VectorIndex.IndexingStrategy;
1616
import io.weaviate.client6.v1.collections.Vectorizer;
1717
import io.weaviate.client6.v1.collections.object.Vectors;
18+
import io.weaviate.client6.v1.collections.object.WeaviateObject;
1819
import io.weaviate.containers.Container;
1920

2021
public class DataITest extends ConcurrentTest {
@@ -62,6 +63,29 @@ public void testCreateGetDelete() throws IOException {
6263
Assertions.assertThat(object).isEmpty().as("object not exists after deletion");
6364
}
6465

66+
@Test
67+
public void testBlobData() throws IOException {
68+
var nsCats = ns("Cats");
69+
70+
client.collections.create(nsCats,
71+
collection -> collection.properties(
72+
Property.text("breed"),
73+
Property.blob("img")));
74+
75+
var cats = client.collections.use(nsCats);
76+
var ragdollPng = EncodedMedia.IMAGE;
77+
var ragdoll = cats.data.insert(Map.of(
78+
"breed", "ragdoll",
79+
"img", ragdollPng));
80+
81+
var got = cats.data.get(ragdoll.metadata().id(),
82+
cat -> cat.returnProperties("img"));
83+
84+
Assertions.assertThat(got).get()
85+
.extracting(WeaviateObject::properties, InstanceOfAssertFactories.MAP)
86+
.extractingByKey("img").isEqualTo(ragdollPng);
87+
}
88+
6589
private static void createTestCollections() throws IOException {
6690
var awardsGrammy = unique("Grammy");
6791
client.collections.create(awardsGrammy);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.weaviate.integration;
2+
3+
class EncodedMedia {
4+
public static final String IMAGE = "/9j/4AAQSkZJRgABAQAAkACQAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAACQAAAAAQAAAJAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAnKgAwAEAAAAAQAAAbsAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/AABEIAbsCcgMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2wBDABwcHBwcHDAcHDBEMDAwRFxEREREXHRcXFxcXHSMdHR0dHR0jIyMjIyMjIyoqKioqKjExMTExNzc3Nzc3Nzc3Nz/2wBDASIkJDg0OGA0NGDmnICc5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ub/3QAEACj/2gAMAwEAAhEDEQA/AOkooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9DpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/R6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/0ukooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9PpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/U6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/1ekooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9bpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/X6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoophdR3pN23AfRUfmj0pRIDU+0j3HYfRRRViCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooJA5NABRURlXOBzR5hPas3Viuo7EtFM3+tPBzVKSewgoooqgCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//Q6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKQkDk0ALTWdV61C0pPC1HWMqvYdhzOzewpAKMU6uaUmyhRS4BoBFPAqUmwGqxBwelTVXPWplORXRRn9liaHUUUV0khRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAASAMmqruWNSSN2qsa5a9S3uoqKJBUgquCRT9xrl5irE+aX6VEGp4NaRmKw8N60+o6OR0rojV7isSUU0MDTq3TT2JCiiimAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH/9HpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACimNIq1XZ2br0rOVRIdiZpQOF5qAkscmiiueU29x2ClxRS1k2MKQmjNFZtjFXrVioV61N2roo7EsgPWpE61F3qRfvCsoP3kNk1FFFekQFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUhOBmlpr/doYFZzUZqRutRmvMqfEzRCU4U2nCsxjqXOKSigCQN608GoaASKpTsKxP1oBI61GG9akzW8agrDwQaWo8elKG7GumNTuTYfRR1orUQUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/9LpKKKKACiiigAooooAKKKKACiiigAoozjk1A8vZfzqZSS3AlZ1XrVdpGb2FM+tFc8qjZVgpaSlrFsYtFJmiochi5pKSlrNsYUtJSimgJFqQ9KjWnt0rqhsQyv3qVeoqIdalXqKwj8SGyeiiivTICiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKa/3adTX+7SewFZutRmnt1phrzanxMtCU6m06oGLS0lFIYtFFFAC0oJFNooAlDZp1Q0oYirjMViXp0p4b1qMHNL1rohUtsS0S0VGCRTwQa6YzTFYWiiirEFFFFABRRRQAUUUUAFFFFABRRRQB//T6SiiigAooooAKKKKACiiigAoJwMmioJmx8tTKXKrgMdyx9qjpM0VxuV9ShaWkorNyGOpM0lFQ2MWiiipGFFFFAC0opKUVSESLTm+7TVpz9K6V8JJXHWpF+8KiHWpV+8KwhuhlmiiivTICiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKa/3adTX+6aT2AqN1plPbrTK82puy0FLSUtQMWlpKKAFooopDFopKKAFooooAWnBvWm0UJtATA0vuKhBxU1dEJXJY8HNLUIJDVNXXTnzIlhRRRWggooooAKKKKACiiigAooooA//U6SiiigAooooAKKKKACiiigAqlKcuau1Qb7xrnxD0SGhtFJS1xModRSUtIBaKSloGFLSUUALRRRQAtKKSlqkIkWlfpSLSv0rdfCIrjrUi/eFRCpV+8KxhuhlqiiivTICiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKa/3TTqY/3TSewFRutMpzdabXmz3ZYUtJS1IC0tJS0hhRRRQAtFJS0ALRSUtAC0tJS07AFSKcio6cnWrhuDFbrUw6VC/WpV6V00n7zRLHUUUV0khRRRQAUUUUAFFFFABRRRQB//1ekooooAKKKKACiiigAooooAKzj1P1rRrNPU1zYjoNBS02lrkKHUUUUgFpaSikMWiiigApaSimAtOptLVIRItK/SmrTn+7Wy+ERXHWpV+8KiFSL94VlDdAW6KKK9IkKKKKACiiigAooooAKKKaXUUXAdRURlHYU3zT7VPOhXJ6Kr+aaPNNLnQXRYoqES04SKafMguSUUgYHoaWqGFFFFABRRRQAUUUUAFMk+4afTJPuGk9gKZplOam1509ygp1NpagY6ikpaAClpKKQxaKSloAWlptOpgLRRRTAWnL1plOXrVLcBz9alT7tQv1qZPu10UviZL2HUUUV1EhRRRQAUUUUAFFFFABRRRQB//9bpKKKKACiiigAooooAKKKKACs09TWlWaeprmxHQaEpaSlFcjGLS0UUhi0UUUAFLSUUALRSUUAOpabSiqQEi0r9KatK/StVsIg71Kv3hUPepV++KzhuhFyiiivSEFFFFABRRUTSgcCk2luBISB1qIy+lQFyTSYJrJ1OxPN2HF80mSaUAU6s7hbuN2ml2inUUgshNoo2ilophoJtFJt9KfRQFkR/MKcshFOpCAaabQvQlWQHrUlUypHSlWQitFU7j5u5bopiyA0+tE77FBRRRTAKZJ9w0+mSfcNJ7AUT1ptOPWm9686W7GLS02nVIxaKSlpDFopKKAFooooAWlpKKBjqWkpaYgpy9abTl61S3AV+tTJ92oX61Mn3a6KXxMT2HUUUV1EhRRRQAUUUUAFFFFABRRRQB//X6SiiigAooooAKKKKACiiigArNPU1pVmnqfrXNiOg0JSikpRXIxjqKKKQxaKKSgBaKSigBaKSigBaWkopiJVpX6VGDT2PFaJ6AVz1qVfvioj1qRP9YKUNyS9RRTGdU616DdgH1G0ir7mqzzFuOgqIZaspVexLkStKWpvLdaUKBTqy1e4t9xAAKdRRVWC4tFJmjNOwrjqKZupN1AXJKKi3ijfQLmJaKj30bqAuSUtR7qXdQO4+mlQaM0tFguQnKmpUlI4NLUTJ3FTqtg22LgYN0p1Z2StTpPjhua1jV7lKRapkn3DTlYMMimyfcNaPYooHrTe9OPWm96857sBaWm06pGhaKSloGFLSUUhi0tJRQAtLSUUAOpabTqaAWnL1ptKvWqW4Dn61Kn3ahfrUyfdropfEJj6KKK6iQooooAKKKKACiiigAooooA//0OkooooAKKKKACiiigAooooAKzT1P1rSrMPU/WubEdBoSnCm04VysY6iiipGFFFFABRRRQIKKKSgBaKSimA8U5ulMWkZwoqkK4w9adGwDg9hUBJbk8CkU54qoaMzlKxde47JVfJagAU8YrVtvchtsRVqUU3NGapIE0h9GaiL0wuaLicyYtTS9Q5NJRclyJS9N3GmUUrk3HZNJmkopCuLmjNNooC47NGabRmgLj9xpdxqOjNMdyYPTw9V80ZouNSLgYGlzVQMRUoequWpEpUGoihFPDUuQaTRW5CGZTxUxn3JtbrTWUGoCMUrtC5mhSRmk71EfvEUoYjrWLRalclpaYCDTqgtC0tNpaQxaWm0tAxaWm0tAC0tJRSGOpabS0AOpy9abTl61aAV+tTR/dqF+tTR/dropfEJj6KKK6iQooooAKKKKACiiigAooooA//R6SiiigAooooAKKKKACiiigArMPU1p1mHqa5sR0GhKcKbThXKxi0tJRSAWikopALSUUUwCikozQIKCQOTUbSdl5NRn1c5p2E2Sby3C/nShMc9TTVOTU+OKtbE7lZqanSnNTE6VUDGoSAkUu41HS1oZXH5NGTTaKYXFopKWkAUUUlAC0UmaaWAoAdmjNRFx2pNzHtRYCXNGag3N3IpNx9RTsBYzRmoNx9KcHFFgJc0ZpgNLmkIfmjNNzRmgLj6XNMzS5oGPDEU4OajopjuT76YxzTKWgfMQ/8ALSpKj/jqSsZlwGYxyKcH7GlppGai/c1uS5par5ZakVwaLFJklLTaKRQ6lptLSGOpabS0ALS0lLSAdTl60wU4dapAPfrU0f3agbrU8f3a6aXxCY+iiiuoQUUUUAFFFFABRRRQAUUUUAf/0ukooooAKKKKACiiigAooooAKzD1NadZbdT9a5q/QaEp4pgp4rlYxaKKKQBRSUUCCjNMZwtREs3XgU0hNkhkGcLzTGJPU49qTIHC0lMhsM44HFFFFAh6datH7tVk61Yb7tXEpFRu9Rr0qR+hqJTxVQMKg+nUynCtDEdRSUUwuLRTc00sB1oC4/NMMgHHU1EWZunApMqtFhkhYn2qMso96jLE0lOwDy57cU3JPWkop2ELRSUtABS5NJRRYBwNPDn61FR0pWHcsBgelOzVYH8DUgfsaVgt2Js0ZpmaWkIfmnZqOlBoC5LQaaDQaBkf8VSVF/FUprKZrTAUtNp1ZmolNK0+koAbuZfenhwaSmFc0x3J6KgDMvWpAwNJopMkzS5ptFIq5JS1HmnA0hj6eOtR04U0BI3Wp4/u1XNWI/u100fiEx9FFFdQgooooAKKKKACiiigAooooA//0+kooooAKKKKACiiigAooooAKxUYng9a2qxSpzleornr9BNjwalFQBgalBrlZSY+kpCwFRGQnhKSQmyQsB1qIuzfd4HrTcd25NBOadrEthwOnJo60UUyRKWiikAUUUUwJE61Yb7tVl61aP3apFIpP0NRL0qV+9Qr0q6exz1R9Opopa1MLi5pM0xnC9ahLF/YUWGkStJ2Xmo/dqbuA4FMJJ607FEhc9qZSUU7ALRRRTsAtFJRRYBaKSiiwC0UlFFgFopKKLALS57Gm0UWAeCR06VIrg1BR70mgLWaXNVxJjg1KDmpsS0Sg049KiBp+eKQXGfxVLUP8VTdqyqG1MKWm0tZGwtFFFABRRRQAlMK45FPoouA0ORw1Sgg1GRmm4I+7T0Y0yxSg1CH7NxT8ilYtMmFOFRhhjmm+YW4T86SQ7k4bLEDtVuP7tUkXaMVdj+7XTR3AfRRRXUIKKKKACiiigAooooAKKKKAP/U6SiiigAooooAKKKKACiiigArKkG2Rh71q1n3aYcOO9Y1o3iJlchW69aTYezUmaWuO5Nxuwdzml9hxRRTuAlFFFIQUUUUwCiiigAooooAcvWrR+7VQdanz8tNMaK79DUK9Kmboar7go5rWmc9UkzULydlqNnLewqPPpWyiZqI/POTyaQsTTaKqxdhaWm0tOwC0tJRRYBaKSiiwhaKSigBaKSigBaKSkzQA6lpuaM0WAWikzS5osFgopKKLBYWkDFaKKLATq4apQapcjkVKkvZqhxJcexMT81Tdqr5ywqftWFQ0pC0UlFZG46ikooAWikpaACiiikAUUUlABSYX3FLSUwFCL3OasxhegqsBV2JQo96aV3qUhrthtoq7HwgqiPmkyK0QMDFdNJFBRRRW4BRRRQAUUUUAFFFFABRRRQB/9XpKKKKACiiigAooooAKKKKACmOgdStPooaAx3UqcUytOaIMNwrOZCprinCzIaG0tNorMQtFFFABRRRQAUUUUAFJRRQAoqXPy1XLAU4fN1ppBciduDVEnJq5JwpqjXTRWhmx1FNzS1vYkWlptLRYB1FJRRYQ6im0UWAdRSUoFFgsFOCmngU8D0FA7EeyjZU+1vSk2t6UFcpBtppFTn3FMNArENJmnGo6LCsPzRmmZpc0WCw+im5pc07CFoopaLDsJSGn4prUrBYapINX1YEVnd6vL0rnrIqJLRUe4jrTwc1z2LFpaSikMWlptLQAtFJRQAtFJRQAtFAGalAC8mgY+NMfM1PZi3yr0pnzP8ASrkUW3k1tCFykLDHtGT1qaiiupK2gwooopgFFFFABRRRQAUUUUAFFFFAH//W6SiiigAooooAKKKKACiiigAooooAKhkiDcipqKTV9wMp4iDUJGK2WRW61Sltz1H51zzpW2JaKVFKQQeabzWDRItFJmjNIBaKbuppaiwrjyQKjLE9KSlqrCuJipk9Kjp68Uxoim6Gs+tCXoazc8100NmSx1FNpa6LCH0UlFFhC0tNoosA6lFAFOAzSCwAVOkZapYYCxxWvFbqgy3JpMuMClFalucVdW1QDnmrNFI1UUiMRRjtQYoz2qSigZWe1iboMVSlsmHK81rUUCcUzl3QioCMV081uko9D61iT27RHDCmjKULFCjNOZSKZVWJHZpc0zNGaLCsSg04Gos04GiwE1RtTs0xqVhsZ3q8vSqA61fHSuesNDqTGOlKKK5hgG9afTKTBHSiwElLTAfWnZpWGLS02lFIYtOA9aBgU4AtTSGKPapFQk1LHCW9quKgQYFbwpjSI44gnJ61NRRXQlbYoKKKKYBRRRQAUUUUAFFFFABRRRQAUUUUAf/X6SiiigAooooAKKKKACiiigAooooAKKKKACiiigCCSBZORwazZInjOCK2aRlDDDDNZyppiaMHJpMmtCW07pz7VRZSvWueULENDKSnUlSSFLSUtIBacKbSimhkclZlaj1mPwxFdOH6iEpabRXVYQ+lzTKWiwh1PUUiip1QtQOwiqTwK0Le1ZjnHFTW1oCNz8VpgBRgVDZoojUjWMYWn0UVJYUUUUAFFFFABRRRQAU10V12sMinUUAYtzZFPmXlaynQiuvrPuLJX+aPg+lUmQ4nOEU2rMkZRiCMVCV7irM7Dc04Go6XNFgsTg0jGowaUmiwDR1rRFZ6csK0K5a4C0tFFcwxaKSlpAFGKUU6mMAD3p2ewFAUseauRQZ68VShcaRCkbMelXo4QvLVKqqowKdXRGmkWkFFFFaDCiiigAooooAKKKKACiiigAooooAKKKKACiiigD//0OkooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACopIUk68H1qWik1cDIlt3j7cVWIroCM8Gqc1qG+ZOD6VjKl2IcTKoqRo2U4IphFYNEBSim0A0APYZFZs6kPn1rSBzUM0e8e9aU58srgZtFOKMO1JXemAoGakVaavJwBk1qW9q74yMUNgkV4oWc9K2YLVYxl+T6VPFCkQ46+tTVm3c0SCiiikUFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAV57aOdeeD61hTW7wthxXS0x0SRdjjIpp2E1c5NkzyKiKkVr3Nm8Pzp8y/yrOODxWikjNogoNPKntSBCTihyRJJCuWz6VcpiJsXFSVw1JXYC0tJS1iMKUClAqVUzTSHYYBmpkiLHirEduTyauKoUYFbRplqJDHAF5arFFFbpJbFBRRRTAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//R6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigBjxq45qhJasORyPatKiplBMTRhGMimYrdaNH+8Kha1Q9DisXSfQlxMjFO5rR+yD1oFoPWp9mxcplsgbtTfs241tC1QdTmpljRPuitIwkh8pnQWWME8CtJVCDaKdRWqRSVgooopjCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKz7ixR8tHwfTtWhRSaBo5trd1OGFATb2romRX+8M1A1qh6HFZyjLuQ4GNg0uK1DZ+/6Ugs/esvZsXKZwU08Ia0har3NTLDGvQVSpMfKUI4GbtV5IVXrzUtFaxgkUkFFFFWMKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//S6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/2Q==";
5+
6+
}

0 commit comments

Comments
 (0)