Skip to content

Commit ae69a10

Browse files
committed
apply and fix code features
Signed-off-by: Akshat Batra <[email protected]>
1 parent 61f3865 commit ae69a10

File tree

16 files changed

+631
-115
lines changed

16 files changed

+631
-115
lines changed

LICENSE

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,39 @@
198198
distributed under the License is distributed on an "AS IS" BASIS,
199199
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200200
See the License for the specific language governing permissions and
201-
limitations under the License.
201+
limitations under the License.
202+
203+
-------------------------------------------------------------------------------
204+
205+
This project includes third-party software components governed by separate license terms, as follows:
206+
207+
-------------------------------------------------------------------------------
208+
jsdiff (https://github.com/kpdecker/jsdiff) — BSD 3-Clause License
209+
210+
Copyright (c) 2009-2015, Kevin Decker <[email protected]>
211+
All rights reserved.
212+
213+
Redistribution and use in source and binary forms, with or without
214+
modification, are permitted provided that the following conditions are met:
215+
1. Redistributions of source code must retain the above copyright notice, this
216+
list of conditions and the following disclaimer.
217+
2. Redistributions in binary form must reproduce the above copyright notice,
218+
this list of conditions and the following disclaimer in the documentation
219+
and/or other materials provided with the distribution.
220+
3. Neither the name of the copyright holder nor the names of its
221+
contributors may be used to endorse or promote products derived from
222+
this software without specific prior written permission.
223+
224+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
225+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
226+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
227+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
228+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
229+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
230+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
231+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
232+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
233+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
234+
235+
For the most up-to-date full license text, see the jsdiff repository: https://github.com/kpdecker/jsdiff/blob/master/LICENSE
236+
-------------------------------------------------------------------------------

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@types/styled-components": "^5.1.34",
3030
"antd": "^5.7.2",
3131
"core-js": "^3.37.1",
32+
"diff": "^8.0.2",
3233
"highlight.js": "^11.10.0",
3334
"immer": "^10.1.1",
3435
"jest-canvas-mock": "^2.5.2",

src/ai-assistant/prompts.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ export const prepareSystemPrompt = {
5252
},
5353

5454
default: (editorsContent: editorsContent, aiConfig?: any) => {
55-
let prompt = `You are a helpful assistant that answers questions about open source Accord Project. You assist the user
56-
to work with TemplateMark, Concerto models and JSON data. Code blocks returned by you should enclosed in backticks\n\n`;
55+
let prompt = `You are a helpful assistant that answers questions about open source Accord Project. You assist the user in working with TemplateMark, Concerto models and JSON data. Code blocks returned by you should be enclosed in backticks, the language names that you can use after three backticks are- "concerto","templatemark" and "json", suffix 'Apply' to the language name if it is a complete code block that can be used to replace the corresponding editor content, precisely, concertoApply, templatemarkApply and jsonApply. You must always try to return complete code block that can be applied to the editors. Concerto code, TemplateMark code and JSON data are supplied to TemplateEngine to produce the final output. For instance, a data field that is not in Concerto data model can't be in JSON data and therefore can't be used in TemplateMark you generate. Analyze the JSON data and Concerto model (if provided) carefully, only the fields with simple data types (String, Integer etc.) present in concept annotated with @template decorator can be directly accessed anywhere in the template. Other complex data fields that have custom concept declaration in the Concerto model and are represented as nested fields in JSON data, can only be used within {{#clause conceptName}} {{concept_property_name}} {{/clause}} tags. Therefore, in most cases you have to create a scope using clause tag in TemplateMark to access properties defined under a concept in Concerto. For enumerating through a list you can create a scope to access the properties in list items via {{#olist listName}} {{instancePropertyName}} {{/olist}} or {{#ulist listName}} {{instancePropertyName}} {{/ulist}}. For TemplateMark code, there's no such thing as 'this' keyword within list scope. Optional fields shouldn't be wrapped in an if or with block to check for their availability e.g. if Concerto model has age as optional don't wrap it in if block in TemplateMark. You can also use Typescript within TemplateMark by enclosing the Typescript code in {{% %}}, you must write all of the Typescript code within a single line enclosed in a single pair of opening {{% and closing %}}. You may use Typescript to achieve an objective in TemplateMark only if TemplateMark syntax makes doing something hard, the data objects from JSON are readily available within {{% %}} enclosed Typescript using direct access, e.g. {{% return order.orderLines %}}. For e.g., you could use TypeScript to render ordered/unordered primitive list types such as String[]. Keep your focus on generating valid output based on current editors' contents but if you make a change that isn't compatible with the content of existing editors, you must return the full code for those editors as well. You mustn't add any placeholder in TemplateMark which isn't in Concerto model and JSON data unless you modify the Concerto and JSON data to have that field at the appropriate place.\n\n`;
5756
return includeEditorContents(prompt, aiConfig, editorsContent);
5857
}
5958
};

