mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
progress
This commit is contained in:
+2
-1
@@ -3,4 +3,5 @@ PUBLIC_CONSOLE_FEATURE_FLAGS=
|
||||
PUBLIC_APPWRITE_MULTI_REGION=false
|
||||
PUBLIC_APPWRITE_ENDPOINT=http://localhost/v1
|
||||
PUBLIC_STRIPE_KEY=
|
||||
PUBLIC_GROWTH_ENDPOINT=
|
||||
PUBLIC_GROWTH_ENDPOINT=
|
||||
PUBLIC_AI_SERVICE_BASE_URL=http://localhost:8889
|
||||
@@ -15,21 +15,23 @@ const exec = promisify(_exec);
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { createImagineClient } from '@/lib/imagine/create-artifact-client';
|
||||
import { IMAGINE_JWT } from '@/lib/constants';
|
||||
|
||||
export const handleChatRequest = async (c: Context) => {
|
||||
console.log("incoming request");
|
||||
const signal = c.req.raw.signal;
|
||||
|
||||
const token = c.req.header('X-Imagine-Token');
|
||||
if (!token) {
|
||||
return c.json({ error: 'Unauthorized' }, 401);
|
||||
}
|
||||
|
||||
let body: ChatRequestBodyType;
|
||||
|
||||
// Parse request body
|
||||
try {
|
||||
const json = await c.req.json();
|
||||
// console.log("json", json);
|
||||
body = chatRequestBodySchema.parse(json);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
console.log("zod error", error);
|
||||
return c.json({ errors: error.issues }, 400);
|
||||
}
|
||||
|
||||
@@ -37,51 +39,47 @@ export const handleChatRequest = async (c: Context) => {
|
||||
return c.json('An unknown error occurred', 500);
|
||||
}
|
||||
|
||||
const { id: conversationId, messages, trigger } = body;
|
||||
const { id: conversationId, messages, trigger, artifactId, projectId } = body;
|
||||
|
||||
if (trigger === "submit-tool-result") {
|
||||
// Skip
|
||||
return c.body(null, 200);
|
||||
if (trigger === 'submit-tool-result') {
|
||||
// Skip
|
||||
return c.body(null, 200);
|
||||
}
|
||||
|
||||
console.log("body", body);
|
||||
|
||||
const artifactId = process.env.IMAGINE_ARTIFACT_ID!; // TODO: dynamically from body
|
||||
const projectId = process.env.IMAGINE_PROJECT_ID!; // TODO: dynamically from body
|
||||
|
||||
const imagineClient = await createImagineClient({
|
||||
projectId,
|
||||
token: IMAGINE_JWT, // TODO: use the token from the request
|
||||
projectId,
|
||||
token // TODO: use the token from the request
|
||||
});
|
||||
const convos = await imagineClient.listConversations(artifactId);
|
||||
const imagineConvo = convos.conversations[0];
|
||||
const uiMessages = imagineConvo.messages.messages;
|
||||
const isNewConversation = uiMessages.length === 0;
|
||||
const convertedMessages = convertToModelMessages(messages);
|
||||
const latestMessage = convertedMessages[convertedMessages.length - 1];
|
||||
|
||||
|
||||
const latestMessageTextPart = latestMessage.content[0] as TextPart;
|
||||
const restMessages = convertedMessages.slice(0, -1);
|
||||
|
||||
|
||||
// If it's a new conversation, we need to clone the workspace
|
||||
// This is temporary and will be handled by Synapse shortly!
|
||||
const isNewConversation = restMessages.length === 0;
|
||||
if (isNewConversation) {
|
||||
const workspaceDir = path.resolve(process.cwd(), `./tmp/workspace/artifact/${artifactId}`)
|
||||
console.log("workspaceDir", workspaceDir);
|
||||
const exists = fs.existsSync(workspaceDir);
|
||||
const workspaceDir = path.resolve(process.cwd(), `./tmp/workspace/artifact/${artifactId}`);
|
||||
console.log('workspaceDir', workspaceDir);
|
||||
const exists = fs.existsSync(workspaceDir);
|
||||
|
||||
if (exists) {
|
||||
console.log("Workspace already exists, skipping clone");
|
||||
} else {
|
||||
console.log("Workspace does not exist, creating directory...");
|
||||
await exec(`mkdir -p ${workspaceDir}`);
|
||||
console.log("Cloning template 'base-vite-template'...");
|
||||
await exec(`pnpx degit appwrite/templates-for-frameworks/base-vite-template .`, { cwd: workspaceDir });
|
||||
console.log("Installing dependencies...");
|
||||
await exec(`bun install`, { cwd: workspaceDir });
|
||||
}
|
||||
if (exists) {
|
||||
console.log('Workspace already exists, skipping clone');
|
||||
} else {
|
||||
console.log('Workspace does not exist, creating directory...');
|
||||
await exec(`mkdir -p ${workspaceDir}`);
|
||||
console.log("Cloning template 'base-vite-template'...");
|
||||
await exec(`pnpx degit appwrite/templates-for-frameworks/base-vite-template .`, {
|
||||
cwd: workspaceDir
|
||||
});
|
||||
console.log('Installing dependencies...');
|
||||
await exec(`bun install`, { cwd: workspaceDir });
|
||||
}
|
||||
} else {
|
||||
console.log("Not a new conversation, skipping workspace clone");
|
||||
console.log('Not a new conversation, skipping workspace clone');
|
||||
}
|
||||
|
||||
let didError = false;
|
||||
@@ -90,22 +88,28 @@ export const handleChatRequest = async (c: Context) => {
|
||||
originalMessages: messages,
|
||||
execute: async (params) => {
|
||||
const writer = params.writer as WriterType;
|
||||
const runtimeContext = createRuntimeContext({ writer, artifactId, restMessages, isFirstMessage: isNewConversation, signal });
|
||||
const runtimeContext = createRuntimeContext({
|
||||
writer,
|
||||
artifactId,
|
||||
restMessages,
|
||||
isFirstMessage: isNewConversation,
|
||||
signal
|
||||
});
|
||||
const run = await mastra.getWorkflow('codeWorkflow').createRunAsync();
|
||||
|
||||
const result = run.stream({
|
||||
inputData: {
|
||||
userPrompt: latestMessageTextPart.text
|
||||
userPrompt: latestMessageTextPart.text
|
||||
},
|
||||
runtimeContext
|
||||
});
|
||||
|
||||
writer.write({
|
||||
type: 'start',
|
||||
type: 'start'
|
||||
});
|
||||
|
||||
writer.write({
|
||||
type: 'start-step'
|
||||
type: 'start-step'
|
||||
});
|
||||
|
||||
for await (const chunk of result.stream) {
|
||||
@@ -117,7 +121,7 @@ export const handleChatRequest = async (c: Context) => {
|
||||
});
|
||||
|
||||
writer.write({
|
||||
type: 'finish',
|
||||
type: 'finish'
|
||||
});
|
||||
},
|
||||
onFinish: async (event) => {
|
||||
@@ -128,17 +132,14 @@ export const handleChatRequest = async (c: Context) => {
|
||||
|
||||
const { messages } = event;
|
||||
|
||||
console.log("Saving messages to imagine");
|
||||
await imagineClient.updateConversation(artifactId, imagineConvo.$id, "Test Conversation", { messages } as any);
|
||||
console.log("Messages saved to imagine");
|
||||
// await updateConversation({
|
||||
// conversation: {
|
||||
// ...conversation,
|
||||
// uiMessages: messages,
|
||||
// },
|
||||
// artifactId,
|
||||
// projectId,
|
||||
// });
|
||||
console.log('Saving messages to imagine');
|
||||
await imagineClient.updateConversation(
|
||||
artifactId,
|
||||
imagineConvo.$id,
|
||||
'Test Conversation',
|
||||
messages
|
||||
);
|
||||
console.log('Messages saved to imagine');
|
||||
},
|
||||
onError: (error) => {
|
||||
didError = true;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { UIMessage } from "ai";
|
||||
import { z } from "zod";
|
||||
import { ImagineUIMessage } from "@/shared-types";
|
||||
|
||||
export const chatRequestBodySchema = z.object({
|
||||
messages: z.array(z.custom<UIMessage>()).min(1),
|
||||
messages: z.array(z.custom<ImagineUIMessage>()).min(1),
|
||||
trigger: z.string(),
|
||||
id: z.string(),
|
||||
artifactId: z.string(),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { IMAGINE_JWT } from "@/lib/constants";
|
||||
import { createImagineClient } from "@/lib/imagine/create-artifact-client";
|
||||
import { Conversation } from "@/lib/imagine/imagine-api-client";
|
||||
import { Context } from "hono";
|
||||
@@ -7,20 +6,24 @@ const mapConversation = (conversation: Conversation) => {
|
||||
return {
|
||||
id: conversation.$id,
|
||||
name: conversation.name,
|
||||
messages: conversation.messages.messages,
|
||||
messages: conversation.messages,
|
||||
};
|
||||
};
|
||||
|
||||
export const getConversation = async (c: Context) => {
|
||||
console.log("getConversation");
|
||||
const { conversationId } = c.req.param();
|
||||
const token = c.req.header("X-Imagine-Token");
|
||||
|
||||
if (!token) {
|
||||
return c.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const projectId = process.env.IMAGINE_PROJECT_ID!;
|
||||
const artifactId = process.env.IMAGINE_ARTIFACT_ID!;
|
||||
|
||||
const imagineClient = await createImagineClient({
|
||||
projectId,
|
||||
token: IMAGINE_JWT, // TODO: use the token from the request
|
||||
token
|
||||
});
|
||||
|
||||
const conversation = await imagineClient.getConversation(artifactId, conversationId);
|
||||
@@ -32,10 +35,15 @@ export const getConversations = async (c: Context) => {
|
||||
console.log("getConversations");
|
||||
const projectId = process.env.IMAGINE_PROJECT_ID!;
|
||||
const artifactId = process.env.IMAGINE_ARTIFACT_ID!;
|
||||
const token = c.req.header("X-Imagine-Token");
|
||||
|
||||
if (!token) {
|
||||
return c.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const imagineClient = await createImagineClient({
|
||||
projectId,
|
||||
token: IMAGINE_JWT, // TODO: use the token from the request
|
||||
token
|
||||
});
|
||||
|
||||
const conversations = await imagineClient.listConversations(artifactId);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const checkpointDataUIPartSchema = z.object({
|
||||
export const checkpointUIDataPartSchema = z.object({
|
||||
commitSha: z.string(),
|
||||
commitMessage: z.string(),
|
||||
timestamp: z.string(),
|
||||
});
|
||||
|
||||
export interface CheckpointDataUIPart {
|
||||
export interface CheckpointUIDataPart {
|
||||
type: "checkpoint";
|
||||
id: string;
|
||||
data: z.infer<typeof checkpointDataUIPartSchema>;
|
||||
data: z.infer<typeof checkpointUIDataPartSchema>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const thinkingUIDataPartSchema = z.union([
|
||||
z.object({
|
||||
text: z.string(),
|
||||
durationMs: z.null(),
|
||||
state: z.enum(["streaming"]),
|
||||
}),
|
||||
z.object({
|
||||
text: z.string(),
|
||||
durationMs: z.number(),
|
||||
state: z.enum(["done"]),
|
||||
})
|
||||
]);
|
||||
|
||||
export interface ThinkingUIDataPart {
|
||||
type: "thinking";
|
||||
id: string;
|
||||
data: z.infer<typeof thinkingUIDataPartSchema>;
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
import { RuntimeContext } from "@mastra/core/runtime-context";
|
||||
import { InferUITool } from "ai";
|
||||
import { fileTools } from "../tools/file-tools";
|
||||
import { UIMessage, UIMessageStreamWriter } from "ai";
|
||||
import { z } from "zod";
|
||||
import { checkpointDataUIPartSchema } from "../../custom-parts/checkpoint";
|
||||
import { UIMessageStreamWriter } from "ai";
|
||||
import { createSynapseClient, SynapseHTTPClient } from "@/lib/synapse-http-client";
|
||||
import { ImagineUIMessage } from "@/shared-types";
|
||||
|
||||
export type WriterType = UIMessageStreamWriter<UIMessage<unknown, {
|
||||
checkpoint: z.infer<typeof checkpointDataUIPartSchema>;
|
||||
}, {
|
||||
readFile: InferUITool<typeof fileTools.readFileTool>;
|
||||
writeFile: InferUITool<typeof fileTools.writeFileTool>;
|
||||
}>>;
|
||||
export type WriterType = UIMessageStreamWriter<ImagineUIMessage>;
|
||||
export type RuntimeContextPayload = {
|
||||
writer: WriterType;
|
||||
skipWritingToolCalls?: boolean;
|
||||
|
||||
@@ -81,10 +81,22 @@ const planStep = createStep({
|
||||
|
||||
const id = createIdGenerator({ size: 10 })();
|
||||
|
||||
// writer.write({
|
||||
// type: "reasoning-start",
|
||||
// id,
|
||||
// });
|
||||
|
||||
const thinkingId = createIdGenerator({ size: 10 })();
|
||||
|
||||
writer.write({
|
||||
type: "reasoning-start",
|
||||
id,
|
||||
});
|
||||
id: thinkingId,
|
||||
type: "data-thinking",
|
||||
data: {
|
||||
text: "",
|
||||
durationMs: null,
|
||||
state: "streaming",
|
||||
}
|
||||
})
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
@@ -161,20 +173,21 @@ ${userPrompt}
|
||||
const delta = partialObject.plan.slice(previousPlan.length);
|
||||
previousPlan = partialObject.plan;
|
||||
|
||||
writer.write({
|
||||
type: "reasoning-delta",
|
||||
id,
|
||||
delta,
|
||||
});
|
||||
|
||||
// writer.write({
|
||||
// type: "data-thoughts",
|
||||
// type: "reasoning-delta",
|
||||
// id,
|
||||
// data: {
|
||||
// status: "streaming",
|
||||
// text: partialObject.plan,
|
||||
// }
|
||||
// delta,
|
||||
// });
|
||||
|
||||
writer.write({
|
||||
type: "data-thinking",
|
||||
id: thinkingId,
|
||||
data: {
|
||||
text: partialObject.plan ?? "",
|
||||
durationMs: null,
|
||||
state: "streaming",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log("[planStep] postStream", latestPartialObject);
|
||||
@@ -185,26 +198,26 @@ ${userPrompt}
|
||||
|
||||
const endTime = Date.now();
|
||||
const timeTaken = endTime - startTime;
|
||||
// writer.write({
|
||||
// type: "data-thoughts",
|
||||
// id,
|
||||
// data: {
|
||||
// status: "done",
|
||||
// timeTaken,
|
||||
// text: latestPartialObject.plan,
|
||||
// }
|
||||
// });
|
||||
|
||||
writer.write({
|
||||
type: "reasoning-end",
|
||||
id,
|
||||
providerMetadata: {
|
||||
imagine: {
|
||||
durationInMs: timeTaken,
|
||||
}
|
||||
} as any,
|
||||
type: "data-thinking",
|
||||
id: thinkingId,
|
||||
data: {
|
||||
text: latestPartialObject.plan,
|
||||
durationMs: timeTaken,
|
||||
state: "done",
|
||||
}
|
||||
});
|
||||
|
||||
// writer.write({
|
||||
// type: "reasoning-end",
|
||||
// id,
|
||||
// providerMetadata: {
|
||||
// imagine: {
|
||||
// durationInMs: timeTaken,
|
||||
// }
|
||||
// } as any,
|
||||
// });
|
||||
|
||||
const { plan, shouldInvolveUIDeveloper } = latestPartialObject;
|
||||
return {
|
||||
plan,
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
import { createSynapseClient } from "../../synapse-http-client";
|
||||
|
||||
const readFileTool = tool({
|
||||
name: "readFile",
|
||||
description: "Read a file from the repository",
|
||||
inputSchema: z.object({
|
||||
path: z.string().describe("The path to the file to read"),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
path: z.string().describe("The path to the file"),
|
||||
content: z.string().describe("The content of the file"),
|
||||
}),
|
||||
execute: async ({ path }) => {
|
||||
console.log(`[TOOL - readFile] Reading file at path: ${path}`);
|
||||
const synapse = createSynapseClient();
|
||||
const result = await synapse.readFile({ path });
|
||||
|
||||
return {
|
||||
path: result.path,
|
||||
content: result.content,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const writeFilesTool = tool({
|
||||
name: "writeFiles",
|
||||
description: "Write multiple files to the repository",
|
||||
inputSchema: z.object({
|
||||
files: z.array(z.object({
|
||||
path: z.string().describe("The path to the file to write"),
|
||||
content: z.string().describe("The content of the file to write"),
|
||||
})),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
successFiles: z.array(z.object({ path: z.string().describe("The path to the file") })),
|
||||
errorFiles: z.array(z.object({ path: z.string().describe("The path to the file"), error: z.string().describe("The error message") })),
|
||||
}),
|
||||
execute: async ({ files }) => {
|
||||
const { successFiles, errorFiles } = await writeFiles(files);
|
||||
|
||||
return {
|
||||
successFiles,
|
||||
errorFiles,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const writeFileTool = tool({
|
||||
name: "writeFile",
|
||||
description: "Write a single file to the repository. If the file does not exist, it will be created. If the file exists, it will be overwritten. You must provide the full file contents.",
|
||||
inputSchema: z.object({
|
||||
path: z.string().describe("The path to the file to write"),
|
||||
content: z.string().describe("The content of the file to write"),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
success: z.boolean().describe("Whether the file was written successfully"),
|
||||
error: z.string().optional().describe("The error message if the file was not written successfully"),
|
||||
}),
|
||||
execute: async ({ path, content }) => {
|
||||
const { errorFiles } = await writeFiles([{ path, content }]);
|
||||
|
||||
if (errorFiles.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: errorFiles[0].error,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
async function writeFiles(files: { path: string, content: string }[]) {
|
||||
const synapse = createSynapseClient();
|
||||
|
||||
const successFiles: { path: string }[] = [];
|
||||
const errorFiles: { path: string, error: string }[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
await synapse.createOrUpdateFile({ filepath: file.path, content: file.content });
|
||||
successFiles.push({ path: file.path });
|
||||
} catch (error) {
|
||||
errorFiles.push({ path: file.path, error: error instanceof Error ? error.message : "Unknown error" });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
successFiles,
|
||||
errorFiles,
|
||||
};
|
||||
}
|
||||
|
||||
const listFilesInDirectoryTool = tool({
|
||||
name: "listFilesInDirectory",
|
||||
description: "List all files in a directory",
|
||||
inputSchema: z.object({
|
||||
path: z.string().default("/").describe("The path to the directory to list files from"),
|
||||
recursive: z.boolean().default(false).describe("Whether to list files recursively (dig into subdirectories)"),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
files: z.array(z.object({
|
||||
path: z.string().describe("The path to the file"),
|
||||
})),
|
||||
}),
|
||||
execute: async ({ path, recursive }) => {
|
||||
console.log(`[TOOL - listFilesInDirectory] Listing files in directory at path: ${path}, recursive: ${recursive}`);
|
||||
const synapse = createSynapseClient();
|
||||
const files = await synapse.listFilesInDir({
|
||||
dirPath: path || "/",
|
||||
recursive: recursive || false,
|
||||
withContent: false,
|
||||
additionalIgnorePatterns: [],
|
||||
})
|
||||
|
||||
console.log(`[TOOL - listFilesInDirectory] Found ${files.length} files in directory at path: ${path}`);
|
||||
|
||||
return {
|
||||
files,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const deleteFileTool = tool({
|
||||
name: "deleteFile",
|
||||
description: "Delete a file from the repository",
|
||||
inputSchema: z.object({
|
||||
path: z.string().describe("The path to the file to delete"),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
success: z.boolean().describe("Whether the file was deleted successfully"),
|
||||
}),
|
||||
execute: async ({ path }) => {
|
||||
const synapse = createSynapseClient();
|
||||
await synapse.deleteFile({ filepath: path });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const moveFileTool = tool({
|
||||
name: "moveFile",
|
||||
description: "Move a file from one path to another. Also useful when renaming. If the directory does not exist, it will be created.",
|
||||
inputSchema: z.object({
|
||||
path: z.string().describe("The path to the file to move"),
|
||||
newPath: z.string().describe("The new path to the file"),
|
||||
intent: z.enum(["move", "rename"]).describe("The intent of the move operation. This will help render the appropriate UI to the user."),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
success: z.boolean().describe("Whether the file was moved successfully"),
|
||||
}),
|
||||
execute: async ({ path, newPath }) => {
|
||||
const synapse = createSynapseClient();
|
||||
await synapse.updateFilePath({ filepath: path, newPath });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const fileTools = {
|
||||
readFileTool,
|
||||
// writeFilesTool,
|
||||
writeFileTool,
|
||||
listFilesInDirectoryTool,
|
||||
deleteFileTool,
|
||||
moveFileTool,
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import z from "zod";
|
||||
import { tool } from "ai";
|
||||
|
||||
export const describeThoughtTool = tool({
|
||||
name: "describeThought",
|
||||
description: "You use this tool to describe your thoughts in between steps, instead of outputting simple text.",
|
||||
inputSchema: z.object({
|
||||
thought: z.string().describe("The thought you want to describe. E.g. 'I will read all files in the repository to better understand the structure of the project.'"),
|
||||
}),
|
||||
execute: async ({ thought }) => {
|
||||
console.log("[thought] Thought recorded", thought);
|
||||
return "Thought recorded";
|
||||
},
|
||||
})
|
||||
|
||||
export const respondToUser = tool({
|
||||
name: "summary",
|
||||
description: "You use this tool to summarize what you did to the user after you are done.",
|
||||
inputSchema: z.object({
|
||||
summary: z.string().describe("The summary of what you did to the user after you are done. This supports markdown format. Keep it concise, 3-4 sentences max. No code blocks in the markdown."),
|
||||
}),
|
||||
execute: async ({ summary }) => {
|
||||
console.log("[summary] Summary recorded", summary);
|
||||
return "Summary recorded";
|
||||
},
|
||||
})
|
||||
|
||||
export const reasoningTools = {
|
||||
describeThoughtTool,
|
||||
respondToUser,
|
||||
}
|
||||
@@ -1,4 +1,2 @@
|
||||
export const OVERRIDE_BASE_DIR = `./tmp/workspace/artifact/${process.env.ARTIFACT_ID}`;
|
||||
export const ANTHROPIC_MAX_CACHE_CONTROL_BLOCKS = 4; // Anthropic only allows 4 cache control blocks
|
||||
export const IMAGINE_JWT
|
||||
= "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2ODc2ZjJlOTAwMDczNzExZjljMiIsInNlc3Npb25JZCI6IjY4NzZmMmU5NTJlZGZjODkxMWViIiwiZXhwIjoxNzUyNjM5MzAzfQ._bezYFaxf5vj_pkhEqAhR9Dy2Dzmv2iV_kVMWr-UKWE";
|
||||
@@ -15,9 +15,6 @@ export class GitRepositoryUtils {
|
||||
withContent: true,
|
||||
additionalIgnorePatterns: [],
|
||||
});
|
||||
|
||||
console.log("files", files);
|
||||
|
||||
return files.map((file) => ({
|
||||
path: file.path,
|
||||
content: file.content!,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ImagineUIMessage } from '@/shared-types';
|
||||
import { AppwriteException } from '@appwrite.io/console';
|
||||
import type { Client, Payload } from '@appwrite.io/console';
|
||||
import { UIMessage } from 'ai';
|
||||
|
||||
export class Imagine {
|
||||
client: Client;
|
||||
@@ -234,7 +234,7 @@ and all associated resources will be removed.
|
||||
artifactId: string,
|
||||
conversationId: string,
|
||||
name?: string,
|
||||
messages?: UIMessage[]
|
||||
messages?: ImagineUIMessage[]
|
||||
): Promise<Conversation> {
|
||||
if (typeof artifactId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "artifactId"');
|
||||
@@ -569,9 +569,7 @@ export type Conversation = {
|
||||
/**
|
||||
* Messages (UI)
|
||||
*/
|
||||
messages: {
|
||||
messages: UIMessage[];
|
||||
};
|
||||
messages: ImagineUIMessage[];
|
||||
};
|
||||
/**
|
||||
* ConversationsMessage
|
||||
|
||||
@@ -6,6 +6,7 @@ dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
||||
import { serve } from '@hono/node-server';
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { handleChatRequest } from '@/handlers/chat/route';
|
||||
import { getConversation, getConversations } from './handlers/conversation';
|
||||
@@ -14,6 +15,13 @@ const app = new Hono();
|
||||
|
||||
// Middleware
|
||||
app.use('*', cors());
|
||||
app.onError((err, c) => {
|
||||
if (err instanceof HTTPException) {
|
||||
return err.getResponse();
|
||||
}
|
||||
console.error(err);
|
||||
return new Response('Something went wrong', { status: 500 });
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.post("/api/chat", handleChatRequest);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { checkpointUIDataPartSchema } from '@/lib/ai/custom-parts/checkpoint';
|
||||
import { thinkingUIDataPartSchema } from '@/lib/ai/custom-parts/thinking';
|
||||
import { fileTools } from '@/lib/ai/mastra/tools/file-tools';
|
||||
import { InferUIDataParts, InferUITool, ToolUIPart, UIMessage } from 'ai';
|
||||
|
||||
type ImagineTools = {
|
||||
readFile: InferUITool<typeof fileTools.readFileTool>;
|
||||
writeFile: InferUITool<typeof fileTools.writeFileTool>;
|
||||
listFilesInDirectory: InferUITool<typeof fileTools.listFilesInDirectoryTool>;
|
||||
deleteFile: InferUITool<typeof fileTools.deleteFileTool>;
|
||||
moveFile: InferUITool<typeof fileTools.moveFileTool>;
|
||||
};
|
||||
|
||||
export type ImagineUIToolParts = ToolUIPart<ImagineTools>;
|
||||
|
||||
export type ImagineUIDataParts = InferUIDataParts<{
|
||||
checkpoint: typeof checkpointUIDataPartSchema;
|
||||
thinking: typeof thinkingUIDataPartSchema;
|
||||
}>;
|
||||
|
||||
export type ImagineUIMessage = UIMessage<never, ImagineUIDataParts, ImagineTools>;
|
||||
@@ -6,7 +6,6 @@
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
@@ -14,7 +13,8 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [],
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
"dev": "vite dev",
|
||||
"build": "node build.js",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i --force",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
@@ -81,7 +81,7 @@
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"sass": "^1.86.0",
|
||||
"svelte": "^5.25.3",
|
||||
"svelte": "^5.36.3",
|
||||
"svelte-check": "^4.1.5",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.2",
|
||||
|
||||
Generated
+85
-85
@@ -10,7 +10,7 @@ importers:
|
||||
dependencies:
|
||||
'@ai-sdk/svelte':
|
||||
specifier: 3.0.0-beta.19
|
||||
version: 3.0.0-beta.19(svelte@5.25.3)(zod@3.25.67)
|
||||
version: 3.0.0-beta.19(svelte@5.36.3)(zod@3.25.67)
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564
|
||||
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564
|
||||
@@ -19,19 +19,19 @@ importers:
|
||||
version: 0.25.0
|
||||
'@appwrite.io/pink-icons-svelte':
|
||||
specifier: ^2.0.0-RC.1
|
||||
version: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-icons-svelte@12707b9(svelte@5.25.3)
|
||||
version: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-icons-svelte@12707b9(svelte@5.36.3)
|
||||
'@appwrite.io/pink-legacy':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
'@appwrite.io/pink-svelte':
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778(svelte@5.25.3)
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778(svelte@5.36.3)
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
'@sentry/sveltekit':
|
||||
specifier: ^8.38.0
|
||||
version: 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
version: 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@stripe/stripe-js':
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
@@ -79,10 +79,10 @@ importers:
|
||||
version: 2.0.1
|
||||
svelte-confetti:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0(svelte@5.25.3)
|
||||
version: 1.4.0(svelte@5.36.3)
|
||||
svelte-exmarkdown:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.1(svelte@5.25.3)
|
||||
version: 5.0.1(svelte@5.36.3)
|
||||
tippy.js:
|
||||
specifier: ^6.3.7
|
||||
version: 6.3.7
|
||||
@@ -95,22 +95,22 @@ importers:
|
||||
version: 9.24.0
|
||||
'@melt-ui/pp':
|
||||
specifier: ^0.3.2
|
||||
version: 0.3.2(@melt-ui/svelte@0.86.5(svelte@5.25.3))(svelte@5.25.3)
|
||||
version: 0.3.2(@melt-ui/svelte@0.86.5(svelte@5.36.3))(svelte@5.36.3)
|
||||
'@melt-ui/svelte':
|
||||
specifier: ^0.86.5
|
||||
version: 0.86.5(svelte@5.25.3)
|
||||
version: 0.86.5(svelte@5.36.3)
|
||||
'@playwright/test':
|
||||
specifier: ^1.51.1
|
||||
version: 1.51.1
|
||||
'@sveltejs/adapter-static':
|
||||
specifier: ^3.0.8
|
||||
version: 3.0.8(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))
|
||||
version: 3.0.8(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))
|
||||
'@sveltejs/kit':
|
||||
specifier: ^2.20.2
|
||||
version: 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
version: 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
version: 5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@testing-library/dom':
|
||||
specifier: ^10.4.0
|
||||
version: 10.4.0
|
||||
@@ -119,7 +119,7 @@ importers:
|
||||
version: 6.6.3
|
||||
'@testing-library/svelte':
|
||||
specifier: ^5.2.4
|
||||
version: 5.2.7(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))(vitest@3.0.9)
|
||||
version: 5.2.7(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))(vitest@3.0.9)
|
||||
'@testing-library/user-event':
|
||||
specifier: ^14.6.1
|
||||
version: 14.6.1(@testing-library/dom@10.4.0)
|
||||
@@ -158,7 +158,7 @@ importers:
|
||||
version: 10.1.1(eslint@9.23.0)
|
||||
eslint-plugin-svelte:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3(eslint@9.23.0)(svelte@5.25.3)
|
||||
version: 3.3.3(eslint@9.23.0)(svelte@5.36.3)
|
||||
globals:
|
||||
specifier: ^16.0.0
|
||||
version: 16.0.0
|
||||
@@ -176,7 +176,7 @@ importers:
|
||||
version: 3.5.3
|
||||
prettier-plugin-svelte:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3(prettier@3.5.3)(svelte@5.25.3)
|
||||
version: 3.3.3(prettier@3.5.3)(svelte@5.36.3)
|
||||
rehype-highlight:
|
||||
specifier: ^7.0.2
|
||||
version: 7.0.2
|
||||
@@ -184,14 +184,14 @@ importers:
|
||||
specifier: ^1.86.0
|
||||
version: 1.86.0
|
||||
svelte:
|
||||
specifier: ^5.25.3
|
||||
version: 5.25.3
|
||||
specifier: ^5.36.3
|
||||
version: 5.36.3
|
||||
svelte-check:
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.5(picomatch@4.0.2)(svelte@5.25.3)(typescript@5.8.2)
|
||||
version: 4.1.5(picomatch@4.0.2)(svelte@5.36.3)(typescript@5.8.2)
|
||||
svelte-preprocess:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3(@babel/core@7.26.10)(postcss-load-config@3.1.4(postcss@8.5.3))(postcss@8.5.3)(sass@1.86.0)(svelte@5.25.3)(typescript@5.8.2)
|
||||
version: 6.0.3(@babel/core@7.26.10)(postcss-load-config@3.1.4(postcss@8.5.3))(postcss@8.5.3)(sass@1.86.0)(svelte@5.36.3)(typescript@5.8.2)
|
||||
svelte-sequential-preprocessor:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
@@ -2121,8 +2121,8 @@ packages:
|
||||
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
esrap@1.4.5:
|
||||
resolution: {integrity: sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==}
|
||||
esrap@2.1.0:
|
||||
resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==}
|
||||
|
||||
esrecurse@4.3.0:
|
||||
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
|
||||
@@ -3312,8 +3312,8 @@ packages:
|
||||
resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
svelte@5.25.3:
|
||||
resolution: {integrity: sha512-J9rcZ/xVJonAoESqVGHHZhrNdVbrCfkdB41BP6eiwHMoFShD9it3yZXApVYMHdGfCshBsZCKsajwJeBbS/M1zg==}
|
||||
svelte@5.36.3:
|
||||
resolution: {integrity: sha512-0NsgzxP60BXZ0SDAJzFb4CEIuHK5Qtubx69bVSDSORvS053+IZqA+wKK1b742US4eCuKo1KJOu7sAB+vRpK9JQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
symbol-tree@3.2.4:
|
||||
@@ -3701,11 +3701,11 @@ snapshots:
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@ai-sdk/svelte@3.0.0-beta.19(svelte@5.25.3)(zod@3.25.67)':
|
||||
'@ai-sdk/svelte@3.0.0-beta.19(svelte@5.36.3)(zod@3.25.67)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider-utils': 3.0.0-beta.3(zod@3.25.67)
|
||||
ai: 5.0.0-beta.19(zod@3.25.67)
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
optionalDependencies:
|
||||
zod: 3.25.67
|
||||
|
||||
@@ -3750,13 +3750,13 @@ snapshots:
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.36.3)':
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-icons-svelte@12707b9(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-icons-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-icons-svelte@12707b9(svelte@5.36.3)':
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
'@appwrite.io/pink-icons@0.25.0': {}
|
||||
|
||||
@@ -3767,20 +3767,20 @@ snapshots:
|
||||
'@appwrite.io/pink-icons': 1.0.0
|
||||
the-new-css-reset: 1.11.3
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778(svelte@5.36.3)':
|
||||
dependencies:
|
||||
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.25.3)
|
||||
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.36.3)
|
||||
'@floating-ui/dom': 1.6.13
|
||||
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.25.3))(svelte@5.25.3)
|
||||
'@melt-ui/svelte': 0.86.6(svelte@5.25.3)
|
||||
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.36.3))(svelte@5.36.3)
|
||||
'@melt-ui/svelte': 0.86.6(svelte@5.36.3)
|
||||
ansicolor: 2.0.3
|
||||
d3: 7.9.0
|
||||
fuse.js: 7.1.0
|
||||
pretty-bytes: 6.1.1
|
||||
shiki: 1.29.2
|
||||
svelte: 5.25.3
|
||||
svelte-motion: 0.12.2(svelte@5.25.3)
|
||||
svelte-sonner: 0.3.28(svelte@5.25.3)
|
||||
svelte: 5.36.3
|
||||
svelte-motion: 0.12.2(svelte@5.36.3)
|
||||
svelte-sonner: 0.3.28(svelte@5.36.3)
|
||||
|
||||
'@asamuzakjp/css-color@3.1.1':
|
||||
dependencies:
|
||||
@@ -4082,21 +4082,21 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@melt-ui/pp@0.3.2(@melt-ui/svelte@0.86.5(svelte@5.25.3))(svelte@5.25.3)':
|
||||
'@melt-ui/pp@0.3.2(@melt-ui/svelte@0.86.5(svelte@5.36.3))(svelte@5.36.3)':
|
||||
dependencies:
|
||||
'@melt-ui/svelte': 0.86.5(svelte@5.25.3)
|
||||
'@melt-ui/svelte': 0.86.5(svelte@5.36.3)
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
'@melt-ui/pp@0.3.2(@melt-ui/svelte@0.86.6(svelte@5.25.3))(svelte@5.25.3)':
|
||||
'@melt-ui/pp@0.3.2(@melt-ui/svelte@0.86.6(svelte@5.36.3))(svelte@5.36.3)':
|
||||
dependencies:
|
||||
'@melt-ui/svelte': 0.86.6(svelte@5.25.3)
|
||||
'@melt-ui/svelte': 0.86.6(svelte@5.36.3)
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
'@melt-ui/svelte@0.86.5(svelte@5.25.3)':
|
||||
'@melt-ui/svelte@0.86.5(svelte@5.36.3)':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.6.9
|
||||
'@floating-ui/dom': 1.6.13
|
||||
@@ -4104,9 +4104,9 @@ snapshots:
|
||||
dequal: 2.0.3
|
||||
focus-trap: 7.6.4
|
||||
nanoid: 5.1.5
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
'@melt-ui/svelte@0.86.6(svelte@5.25.3)':
|
||||
'@melt-ui/svelte@0.86.6(svelte@5.36.3)':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.6.9
|
||||
'@floating-ui/dom': 1.6.13
|
||||
@@ -4114,7 +4114,7 @@ snapshots:
|
||||
dequal: 2.0.3
|
||||
focus-trap: 7.6.4
|
||||
nanoid: 5.1.5
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
@@ -4696,21 +4696,21 @@ snapshots:
|
||||
'@opentelemetry/semantic-conventions': 1.30.0
|
||||
'@sentry/core': 8.55.0
|
||||
|
||||
'@sentry/svelte@8.55.0(svelte@5.25.3)':
|
||||
'@sentry/svelte@8.55.0(svelte@5.36.3)':
|
||||
dependencies:
|
||||
'@sentry/browser': 8.55.0
|
||||
'@sentry/core': 8.55.0
|
||||
magic-string: 0.30.7
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
'@sentry/sveltekit@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))':
|
||||
'@sentry/sveltekit@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))':
|
||||
dependencies:
|
||||
'@sentry/core': 8.55.0
|
||||
'@sentry/node': 8.55.0
|
||||
'@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)
|
||||
'@sentry/svelte': 8.55.0(svelte@5.25.3)
|
||||
'@sentry/svelte': 8.55.0(svelte@5.36.3)
|
||||
'@sentry/vite-plugin': 2.22.6
|
||||
'@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
magic-string: 0.30.7
|
||||
magicast: 0.2.8
|
||||
sorcery: 1.0.0
|
||||
@@ -4778,13 +4778,13 @@ snapshots:
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
|
||||
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))':
|
||||
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))':
|
||||
dependencies:
|
||||
'@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
|
||||
'@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))':
|
||||
'@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@types/cookie': 0.6.0
|
||||
cookie: 0.6.0
|
||||
devalue: 5.1.1
|
||||
@@ -4796,26 +4796,26 @@ snapshots:
|
||||
sade: 1.8.1
|
||||
set-cookie-parser: 2.7.1
|
||||
sirv: 3.0.1
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
vite: 6.2.3(@types/node@22.13.14)(sass@1.86.0)
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))':
|
||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
debug: 4.4.0
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
vite: 6.2.3(@types/node@22.13.14)(sass@1.86.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))':
|
||||
'@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
debug: 4.4.0
|
||||
deepmerge: 4.3.1
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.17
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
vite: 6.2.3(@types/node@22.13.14)(sass@1.86.0)
|
||||
vitefu: 1.0.6(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
transitivePeerDependencies:
|
||||
@@ -4846,10 +4846,10 @@ snapshots:
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
|
||||
'@testing-library/svelte@5.2.7(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))(vitest@3.0.9)':
|
||||
'@testing-library/svelte@5.2.7(svelte@5.36.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))(vitest@3.0.9)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.0
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
optionalDependencies:
|
||||
vite: 6.2.3(@types/node@22.13.14)(sass@1.86.0)
|
||||
vitest: 3.0.9(@types/debug@4.1.12)(@types/node@22.13.14)(@vitest/ui@3.0.9)(jsdom@26.0.0)(sass@1.86.0)
|
||||
@@ -5732,7 +5732,7 @@ snapshots:
|
||||
dependencies:
|
||||
eslint: 9.23.0
|
||||
|
||||
eslint-plugin-svelte@3.3.3(eslint@9.23.0)(svelte@5.25.3):
|
||||
eslint-plugin-svelte@3.3.3(eslint@9.23.0)(svelte@5.36.3):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0)
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
@@ -5744,9 +5744,9 @@ snapshots:
|
||||
postcss-load-config: 3.1.4(postcss@8.5.3)
|
||||
postcss-safe-parser: 7.0.1(postcss@8.5.3)
|
||||
semver: 7.7.1
|
||||
svelte-eslint-parser: 1.1.1(svelte@5.25.3)
|
||||
svelte-eslint-parser: 1.1.1(svelte@5.36.3)
|
||||
optionalDependencies:
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
@@ -5813,7 +5813,7 @@ snapshots:
|
||||
dependencies:
|
||||
estraverse: 5.3.0
|
||||
|
||||
esrap@1.4.5:
|
||||
esrap@2.1.0:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
@@ -6799,10 +6799,10 @@ snapshots:
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.25.3):
|
||||
prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.36.3):
|
||||
dependencies:
|
||||
prettier: 3.5.3
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
prettier@3.5.3: {}
|
||||
|
||||
@@ -7116,23 +7116,23 @@ snapshots:
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
svelte-check@4.1.5(picomatch@4.0.2)(svelte@5.25.3)(typescript@5.8.2):
|
||||
svelte-check@4.1.5(picomatch@4.0.2)(svelte@5.36.3)(typescript@5.8.2):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
chokidar: 4.0.3
|
||||
fdir: 6.4.3(picomatch@4.0.2)
|
||||
picocolors: 1.1.1
|
||||
sade: 1.8.1
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
typescript: 5.8.2
|
||||
transitivePeerDependencies:
|
||||
- picomatch
|
||||
|
||||
svelte-confetti@1.4.0(svelte@5.25.3):
|
||||
svelte-confetti@1.4.0(svelte@5.36.3):
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
svelte-eslint-parser@1.1.1(svelte@5.25.3):
|
||||
svelte-eslint-parser@1.1.1(svelte@5.36.3):
|
||||
dependencies:
|
||||
eslint-scope: 8.3.0
|
||||
eslint-visitor-keys: 4.2.0
|
||||
@@ -7141,30 +7141,30 @@ snapshots:
|
||||
postcss-scss: 4.0.9(postcss@8.5.3)
|
||||
postcss-selector-parser: 7.1.0
|
||||
optionalDependencies:
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
svelte-exmarkdown@5.0.1(svelte@5.25.3):
|
||||
svelte-exmarkdown@5.0.1(svelte@5.36.3):
|
||||
dependencies:
|
||||
remark-gfm: 4.0.1
|
||||
remark-parse: 11.0.0
|
||||
remark-rehype: 11.1.2
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
unified: 11.0.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
svelte-motion@0.12.2(svelte@5.25.3):
|
||||
svelte-motion@0.12.2(svelte@5.36.3):
|
||||
dependencies:
|
||||
'@types/react': 18.3.22
|
||||
framesync: 6.1.2
|
||||
popmotion: 11.0.5
|
||||
style-value-types: 5.1.2
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
tslib: 2.8.1
|
||||
|
||||
svelte-preprocess@6.0.3(@babel/core@7.26.10)(postcss-load-config@3.1.4(postcss@8.5.3))(postcss@8.5.3)(sass@1.86.0)(svelte@5.25.3)(typescript@5.8.2):
|
||||
svelte-preprocess@6.0.3(@babel/core@7.26.10)(postcss-load-config@3.1.4(postcss@8.5.3))(postcss@8.5.3)(sass@1.86.0)(svelte@5.36.3)(typescript@5.8.2):
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.26.10
|
||||
postcss: 8.5.3
|
||||
@@ -7177,9 +7177,9 @@ snapshots:
|
||||
svelte: 4.2.19
|
||||
tslib: 2.7.0
|
||||
|
||||
svelte-sonner@0.3.28(svelte@5.25.3):
|
||||
svelte-sonner@0.3.28(svelte@5.36.3):
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
svelte: 5.36.3
|
||||
|
||||
svelte@4.2.19:
|
||||
dependencies:
|
||||
@@ -7198,7 +7198,7 @@ snapshots:
|
||||
magic-string: 0.30.17
|
||||
periscopic: 3.1.0
|
||||
|
||||
svelte@5.25.3:
|
||||
svelte@5.36.3:
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
@@ -7209,7 +7209,7 @@ snapshots:
|
||||
axobject-query: 4.1.0
|
||||
clsx: 2.1.1
|
||||
esm-env: 1.2.2
|
||||
esrap: 1.4.5
|
||||
esrap: 2.1.0
|
||||
is-reference: 3.0.3
|
||||
locate-character: 3.0.0
|
||||
magic-string: 0.30.17
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { VARS } from "../system";
|
||||
|
||||
export class AIService {
|
||||
private baseUrl = `${VARS.AI_SERVICE_BASE_URL}/api`;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import Conversation from './conversation.svelte';
|
||||
import type { StreamParser } from './parser';
|
||||
import type { EventHandler } from 'svelte/elements';
|
||||
import { page } from '$app/state';
|
||||
import { studio } from '../studio.svelte';
|
||||
@@ -18,44 +17,32 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { conversation, showChat } from '$lib/stores/chat';
|
||||
import { DefaultChatTransport } from 'ai';
|
||||
import type { ImagineUIMessage } from '$shared-types';
|
||||
import { VARS } from '../../../system';
|
||||
|
||||
type Props = {
|
||||
width: number;
|
||||
hasSubNavigation: boolean;
|
||||
parser: StreamParser;
|
||||
};
|
||||
let { width, hasSubNavigation, parser }: Props = $props();
|
||||
let { width, hasSubNavigation }: Props = $props();
|
||||
|
||||
let minimizeChat = $state($isSmallViewport ? true : false);
|
||||
let message = $state('');
|
||||
let firstByteReceived = $state(true);
|
||||
|
||||
let chatTextareaRef: HTMLTextAreaElement | null = $state(null);
|
||||
|
||||
const chatBody: {
|
||||
token: string | null;
|
||||
projectId: string;
|
||||
artifactId: string | null;
|
||||
conversationId: string | null;
|
||||
} = {
|
||||
token: null,
|
||||
projectId: page.params.project,
|
||||
artifactId: null,
|
||||
conversationId: null
|
||||
};
|
||||
|
||||
const chat = new Chat({
|
||||
const chat = new Chat<ImagineUIMessage>({
|
||||
id: $conversation.data?.$id,
|
||||
transport: new DefaultChatTransport({
|
||||
api: 'http://localhost:8889/api/chat',
|
||||
// body: chatBody
|
||||
api: `${VARS.AI_SERVICE_BASE_URL}/api/chat`,
|
||||
})
|
||||
});
|
||||
// const chat = new Chat({
|
||||
// transport: new DefaultChatTransport({
|
||||
// api: 'http://localhost:8889/api/chat',
|
||||
// body: chatBody
|
||||
// })
|
||||
// });
|
||||
|
||||
$effect(() => {
|
||||
if ($conversation.data?.messages) {
|
||||
chat.messages = $conversation.data.messages;
|
||||
}
|
||||
});
|
||||
|
||||
const onkeydown: EventHandler<KeyboardEvent, HTMLTextAreaElement> = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
@@ -77,116 +64,34 @@
|
||||
if (chatTextareaRef && !$isSmallViewport) {
|
||||
chatTextareaRef.focus();
|
||||
}
|
||||
|
||||
refreshToken(); // TODO: remove
|
||||
});
|
||||
|
||||
async function refreshToken() {
|
||||
// const token = await sdk.forConsoleIn(page.params.region).account.createJWT();
|
||||
const token = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.account.createJWT();
|
||||
chatBody.token = token.jwt;
|
||||
return token;
|
||||
}
|
||||
|
||||
// let controller: AbortController;
|
||||
|
||||
async function createMessage() {
|
||||
await refreshToken();
|
||||
const token = await refreshToken();
|
||||
|
||||
console.log('$conversation', $conversation);
|
||||
chatBody.artifactId = $conversation?.data?.artifactId ?? null;
|
||||
chatBody.conversationId = $conversation?.data?.$id ?? null;
|
||||
|
||||
const group = Symbol();
|
||||
if (message.startsWith('!')) {
|
||||
const [type, ...segments] = message.split(' ');
|
||||
const content = segments.join(' ');
|
||||
message = '';
|
||||
const value = `<action type="${type.replace('!', '')}">${content}</action>`;
|
||||
parser.chunk(value, 'system', { group });
|
||||
parser.end();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('artifactId', $conversation?.data?.artifactId);
|
||||
|
||||
// chatIns
|
||||
chat.sendMessage({
|
||||
role: "user",
|
||||
text: message,
|
||||
}, {
|
||||
body: {
|
||||
projectId: page.params.project,
|
||||
artifactId: page.params.artifact,
|
||||
},
|
||||
headers: {
|
||||
"X-Imagine-Token": token.jwt
|
||||
}
|
||||
});
|
||||
|
||||
message = '';
|
||||
|
||||
// const initialMessage = message;
|
||||
|
||||
// try {
|
||||
// parser.chunk(message, 'user');
|
||||
// firstByteReceived = false;
|
||||
// message = '';
|
||||
// controller = new AbortController();
|
||||
// studio.streaming = true;
|
||||
|
||||
// const url = `${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}/imagine/artifacts/${$conversation.data.artifactId}/conversations/${$conversation.data.$id}/messages`;
|
||||
// const response = await fetch(
|
||||
// url,
|
||||
// {
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'X-Appwrite-Project': page.params.project,
|
||||
// 'X-Appwrite-Mode': 'admin'
|
||||
// },
|
||||
// credentials: 'include',
|
||||
// body: JSON.stringify({
|
||||
// content: initialMessage,
|
||||
// type: 'text'
|
||||
// }),
|
||||
// signal: controller.signal
|
||||
// }
|
||||
// )
|
||||
|
||||
// const response = await fetch(
|
||||
// url,
|
||||
// {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'X-Appwrite-Project': page.params.project,
|
||||
// 'X-Appwrite-Mode': 'admin'
|
||||
// },
|
||||
// credentials: 'include',
|
||||
// body: JSON.stringify({
|
||||
// content: initialMessage,
|
||||
// type: 'text'
|
||||
// }),
|
||||
// signal: controller.signal
|
||||
// }
|
||||
// );
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`${response.status} Error: ${await response.text()}`);
|
||||
// }
|
||||
|
||||
// invalidate(Dependencies.ARTIFACTS);
|
||||
|
||||
// const reader = response.body.getReader();
|
||||
// const decoder = new TextDecoder();
|
||||
|
||||
// let chunk = await reader.read();
|
||||
// while (!chunk.done) {
|
||||
// if (!firstByteReceived) firstByteReceived = true;
|
||||
// parser.chunk(decoder.decode(chunk.value), 'system', {
|
||||
// group
|
||||
// });
|
||||
// chunk = await reader.read();
|
||||
// }
|
||||
// parser.end();
|
||||
// } catch (error) {
|
||||
// if (error instanceof Error) {
|
||||
// parser.chunk(error.message, 'error');
|
||||
// }
|
||||
// message = initialMessage;
|
||||
// } finally {
|
||||
// firstByteReceived = true;
|
||||
// studio.streaming = false;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
let tokens = $state(2);
|
||||
@@ -226,7 +131,7 @@
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
<Conversation {chat} {parser} thinking={!firstByteReceived} />
|
||||
<Conversation {chat} />
|
||||
|
||||
{#if tokens < 2}
|
||||
<UpgradePrompt>
|
||||
@@ -242,7 +147,6 @@
|
||||
{/if}
|
||||
</UpgradePrompt>
|
||||
{/if}
|
||||
<Conversation {chat} {parser} thinking={chat.status === 'submitted'} />
|
||||
{/if}
|
||||
<form {onsubmit} class="input" class:minimize-chat={minimizeChat}>
|
||||
<Layout.Stack
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
<script lang="ts">
|
||||
import 'highlight.js/styles/atom-one-light.css';
|
||||
import { StreamParser, type ParsedItem } from './parser';
|
||||
import { Icon, Layout, ShimmerText, Tag, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Icon, Spinner, Layout, Tag, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconArrowDown } from '@appwrite.io/pink-icons-svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import type { UIEventHandler, WheelEventHandler } from 'svelte/elements';
|
||||
import Message from './message.svelte';
|
||||
import { studio } from '../studio.svelte';
|
||||
import { Chat } from '@ai-sdk/svelte';
|
||||
import type { ImagineUIMessage } from '$shared-types';
|
||||
|
||||
type Props = {
|
||||
parser: StreamParser;
|
||||
autoscroll?: boolean;
|
||||
thinking?: boolean;
|
||||
chat: Chat;
|
||||
chat: Chat<ImagineUIMessage>;
|
||||
};
|
||||
let { autoscroll = $bindable(true), thinking = false, chat }: Props = $props();
|
||||
|
||||
// const chunks = writable<ParsedItem[]>([
|
||||
// {
|
||||
// id: Symbol(),
|
||||
// from: 'user',
|
||||
// group: null,
|
||||
// content: "Test",
|
||||
// complete: true,
|
||||
// }
|
||||
// ]);
|
||||
let { autoscroll = $bindable(true), chat }: Props = $props();
|
||||
|
||||
function scrollToBottom(smooth: boolean = true) {
|
||||
document
|
||||
@@ -33,7 +21,7 @@
|
||||
.scrollIntoView({ behavior: smooth ? 'smooth' : 'instant' });
|
||||
}
|
||||
|
||||
const onwheel: WheelEventHandler<HTMLDivElement> = (event) => {
|
||||
const onwheel: WheelEventHandler<HTMLDivElement> = () => {
|
||||
if (studio.streaming) autoscroll = false;
|
||||
};
|
||||
|
||||
@@ -51,35 +39,22 @@
|
||||
autoscroll = false;
|
||||
}
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
console.log("DATA", chat.data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div class="overflow" {onwheel} {onscroll}>
|
||||
<section>
|
||||
<!-- {#each $chunks as message (message.id)}
|
||||
{#each chat.messages as message (message.id)}
|
||||
<Message {message} />
|
||||
{/each} -->
|
||||
{#each chat.messages as message, messageIndex (messageIndex)}
|
||||
<Message
|
||||
message={{
|
||||
id: Symbol(message.id),
|
||||
from: (message.role === 'user' ? 'user' : 'assistant') as any,
|
||||
group: null,
|
||||
content: message.content,
|
||||
complete: true
|
||||
}} />
|
||||
<!-- <pre style:border="1px solid black" style:padding="1rem">{JSON.stringify(message, null, 2)}</pre> -->
|
||||
{/each}
|
||||
|
||||
{#if thinking}
|
||||
<Typography.Code size="s">
|
||||
<ShimmerText>thinking...</ShimmerText>
|
||||
</Typography.Code>
|
||||
{#if chat.status === "submitted" || chat.status === "streaming"}
|
||||
<Icon size="m" icon={Spinner} />
|
||||
{/if}
|
||||
|
||||
{#if chat.status === "error"}
|
||||
<span style:color="var(--fgcolor-error)">{chat.error?.message}</span>
|
||||
{/if}
|
||||
|
||||
<div id="bottom"></div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<script lang="ts">
|
||||
import 'highlight.js/styles/atom-one-light.css';
|
||||
import type { ParsedItem } from './parser';
|
||||
import { Card, Layout, ShimmerText, Spinner, Typography, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconCheckCircle } from '@appwrite.io/pink-icons-svelte';
|
||||
import Markdown, { type Plugin } from 'svelte-exmarkdown';
|
||||
import Li from './(markdown)/Li.svelte';
|
||||
import H1 from './(markdown)/H1.svelte';
|
||||
import H2 from './(markdown)/H2.svelte';
|
||||
import A from './(markdown)/A.svelte';
|
||||
import Strong from './(markdown)/Strong.svelte';
|
||||
import Em from './(markdown)/Em.svelte';
|
||||
import Ul from './(markdown)/Ul.svelte';
|
||||
import Ol from './(markdown)/Ol.svelte';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import { queue } from './queue.svelte';
|
||||
import { studio } from '../studio.svelte';
|
||||
|
||||
type Props = {
|
||||
message: ParsedItem;
|
||||
};
|
||||
let { message }: Props = $props();
|
||||
const tickets = $state([message.group]);
|
||||
|
||||
async function processQueueItem() {
|
||||
if (tickets.length === 0 || !message.group) return;
|
||||
|
||||
const ticket = tickets.pop();
|
||||
if (!ticket) return;
|
||||
|
||||
const list = queue.lists[message.group];
|
||||
if (!list || !list.some((item) => item.status === 'waiting')) {
|
||||
tickets.push(ticket);
|
||||
return;
|
||||
}
|
||||
|
||||
const item = queue.dequeue(message.group);
|
||||
if (!item) {
|
||||
tickets.push(ticket);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const action = item.data;
|
||||
switch (action.type) {
|
||||
case 'file':
|
||||
await studio.synapse.dispatch('fs', {
|
||||
operation: 'updateFile',
|
||||
params: {
|
||||
filepath: action.src,
|
||||
content: action.content
|
||||
}
|
||||
});
|
||||
studio.filesystem.add(action.src);
|
||||
break;
|
||||
case 'shell':
|
||||
await studio.synapse.dispatch(
|
||||
'terminal',
|
||||
{
|
||||
operation: 'createCommand',
|
||||
params: {
|
||||
command: action.content + '\n'
|
||||
}
|
||||
},
|
||||
{
|
||||
noReturn: action.content.endsWith('run dev'),
|
||||
timeout: 30_000
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.group) {
|
||||
const status = action.complete ? 'done' : 'waiting';
|
||||
queue.update(message.group, item.id, { status });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing queue item:', error);
|
||||
|
||||
if (message.group) {
|
||||
queue.update(message.group, item.id, { status: 'failed' });
|
||||
}
|
||||
} finally {
|
||||
tickets.push(ticket);
|
||||
|
||||
setTimeout(checkForMoreItems, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function checkForMoreItems() {
|
||||
if (!message.group) return;
|
||||
|
||||
const list = queue.lists[message.group];
|
||||
if (tickets.length > 0 && list && list.some((item) => item.status === 'waiting')) {
|
||||
processQueueItem();
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!('type' in message)) return;
|
||||
if (message.type !== 'actions') return;
|
||||
if (!message.group) return;
|
||||
|
||||
const list = queue.lists[message.group];
|
||||
if (!list) return;
|
||||
|
||||
if (tickets.length > 0 && list.some((item) => item.status === 'waiting')) {
|
||||
processQueueItem();
|
||||
}
|
||||
});
|
||||
|
||||
const plugins: Plugin[] = [
|
||||
{
|
||||
rehypePlugin: rehypeHighlight,
|
||||
renderer: {
|
||||
h1: H1,
|
||||
h2: H2,
|
||||
a: A,
|
||||
strong: Strong,
|
||||
em: Em,
|
||||
ul: Ul,
|
||||
ol: Ol,
|
||||
li: Li
|
||||
}
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
{#if 'type' in message}
|
||||
{#key message.content}
|
||||
{#if message.type === 'actions'}
|
||||
<Card.Base variant="primary" padding="none">
|
||||
<Card.Base variant="secondary" padding="xs" class="title">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
<Typography.Text variant="m-500">Version 0</Typography.Text>
|
||||
{#if !message.complete}
|
||||
<Spinner />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
<div class="actions">
|
||||
<div class="grid-line"></div>
|
||||
{#each message.actions as action}
|
||||
{@const actionInQueue = queue.lists[action.group]?.find(
|
||||
(n) => n.data.id === action.id
|
||||
)}
|
||||
{#if actionInQueue}
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
{#if action.type === 'file'}
|
||||
<Typography.Code size="s" class="file">
|
||||
<span class="icon">
|
||||
{#if actionInQueue.status === 'done'}
|
||||
<Icon
|
||||
size="s"
|
||||
--icon-size-s="12px"
|
||||
icon={IconCheckCircle} />
|
||||
{:else}
|
||||
<Spinner size="s" --icon-size-s="12px" />
|
||||
{/if}
|
||||
</span>
|
||||
{action.src}</Typography.Code>
|
||||
<Typography.Code size="s">
|
||||
{#if actionInQueue.status === 'waiting'}
|
||||
Waiting
|
||||
{:else if actionInQueue.status === 'processing'}
|
||||
<ShimmerText>Generating</ShimmerText>
|
||||
{:else if actionInQueue.status === 'done'}
|
||||
Generated
|
||||
{:else if actionInQueue.status === 'failed'}
|
||||
<span style:color="var(--fgcolor-error)">Failed</span>
|
||||
{/if}
|
||||
</Typography.Code>
|
||||
{:else if action.type === 'shell'}
|
||||
<Typography.Code class="file" size="s">
|
||||
<span class="icon">
|
||||
{#if actionInQueue.status === 'done'}
|
||||
<Icon
|
||||
size="s"
|
||||
--icon-size-s="12px"
|
||||
icon={IconCheckCircle} />
|
||||
{:else}
|
||||
<Spinner size="s" --icon-size-s="12px" />
|
||||
{/if}
|
||||
</span>
|
||||
{action.content}</Typography.Code>
|
||||
<Typography.Code size="s">
|
||||
{#if actionInQueue.status === 'waiting'}
|
||||
Waiting
|
||||
{:else if actionInQueue.status === 'processing'}
|
||||
<ShimmerText>Running</ShimmerText>
|
||||
{:else if actionInQueue.status === 'done'}
|
||||
Completed
|
||||
{:else if actionInQueue.status === 'failed'}
|
||||
<span style:color="var(--fgcolor-error)">Failed</span>
|
||||
{/if}
|
||||
</Typography.Code>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if !message.complete}
|
||||
<Typography.Code size="s">
|
||||
<ShimmerText>thinking...</ShimmerText>
|
||||
</Typography.Code>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Base>
|
||||
{/if}
|
||||
{/key}
|
||||
{:else}
|
||||
{#snippet text()}
|
||||
<Markdown md={message.content} {plugins} />
|
||||
{/snippet}
|
||||
{#if message.from === 'user'}
|
||||
<div class="message">
|
||||
{@render text()}
|
||||
</div>
|
||||
{:else if message.from === 'error'}
|
||||
<div class="message">
|
||||
{@render text()}
|
||||
</div>
|
||||
{:else}
|
||||
{@render text()}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.message {
|
||||
width: 90%;
|
||||
float: right;
|
||||
display: inline-flex;
|
||||
padding: 0.5rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
margin-inline-start: auto;
|
||||
border-radius: 0.5rem 0px 0.5rem 0.5rem;
|
||||
background: var(--bgcolor-neutral-default);
|
||||
box-shadow:
|
||||
0px 1.022px 4.089px 0px rgba(55, 59, 77, 0.1),
|
||||
0px 1.022px 4.089px -1.022px rgba(55, 59, 77, 0.1);
|
||||
}
|
||||
|
||||
:global(.title) {
|
||||
margin-top: -2px;
|
||||
margin-left: -1px;
|
||||
width: calc(100% + 2px) !important;
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: var(--space-6);
|
||||
position: relative;
|
||||
margin-left: var(--space-6);
|
||||
|
||||
:global(.file) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: -18px;
|
||||
margin-right: 4px;
|
||||
background-color: var(--bgcolor-neutral-primary);
|
||||
flex: 0 0;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-line {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 1px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
var(--border-neutral) 25%,
|
||||
var(--border-neutral) 75%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
:global(pre) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(pre code.hljs) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,5 @@
|
||||
<script lang="ts">
|
||||
import 'highlight.js/styles/atom-one-light.css';
|
||||
import type { ParsedItem } from './parser';
|
||||
import { Card, Layout, ShimmerText, Spinner, Typography, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconCheckCircle } from '@appwrite.io/pink-icons-svelte';
|
||||
import Markdown, { type Plugin } from 'svelte-exmarkdown';
|
||||
import Li from './(markdown)/Li.svelte';
|
||||
import H1 from './(markdown)/H1.svelte';
|
||||
@@ -13,101 +10,14 @@
|
||||
import Ul from './(markdown)/Ul.svelte';
|
||||
import Ol from './(markdown)/Ol.svelte';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import { queue } from './queue.svelte';
|
||||
import { studio } from '../studio.svelte';
|
||||
import type { ImagineUIMessage, ImagineUIToolParts } from '$shared-types';
|
||||
import Thinking from './thinking.svelte';
|
||||
import ToolCalls from './toolCalls.svelte';
|
||||
|
||||
type Props = {
|
||||
message: ParsedItem;
|
||||
message: ImagineUIMessage;
|
||||
};
|
||||
let { message }: Props = $props();
|
||||
const tickets = $state([message.group]);
|
||||
|
||||
async function processQueueItem() {
|
||||
if (tickets.length === 0 || !message.group) return;
|
||||
|
||||
const ticket = tickets.pop();
|
||||
if (!ticket) return;
|
||||
|
||||
const list = queue.lists[message.group];
|
||||
if (!list || !list.some((item) => item.status === 'waiting')) {
|
||||
tickets.push(ticket);
|
||||
return;
|
||||
}
|
||||
|
||||
const item = queue.dequeue(message.group);
|
||||
if (!item) {
|
||||
tickets.push(ticket);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const action = item.data;
|
||||
switch (action.type) {
|
||||
case 'file':
|
||||
await studio.synapse.dispatch('fs', {
|
||||
operation: 'updateFile',
|
||||
params: {
|
||||
filepath: action.src,
|
||||
content: action.content
|
||||
}
|
||||
});
|
||||
studio.filesystem.add(action.src);
|
||||
break;
|
||||
case 'shell':
|
||||
await studio.synapse.dispatch(
|
||||
'terminal',
|
||||
{
|
||||
operation: 'createCommand',
|
||||
params: {
|
||||
command: action.content + '\n'
|
||||
}
|
||||
},
|
||||
{
|
||||
noReturn: action.content.endsWith('run dev'),
|
||||
timeout: 30_000
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.group) {
|
||||
const status = action.complete ? 'done' : 'waiting';
|
||||
queue.update(message.group, item.id, { status });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing queue item:', error);
|
||||
|
||||
if (message.group) {
|
||||
queue.update(message.group, item.id, { status: 'failed' });
|
||||
}
|
||||
} finally {
|
||||
tickets.push(ticket);
|
||||
|
||||
setTimeout(checkForMoreItems, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function checkForMoreItems() {
|
||||
if (!message.group) return;
|
||||
|
||||
const list = queue.lists[message.group];
|
||||
if (tickets.length > 0 && list && list.some((item) => item.status === 'waiting')) {
|
||||
processQueueItem();
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!('type' in message)) return;
|
||||
if (message.type !== 'actions') return;
|
||||
if (!message.group) return;
|
||||
|
||||
const list = queue.lists[message.group];
|
||||
if (!list) return;
|
||||
|
||||
if (tickets.length > 0 && list.some((item) => item.status === 'waiting')) {
|
||||
processQueueItem();
|
||||
}
|
||||
});
|
||||
|
||||
const plugins: Plugin[] = [
|
||||
{
|
||||
@@ -124,115 +34,53 @@
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const organizedParts = $derived(() => {
|
||||
const nonTextParts = message.parts.filter(p => p.type !== 'text');
|
||||
const textParts = message.parts.filter(p => p.type === 'text');
|
||||
const finalText = textParts[textParts.length - 1];
|
||||
|
||||
return {
|
||||
nonTextParts,
|
||||
toolCalls: nonTextParts.filter(p => p.type.startsWith('tool-')) as ImagineUIToolParts[],
|
||||
finalText
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if 'type' in message}
|
||||
{#key message.content}
|
||||
{#if message.type === 'actions'}
|
||||
<Card.Base variant="primary" padding="none">
|
||||
<Card.Base variant="secondary" padding="xs" class="title">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
<Typography.Text variant="m-500">Version 0</Typography.Text>
|
||||
{#if !message.complete}
|
||||
<Spinner />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
<div class="actions">
|
||||
<div class="grid-line"></div>
|
||||
{#each message.actions as action}
|
||||
{@const actionInQueue = queue.lists[action.group]?.find(
|
||||
(n) => n.data.id === action.id
|
||||
)}
|
||||
{#if actionInQueue}
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
{#if action.type === 'file'}
|
||||
<Typography.Code size="s" class="file">
|
||||
<span class="icon">
|
||||
{#if actionInQueue.status === 'done'}
|
||||
<Icon
|
||||
size="s"
|
||||
--icon-size-s="12px"
|
||||
icon={IconCheckCircle} />
|
||||
{:else}
|
||||
<Spinner size="s" --icon-size-s="12px" />
|
||||
{/if}
|
||||
</span>
|
||||
{action.src}</Typography.Code>
|
||||
<Typography.Code size="s">
|
||||
{#if actionInQueue.status === 'waiting'}
|
||||
Waiting
|
||||
{:else if actionInQueue.status === 'processing'}
|
||||
<ShimmerText>Generating</ShimmerText>
|
||||
{:else if actionInQueue.status === 'done'}
|
||||
Generated
|
||||
{:else if actionInQueue.status === 'failed'}
|
||||
<span style:color="var(--fgcolor-error)">Failed</span>
|
||||
{/if}
|
||||
</Typography.Code>
|
||||
{:else if action.type === 'shell'}
|
||||
<Typography.Code class="file" size="s">
|
||||
<span class="icon">
|
||||
{#if actionInQueue.status === 'done'}
|
||||
<Icon
|
||||
size="s"
|
||||
--icon-size-s="12px"
|
||||
icon={IconCheckCircle} />
|
||||
{:else}
|
||||
<Spinner size="s" --icon-size-s="12px" />
|
||||
{/if}
|
||||
</span>
|
||||
{action.content}</Typography.Code>
|
||||
<Typography.Code size="s">
|
||||
{#if actionInQueue.status === 'waiting'}
|
||||
Waiting
|
||||
{:else if actionInQueue.status === 'processing'}
|
||||
<ShimmerText>Running</ShimmerText>
|
||||
{:else if actionInQueue.status === 'done'}
|
||||
Completed
|
||||
{:else if actionInQueue.status === 'failed'}
|
||||
<span style:color="var(--fgcolor-error)">Failed</span>
|
||||
{/if}
|
||||
</Typography.Code>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if !message.complete}
|
||||
<Typography.Code size="s">
|
||||
<ShimmerText>thinking...</ShimmerText>
|
||||
</Typography.Code>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Base>
|
||||
<!-- User Message -->
|
||||
{#if message.role === 'user'}
|
||||
{#each message.parts as part, partIndex (partIndex)}
|
||||
{#if part.type === 'text'}
|
||||
<div class="message">
|
||||
{part.text}
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
{:else}
|
||||
{#snippet text()}
|
||||
<Markdown md={message.content} {plugins} />
|
||||
{/snippet}
|
||||
{#if message.from === 'user'}
|
||||
<div class="message">
|
||||
{@render text()}
|
||||
</div>
|
||||
{:else if message.from === 'error'}
|
||||
<div class="message">
|
||||
{@render text()}
|
||||
</div>
|
||||
{:else}
|
||||
{@render text()}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<!-- Assistant Messages -->
|
||||
{#if message.role === 'assistant'}
|
||||
<!-- Non-text parts -->
|
||||
{#each organizedParts().nonTextParts as part, partIndex (partIndex)}
|
||||
{#if part.type === 'data-thinking'}
|
||||
<Thinking data={part.data} didReceiveFirstAsisstantTextChunk={part.data.text.length > 0} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<!-- Tool calls -->
|
||||
{#if organizedParts().toolCalls.length > 0}
|
||||
<ToolCalls toolCallParts={organizedParts().toolCalls} />
|
||||
{/if}
|
||||
|
||||
<!-- Final text -->
|
||||
{#if organizedParts().finalText}
|
||||
<Markdown md={organizedParts().finalText.text} {plugins} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.message {
|
||||
width: 90%;
|
||||
float: right;
|
||||
display: inline-flex;
|
||||
padding: 0.5rem;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { Typography, ShimmerText } from '@appwrite.io/pink-svelte';
|
||||
import { IconChevronDown, IconChevronUp } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
|
||||
import type { ImagineUIDataParts } from '$shared-types';
|
||||
|
||||
let {
|
||||
data,
|
||||
didReceiveFirstAsisstantTextChunk
|
||||
}: { data: ImagineUIDataParts['thinking']; didReceiveFirstAsisstantTextChunk: boolean } =
|
||||
$props();
|
||||
|
||||
let expanded = $state(true);
|
||||
|
||||
$effect(() => {
|
||||
if (data.state === 'done' && didReceiveFirstAsisstantTextChunk) {
|
||||
setTimeout(() => {
|
||||
expanded = false;
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
|
||||
function toggle() {
|
||||
expanded = !expanded;
|
||||
}
|
||||
</script>
|
||||
|
||||
<button class="thinking-container" onclick={toggle} type="button">
|
||||
{#if data.state === 'streaming'}
|
||||
<Typography.Code size="s">
|
||||
<ShimmerText>Thinking...</ShimmerText>
|
||||
{#if expanded && data.text}
|
||||
<div class="thoughts">
|
||||
{data.text}
|
||||
</div>
|
||||
{/if}
|
||||
</Typography.Code>
|
||||
{:else if data.state === 'done'}
|
||||
<Typography.Code size="s">
|
||||
<span class="summary">
|
||||
Thought for {Math.floor(data.durationMs / 1000)} seconds
|
||||
<Icon icon={expanded ? IconChevronUp : IconChevronDown} size="s" />
|
||||
</span>
|
||||
{#if expanded && data.text}
|
||||
<div class="thoughts">
|
||||
{data.text}
|
||||
</div>
|
||||
{/if}
|
||||
</Typography.Code>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.thinking-container {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.thinking-container:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.toggle-hint {
|
||||
font-size: 0.8em;
|
||||
opacity: 0.6;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.thoughts {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
font-family: inherit;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,196 @@
|
||||
<script lang="ts">
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconCheckCircle } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Spinner } from '@appwrite.io/pink-svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import type { ImagineUIToolParts } from '$shared-types';
|
||||
|
||||
let { toolCallParts }: { toolCallParts: ImagineUIToolParts[] } = $props();
|
||||
|
||||
const EXPANDED_SHOW_ITEMS = 3;
|
||||
let isExpanded = $state(false);
|
||||
|
||||
// Filter and process parts
|
||||
let filteredParts = $derived(() => {
|
||||
let parts = toolCallParts.filter(
|
||||
(part) =>
|
||||
part.type.startsWith('tool-readFile') || part.type.startsWith('tool-writeFile')
|
||||
);
|
||||
|
||||
const showMoreButton = parts.length > EXPANDED_SHOW_ITEMS;
|
||||
|
||||
// If showMoreButton is true and not expanded, only show last N items
|
||||
if (showMoreButton && !isExpanded) {
|
||||
parts = parts.slice(-EXPANDED_SHOW_ITEMS);
|
||||
}
|
||||
|
||||
return { parts, showMoreButton };
|
||||
});
|
||||
|
||||
function toggleExpanded() {
|
||||
isExpanded = !isExpanded;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if filteredParts().parts.length > 0}
|
||||
<div class="tool-calls-container">
|
||||
<div class="header">
|
||||
<span class="version-text">Version 1</span>
|
||||
</div>
|
||||
|
||||
<div class="content" class:has-more={filteredParts.length > EXPANDED_SHOW_ITEMS}>
|
||||
{#each filteredParts().parts as toolCall, i (i)}
|
||||
{@const isLoading = toolCall.state === 'input-available'}
|
||||
|
||||
<div
|
||||
class="tool-item"
|
||||
style:opacity={isExpanded || i < EXPANDED_SHOW_ITEMS ? 1 : 0}
|
||||
transition:slide={{ duration: 300 }}>
|
||||
<div class={`icon-container ${isLoading ? 'icon-xs' : ''}`}>
|
||||
{#if isLoading}
|
||||
<Icon icon={Spinner} size="s" />
|
||||
{:else}
|
||||
<Icon icon={IconCheckCircle} size="s" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="tool-text">
|
||||
{#if toolCall.type === 'tool-writeFile'}
|
||||
{isLoading
|
||||
? `Writing file ${toolCall.input.path}...`
|
||||
: `Wrote file ${toolCall.input.path}`}
|
||||
{:else if toolCall.type === 'tool-readFile'}
|
||||
{isLoading
|
||||
? `Reading file ${toolCall.input.path}...`
|
||||
: `Read file ${toolCall.input.path}`}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="connector-line"></div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if filteredParts().showMoreButton}
|
||||
<button class="show-more-btn" onclick={toggleExpanded}>
|
||||
{isExpanded ? 'Show less' : 'Show more'}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.tool-calls-container {
|
||||
position: relative;
|
||||
margin-top: 0.4rem;
|
||||
margin-bottom: 0.4rem;
|
||||
font-size: 0.75rem;
|
||||
background: white;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-top: 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: rgba(245, 245, 245, 0.5);
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 0.125rem 0.125rem 0 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
color: #737373;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.version-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content.has-more {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.tool-item {
|
||||
font-size: 0.725rem;
|
||||
color: #a3a3a3;
|
||||
height: 1.375rem;
|
||||
font-family: monospace;
|
||||
gap: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 0.875rem;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.tool-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.connector-line {
|
||||
position: absolute;
|
||||
background: #e5e5e5;
|
||||
height: 0.625rem;
|
||||
top: -0.3125rem;
|
||||
width: 1px;
|
||||
left: 0.4375rem;
|
||||
}
|
||||
|
||||
.icon-xs {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
:global(.show-more-btn) {
|
||||
font-size: 0.625rem !important;
|
||||
background: #fafafa !important;
|
||||
border-radius: 9999px !important;
|
||||
border: 1px solid #e5e5e5 !important;
|
||||
height: 1.25rem !important;
|
||||
padding: 0 0.5rem !important;
|
||||
position: absolute !important;
|
||||
z-index: 20 !important;
|
||||
left: 50% !important;
|
||||
transform: translateX(-50%) translateY(50%) !important;
|
||||
bottom: 0 !important;
|
||||
line-height: 1.2 !important;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ImagineUIMessage } from '$shared-types';
|
||||
import { AppwriteException } from '@appwrite.io/console';
|
||||
import type { Client, Payload } from '@appwrite.io/console';
|
||||
|
||||
@@ -561,6 +562,10 @@ export type Conversation = {
|
||||
* Search index string.
|
||||
*/
|
||||
search: string;
|
||||
/**
|
||||
* Messages (UI)
|
||||
*/
|
||||
messages: ImagineUIMessage[];
|
||||
};
|
||||
/**
|
||||
* ConversationsMessage
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ export const VARS = {
|
||||
APPWRITE_ENDPOINT: env.PUBLIC_APPWRITE_ENDPOINT ?? undefined,
|
||||
GROWTH_ENDPOINT: env.PUBLIC_GROWTH_ENDPOINT ?? undefined,
|
||||
PUBLIC_STRIPE_KEY: env.PUBLIC_STRIPE_KEY ?? undefined,
|
||||
PROJECT_PROFILE: (env.PUBLIC_PROJECT_PROFILE as Profile) ?? undefined
|
||||
PROJECT_PROFILE: (env.PUBLIC_PROJECT_PROFILE as Profile) ?? undefined,
|
||||
AI_SERVICE_BASE_URL: env.PUBLIC_AI_SERVICE_BASE_URL ?? undefined
|
||||
};
|
||||
|
||||
export const ENV = {
|
||||
|
||||
+10
-2
@@ -12,6 +12,14 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"$shared-types": [
|
||||
"./ai-service/src/shared-types"
|
||||
]
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{ "path": "./ai-service" }
|
||||
]
|
||||
}
|
||||
|
||||
+4
-1
@@ -35,7 +35,10 @@ export default defineConfig({
|
||||
]
|
||||
},
|
||||
server: {
|
||||
port: 3000
|
||||
port: 3000,
|
||||
watch: {
|
||||
ignored: ['**/ai-service/**']
|
||||
}
|
||||
},
|
||||
test: {
|
||||
workspace: [
|
||||
|
||||
Reference in New Issue
Block a user