@rebasepro/plugin-ai 0.0.1-canary.4829d6e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +81 -0
- package/dist/api.d.ts +34 -0
- package/dist/components/DataEnhancementControllerProvider.d.ts +14 -0
- package/dist/components/FormEnhanceAction.d.ts +3 -0
- package/dist/editor/useEditorAIController.d.ts +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +741 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +772 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/types/data_enhancement_controller.d.ts +71 -0
- package/dist/useDataEnhancementPlugin.d.ts +29 -0
- package/dist/utils/diffStrings.d.ts +7 -0
- package/dist/utils/properties.d.ts +3 -0
- package/dist/utils/strings_counter.d.ts +2 -0
- package/dist/utils/suggestions.d.ts +1 -0
- package/dist/utils/values.d.ts +1 -0
- package/package.json +99 -0
- package/src/api.ts +198 -0
- package/src/components/DataEnhancementControllerProvider.tsx +342 -0
- package/src/components/FormEnhanceAction.tsx +277 -0
- package/src/editor/useEditorAIController.tsx +37 -0
- package/src/index.ts +9 -0
- package/src/tests/diffStrings.test.ts +128 -0
- package/src/tests/strings_counter.test.ts +117 -0
- package/src/tests/suggestions.test.ts +53 -0
- package/src/tests/useDataEnhancementPlugin.test.tsx +51 -0
- package/src/tests/values.test.ts +87 -0
- package/src/types/data_enhancement_controller.tsx +75 -0
- package/src/useDataEnhancementPlugin.tsx +66 -0
- package/src/utils/diffStrings.ts +70 -0
- package/src/utils/properties.ts +168 -0
- package/src/utils/strings_counter.ts +22 -0
- package/src/utils/suggestions.ts +6 -0
- package/src/utils/values.ts +12 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Import the diffStrings function from your module
|
|
2
|
+
// import { diffStrings } from './your-module';
|
|
3
|
+
|
|
4
|
+
import { Change, diffStrings } from "../utils/diffStrings";
|
|
5
|
+
|
|
6
|
+
describe("diffStrings", () => {
|
|
7
|
+
test("equal strings", () => {
|
|
8
|
+
const oldStr = "This is a test string";
|
|
9
|
+
const newStr = "This is a test string";
|
|
10
|
+
const expected: Change[] = [
|
|
11
|
+
{
|
|
12
|
+
type: "equal",
|
|
13
|
+
value: "This is a test string"
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
expect(diffStrings(oldStr, newStr)).toEqual(expected);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("insertions only", () => {
|
|
20
|
+
const oldStr = "This is a test string";
|
|
21
|
+
const newStr = "This is a new test string";
|
|
22
|
+
const expected: Change[] = [
|
|
23
|
+
{
|
|
24
|
+
type: "equal",
|
|
25
|
+
value: "This is a"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: "insert",
|
|
29
|
+
value: " new"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: "equal",
|
|
33
|
+
value: " test string"
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
expect(diffStrings(oldStr, newStr)).toEqual(expected);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("deletions only", () => {
|
|
40
|
+
const oldStr = "This is an old test string";
|
|
41
|
+
const newStr = "This is a test string";
|
|
42
|
+
const expected: Change[] = [
|
|
43
|
+
{
|
|
44
|
+
type: "equal",
|
|
45
|
+
value: "This is a"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: "delete",
|
|
49
|
+
value: "n old"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "equal",
|
|
53
|
+
value: " test string"
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
expect(diffStrings(oldStr, newStr)).toEqual(expected);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("insertions and deletions", () => {
|
|
60
|
+
const oldStr = "This is an old test string";
|
|
61
|
+
const newStr = "This is a new modified test string";
|
|
62
|
+
// The LCS algorithm finds the longest common substrings correctly
|
|
63
|
+
// Even though the output is more granular, it correctly represents the diff
|
|
64
|
+
const expected: Change[] = [
|
|
65
|
+
{
|
|
66
|
+
type: "equal",
|
|
67
|
+
value: "This is a"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: "insert",
|
|
71
|
+
value: " "
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: "equal",
|
|
75
|
+
value: "n"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "insert",
|
|
79
|
+
value: "ew"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "equal",
|
|
83
|
+
value: " "
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: "insert",
|
|
87
|
+
value: "m"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: "equal",
|
|
91
|
+
value: "o"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "delete",
|
|
95
|
+
value: "l"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: "insert",
|
|
99
|
+
value: "difie"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: "equal",
|
|
103
|
+
value: "d test string"
|
|
104
|
+
}
|
|
105
|
+
];
|
|
106
|
+
expect(diffStrings(oldStr, newStr)).toEqual(expected);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("completely different strings", () => {
|
|
110
|
+
const oldStr = "Old string";
|
|
111
|
+
const newStr = "New string";
|
|
112
|
+
const expected: Change[] = [
|
|
113
|
+
{
|
|
114
|
+
type: "delete",
|
|
115
|
+
value: "Old"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: "insert",
|
|
119
|
+
value: "New"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: "equal",
|
|
123
|
+
value: " string"
|
|
124
|
+
}
|
|
125
|
+
];
|
|
126
|
+
expect(diffStrings(oldStr, newStr)).toEqual(expected);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { countStringCharacters } from "../utils/strings_counter";
|
|
2
|
+
import type { Properties } from "@rebasepro/types";
|
|
3
|
+
|
|
4
|
+
describe("countStringCharacters", () => {
|
|
5
|
+
it("counts characters in a string property", () => {
|
|
6
|
+
const values = { title: "Hello World" };
|
|
7
|
+
const properties: Properties = {
|
|
8
|
+
title: { name: "title",
|
|
9
|
+
type: "string" }
|
|
10
|
+
};
|
|
11
|
+
expect(countStringCharacters(values, properties)).toBe(11);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("counts characters in a number property (stringified)", () => {
|
|
15
|
+
const values = { price: 12345 };
|
|
16
|
+
const properties: Properties = {
|
|
17
|
+
price: { name: "price",
|
|
18
|
+
type: "number" }
|
|
19
|
+
};
|
|
20
|
+
expect(countStringCharacters(values, properties)).toBe(5);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("skips disabled properties", () => {
|
|
24
|
+
const values = { title: "Hello",
|
|
25
|
+
hidden: "Secret" };
|
|
26
|
+
const properties: Properties = {
|
|
27
|
+
title: { name: "title",
|
|
28
|
+
type: "string" },
|
|
29
|
+
hidden: { name: "hidden",
|
|
30
|
+
type: "string",
|
|
31
|
+
ui: { disabled: true } }
|
|
32
|
+
};
|
|
33
|
+
expect(countStringCharacters(values, properties)).toBe(5);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("counts array of strings", () => {
|
|
37
|
+
const values = { tags: ["hello", "world", "test"] };
|
|
38
|
+
const properties: Properties = {
|
|
39
|
+
tags: {
|
|
40
|
+
name: "tags",
|
|
41
|
+
type: "array",
|
|
42
|
+
of: { name: "tag",
|
|
43
|
+
type: "string" }
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// "hello" (5) + "world" (5) + "test" (4) = 14
|
|
47
|
+
expect(countStringCharacters(values, properties)).toBe(14);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("recurses into map properties", () => {
|
|
51
|
+
const values = {
|
|
52
|
+
address: { city: "New York",
|
|
53
|
+
country: "USA" }
|
|
54
|
+
};
|
|
55
|
+
const properties: Properties = {
|
|
56
|
+
address: {
|
|
57
|
+
name: "address",
|
|
58
|
+
type: "map",
|
|
59
|
+
properties: {
|
|
60
|
+
city: { name: "city",
|
|
61
|
+
type: "string" },
|
|
62
|
+
country: { name: "country",
|
|
63
|
+
type: "string" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
// "New York" (8) + "USA" (3) = 11
|
|
68
|
+
expect(countStringCharacters(values, properties)).toBe(11);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns 0 for empty values", () => {
|
|
72
|
+
const properties: Properties = {
|
|
73
|
+
title: { name: "title",
|
|
74
|
+
type: "string" }
|
|
75
|
+
};
|
|
76
|
+
expect(countStringCharacters({}, properties)).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("handles mixed property types", () => {
|
|
80
|
+
const values = {
|
|
81
|
+
title: "Hello",
|
|
82
|
+
count: 42,
|
|
83
|
+
active: true,
|
|
84
|
+
tags: ["a", "bb"]
|
|
85
|
+
};
|
|
86
|
+
const properties: Properties = {
|
|
87
|
+
title: { name: "title",
|
|
88
|
+
type: "string" },
|
|
89
|
+
count: { name: "count",
|
|
90
|
+
type: "number" },
|
|
91
|
+
active: { name: "active",
|
|
92
|
+
type: "boolean" },
|
|
93
|
+
tags: {
|
|
94
|
+
name: "tags",
|
|
95
|
+
type: "array",
|
|
96
|
+
of: { name: "tag",
|
|
97
|
+
type: "string" }
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
// "Hello" (5) + "42" (2) + "a" (1) + "bb" (2) = 10
|
|
101
|
+
expect(countStringCharacters(values, properties)).toBe(10);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("handles null values in array gracefully", () => {
|
|
105
|
+
const values = { tags: [null, "hello", null] };
|
|
106
|
+
const properties: Properties = {
|
|
107
|
+
tags: {
|
|
108
|
+
name: "tags",
|
|
109
|
+
type: "array",
|
|
110
|
+
of: { name: "tag",
|
|
111
|
+
type: "string" }
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
// null?.length = 0, "hello" = 5, null?.length = 0
|
|
115
|
+
expect(countStringCharacters(values, properties)).toBe(5);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getAppendableSuggestion } from "../utils/suggestions";
|
|
2
|
+
|
|
3
|
+
describe("getAppendableSuggestion", () => {
|
|
4
|
+
it("returns the remaining part when suggestion starts with value", () => {
|
|
5
|
+
const result = getAppendableSuggestion("Hello World", "Hello");
|
|
6
|
+
expect(result).toBe(" World");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("returns undefined when suggestion does not start with value", () => {
|
|
10
|
+
const result = getAppendableSuggestion("Goodbye World", "Hello");
|
|
11
|
+
expect(result).toBeUndefined();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("performs case-insensitive matching", () => {
|
|
15
|
+
const result = getAppendableSuggestion("Hello World", "hello");
|
|
16
|
+
expect(result).toBe(" World");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns undefined for number suggestion", () => {
|
|
20
|
+
const result = getAppendableSuggestion(42, "4");
|
|
21
|
+
expect(result).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns undefined for undefined suggestion", () => {
|
|
25
|
+
const result = getAppendableSuggestion(undefined, "hello");
|
|
26
|
+
expect(result).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("returns undefined for non-string value", () => {
|
|
30
|
+
const result = getAppendableSuggestion("Hello", 42);
|
|
31
|
+
expect(result).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns full suggestion when value is empty string", () => {
|
|
35
|
+
const result = getAppendableSuggestion("Hello World", "");
|
|
36
|
+
expect(result).toBe("Hello World");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("handles whitespace trimming", () => {
|
|
40
|
+
const result = getAppendableSuggestion("Hello World", "Hello");
|
|
41
|
+
expect(result).toBe(" World");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("returns empty string when suggestion equals value", () => {
|
|
45
|
+
const result = getAppendableSuggestion("Hello", "Hello");
|
|
46
|
+
expect(result).toBe("");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns undefined when value is longer than suggestion", () => {
|
|
50
|
+
const result = getAppendableSuggestion("Hi", "Hello World");
|
|
51
|
+
expect(result).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { TextEncoder, TextDecoder } from "util";
|
|
2
|
+
Object.assign(global, { TextEncoder,
|
|
3
|
+
TextDecoder });
|
|
4
|
+
|
|
5
|
+
// Mock window.matchMedia
|
|
6
|
+
if (typeof window !== "undefined") {
|
|
7
|
+
Object.defineProperty(window, "matchMedia", {
|
|
8
|
+
writable: true,
|
|
9
|
+
value: jest.fn().mockImplementation(query => ({
|
|
10
|
+
matches: false,
|
|
11
|
+
media: query,
|
|
12
|
+
onchange: null,
|
|
13
|
+
addEventListener: jest.fn(),
|
|
14
|
+
removeEventListener: jest.fn(),
|
|
15
|
+
dispatchEvent: jest.fn()
|
|
16
|
+
}))
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
import { renderHook } from "@testing-library/react";
|
|
21
|
+
import { useDataEnhancementPlugin } from "../useDataEnhancementPlugin";
|
|
22
|
+
|
|
23
|
+
jest.mock("@rebasepro/admin", () => ({
|
|
24
|
+
useUrlController: () => ({})
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
describe("useDataEnhancementPlugin hook", () => {
|
|
28
|
+
it("returns data enhancement plugin with correct metadata", () => {
|
|
29
|
+
const { result } = renderHook(() => useDataEnhancementPlugin());
|
|
30
|
+
const plugin = result.current;
|
|
31
|
+
|
|
32
|
+
expect(plugin.key).toBe("data_enhancement");
|
|
33
|
+
expect(plugin.slots).toBeDefined();
|
|
34
|
+
expect(plugin.slots[0].slot).toBe("form.actions");
|
|
35
|
+
expect(plugin.providers).toBeDefined();
|
|
36
|
+
expect(plugin.providers[0].scope).toBe("form");
|
|
37
|
+
expect(plugin.providers[0].props.apiKey).toBe("fcms-U9jdDii0xXWSDC34asfrf54lbkFJBfKfRWcEDEwdc4V5wDWEDF");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("accepts and forwards custom apiKey and host props", () => {
|
|
41
|
+
const customProps = {
|
|
42
|
+
apiKey: "custom-key",
|
|
43
|
+
host: "https://custom-host.com"
|
|
44
|
+
};
|
|
45
|
+
const { result } = renderHook(() => useDataEnhancementPlugin(customProps));
|
|
46
|
+
const plugin = result.current;
|
|
47
|
+
|
|
48
|
+
expect(plugin.providers[0].props.apiKey).toBe("custom-key");
|
|
49
|
+
expect(plugin.providers[0].props.host).toBe("https://custom-host.com");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { flatMapEntityValues } from "../utils/values";
|
|
2
|
+
|
|
3
|
+
describe("flatMapEntityValues", () => {
|
|
4
|
+
it("returns flat object unchanged", () => {
|
|
5
|
+
const values = { name: "John",
|
|
6
|
+
age: 30 };
|
|
7
|
+
const result = flatMapEntityValues(values);
|
|
8
|
+
expect(result).toEqual({ name: "John",
|
|
9
|
+
age: 30 });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("flattens nested object to dot-notation keys", () => {
|
|
13
|
+
const values = {
|
|
14
|
+
address: { city: "NYC",
|
|
15
|
+
zip: "10001" }
|
|
16
|
+
};
|
|
17
|
+
const result = flatMapEntityValues(values);
|
|
18
|
+
expect(result).toEqual({
|
|
19
|
+
"address.city": "NYC",
|
|
20
|
+
"address.zip": "10001"
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("flattens deeply nested objects", () => {
|
|
25
|
+
const values = {
|
|
26
|
+
a: { b: { c: { d: "deep" } } }
|
|
27
|
+
};
|
|
28
|
+
const result = flatMapEntityValues(values);
|
|
29
|
+
expect(result).toEqual({ "a.b.c.d": "deep" });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("returns empty object for empty input", () => {
|
|
33
|
+
expect(flatMapEntityValues({})).toEqual({});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns empty object for null input", () => {
|
|
37
|
+
expect(flatMapEntityValues(null as unknown as object)).toEqual({});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns empty object for undefined input", () => {
|
|
41
|
+
expect(flatMapEntityValues(undefined as unknown as object)).toEqual({});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("handles mixed nested and flat values", () => {
|
|
45
|
+
const values = {
|
|
46
|
+
name: "John",
|
|
47
|
+
address: { city: "NYC" },
|
|
48
|
+
active: true
|
|
49
|
+
};
|
|
50
|
+
const result = flatMapEntityValues(values);
|
|
51
|
+
expect(result).toEqual({
|
|
52
|
+
name: "John",
|
|
53
|
+
"address.city": "NYC",
|
|
54
|
+
active: true
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("handles multiple nested objects", () => {
|
|
59
|
+
const values = {
|
|
60
|
+
home: { city: "NYC" },
|
|
61
|
+
work: { city: "SF" }
|
|
62
|
+
};
|
|
63
|
+
const result = flatMapEntityValues(values);
|
|
64
|
+
expect(result).toEqual({
|
|
65
|
+
"home.city": "NYC",
|
|
66
|
+
"work.city": "SF"
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("handles numeric values in nested objects", () => {
|
|
71
|
+
const values = {
|
|
72
|
+
stats: { visits: 100,
|
|
73
|
+
clicks: 50 }
|
|
74
|
+
};
|
|
75
|
+
const result = flatMapEntityValues(values);
|
|
76
|
+
expect(result).toEqual({
|
|
77
|
+
"stats.visits": 100,
|
|
78
|
+
"stats.clicks": 50
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("uses custom path prefix", () => {
|
|
83
|
+
const values = { name: "John" };
|
|
84
|
+
const result = flatMapEntityValues(values, "user");
|
|
85
|
+
expect(result).toEqual({ "user.name": "John" });
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { EntityValues } from "@rebasepro/types";
|
|
2
|
+
import { EditorAIController } from "@rebasepro/admin";
|
|
3
|
+
|
|
4
|
+
export type EnhanceParams<M extends Record<string, unknown>> = {
|
|
5
|
+
entityId?: string | number;
|
|
6
|
+
propertyKey?: string;
|
|
7
|
+
propertyInstructions?: string;
|
|
8
|
+
values: EntityValues<M>;
|
|
9
|
+
instructions?: string;
|
|
10
|
+
replaceValues: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type DataEnhancementController = {
|
|
14
|
+
/**
|
|
15
|
+
* Whether the data enhancement is enabled for the current path
|
|
16
|
+
*/
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
suggestions: Record<string, string | number>;
|
|
19
|
+
enhance: <M extends Record<string, unknown>>(props: EnhanceParams<M>) => Promise<EnhancedDataResult | null>;
|
|
20
|
+
clearSuggestion: (key: string, suggestion: string | number) => void;
|
|
21
|
+
allowReferenceDataSelection: boolean;
|
|
22
|
+
clearAllSuggestions: () => void;
|
|
23
|
+
getSamplePrompts: (entityName: string, input?: string) => Promise<SamplePromptsResult>;
|
|
24
|
+
loadingSuggestions: string[],
|
|
25
|
+
editorAIController?: EditorAIController;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type EnhancedDataResult = {
|
|
29
|
+
entityId?: string | number;
|
|
30
|
+
suggestions: {
|
|
31
|
+
[key: string]: string[];
|
|
32
|
+
};
|
|
33
|
+
errors: string[];
|
|
34
|
+
usage: { promptTokens?: number, completionTokens?: number, totalTokens?: number }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type SamplePrompt = {
|
|
38
|
+
prompt: string;
|
|
39
|
+
type: "recent" | "sample";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SamplePromptsResult = {
|
|
43
|
+
prompts: SamplePrompt[];
|
|
44
|
+
host?: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type DataEnhancementRequest = {
|
|
48
|
+
entityName: string;
|
|
49
|
+
entityDescription?: string;
|
|
50
|
+
inputEntity: InputEntity;
|
|
51
|
+
properties: Record<string, InputProperty>;
|
|
52
|
+
propertyKey?: string;
|
|
53
|
+
propertyInstructions?: string,
|
|
54
|
+
instructions?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type InputEntity = {
|
|
58
|
+
entityId?: string | number;
|
|
59
|
+
values: Record<string, any>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type InputProperty = {
|
|
63
|
+
name?: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
type: string;
|
|
66
|
+
fieldConfigId: string;
|
|
67
|
+
enum?: string[];
|
|
68
|
+
disabled?: boolean;
|
|
69
|
+
of?: InputProperty;
|
|
70
|
+
oneOf?: {
|
|
71
|
+
properties: Record<string, InputProperty>;
|
|
72
|
+
typeField?: string;
|
|
73
|
+
valueField?: string;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { CollectionConfig, RebasePlugin, User } from "@rebasepro/types";
|
|
4
|
+
import { DataEnhancementControllerProvider } from "./components/DataEnhancementControllerProvider";
|
|
5
|
+
import { FormEnhanceAction } from "./components/FormEnhanceAction";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_API_KEY = "fcms-U9jdDii0xXWSDC34asfrf54lbkFJBfKfRWcEDEwdc4V5wDWEDF";
|
|
8
|
+
|
|
9
|
+
export interface DataEnhancementPluginProps {
|
|
10
|
+
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Use this function to determine if the data enhancement plugin should be enabled for a given path.
|
|
15
|
+
* If this function is not provided, the plugin will be enabled for all paths.
|
|
16
|
+
* If the function returns false, the plugin will be disabled for the given path.
|
|
17
|
+
* You can also return a configuration object to override the default configuration.
|
|
18
|
+
*
|
|
19
|
+
* @param path
|
|
20
|
+
* @param collection
|
|
21
|
+
*/
|
|
22
|
+
getConfigForPath?: (props: {
|
|
23
|
+
path: string,
|
|
24
|
+
collection: CollectionConfig,
|
|
25
|
+
user: User | null
|
|
26
|
+
}) => boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Host to use for the data enhancement API.
|
|
30
|
+
* This prop is only use in development mode.
|
|
31
|
+
*/
|
|
32
|
+
host?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Use this hook to initialise the data enhancement plugin.
|
|
37
|
+
* This is likely the only hook you will need to use.
|
|
38
|
+
* @param props
|
|
39
|
+
*/
|
|
40
|
+
export function useDataEnhancementPlugin(props?: DataEnhancementPluginProps): RebasePlugin {
|
|
41
|
+
|
|
42
|
+
const apiKey = props?.apiKey ?? DEFAULT_API_KEY;
|
|
43
|
+
const getConfigForPath = props?.getConfigForPath;
|
|
44
|
+
|
|
45
|
+
return React.useMemo(() => ({
|
|
46
|
+
key: "data_enhancement",
|
|
47
|
+
slots: [
|
|
48
|
+
{
|
|
49
|
+
slot: "form.actions",
|
|
50
|
+
Component: FormEnhanceAction,
|
|
51
|
+
order: 40
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
providers: [
|
|
55
|
+
{
|
|
56
|
+
scope: "form" as const,
|
|
57
|
+
Component: DataEnhancementControllerProvider as React.ComponentType<any>,
|
|
58
|
+
props: {
|
|
59
|
+
apiKey,
|
|
60
|
+
getConfigForPath,
|
|
61
|
+
host: props?.host
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}), [apiKey, getConfigForPath, props?.host]);
|
|
66
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
type ChangeType = "equal" | "delete" | "insert";
|
|
2
|
+
|
|
3
|
+
export interface Change {
|
|
4
|
+
type: ChangeType;
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function diffStrings(oldStr: string, newStr: string): Change[] {
|
|
9
|
+
// Find the longest common substring
|
|
10
|
+
function longestCommonSubstring(s1: string, s2: string): string {
|
|
11
|
+
const longest = { start: 0,
|
|
12
|
+
length: 0 };
|
|
13
|
+
const matrix = new Array(s1.length + 1).fill(null).map(() => new Array(s2.length + 1).fill(0));
|
|
14
|
+
|
|
15
|
+
for (let i = 1; i <= s1.length; i++) {
|
|
16
|
+
for (let j = 1; j <= s2.length; j++) {
|
|
17
|
+
if (s1[i - 1] === s2[j - 1]) {
|
|
18
|
+
matrix[i][j] = matrix[i - 1][j - 1] + 1;
|
|
19
|
+
if (matrix[i][j] > longest.length) {
|
|
20
|
+
longest.start = i - matrix[i][j];
|
|
21
|
+
longest.length = matrix[i][j];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return s1.slice(longest.start, longest.start + longest.length);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Recursively find changes and create Change objects
|
|
31
|
+
function findChanges(s1: string, s2: string): Change[] {
|
|
32
|
+
if (s1 === s2) return s1.length > 0 ? [{
|
|
33
|
+
type: "equal",
|
|
34
|
+
value: s1
|
|
35
|
+
}] : [];
|
|
36
|
+
|
|
37
|
+
const common = longestCommonSubstring(s1, s2);
|
|
38
|
+
|
|
39
|
+
if (common.length === 0) {
|
|
40
|
+
const changes: Change[] = [];
|
|
41
|
+
if (s1.length > 0) {
|
|
42
|
+
changes.push({ type: "delete",
|
|
43
|
+
value: s1 });
|
|
44
|
+
}
|
|
45
|
+
if (s2.length > 0) {
|
|
46
|
+
changes.push({ type: "insert",
|
|
47
|
+
value: s2 });
|
|
48
|
+
}
|
|
49
|
+
return changes;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const s1Before = s1.slice(0, s1.indexOf(common));
|
|
53
|
+
const s1After = s1.slice(s1.indexOf(common) + common.length);
|
|
54
|
+
const s2Before = s2.slice(0, s2.indexOf(common));
|
|
55
|
+
const s2After = s2.slice(s2.indexOf(common) + common.length);
|
|
56
|
+
|
|
57
|
+
const changesBefore = findChanges(s1Before, s2Before);
|
|
58
|
+
const changesAfter = findChanges(s1After, s2After);
|
|
59
|
+
|
|
60
|
+
return [
|
|
61
|
+
...changesBefore,
|
|
62
|
+
{ type: "equal",
|
|
63
|
+
value: common },
|
|
64
|
+
...changesAfter
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return findChanges(oldStr, newStr);
|
|
69
|
+
}
|
|
70
|
+
|