src/components/AIChatPanel.tsx

Lines changed: 105 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
22
import ReactMarkdown from "react-markdown";
33
import useAppStore from "../store/store";
44
import { sendMessage, stopMessage } from "../ai-assistant/chatRelay";
5+
import CodeDiffPopup from "./CodeDiffPopup";
56

67
export const AIChatPanel = () => {
78
const [promptPreset, setPromptPreset] = useState<string | null>(null);
@@ -15,7 +16,21 @@ export const AIChatPanel = () => {
1516
editorAgreementData: state.editorAgreementData,
1617
}));
1718

18-
const { chatState, resetChat, aiConfig, setAIConfig, setAIConfigOpen, setAIChatOpen, textColor } = useAppStore.getState()
19+
const {
20+
chatState,
21+
resetChat,
22+
aiConfig,
23+
setAIConfig,
24+
setAIConfigOpen,
25+
setAIChatOpen,
26+
setEditorValue,
27+
setTemplateMarkdown,
28+
setEditorModelCto,
29+
setModelCto,
30+
setEditorAgreementData,
31+
setData,
32+
textColor
33+
} = useAppStore.getState()
1934

2035
const latestMessageRef = useRef<HTMLDivElement>(null);
2136

@@ -105,64 +120,88 @@ export const AIChatPanel = () => {
105120
}
106121
};
107122

