feat: split individual semantic change (#13155)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a new AI-powered document update feature, allowing users to apply multiple independent block-level edits to Markdown documents. * Added support for applying document updates via a new GraphQL query, enabling seamless integration with the frontend. * **Enhancements** * Improved the document editing tool to handle and display multiple simultaneous edit operations with better UI feedback and state management. * Expanded model support with new "morph-v3-fast" and "morph-v3-large" options for document update operations. * Enhanced frontend components and services to support asynchronous application and acceptance of multiple document edits independently. * **Bug Fixes** * Enhanced error handling and user notifications for failed document update operations. * **Documentation** * Updated tool descriptions and examples to clarify the new multi-edit workflow and expected input/output formats. <!-- end of auto-generated comment: release notes by coderabbit.ai --> > CLOSE AI-337
This commit is contained in:
@@ -1624,6 +1624,166 @@ const imageActions: Prompt[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const modelActions: Prompt[] = [
|
||||||
|
{
|
||||||
|
name: 'Apply Updates',
|
||||||
|
action: 'Apply Updates',
|
||||||
|
model: 'claude-sonnet-4@20250514',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: `
|
||||||
|
You are a Markdown document update engine.
|
||||||
|
|
||||||
|
You will be given:
|
||||||
|
|
||||||
|
1. content: The original Markdown document
|
||||||
|
- The content is structured into blocks.
|
||||||
|
- Each block starts with a comment like <!-- block_id=... flavour=... --> and contains the block's content.
|
||||||
|
- The content is {{content}}
|
||||||
|
|
||||||
|
2. op: A description of the edit intention
|
||||||
|
- This describes the semantic meaning of the edit, such as "Bold the first paragraph".
|
||||||
|
- The op is {{op}}
|
||||||
|
|
||||||
|
3. updates: A Markdown snippet
|
||||||
|
- The updates is {{updates}}
|
||||||
|
- This represents the block-level changes to apply to the original Markdown.
|
||||||
|
- The update may:
|
||||||
|
- **Replace** an existing block (same block_id, new content)
|
||||||
|
- **Delete** block(s) using <!-- delete block BLOCK_ID -->
|
||||||
|
- **Insert** new block(s) with a new unique block_id
|
||||||
|
- When performing deletions, the update will include **surrounding context blocks** (or use <!-- existing blocks -->) to help you determine where and what to delete.
|
||||||
|
|
||||||
|
Your task:
|
||||||
|
- Apply the update in <updates> to the document in <code>, following the intent described in <op>.
|
||||||
|
- Preserve all block_id and flavour comments.
|
||||||
|
- Maintain the original block order unless the update clearly appends new blocks.
|
||||||
|
- Do not remove or alter unrelated blocks.
|
||||||
|
- Output only the fully updated Markdown content. Do not wrap the content in \`\`\`markdown.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
✍️ Examples
|
||||||
|
|
||||||
|
✅ Replacement (modifying an existing block)
|
||||||
|
|
||||||
|
<code>
|
||||||
|
<!-- block_id=101 flavour=paragraph -->
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
<!-- block_id=102 flavour=paragraph -->
|
||||||
|
This document provides an overview of the system architecture and its components.
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<op>
|
||||||
|
Make the introduction more formal.
|
||||||
|
</op>
|
||||||
|
|
||||||
|
<updates>
|
||||||
|
<!-- block_id=102 flavour=paragraph -->
|
||||||
|
This document outlines the architectural design and individual components of the system in detail.
|
||||||
|
</updates>
|
||||||
|
|
||||||
|
Expected Output:
|
||||||
|
<!-- block_id=101 flavour=paragraph -->
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
<!-- block_id=102 flavour=paragraph -->
|
||||||
|
This document outlines the architectural design and individual components of the system in detail.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
➕ Insertion (adding new content)
|
||||||
|
|
||||||
|
<code>
|
||||||
|
<!-- block_id=201 flavour=paragraph -->
|
||||||
|
# Project Summary
|
||||||
|
|
||||||
|
<!-- block_id=202 flavour=paragraph -->
|
||||||
|
This project aims to build a collaborative text editing tool.
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<op>
|
||||||
|
Add a disclaimer section at the end.
|
||||||
|
</op>
|
||||||
|
|
||||||
|
<updates>
|
||||||
|
<!-- block_id=new-301 flavour=paragraph -->
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
<!-- block_id=new-302 flavour=paragraph -->
|
||||||
|
This document is subject to change. Do not distribute externally.
|
||||||
|
</updates>
|
||||||
|
|
||||||
|
Expected Output:
|
||||||
|
<!-- block_id=201 flavour=paragraph -->
|
||||||
|
# Project Summary
|
||||||
|
|
||||||
|
<!-- block_id=202 flavour=paragraph -->
|
||||||
|
This project aims to build a collaborative text editing tool.
|
||||||
|
|
||||||
|
<!-- block_id=new-301 flavour=paragraph -->
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
<!-- block_id=new-302 flavour=paragraph -->
|
||||||
|
This document is subject to change. Do not distribute externally.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
❌ Deletion (removing blocks)
|
||||||
|
|
||||||
|
<code>
|
||||||
|
<!-- block_id=401 flavour=paragraph -->
|
||||||
|
## Author
|
||||||
|
|
||||||
|
<!-- block_id=402 flavour=paragraph -->
|
||||||
|
Written by the AI team at OpenResearch.
|
||||||
|
|
||||||
|
<!-- block_id=403 flavour=paragraph -->
|
||||||
|
## Experimental Section
|
||||||
|
|
||||||
|
<!-- block_id=404 flavour=paragraph -->
|
||||||
|
The following section is still under development and may change without notice.
|
||||||
|
|
||||||
|
<!-- block_id=405 flavour=paragraph -->
|
||||||
|
## License
|
||||||
|
|
||||||
|
<!-- block_id=406 flavour=paragraph -->
|
||||||
|
This document is licensed under CC BY-NC 4.0.
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<op>
|
||||||
|
Remove the experimental section.
|
||||||
|
</op>
|
||||||
|
|
||||||
|
<updates>
|
||||||
|
<!-- delete block_id=403 -->
|
||||||
|
<!-- delete block_id=404 -->
|
||||||
|
</updates>
|
||||||
|
|
||||||
|
Expected Output:
|
||||||
|
<!-- block_id=401 flavour=paragraph -->
|
||||||
|
## Author
|
||||||
|
|
||||||
|
<!-- block_id=402 flavour=paragraph -->
|
||||||
|
Written by the AI team at OpenResearch.
|
||||||
|
|
||||||
|
<!-- block_id=405 flavour=paragraph -->
|
||||||
|
## License
|
||||||
|
|
||||||
|
<!-- block_id=406 flavour=paragraph -->
|
||||||
|
This document is licensed under CC BY-NC 4.0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Now apply the \`updates\` to the \`content\`, following the intent in \`op\`, and return the updated Markdown.
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const CHAT_PROMPT: Omit<Prompt, 'name'> = {
|
const CHAT_PROMPT: Omit<Prompt, 'name'> = {
|
||||||
model: 'claude-sonnet-4@20250514',
|
model: 'claude-sonnet-4@20250514',
|
||||||
optionalModels: [
|
optionalModels: [
|
||||||
@@ -1861,6 +2021,7 @@ const artifactActions: Prompt[] = [
|
|||||||
export const prompts: Prompt[] = [
|
export const prompts: Prompt[] = [
|
||||||
...textActions,
|
...textActions,
|
||||||
...imageActions,
|
...imageActions,
|
||||||
|
...modelActions,
|
||||||
...chat,
|
...chat,
|
||||||
...workflows,
|
...workflows,
|
||||||
...artifactActions,
|
...artifactActions,
|
||||||
|
|||||||
@@ -37,6 +37,24 @@ export class MorphProvider extends CopilotProvider<MorphConfig> {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'morph-v3-fast',
|
||||||
|
capabilities: [
|
||||||
|
{
|
||||||
|
input: [ModelInputType.Text],
|
||||||
|
output: [ModelOutputType.Text],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'morph-v3-large',
|
||||||
|
capabilities: [
|
||||||
|
{
|
||||||
|
input: [ModelInputType.Text],
|
||||||
|
output: [ModelOutputType.Text],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
#instance!: VercelOpenAICompatibleProvider;
|
#instance!: VercelOpenAICompatibleProvider;
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ export abstract class CopilotProvider<C = any> {
|
|||||||
const getDocContent = buildContentGetter(ac, docReader);
|
const getDocContent = buildContentGetter(ac, docReader);
|
||||||
tools.doc_edit = createDocEditTool(
|
tools.doc_edit = createDocEditTool(
|
||||||
this.factory,
|
this.factory,
|
||||||
|
prompt,
|
||||||
getDocContent.bind(null, options)
|
getDocContent.bind(null, options)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -472,10 +472,18 @@ export class TextStreamParser {
|
|||||||
result = this.addPrefix(result);
|
result = this.addPrefix(result);
|
||||||
switch (chunk.toolName) {
|
switch (chunk.toolName) {
|
||||||
case 'doc_edit': {
|
case 'doc_edit': {
|
||||||
if (chunk.result && typeof chunk.result === 'object') {
|
if (
|
||||||
result += `\n${chunk.result.result}\n`;
|
chunk.result &&
|
||||||
|
typeof chunk.result === 'object' &&
|
||||||
|
Array.isArray(chunk.result.result)
|
||||||
|
) {
|
||||||
|
result += chunk.result.result
|
||||||
|
.map(item => {
|
||||||
|
return `\n${item.changedContent}\n`;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
this.docEditFootnotes[this.docEditFootnotes.length - 1].result =
|
this.docEditFootnotes[this.docEditFootnotes.length - 1].result =
|
||||||
chunk.result.result;
|
result;
|
||||||
} else {
|
} else {
|
||||||
this.docEditFootnotes.pop();
|
this.docEditFootnotes.pop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
CallMetric,
|
CallMetric,
|
||||||
CopilotDocNotFound,
|
CopilotDocNotFound,
|
||||||
CopilotFailedToCreateMessage,
|
CopilotFailedToCreateMessage,
|
||||||
|
CopilotProviderSideError,
|
||||||
CopilotSessionNotFound,
|
CopilotSessionNotFound,
|
||||||
type FileUpload,
|
type FileUpload,
|
||||||
paginate,
|
paginate,
|
||||||
@@ -31,15 +32,18 @@ import {
|
|||||||
RequestMutex,
|
RequestMutex,
|
||||||
Throttle,
|
Throttle,
|
||||||
TooManyRequest,
|
TooManyRequest,
|
||||||
|
UserFriendlyError,
|
||||||
} from '../../base';
|
} from '../../base';
|
||||||
import { CurrentUser } from '../../core/auth';
|
import { CurrentUser } from '../../core/auth';
|
||||||
import { Admin } from '../../core/common';
|
import { Admin } from '../../core/common';
|
||||||
|
import { DocReader } from '../../core/doc';
|
||||||
import { AccessController } from '../../core/permission';
|
import { AccessController } from '../../core/permission';
|
||||||
import { UserType } from '../../core/user';
|
import { UserType } from '../../core/user';
|
||||||
import type { ListSessionOptions, UpdateChatSession } from '../../models';
|
import type { ListSessionOptions, UpdateChatSession } from '../../models';
|
||||||
import { CopilotCronJobs } from './cron';
|
import { CopilotCronJobs } from './cron';
|
||||||
import { PromptService } from './prompt';
|
import { PromptService } from './prompt';
|
||||||
import { PromptMessage, StreamObject } from './providers';
|
import { PromptMessage, StreamObject } from './providers';
|
||||||
|
import { CopilotProviderFactory } from './providers/factory';
|
||||||
import { ChatSessionService } from './session';
|
import { ChatSessionService } from './session';
|
||||||
import { CopilotStorage } from './storage';
|
import { CopilotStorage } from './storage';
|
||||||
import { type ChatHistory, type ChatMessage, SubmittedMessage } from './types';
|
import { type ChatHistory, type ChatMessage, SubmittedMessage } from './types';
|
||||||
@@ -397,7 +401,9 @@ export class CopilotResolver {
|
|||||||
private readonly ac: AccessController,
|
private readonly ac: AccessController,
|
||||||
private readonly mutex: RequestMutex,
|
private readonly mutex: RequestMutex,
|
||||||
private readonly chatSession: ChatSessionService,
|
private readonly chatSession: ChatSessionService,
|
||||||
private readonly storage: CopilotStorage
|
private readonly storage: CopilotStorage,
|
||||||
|
private readonly docReader: DocReader,
|
||||||
|
private readonly providerFactory: CopilotProviderFactory
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ResolveField(() => CopilotQuotaType, {
|
@ResolveField(() => CopilotQuotaType, {
|
||||||
@@ -725,6 +731,65 @@ export class CopilotResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Query(() => String, {
|
||||||
|
description:
|
||||||
|
'Apply updates to a doc using LLM and return the merged markdown.',
|
||||||
|
})
|
||||||
|
async applyDocUpdates(
|
||||||
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args({ name: 'workspaceId', type: () => String })
|
||||||
|
workspaceId: string,
|
||||||
|
@Args({ name: 'docId', type: () => String })
|
||||||
|
docId: string,
|
||||||
|
@Args({ name: 'op', type: () => String })
|
||||||
|
op: string,
|
||||||
|
@Args({ name: 'updates', type: () => String })
|
||||||
|
updates: string
|
||||||
|
): Promise<string> {
|
||||||
|
await this.assertPermission(user, { workspaceId, docId });
|
||||||
|
|
||||||
|
const docContent = await this.docReader.getDocMarkdown(
|
||||||
|
workspaceId,
|
||||||
|
docId,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (!docContent || !docContent.markdown) {
|
||||||
|
throw new NotFoundException('Doc not found or empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
const markdown = docContent.markdown.trim();
|
||||||
|
|
||||||
|
// Get LLM provider
|
||||||
|
const provider =
|
||||||
|
await this.providerFactory.getProviderByModel('morph-v3-large');
|
||||||
|
if (!provider) {
|
||||||
|
throw new BadRequestException('No LLM provider available');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await provider.text(
|
||||||
|
{ modelId: 'morph-v3-large' },
|
||||||
|
[
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: `<instruction>${op}</instruction>\n<code>${markdown}</code>\n<update>${updates}</update>`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ reasoning: false }
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e instanceof UserFriendlyError) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
throw new CopilotProviderSideError({
|
||||||
|
provider: provider.type,
|
||||||
|
kind: 'unexpected_response',
|
||||||
|
message: e?.message || 'Unexpected apply response',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private transformToSessionType(
|
private transformToSessionType(
|
||||||
session: Omit<ChatHistory, 'messages'>
|
session: Omit<ChatHistory, 'messages'>
|
||||||
): CopilotSessionType {
|
): CopilotSessionType {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { DocReader } from '../../../core/doc';
|
import { DocReader } from '../../../core/doc';
|
||||||
import { AccessController } from '../../../core/permission';
|
import { AccessController } from '../../../core/permission';
|
||||||
|
import { type PromptService } from '../prompt';
|
||||||
import type { CopilotChatOptions, CopilotProviderFactory } from '../providers';
|
import type { CopilotChatOptions, CopilotProviderFactory } from '../providers';
|
||||||
|
|
||||||
export const buildContentGetter = (ac: AccessController, doc: DocReader) => {
|
export const buildContentGetter = (ac: AccessController, doc: DocReader) => {
|
||||||
@@ -24,14 +25,20 @@ export const buildContentGetter = (ac: AccessController, doc: DocReader) => {
|
|||||||
|
|
||||||
export const createDocEditTool = (
|
export const createDocEditTool = (
|
||||||
factory: CopilotProviderFactory,
|
factory: CopilotProviderFactory,
|
||||||
|
prompt: PromptService,
|
||||||
getContent: (targetId?: string) => Promise<string | undefined>
|
getContent: (targetId?: string) => Promise<string | undefined>
|
||||||
) => {
|
) => {
|
||||||
return tool({
|
return tool({
|
||||||
description: `
|
description: `
|
||||||
Use this tool to propose an edit to a structured Markdown document with identifiable blocks. Each block begins with a comment like <!-- block_id=... -->, and represents a unit of editable content such as a heading, paragraph, list, or code snippet.
|
Use this tool to propose an edit to a structured Markdown document with identifiable blocks.
|
||||||
|
Each block begins with a comment like <!-- block_id=... -->, and represents a unit of editable content such as a heading, paragraph, list, or code snippet.
|
||||||
This will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write.
|
This will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write.
|
||||||
|
|
||||||
Your task is to return a list of block-level changes needed to fulfill the user's intent. Each change should correspond to a specific user instruction and be represented by one of the following operations:
|
If you receive a markdown without block_id comments, you should call \`doc_read\` tool to get the content.
|
||||||
|
|
||||||
|
Your task is to return a list of block-level changes needed to fulfill the user's intent. **Each change in code_edit must be completely independent: each code_edit entry should only perform a single, isolated change, and must not include the effects of other changes. For example, the updates for a delete operation should only show the context related to the deletion, and must not include any content modified by other operations (such as bolding or insertion). This ensures that each change can be applied independently and in any order.**
|
||||||
|
|
||||||
|
Each change should correspond to a specific user instruction and be represented by one of the following operations:
|
||||||
|
|
||||||
replace: Replace the content of a block with updated Markdown.
|
replace: Replace the content of a block with updated Markdown.
|
||||||
|
|
||||||
@@ -41,83 +48,75 @@ insert: Add a new block, and specify its block_id and content.
|
|||||||
|
|
||||||
Important Instructions:
|
Important Instructions:
|
||||||
- Use the existing block structure as-is. Do not reformat or reorder blocks unless explicitly asked.
|
- Use the existing block structure as-is. Do not reformat or reorder blocks unless explicitly asked.
|
||||||
- Always preserve block_id and type in your replacements.
|
|
||||||
- When replacing a block, use the full new block including <!-- block_id=... type=... --> and the updated content.
|
|
||||||
- When inserting, follow the same format as a replacement, but ensure the new block_id does not conflict with existing IDs.
|
- When inserting, follow the same format as a replacement, but ensure the new block_id does not conflict with existing IDs.
|
||||||
|
- When replacing content, always keep the original block_id unchanged.
|
||||||
|
- When deleting content, only use the format <!-- delete block_id=xxx -->, and only for valid block_id present in the original <code> content.
|
||||||
- Each list item should be a block.
|
- Each list item should be a block.
|
||||||
- Use <!-- existing blocks ... --> for unchanged sections.
|
- Your task is to return a list of block-level changes needed to fulfill the user's intent.
|
||||||
- If you plan on deleting a section, you must provide surrounding context to indicate the deletion.
|
- **Each change in code_edit must be completely independent: each code_edit entry should only perform a single, isolated change, and must not include the effects of other changes. For example, the updates for a delete operation should only show the context related to the deletion, and must not include any content modified by other operations (such as bolding or insertion). This ensures that each change can be applied independently and in any order.**
|
||||||
|
|
||||||
Example Input Document:
|
Original Content:
|
||||||
\`\`\`md
|
\`\`\`markdown
|
||||||
<!-- block_id=block-001 type=paragraph -->
|
<!-- block_id=001 flavour=paragraph -->
|
||||||
# My Holiday Plan
|
# Andriy Shevchenko
|
||||||
|
|
||||||
<!-- block_id=block-002 type=paragraph -->
|
<!-- block_id=002 flavour=paragraph -->
|
||||||
I plan to travel to Paris, France, where I will visit the Eiffel Tower, the Louvre, and the Champs-Élysées.
|
## Player Profile
|
||||||
|
|
||||||
<!-- block_id=block-003 type=paragraph -->
|
<!-- block_id=003 flavour=paragraph -->
|
||||||
I love Paris.
|
Andriy Shevchenko is a legendary Ukrainian striker, best known for his time at AC Milan and Dynamo Kyiv. He won the Ballon d'Or in 2004.
|
||||||
|
|
||||||
<!-- block_id=block-004 type=paragraph -->
|
<!-- block_id=004 flavour=paragraph -->
|
||||||
## Reason for the delay
|
## Career Overview
|
||||||
|
|
||||||
<!-- block_id=block-005 type=paragraph -->
|
<!-- block_id=005 flavour=list -->
|
||||||
This plan has been brewing for a long time, but I always postponed it because I was too busy with work.
|
- Born in 1976 in Ukraine.
|
||||||
|
<!-- block_id=006 flavour=list -->
|
||||||
<!-- block_id=block-006 type=paragraph -->
|
- Rose to fame at Dynamo Kyiv in the 1990s.
|
||||||
## Trip Steps
|
<!-- block_id=007 flavour=list -->
|
||||||
|
- Starred at AC Milan (1999–2006), scoring over 170 goals.
|
||||||
<!-- block_id=block-007 type=list -->
|
<!-- block_id=008 flavour=list -->
|
||||||
- Book flight tickets
|
- Played for Chelsea (2006–2009) before returning to Kyiv.
|
||||||
<!-- block_id=block-008 type=list -->
|
<!-- block_id=009 flavour=list -->
|
||||||
- Reserve a hotel
|
- Coached Ukraine national team, reaching Euro 2020 quarter-finals.
|
||||||
<!-- block_id=block-009 type=list -->
|
|
||||||
- Prepare visa documents
|
|
||||||
<!-- block_id=block-010 type=list -->
|
|
||||||
- Plan the itinerary
|
|
||||||
|
|
||||||
<!-- block_id=block-011 type=paragraph -->
|
|
||||||
Additionally, I plan to learn some basic French to make communication easier during the trip.
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Example User Request:
|
User Request:
|
||||||
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
Translate the trip steps to Chinese, remove the reason for the delay, and bold the final paragraph.
|
Bold the player’s name in the intro, add a summary section at the end, and remove the career overview.
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Expected Output:
|
Example response:
|
||||||
|
\`\`\`json
|
||||||
\`\`\`md
|
[
|
||||||
<!-- existing blocks ... -->
|
{
|
||||||
|
"op": "Bold the player's name in the introduction",
|
||||||
<!-- block_id=block-002 type=paragraph -->
|
"updates": "
|
||||||
I plan to travel to Paris, France, where I will visit the Eiffel Tower, the Louvre, and the Champs-Élysées.
|
<!-- block_id=003 flavour=paragraph -->
|
||||||
|
**Andriy Shevchenko** is a legendary Ukrainian striker, best known for his time at AC Milan and Dynamo Kyiv. He won the Ballon d'Or in 2004.
|
||||||
<!-- block_id=block-003 type=paragraph -->
|
"
|
||||||
I love Paris.
|
},
|
||||||
|
{
|
||||||
<!-- delete block-004 -->
|
"op": "Add a summary section at the end",
|
||||||
|
"updates": "
|
||||||
<!-- delete block-005 -->
|
<!-- block_id=new-abc123 flavour=paragraph -->
|
||||||
|
## Summary
|
||||||
<!-- block_id=block-006 type=paragraph -->
|
<!-- block_id=new-def456 flavour=paragraph -->
|
||||||
## Trip Steps
|
Shevchenko is celebrated as one of the greatest Ukrainian footballers of all time. Known for his composure, strength, and goal-scoring instinct, he left a lasting legacy both on and off the pitch.
|
||||||
|
"
|
||||||
<!-- block_id=block-007 type=list -->
|
},
|
||||||
- 订机票
|
{
|
||||||
<!-- block_id=block-008 type=list -->
|
"op": "Delete the career overview section",
|
||||||
- 预定酒店
|
"updates": "
|
||||||
<!-- block_id=block-009 type=list -->
|
<!-- delete block_id=004 -->
|
||||||
- 准备签证材料
|
<!-- delete block_id=005 -->
|
||||||
<!-- block_id=block-010 type=list -->
|
<!-- delete block_id=006 -->
|
||||||
- 规划行程
|
<!-- delete block_id=007 -->
|
||||||
|
<!-- delete block_id=008 -->
|
||||||
<!-- existing blocks ... -->
|
<!-- delete block_id=009 -->
|
||||||
|
"
|
||||||
<!-- block_id=block-011 type=paragraph -->
|
}
|
||||||
**Additionally, I plan to learn some basic French to make communication easier during the trip.**
|
]
|
||||||
\`\`\`
|
\`\`\`
|
||||||
You should specify the following arguments before the others: [doc_id], [origin_content]
|
You should specify the following arguments before the others: [doc_id], [origin_content]
|
||||||
|
|
||||||
@@ -144,14 +143,32 @@ You should specify the following arguments before the others: [doc_id], [origin_
|
|||||||
),
|
),
|
||||||
|
|
||||||
code_edit: z
|
code_edit: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
op: z
|
||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe(
|
||||||
'Specify only the necessary Markdown block-level changes. Return a list of inserted, replaced, or deleted blocks. Each block must start with its <!-- block_id=... type=... --> comment. Use <!-- existing blocks ... --> for unchanged sections.If you plan on deleting a section, you must provide surrounding context to indicate the deletion.'
|
'A short description of the change, such as "Bold intro name"'
|
||||||
|
),
|
||||||
|
updates: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'Markdown block fragments that represent the change, including the block_id and type'
|
||||||
|
),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.describe(
|
||||||
|
'An array of independent semantic changes to apply to the document.'
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async ({ doc_id, origin_content, code_edit }) => {
|
execute: async ({ doc_id, origin_content, code_edit }) => {
|
||||||
try {
|
try {
|
||||||
const provider = await factory.getProviderByModel('morph-v2');
|
const applyPrompt = await prompt.get('Apply Updates');
|
||||||
|
if (!applyPrompt) {
|
||||||
|
return 'Prompt not found';
|
||||||
|
}
|
||||||
|
const model = applyPrompt.model;
|
||||||
|
const provider = await factory.getProviderByModel(model);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return 'Editing docs is not supported';
|
return 'Editing docs is not supported';
|
||||||
}
|
}
|
||||||
@@ -160,14 +177,27 @@ You should specify the following arguments before the others: [doc_id], [origin_
|
|||||||
if (!content) {
|
if (!content) {
|
||||||
return 'Doc not found or doc is empty';
|
return 'Doc not found or doc is empty';
|
||||||
}
|
}
|
||||||
const result = await provider.text({ modelId: 'morph-v2' }, [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: `<code>${content}</code>\n<update>${code_edit}</update>`,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { result, content };
|
const changedContents = await Promise.all(
|
||||||
|
code_edit.map(async edit => {
|
||||||
|
return await provider.text({ modelId: model }, [
|
||||||
|
...applyPrompt.finish({
|
||||||
|
content,
|
||||||
|
op: edit.op,
|
||||||
|
updates: edit.updates,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: changedContents.map((changedContent, index) => ({
|
||||||
|
op: code_edit[index].op,
|
||||||
|
updates: code_edit[index].updates,
|
||||||
|
originalContent: content,
|
||||||
|
changedContent,
|
||||||
|
})),
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return 'Failed to apply edit to the doc';
|
return 'Failed to apply edit to the doc';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1518,6 +1518,9 @@ type PublicUserType {
|
|||||||
type Query {
|
type Query {
|
||||||
"""get the whole app configuration"""
|
"""get the whole app configuration"""
|
||||||
appConfig: JSONObject!
|
appConfig: JSONObject!
|
||||||
|
|
||||||
|
"""Apply updates to a doc using LLM and return the merged markdown."""
|
||||||
|
applyDocUpdates(docId: String!, op: String!, updates: String!, workspaceId: String!): String!
|
||||||
collectAllBlobSizes: WorkspaceBlobSizes! @deprecated(reason: "use `user.quotaUsage` instead")
|
collectAllBlobSizes: WorkspaceBlobSizes! @deprecated(reason: "use `user.quotaUsage` instead")
|
||||||
|
|
||||||
"""Get current user"""
|
"""Get current user"""
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
query applyDocUpdates($workspaceId: String!, $docId: String!, $op: String!, $updates: String!) {
|
||||||
|
applyDocUpdates(workspaceId: $workspaceId, docId: $docId, op: $op, updates: $updates)
|
||||||
|
}
|
||||||
@@ -555,6 +555,19 @@ export const uploadCommentAttachmentMutation = {
|
|||||||
file: true,
|
file: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const applyDocUpdatesQuery = {
|
||||||
|
id: 'applyDocUpdatesQuery' as const,
|
||||||
|
op: 'applyDocUpdates',
|
||||||
|
query: `query applyDocUpdates($workspaceId: String!, $docId: String!, $op: String!, $updates: String!) {
|
||||||
|
applyDocUpdates(
|
||||||
|
workspaceId: $workspaceId
|
||||||
|
docId: $docId
|
||||||
|
op: $op
|
||||||
|
updates: $updates
|
||||||
|
)
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const addContextCategoryMutation = {
|
export const addContextCategoryMutation = {
|
||||||
id: 'addContextCategoryMutation' as const,
|
id: 'addContextCategoryMutation' as const,
|
||||||
op: 'addContextCategory',
|
op: 'addContextCategory',
|
||||||
|
|||||||
@@ -2073,6 +2073,8 @@ export interface Query {
|
|||||||
__typename?: 'Query';
|
__typename?: 'Query';
|
||||||
/** get the whole app configuration */
|
/** get the whole app configuration */
|
||||||
appConfig: Scalars['JSONObject']['output'];
|
appConfig: Scalars['JSONObject']['output'];
|
||||||
|
/** Apply updates to a doc using LLM and return the merged markdown. */
|
||||||
|
applyDocUpdates: Scalars['String']['output'];
|
||||||
/** @deprecated use `user.quotaUsage` instead */
|
/** @deprecated use `user.quotaUsage` instead */
|
||||||
collectAllBlobSizes: WorkspaceBlobSizes;
|
collectAllBlobSizes: WorkspaceBlobSizes;
|
||||||
/** Get current user */
|
/** Get current user */
|
||||||
@@ -2120,6 +2122,13 @@ export interface Query {
|
|||||||
workspaces: Array<WorkspaceType>;
|
workspaces: Array<WorkspaceType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface QueryApplyDocUpdatesArgs {
|
||||||
|
docId: Scalars['String']['input'];
|
||||||
|
op: Scalars['String']['input'];
|
||||||
|
updates: Scalars['String']['input'];
|
||||||
|
workspaceId: Scalars['String']['input'];
|
||||||
|
}
|
||||||
|
|
||||||
export interface QueryErrorArgs {
|
export interface QueryErrorArgs {
|
||||||
name: ErrorNames;
|
name: ErrorNames;
|
||||||
}
|
}
|
||||||
@@ -3509,6 +3518,18 @@ export type UploadCommentAttachmentMutation = {
|
|||||||
uploadCommentAttachment: string;
|
uploadCommentAttachment: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApplyDocUpdatesQueryVariables = Exact<{
|
||||||
|
workspaceId: Scalars['String']['input'];
|
||||||
|
docId: Scalars['String']['input'];
|
||||||
|
op: Scalars['String']['input'];
|
||||||
|
updates: Scalars['String']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ApplyDocUpdatesQuery = {
|
||||||
|
__typename?: 'Query';
|
||||||
|
applyDocUpdates: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AddContextCategoryMutationVariables = Exact<{
|
export type AddContextCategoryMutationVariables = Exact<{
|
||||||
options: AddContextCategoryInput;
|
options: AddContextCategoryInput;
|
||||||
}>;
|
}>;
|
||||||
@@ -6148,6 +6169,11 @@ export type Queries =
|
|||||||
variables: ListCommentsQueryVariables;
|
variables: ListCommentsQueryVariables;
|
||||||
response: ListCommentsQuery;
|
response: ListCommentsQuery;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'applyDocUpdatesQuery';
|
||||||
|
variables: ApplyDocUpdatesQueryVariables;
|
||||||
|
response: ApplyDocUpdatesQuery;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'listContextObjectQuery';
|
name: 'listContextObjectQuery';
|
||||||
variables: ListContextObjectQueryVariables;
|
variables: ListContextObjectQueryVariables;
|
||||||
|
|||||||
@@ -348,6 +348,12 @@ declare global {
|
|||||||
files?: ContextMatchedFileChunk[];
|
files?: ContextMatchedFileChunk[];
|
||||||
docs?: ContextMatchedDocChunk[];
|
docs?: ContextMatchedDocChunk[];
|
||||||
}>;
|
}>;
|
||||||
|
applyDocUpdates: (
|
||||||
|
workspaceId: string,
|
||||||
|
docId: string,
|
||||||
|
op: string,
|
||||||
|
updates: string
|
||||||
|
) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(@Peng): should be refactored to get rid of implement details (like messages, action, role, etc.)
|
// TODO(@Peng): should be refactored to get rid of implement details (like messages, action, role, etc.)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import track from '@affine/track';
|
|||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
|
import { AIStarIconWithAnimation } from '@blocksuite/affine-components/icons';
|
||||||
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import {
|
import {
|
||||||
CloseIcon,
|
CloseIcon,
|
||||||
@@ -14,7 +15,9 @@ import {
|
|||||||
} from '@blocksuite/icons/lit';
|
} from '@blocksuite/icons/lit';
|
||||||
import { css, html, nothing } from 'lit';
|
import { css, html, nothing } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
|
||||||
|
import { AIProvider } from '../../provider';
|
||||||
import { BlockDiffProvider } from '../../services/block-diff';
|
import { BlockDiffProvider } from '../../services/block-diff';
|
||||||
import { diffMarkdown } from '../../utils/apply-model/markdown-diff';
|
import { diffMarkdown } from '../../utils/apply-model/markdown-diff';
|
||||||
import { copyText } from '../../utils/editor-actions';
|
import { copyText } from '../../utils/editor-actions';
|
||||||
@@ -37,8 +40,12 @@ interface DocEditToolResult {
|
|||||||
};
|
};
|
||||||
result:
|
result:
|
||||||
| {
|
| {
|
||||||
result: string;
|
result: {
|
||||||
content: string;
|
op: string;
|
||||||
|
updates: string;
|
||||||
|
originalContent: string;
|
||||||
|
changedContent: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
| ToolError
|
| ToolError
|
||||||
| null;
|
| null;
|
||||||
@@ -199,40 +206,108 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
|
|||||||
@state()
|
@state()
|
||||||
accessor isCollapsed = false;
|
accessor isCollapsed = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor applyingMap: Record<string, boolean> = {};
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor acceptingMap: Record<string, boolean> = {};
|
||||||
|
|
||||||
get blockDiffService() {
|
get blockDiffService() {
|
||||||
return this.host?.std.getOptional(BlockDiffProvider);
|
return this.host?.std.getOptional(BlockDiffProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleApply(markdown: string) {
|
get isBusy() {
|
||||||
if (!this.host || this.data.type !== 'tool-result') {
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
isBusyForOp(op: string) {
|
||||||
|
return this.applyingMap[op] || this.acceptingMap[op];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleApply(op: string, updates: string) {
|
||||||
|
if (
|
||||||
|
!this.host ||
|
||||||
|
this.data.type !== 'tool-result' ||
|
||||||
|
this.isBusyForOp(op)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.applyingMap = { ...this.applyingMap, [op]: true };
|
||||||
|
try {
|
||||||
|
const markdown = await AIProvider.context?.applyDocUpdates(
|
||||||
|
this.host.std.workspace.id,
|
||||||
|
this.data.args.doc_id,
|
||||||
|
op,
|
||||||
|
updates
|
||||||
|
);
|
||||||
|
if (!markdown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
track.applyModel.chat.$.apply({
|
track.applyModel.chat.$.apply({
|
||||||
instruction: this.data.args.instructions,
|
instruction: this.data.args.instructions,
|
||||||
|
operation: op,
|
||||||
});
|
});
|
||||||
await this.blockDiffService?.apply(this.host.store, markdown);
|
await this.blockDiffService?.apply(this.host.store, markdown);
|
||||||
|
} catch (error) {
|
||||||
|
this.notificationService.notify({
|
||||||
|
title: 'Failed to apply updates',
|
||||||
|
message: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
accent: 'error',
|
||||||
|
onClose: function (): void {},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this.applyingMap = { ...this.applyingMap, [op]: false };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleReject(changedMarkdown: string) {
|
private async _handleReject(op: string) {
|
||||||
if (!this.host || this.data.type !== 'tool-result') {
|
if (!this.host || this.data.type !== 'tool-result') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO: set the rejected status
|
||||||
track.applyModel.chat.$.reject({
|
track.applyModel.chat.$.reject({
|
||||||
instruction: this.data.args.instructions,
|
instruction: this.data.args.instructions,
|
||||||
|
operation: op,
|
||||||
});
|
});
|
||||||
this.blockDiffService?.setChangedMarkdown(changedMarkdown);
|
this.blockDiffService?.setChangedMarkdown(null);
|
||||||
this.blockDiffService?.rejectAll();
|
this.blockDiffService?.rejectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleAccept(changedMarkdown: string) {
|
private async _handleAccept(op: string, updates: string) {
|
||||||
if (!this.host || this.data.type !== 'tool-result') {
|
if (
|
||||||
|
!this.host ||
|
||||||
|
this.data.type !== 'tool-result' ||
|
||||||
|
this.isBusyForOp(op)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.acceptingMap = { ...this.acceptingMap, [op]: true };
|
||||||
|
try {
|
||||||
|
const changedMarkdown = await AIProvider.context?.applyDocUpdates(
|
||||||
|
this.host.std.workspace.id,
|
||||||
|
this.data.args.doc_id,
|
||||||
|
op,
|
||||||
|
updates
|
||||||
|
);
|
||||||
|
if (!changedMarkdown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
track.applyModel.chat.$.accept({
|
track.applyModel.chat.$.accept({
|
||||||
instruction: this.data.args.instructions,
|
instruction: this.data.args.instructions,
|
||||||
|
operation: op,
|
||||||
});
|
});
|
||||||
await this.blockDiffService?.apply(this.host.store, changedMarkdown);
|
await this.blockDiffService?.apply(this.host.store, changedMarkdown);
|
||||||
await this.blockDiffService?.acceptAll(this.host.store);
|
await this.blockDiffService?.acceptAll(this.host.store);
|
||||||
|
} catch (error) {
|
||||||
|
this.notificationService.notify({
|
||||||
|
title: 'Failed to apply updates',
|
||||||
|
message: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
accent: 'error',
|
||||||
|
onClose: function (): void {},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this.acceptingMap = { ...this.acceptingMap, [op]: false };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _toggleCollapse() {
|
private async _toggleCollapse() {
|
||||||
@@ -322,15 +397,17 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
|
|||||||
|
|
||||||
const result = this.data.result;
|
const result = this.data.result;
|
||||||
|
|
||||||
if (result && 'result' in result && 'content' in result) {
|
if (result && 'result' in result && Array.isArray(result.result)) {
|
||||||
const { result: changedMarkdown, content } = result;
|
const { doc_id: docId } = this.data.args;
|
||||||
const { instructions, doc_id: docId } = this.data.args;
|
|
||||||
|
|
||||||
const diffs = diffMarkdown(content, changedMarkdown);
|
|
||||||
|
|
||||||
|
return repeat(
|
||||||
|
result.result,
|
||||||
|
change => change.op,
|
||||||
|
({ op, updates, originalContent, changedContent }) => {
|
||||||
|
const diffs = diffMarkdown(originalContent, changedContent);
|
||||||
return html`
|
return html`
|
||||||
<div class="doc-edit-tool-result-wrapper">
|
<div class="doc-edit-tool-result-wrapper">
|
||||||
<div class="doc-edit-tool-result-title">${instructions}</div>
|
<div class="doc-edit-tool-result-title">${op}</div>
|
||||||
<div
|
<div
|
||||||
class="doc-edit-tool-result-card ${this.isCollapsed
|
class="doc-edit-tool-result-card ${this.isCollapsed
|
||||||
? 'collapsed'
|
? 'collapsed'
|
||||||
@@ -349,11 +426,16 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
|
|||||||
? ExpandFullIcon()
|
? ExpandFullIcon()
|
||||||
: ExpandCloseIcon()}</span
|
: ExpandCloseIcon()}</span
|
||||||
>
|
>
|
||||||
<span @click=${() => this._handleCopy(changedMarkdown)}>
|
<span @click=${() => this._handleCopy(changedContent)}>
|
||||||
${CopyIcon()}
|
${CopyIcon()}
|
||||||
</span>
|
</span>
|
||||||
<button @click=${() => this._handleApply(changedMarkdown)}>
|
<button
|
||||||
Apply
|
@click=${() => this._handleApply(op, updates)}
|
||||||
|
?disabled=${this.isBusyForOp(op)}
|
||||||
|
>
|
||||||
|
${this.applyingMap[op]
|
||||||
|
? AIStarIconWithAnimation
|
||||||
|
: html`Apply`}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -365,27 +447,35 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
|
|||||||
<div class="doc-edit-tool-result-card-footer">
|
<div class="doc-edit-tool-result-card-footer">
|
||||||
<div
|
<div
|
||||||
class="doc-edit-tool-result-reject"
|
class="doc-edit-tool-result-reject"
|
||||||
@click=${() => this._handleReject(changedMarkdown)}
|
@click=${() => this._handleReject(op)}
|
||||||
>
|
>
|
||||||
${CloseIcon({
|
${CloseIcon({
|
||||||
style: `color: ${unsafeCSSVarV2('icon/secondary')}`,
|
style: `color: ${unsafeCSSVarV2('icon/secondary')}`,
|
||||||
})}
|
})}
|
||||||
Reject
|
Reject
|
||||||
</div>
|
</div>
|
||||||
<div
|
<button
|
||||||
class="doc-edit-tool-result-accept"
|
class="doc-edit-tool-result-accept"
|
||||||
@click=${() => this._handleAccept(changedMarkdown)}
|
@click=${() => this._handleAccept(op, updates)}
|
||||||
|
?disabled=${this.isBusyForOp(op)}
|
||||||
|
style="${this.isBusyForOp(op)
|
||||||
|
? 'pointer-events: none; opacity: 0.6;'
|
||||||
|
: ''}"
|
||||||
>
|
>
|
||||||
${DoneIcon({
|
${this.acceptingMap[op]
|
||||||
|
? AIStarIconWithAnimation
|
||||||
|
: DoneIcon({
|
||||||
style: `color: ${unsafeCSSVarV2('icon/activated')}`,
|
style: `color: ${unsafeCSSVarV2('icon/activated')}`,
|
||||||
})}
|
})}
|
||||||
Accept
|
${this.acceptingMap[op] ? 'Accepting...' : 'Accept'}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<tool-call-failed
|
<tool-call-failed
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
addContextCategoryMutation,
|
addContextCategoryMutation,
|
||||||
addContextDocMutation,
|
addContextDocMutation,
|
||||||
addContextFileMutation,
|
addContextFileMutation,
|
||||||
|
applyDocUpdatesQuery,
|
||||||
cleanupCopilotSessionMutation,
|
cleanupCopilotSessionMutation,
|
||||||
createCopilotContextMutation,
|
createCopilotContextMutation,
|
||||||
createCopilotMessageMutation,
|
createCopilotMessageMutation,
|
||||||
@@ -500,4 +501,21 @@ export class CopilotClient {
|
|||||||
variables: { workspaceId },
|
variables: { workspaceId },
|
||||||
}).then(res => res.queryWorkspaceEmbeddingStatus);
|
}).then(res => res.queryWorkspaceEmbeddingStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyDocUpdates(
|
||||||
|
workspaceId: string,
|
||||||
|
docId: string,
|
||||||
|
op: string,
|
||||||
|
updates: string
|
||||||
|
) {
|
||||||
|
return this.gql({
|
||||||
|
query: applyDocUpdatesQuery,
|
||||||
|
variables: {
|
||||||
|
workspaceId,
|
||||||
|
docId,
|
||||||
|
op,
|
||||||
|
updates,
|
||||||
|
},
|
||||||
|
}).then(res => res.applyDocUpdates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -733,6 +733,14 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
threshold
|
threshold
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
applyDocUpdates: async (
|
||||||
|
workspaceId: string,
|
||||||
|
docId: string,
|
||||||
|
op: string,
|
||||||
|
updates: string
|
||||||
|
) => {
|
||||||
|
return client.applyDocUpdates(workspaceId, docId, op, updates);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
AIProvider.provide('histories', {
|
AIProvider.provide('histories', {
|
||||||
|
|||||||
@@ -80,13 +80,13 @@ export interface BlockDiffProvider {
|
|||||||
* Set the original markdown
|
* Set the original markdown
|
||||||
* @param originalMarkdown - The original markdown
|
* @param originalMarkdown - The original markdown
|
||||||
*/
|
*/
|
||||||
setOriginalMarkdown(originalMarkdown: string): void;
|
setOriginalMarkdown(originalMarkdown: string | null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the changed markdown
|
* Set the changed markdown
|
||||||
* @param changedMarkdown - The changed markdown
|
* @param changedMarkdown - The changed markdown
|
||||||
*/
|
*/
|
||||||
setChangedMarkdown(changedMarkdown: string): void;
|
setChangedMarkdown(changedMarkdown: string | null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the diff to the doc
|
* Apply the diff to the doc
|
||||||
|
|||||||
Reference in New Issue
Block a user