Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ KB_SYNC_MAX_PROCESSING=10
KB_SYNC_BATCH_SIZE=10
KB_SYNC_TZ="Asia/Shanghai"

APP_URL=""



# NODE_ENV="production"
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
"Hnsw",
"langchain",
"langgraph",
"newticket",
"prty",
"sealos",
"Tentix",
"uisrc",
"uncategorized"
"uncategorized",
"wechat"
]
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,12 @@ bun run build
# 2. Start production server
bun run start

# Dry run build for testing and check build errors
bun run build --dry

# Force build
bun run build --force

# Or use PM2
pm2 start bun --name tentix -- run start
```
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/_provider/sealos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ interface SealosContextType {
sealosToken: string | null;
sealosArea: string | null;
sealosUser: SealosUserInfo | null;
sealosNs: string | null;
currentLanguage: string | null;
}

const SealosContext = createContext<SealosContextType | null>(null);
Expand All @@ -39,6 +41,8 @@ export function SealosProvider({ children }: { children: React.ReactNode }) {
sealosToken: null,
sealosArea: null,
sealosUser: null,
sealosNs: null,
currentLanguage: null,
});

const initializationRef = useRef(false);
Expand Down Expand Up @@ -83,10 +87,11 @@ export function SealosProvider({ children }: { children: React.ReactNode }) {
const sealosSession = await sealosApp.getSession();
const sealosToken = sealosSession.token as unknown as string;
const sealosArea = extractAreaFromSealosToken(sealosToken ?? "");
const sealosNs = sealosSession.user.nsid;

window.localStorage.setItem("sealosToken", sealosToken);
window.localStorage.setItem("sealosArea", sealosArea ?? "");
window.localStorage.setItem("sealosNs", sealosSession.user.nsid ?? "");
window.localStorage.setItem("sealosNs", sealosNs ?? "");

console.info("Sealos data saved to localStorage");

Expand All @@ -97,6 +102,8 @@ export function SealosProvider({ children }: { children: React.ReactNode }) {
sealosToken,
sealosArea,
sealosUser: sealosSession.user,
sealosNs,
currentLanguage: lang.lng,
});

// cleanup
Expand All @@ -105,6 +112,7 @@ export function SealosProvider({ children }: { children: React.ReactNode }) {
cleanupApp?.();
};
} catch (error) {
console.error("Sealos initialization failed:", error);
setState((prev) => ({
...prev,
isLoading: false,
Expand Down
185 changes: 185 additions & 0 deletions frontend/src/components/chat/enhanced-message-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React, { useState, useCallback } from "react";
import { type JSONContentZod } from "tentix-server/types";
import { useTextOptimizer } from "../../hooks/use-text-optimizer";
import { OptimizeButton, OptimizeStatus } from "./optimize-button";
import { useToast } from "tentix-ui";

interface EnhancedMessageInputProps {
ticketId: string;
messageType?: "public" | "internal";
priority?: string;
onSendMessage: (content: JSONContentZod) => Promise<void>;
onTyping?: () => void;
isLoading?: boolean;
children: React.ReactElement;
}

export function EnhancedMessageInput({
ticketId,
messageType = "public",
priority,
onTyping,
isLoading,
children,
}: EnhancedMessageInputProps) {
const { toast } = useToast();
const [currentText, setCurrentText] = useState("");
const [isEnabled, setIsEnabled] = useState(true);

const {
isOptimizing,
lastOptimization,
hasOptimization,
optimizeText,
undoOptimization,
cancelOptimization,
} = useTextOptimizer({ ticketId, messageType, priority });

// 获取文本内容的辅助函数
const extractTextFromContent = useCallback((content: JSONContentZod): string => {
if (!content || !content.content) return "";

const extractFromNode = (node: any): string => {
if (typeof node === "string") return node;
if (node.type === "text") return node.text || "";
if (node.content && Array.isArray(node.content)) {
return node.content.map(extractFromNode).join("");
}
return "";
};

return content.content.map(extractFromNode).join(" ").trim();
}, []);

// 创建优化后的内容
const createOptimizedContent = useCallback((optimizedText: string): JSONContentZod => {
return {
type: "doc",
content: [
{
type: "paragraph",
content: [{ type: "text", text: optimizedText }],
},
],
};
}, []);

// Tab键优化处理
const handleOptimize = useCallback(async (text?: string) => {
const textToOptimize = text || currentText;
if (!textToOptimize.trim()) {
toast({
title: "无法优化",
description: "请先输入一些文本",
variant: "destructive",
});
return;
}

const result = await optimizeText(textToOptimize);
if (result) {
// 通过ref更新编辑器内容
const childRef = (children as any)?.ref;
if (childRef?.current) {
const optimizedContent = createOptimizedContent(result.optimizedText);
childRef.current.setContent?.(optimizedContent);
setCurrentText(result.optimizedText);
}
}
}, [currentText, optimizeText, createOptimizedContent, toast, children]);

// 撤销优化
const handleUndo = useCallback(() => {
const originalText = undoOptimization();
if (originalText) {
const childRef = (children as any)?.ref;
if (childRef?.current) {
const originalContent = createOptimizedContent(originalText);
childRef.current.setContent?.(originalContent);
setCurrentText(originalText);
}
}
}, [undoOptimization, createOptimizedContent, children]);

// 增强子组件的props
const childProps = children.props as any;
const enhancedChildren = React.cloneElement(children, {
...childProps,
onChange: (content: JSONContentZod) => {
// 调用原始的onChange
if (childProps.onChange) {
childProps.onChange(content);
}

// 更新当前文本状态
const text = extractTextFromContent(content);
setCurrentText(text);

// 调用onTyping
if (onTyping) {
onTyping();
}
},

// 添加Tab键处理
editorProps: {
...childProps.editorProps,
handleKeyDown: (view: any, event: KeyboardEvent) => {
// 处理Tab键
if (event.key === 'Tab' && !event.shiftKey && !event.ctrlKey && !event.metaKey) {
event.preventDefault();
if (isEnabled && !isOptimizing && currentText.trim()) {
handleOptimize();
return true;
}
}

// 调用原始的handleKeyDown
if (childProps.editorProps?.handleKeyDown) {
return childProps.editorProps.handleKeyDown(view, event);
}

return false;
},
},
});

return (
<div className="border-t relative">
{/* 优化控制栏 */}
<div className="flex items-center justify-between p-2 border-b bg-gray-50/50">
<div className="flex items-center gap-3">
<OptimizeButton
isOptimizing={isOptimizing}
hasOptimization={hasOptimization}
confidence={lastOptimization?.confidence}
onOptimize={() => handleOptimize()}
onUndo={handleUndo}
onCancel={cancelOptimization}
disabled={isLoading || !isEnabled}
/>

<label className="flex items-center gap-2 text-xs">
<input
type="checkbox"
checked={isEnabled}
onChange={(e) => setIsEnabled(e.target.checked)}
className="w-3 h-3"
/>
<span className="text-gray-600">启用Tab键优化</span>
</label>
</div>

<OptimizeStatus
isOptimizing={isOptimizing}
hasOptimization={hasOptimization}
confidence={lastOptimization?.confidence}
suggestions={lastOptimization?.suggestions}
/>
</div>

{/* 增强的消息输入组件 */}
{enhancedChildren}
</div>
);
}
Loading