108-
const renderMessageContent = (content: string) => {
109-
if (!content || !content.includes('```')) {
110-
console.log("content is", content);
111-
return (
112-
<div className="text-sm prose prose-sm break-all max-w-none">
113-
<ReactMarkdown
114-
components={{
115-
code: ({ children, className }) => <code className={`bg-gray-200 p-1 rounded-md before:content-[''] after:content-[''] ${className}`}>{children}</code>,
116-
}}>
117-
{content}
118-
</ReactMarkdown>
119-
</div>
120-
);
121-
}
123+
const [showDiffPopup, setShowDiffPopup] = useState(false);
124+
const [diffCodeProps, setDiffCodeProps] = useState({
125+
newCode: "",
126+
currentCode: "",
127+
language: "",
128+
onApply: (_code: string) => {}
129+
});
122130

123-
const parts = [];
124-
let key = 0;
131+
const handleApplyCode = (code: string, language: string) => {
132+
let currentCode = "";
133+
let applyFunction = (_code: string) => {};
125134

126-
const segments = content.split('```');
127-
128-
if (segments[0]) {
129-
parts.push(
130-
<div className="text-sm prose prose-sm max-w-none" key={key++}>
131-
<ReactMarkdown>
132-
{segments[0]}
133-
</ReactMarkdown>
134-
</div>
135-
);
135+
if (language === "concerto") {
136+
currentCode = editorsContent.editorModelCto;
137+
applyFunction = (code: string) => {
138+
setEditorModelCto(code);
139+
setModelCto(code);
140+
};
141+
} else if (language === "templatemark") {
142+
currentCode = editorsContent.editorTemplateMark;
143+
applyFunction = (code: string) => {
144+
setEditorValue(code);
145+
setTemplateMarkdown(code);
146+
};
147+
} else if (language === "json") {
148+
currentCode = editorsContent.editorAgreementData;
149+
applyFunction = (code: string) => {
150+
setEditorAgreementData(code);
151+
setData(code);
152+
};
136153
}
137-
138-
for (let i = 1; i < segments.length; i++) {
139-
if (i % 2 === 1 && segments[i]) {
140-
const firstLineBreak = segments[i].indexOf('\n');
141-
let code = segments[i];
142-
143-
if (firstLineBreak > -1) {
144-
code = segments[i].substring(firstLineBreak + 1);
145-
}
146-
147-
parts.push(
148-
<div key={key++} className="relative mt-2 mb-2">
149-
<pre className="bg-gray-800 text-gray-100 p-3 rounded-lg text-xs overflow-x-auto">
150-
{code.trim()}
151-
</pre>
152-
</div>
153-
);
154-
} else if (i % 2 === 0 && segments[i]) {
155-
parts.push(
156-
<div className="text-sm prose prose-sm max-w-none" key={key++}>
157-
<ReactMarkdown>
158-
{segments[i]}
159-
</ReactMarkdown>
160-
</div>
161-
);
162-
}
154+
155+
const normalize = (str: string) => str.trim();
156+
if (normalize(currentCode) === normalize(code)) {
157+
applyFunction(code);
158+
return;
163159
}
164-
165-
return parts;
160+
161+
setDiffCodeProps({
162+
newCode: code,
163+
currentCode,
164+
language,
165+
onApply: applyFunction
166+
});
167+
setShowDiffPopup(true);
168+
};
169+
170+
const renderMessageContent = (content: string) => {
171+
return (
172+
<div className="text-sm prose prose-md break-all max-w-none">
173+
<ReactMarkdown
174+
components={{
175+
code: ({ children, className }) => {
176+
if (className === undefined) return <code className={`rounded-md`}>{children}</code>;
177+
let language = ((className as string).match(/language-[^ ]*/)?.toString())?.split("-")[1] || "";
178+
const isApplicable = ["concertoApply", "templatemarkApply", "jsonApply"].includes(language);
179+
language = language.slice(0, -5)
180+
const codeContent = children?.toString() || "";
181+
182+
return (
183+
<div className="relative">
184+
{isApplicable && (
185+
<button
186+
onClick={() => handleApplyCode(codeContent, language)}
187+
className="sticky top-2 right-2 bg-blue-500 text-white px-2 py-1 rounded text-xs hover:bg-blue-600 transition-colors flex items-center"
188+
title={`Apply to ${language} editor`}
189+
>
190+
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
191+
<path d="M9 14L4 9L9 4" />
192+
<path d="M4 9H16C18.2091 9 20 10.7909 20 13C20 15.2091 18.2091 17 16 17H12" />
193+
</svg>
194+
</button>
195+
)}
196+
<code className={`rounded-md ${className} block p-4`}>{children}</code>
197+
</div>
198+
);
199+
}
200+
}}>
201+
{content}
202+
</ReactMarkdown>
203+
</div>
204+
);
166205
};
167206

168207
useEffect(() => {
@@ -252,7 +291,7 @@ export const AIChatPanel = () => {
252291
</button>
253292
</div>
254293
</div>
255-
<div className="w-full h-[calc(100%-3rem)] flex flex-col">
294+
<div className="w-full h-[calc(100%-1rem)] flex flex-col">
256295
<div className="flex-1 overflow-y-auto mb-4 px-2 mt-4">
257296
<div className="space-y-2">
258297
{chatState.messages.length === 0 ? (
@@ -303,7 +342,7 @@ export const AIChatPanel = () => {
303342
<div className="flex flex-col gap-2">
304343
{promptPreset && (
305344
<div className="inline-flex items-center gap-2 px-3 py-1.5 bg-indigo-600 bg-opacity-95 text-white rounded-lg self-start">
306-
<span className="text-sm font-medium prose lg:prose-md">
345+
<span className="text-sm font-medium">
307346
{
308347
promptPreset === "textToTemplate" ? "Text to TemplateMark" : "Create Concerto Model"
309348
}
@@ -534,6 +573,15 @@ export const AIChatPanel = () => {
534573
</div>
535574
</div>
536575
</div>
576+
{showDiffPopup && (
577+
<CodeDiffPopup
578+
newCode={diffCodeProps.newCode}
579+
currentCode={diffCodeProps.currentCode}
580+
language={diffCodeProps.language}
581+
onApply={diffCodeProps.onApply}
582+
onClose={() => setShowDiffPopup(false)}
583+
/>
584+
)}
537585
</div>
538586
);
539587
}

0 commit comments

Comments
 (0)