Skip to content

Commit

Permalink
fix(web&server):fix web copilot input issue,fix error while database …
Browse files Browse the repository at this point in the history
…not existing (#14)

* fix(web&server): fix web Copilot input issue,fix issue where accidentally deleted databases could not be stopped, add sealaf-app label
  • Loading branch information
HUAHUAI23 authored Aug 14, 2024
1 parent 8d456e9 commit 41d1b90
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,28 @@ export class DedicatedDatabaseTaskService {
const manifest = await this.dbService.getDeployManifest(region, user, appid)

if (!manifest) {
throw new Error(`stop dedicated database ${appid} manifest not found`)
await this.db
.collection<DedicatedDatabase>('DedicatedDatabase')
.updateOne(
{
appid: data.appid,
phase: DedicatedDatabasePhase.Stopping,
},

{
$set: {
phase: DedicatedDatabasePhase.Stopped,
lockedAt: TASK_LOCK_INIT_TIME,
updatedAt: new Date(),
},
},
)

this.logger.debug(
`update dedicated database ${appid} state to stopped,note: ddb manifest not found`,
)

return
}

const stopped =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,11 @@ export class DedicatedDatabaseService {
const requestMemory =
limitMemory * (region.bundleConf?.memoryRequestLimitRatio || 0.5)

const label = appid
const template = region.deployManifest.database
const tmpl = _.template(template)
const manifest = tmpl({
label,
name,
limitCPU,
limitMemory,
Expand All @@ -167,6 +169,12 @@ export class DedicatedDatabaseService {
appid: string,
): Promise<boolean> {
const ddbDeployManifest = await this.getDeployManifest(region, user, appid)

if (!ddbDeployManifest) {
this.logger.debug(`restart ddb, deploy manifest not found for ${appid}`)
return true
}

const replicas = Number(ddbDeployManifest.spec.componentSpecs[0].replicas)

const limitCPU = extractNumber(
Expand Down
1 change: 1 addition & 0 deletions server/src/initializer/deploy-manifest/database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ metadata:
clusterdefinition.kubeblocks.io/name: mongodb
clusterversion.kubeblocks.io/name: mongodb-5.0
sealos-db-provider-cr: <%- name %>
sealaf-app: <%- label %>
annotations: {}
name: <%- name %>
spec:
Expand Down
20 changes: 15 additions & 5 deletions server/src/instance/instance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class InstanceService {
deployment.spec = await this.makeDeploymentSpec(
app,
deployment.spec.template.metadata.labels,
this.getRuntimeLabel(appid),
deployment.spec.template.metadata.labels,
)
const appsV1Api = this.cluster.makeAppsV1Api()
const deploymentResult = await appsV1Api.replaceNamespacedDeployment(
Expand Down Expand Up @@ -242,11 +242,19 @@ export class InstanceService {
// db connection uri
let dbConnectionUri: string
const dedicatedDatabase = await this.dedicatedDatabaseService.findOne(appid)

if (dedicatedDatabase) {
dbConnectionUri = await this.dedicatedDatabaseService.getConnectionUri(
user,
dedicatedDatabase,
)
try {
dbConnectionUri = await this.dedicatedDatabaseService.getConnectionUri(
user,
dedicatedDatabase,
)
} catch (e) {
dbConnectionUri = ''
this.logger.debug(
`get db connection uri for ${appid} failed: ${e}, maybe ddb cluster manifest have been deleted`,
)
}
}

const NODE_MODULES_PUSH_URL =
Expand Down Expand Up @@ -582,10 +590,12 @@ export class InstanceService {

private getRuntimeLabel(appid: string) {
const SEALOS = 'cloud.sealos.io/app-deploy-manager'
const SEALAF_APP = 'sealaf-app'
const labels: Record<string, string> = {
[LABEL_KEY_APP_ID]: appid,
[SEALOS]: this.getAppDeployName(appid),
app: this.getAppDeployName(appid),
[SEALAF_APP]: appid,
}

return labels
Expand Down
157 changes: 108 additions & 49 deletions web/src/pages/app/functions/mods/AIChatPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useRef, useState } from "react";
import { KeyboardEvent, useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { FaRegStopCircle } from "react-icons/fa";
import { Avatar, Button, HStack, Input } from "@chakra-ui/react";
import { Avatar, Box, Button, Textarea, VStack } from "@chakra-ui/react";
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
import { debounce } from "lodash";
import { v4 as uuidv4 } from "uuid";

import { LafAILogoIcon } from "@/components/CommonIcon";
Expand All @@ -19,7 +20,7 @@ export default function AIChatPanel() {
let source = CancelToken.source();

const contentDomRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);

const [prompt, setPrompt] = useState("");

Expand All @@ -31,6 +32,9 @@ export default function AIChatPanel() {
},
]);

const [contentHeight, setContentHeight] = useState("calc(100% - 120px)");
const [isComposing, setIsComposing] = useState(false);

async function handleSubmit() {
if (generateCode.isLoading) {
generateCode.reset();
Expand Down Expand Up @@ -69,14 +73,15 @@ export default function AIChatPanel() {
});
}, 100);
setPrompt("");
resetHeight();
await generateCode.mutateAsync({
value: prompt,
});
}
}

const { data: generateCodeRes, ...generateCode } = useMutation((params: any) => {
inputRef.current?.focus();
textareaRef.current?.focus();
return axios({
url: siteSettings.ai_pilot_url?.value,
method: "POST",
Expand Down Expand Up @@ -104,64 +109,118 @@ export default function AIChatPanel() {
});
});

const adjustTextareaHeight = useCallback(
debounce(() => {
const textarea = textareaRef.current;
if (textarea) {
textarea.style.height = "auto";
const scrollHeight = textarea.scrollHeight;
const newHeight = scrollHeight <= 120 ? 120 : Math.min(scrollHeight, 280);
textarea.style.height = `${newHeight}px`;
setContentHeight(`calc(100% - ${newHeight}px)`);
}
}, 100),
[],
);

useEffect(() => {
return () => {
adjustTextareaHeight.cancel();
};
}, [adjustTextareaHeight]);

const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey && !isComposing) {
e.preventDefault();
handleSubmit();
}
};

const resetHeight = useCallback(() => {
const textarea = textareaRef.current;
if (textarea) {
textarea.style.height = "120px";
setContentHeight("calc(100% - 120px)");
}
}, []);

const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
adjustTextareaHeight();
setPrompt(e.target.value);
};

return (
<div className="flex h-full flex-col overflow-hidden px-2">
<div className="flex-1 overflow-scroll pr-2" ref={contentDomRef}>
<VStack height="100%">
<Box
ref={contentDomRef}
flex={1}
width="100%"
overflow="auto"
height={contentHeight}
transition="height 0.3s ease-in-out"
padding={[1, 1, 1, 2]}
>
<ul>
{aiChatHistory.map((item, index) => {
return (
<li className="mt-4 w-full overflow-hidden" key={item._id}>
<div className="flex">
<div className="mr-2 w-[24px]">
{item.isAi ? (
<LafAILogoIcon className="mr-2" />
) : (
<Avatar className="mr-2" size="xs" />
)}
</div>

{aiChatHistory.map((item, index) => (
<li key={item._id} className="mt-4 w-full overflow-hidden">
<div className="flex">
<div className="mr-2 w-[24px]">
{item.isAi ? (
<div className="mt-1 w-full flex-1 overflow-auto">
<Markdown
source={item.text}
formatLink
isChatting={index === aiChatHistory.length - 1 && generateCode.isLoading}
/>
</div>
<LafAILogoIcon className="mr-2" />
) : (
<div className="mt-1 flex-1 break-all">{item.text}</div>
<Avatar className="mr-2" size="xs" />
)}
</div>
</li>
);
})}
<div className="mt-1 flex-1 overflow-auto">
{item.isAi ? (
<Markdown
source={item.text}
formatLink
isChatting={index === aiChatHistory.length - 1 && generateCode.isLoading}
/>
) : (
<div className="break-all">{item.text}</div>
)}
</div>
</div>
</li>
))}
</ul>
</div>

<HStack className="h-[60px]">
<Input
h={"34px"}
placeholder={String(t("SendMessagePlaceHolder"))}
</Box>
<Box width="100%" position="relative" minHeight="120px" maxHeight="280px">
<Textarea
value={prompt}
ref={inputRef}
onChange={(e) => {
setPrompt(e.target.value);
}}
onKeyPress={(event: any) => {
if (event.key === "Enter") {
handleSubmit();
}
onKeyDown={handleKeyDown}
onChange={handleChange}
onCompositionStart={() => setIsComposing(true)}
onCompositionEnd={() => setIsComposing(false)}
minHeight="120px"
maxHeight="280px"
overflow="auto"
ref={textareaRef}
resize="none"
transition="height 0.3s ease-in-out"
sx={{
"&::-webkit-scrollbar": {
width: "8px",
},
"&::-webkit-scrollbar-thumb": {
backgroundColor: "gray.300",
borderRadius: "4px",
},
}}
/>
<Button
rounded={"md"}
onClick={async () => {
handleSubmit();
}}
rounded="md"
onClick={handleSubmit}
position="absolute"
bottom="4"
right="4"
zIndex={1}
>
{generateCode.isLoading ? <FaRegStopCircle fontSize={22} /> : t("Send")}
</Button>
</HStack>
</div>
</Box>
</VStack>
);
}

0 comments on commit 41d1b90

Please sign in to comment.