mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-15 12:10:16 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e84b17b4b | |||
| 948ee35152 | |||
| 3bb6159efb | |||
| 5fa4fe0e06 |
@@ -114,6 +114,15 @@
|
||||
"defaultAgent.model.desc": "Default model used when creating a new Agent",
|
||||
"defaultAgent.model.title": "Model",
|
||||
"defaultAgent.title": "Default Agent Settings",
|
||||
"googleDataProtection.cannotConnectGoogle.content": "You are currently using Grok or DeepSeek as your model provider. Due to Google's data protection policy, Google services (Gmail, Calendar, Drive, Sheets, Docs) cannot be used with these providers. Please switch to a different model provider first.",
|
||||
"googleDataProtection.cannotConnectGoogle.title": "Cannot Connect Google Services",
|
||||
"googleDataProtection.cannotSendMessage.content": "Your current configuration uses Google tools ({{tools}}) with a restricted AI provider. Due to Google's data protection policy, this combination is not allowed. Please switch to a different model provider or disable the Google tools.",
|
||||
"googleDataProtection.cannotSendMessage.title": "Cannot Send Message",
|
||||
"googleDataProtection.cannotSendWithHistory.content": "This conversation contains data from Google tools ({{tools}}). Due to Google's data protection policy, you cannot continue this conversation with Grok or DeepSeek. Please switch to a different model provider.",
|
||||
"googleDataProtection.cannotSendWithHistory.title": "Cannot Continue Conversation",
|
||||
"googleDataProtection.cannotSwitchModel.content": "You have Google tools enabled ({{tools}}). Due to Google's data protection policy, you cannot use Grok or DeepSeek with Google services. Please disable the Google tools first.",
|
||||
"googleDataProtection.cannotSwitchModel.title": "Cannot Switch to This Provider",
|
||||
"googleDataProtection.understood": "I Understand",
|
||||
"group.aiConfig": "Agent",
|
||||
"group.common": "General",
|
||||
"group.profile": "Account",
|
||||
|
||||
@@ -114,6 +114,15 @@
|
||||
"defaultAgent.model.desc": "创建新助理时使用的默认模型",
|
||||
"defaultAgent.model.title": "模型",
|
||||
"defaultAgent.title": "默认助理设置",
|
||||
"googleDataProtection.cannotConnectGoogle.content": "您当前使用的是 Grok 或 DeepSeek 模型。根据 Google 数据保护政策,这些模型提供商不能与 Google 服务(Gmail、日历、云盘、表格、文档)一起使用。请先切换到其他模型提供商。",
|
||||
"googleDataProtection.cannotConnectGoogle.title": "无法连接 Google 服务",
|
||||
"googleDataProtection.cannotSendMessage.content": "您当前的配置将 Google 工具({{tools}})与受限的 AI 提供商一起使用。根据 Google 数据保护政策,此组合不被允许。请切换到其他模型提供商或禁用 Google 工具。",
|
||||
"googleDataProtection.cannotSendMessage.title": "无法发送消息",
|
||||
"googleDataProtection.cannotSendWithHistory.content": "此对话包含来自 Google 工具({{tools}})的数据。根据 Google 数据保护政策,您不能使用 Grok 或 DeepSeek 继续此对话。请切换到其他模型提供商。",
|
||||
"googleDataProtection.cannotSendWithHistory.title": "无法继续对话",
|
||||
"googleDataProtection.cannotSwitchModel.content": "您已启用 Google 工具({{tools}})。根据 Google 数据保护政策,您不能将 Grok 或 DeepSeek 与 Google 服务一起使用。请先禁用 Google 工具。",
|
||||
"googleDataProtection.cannotSwitchModel.title": "无法切换到此提供商",
|
||||
"googleDataProtection.understood": "我知道了",
|
||||
"group.aiConfig": "智能体",
|
||||
"group.common": "通用",
|
||||
"group.profile": "账号",
|
||||
|
||||
+2
-2
@@ -279,7 +279,7 @@
|
||||
"motion": "^12.29.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"next": "^16.1.4",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"next-mdx-remote": "^6.0.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"nextjs-toploader": "^3.9.17",
|
||||
"node-machine-id": "^1.1.12",
|
||||
@@ -453,4 +453,4 @@
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { Suspense, memo, useMemo } from 'react';
|
||||
import ChatMiniMap from '@/features/ChatMiniMap';
|
||||
import { ChatList, ConversationProvider, TodoProgress } from '@/features/Conversation';
|
||||
import ZenModeToast from '@/features/ZenModeToast';
|
||||
import { useAgentGoogleProtectionHooks } from '@/hooks/useAgentGoogleProtectionHooks';
|
||||
import { useOperationState } from '@/hooks/useOperationState';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
||||
@@ -46,11 +47,15 @@ const Conversation = memo(() => {
|
||||
// Get actionsBar config with branching support from ChatStore
|
||||
const actionsBarConfig = useActionsBarConfig();
|
||||
|
||||
// Google Data Protection hooks
|
||||
const googleProtectionHooks = useAgentGoogleProtectionHooks({ messages });
|
||||
|
||||
return (
|
||||
<ConversationProvider
|
||||
actionsBar={actionsBarConfig}
|
||||
context={context}
|
||||
hasInitMessages={!!messages}
|
||||
hooks={googleProtectionHooks}
|
||||
messages={messages}
|
||||
onMessagesChange={(messages, ctx) => {
|
||||
replaceMessages(messages, { context: ctx });
|
||||
|
||||
@@ -127,7 +127,7 @@ export const useSlashItems = (): SlashOptions['items'] => {
|
||||
...item,
|
||||
extra: (
|
||||
<Text code fontSize={12} type={'secondary'}>
|
||||
{item.key}
|
||||
{String(item.key)}
|
||||
</Text>
|
||||
),
|
||||
style: {
|
||||
|
||||
+6
-2
@@ -6,7 +6,11 @@ import { memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useHomeStore } from '@/store/home';
|
||||
import { type SessionGroupItem } from '@/types/session';
|
||||
|
||||
interface GroupItemProps {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
content: css`
|
||||
@@ -22,7 +26,7 @@ const styles = createStaticStyles(({ css }) => ({
|
||||
`,
|
||||
}));
|
||||
|
||||
const GroupItem = memo<SessionGroupItem>(({ id, name }) => {
|
||||
const GroupItem = memo<GroupItemProps>(({ id, name }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const { message, modal } = App.useApp();
|
||||
|
||||
|
||||
@@ -8,10 +8,14 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useHomeStore } from '@/store/home';
|
||||
import { homeAgentListSelectors } from '@/store/home/selectors';
|
||||
import { type SessionGroupItem } from '@/types/session';
|
||||
|
||||
import GroupItem from './GroupItem';
|
||||
|
||||
interface SortableGroupItem {
|
||||
id: string;
|
||||
name: string;
|
||||
sort: number | null;
|
||||
}
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
height: 36px;
|
||||
@@ -52,10 +56,10 @@ const ConfigGroupModal = memo<ModalProps>(({ open, onCancel }) => {
|
||||
<Flexbox>
|
||||
<SortableList
|
||||
items={sessionGroupItems}
|
||||
onChange={(items: SessionGroupItem[]) => {
|
||||
onChange={(items: SortableGroupItem[]) => {
|
||||
updateGroupSort(items);
|
||||
}}
|
||||
renderItem={(item: SessionGroupItem) => (
|
||||
renderItem={(item: SortableGroupItem) => (
|
||||
<SortableList.Item
|
||||
align={'center'}
|
||||
className={styles.container}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
export const GOOGLE_RESTRICTED_PROVIDER_IDS = ['xai', 'deepseek'] as const;
|
||||
export type GoogleRestrictedProvider = (typeof GOOGLE_RESTRICTED_PROVIDER_IDS)[number];
|
||||
|
||||
// Model prefixes that are restricted from using Google tools
|
||||
export const GOOGLE_RESTRICTED_MODEL_PREFIXES = ['grok', 'deepseek'] as const;
|
||||
|
||||
export const GOOGLE_TOOL_IDENTIFIERS = [
|
||||
'gmail',
|
||||
'google-calendar',
|
||||
'google-drive',
|
||||
'google-sheets',
|
||||
'google-docs',
|
||||
] as const;
|
||||
export type GoogleToolIdentifier = (typeof GOOGLE_TOOL_IDENTIFIERS)[number];
|
||||
|
||||
/**
|
||||
* Check if a provider/model combination is restricted from using Google tools
|
||||
* Checks both provider ID and model ID (model name may contain restricted prefixes)
|
||||
*/
|
||||
export const isGoogleRestrictedProvider = (providerId: string, modelId?: string): boolean => {
|
||||
// Check provider ID
|
||||
if (GOOGLE_RESTRICTED_PROVIDER_IDS.includes(providerId as GoogleRestrictedProvider)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check model ID (model name may start with grok or deepseek)
|
||||
if (modelId) {
|
||||
const lowerModelId = modelId.toLowerCase();
|
||||
if (GOOGLE_RESTRICTED_MODEL_PREFIXES.some((prefix) => lowerModelId.startsWith(prefix))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isGoogleTool = (identifier: string): boolean =>
|
||||
GOOGLE_TOOL_IDENTIFIERS.includes(identifier as GoogleToolIdentifier);
|
||||
@@ -3,6 +3,7 @@ import { Loader2, SquareArrowOutUpRight } from 'lucide-react';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useGoogleDataProtection } from '@/hooks/useGoogleDataProtection';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentSelectors } from '@/store/agent/selectors';
|
||||
import { useToolStore } from '@/store/tool';
|
||||
@@ -167,6 +168,9 @@ const KlavisServerItem = memo<KlavisServerItemProps>(
|
||||
s.togglePlugin,
|
||||
]);
|
||||
|
||||
// Google Data Protection check
|
||||
const { checkGoogleToolConnect } = useGoogleDataProtection();
|
||||
|
||||
const handleConnect = async () => {
|
||||
if (!userId) {
|
||||
return;
|
||||
@@ -176,6 +180,10 @@ const KlavisServerItem = memo<KlavisServerItemProps>(
|
||||
return;
|
||||
}
|
||||
|
||||
// Google Data Protection: Block connection if using restricted provider
|
||||
const isBlocked = await checkGoogleToolConnect(identifier);
|
||||
if (isBlocked) return;
|
||||
|
||||
setIsConnecting(true);
|
||||
try {
|
||||
const newServer = await createKlavisServer({
|
||||
@@ -206,6 +214,14 @@ const KlavisServerItem = memo<KlavisServerItemProps>(
|
||||
|
||||
const handleToggle = async () => {
|
||||
if (!server) return;
|
||||
|
||||
// Google Data Protection: Block enabling (not disabling) if using restricted provider
|
||||
// checked === false means user wants to enable it
|
||||
if (!checked) {
|
||||
const isBlocked = await checkGoogleToolConnect(identifier);
|
||||
if (isBlocked) return;
|
||||
}
|
||||
|
||||
setIsToggling(true);
|
||||
await togglePlugin(pluginId);
|
||||
setIsToggling(false);
|
||||
|
||||
@@ -83,7 +83,7 @@ export const useSlashItems = (): SlashOptions['items'] => {
|
||||
...item,
|
||||
extra: (
|
||||
<Text code fontSize={12} type={'secondary'}>
|
||||
{item.key}
|
||||
{String(item.key)}
|
||||
</Text>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ const buildActionsMap = (items: MessageActionItemOrDivider[]): Map<string, Messa
|
||||
if ('children' in item && item.children) {
|
||||
for (const child of item.children) {
|
||||
if (child.key) {
|
||||
map.set(`${item.key}.${child.key}`, child as unknown as MessageActionItem);
|
||||
map.set(`${String(item.key)}.${String(child.key)}`, child as unknown as MessageActionItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const buildActionsMap = (items: MessageActionItemOrDivider[]): Map<string, Messa
|
||||
if ('children' in item && item.children) {
|
||||
for (const child of item.children) {
|
||||
if (child.key) {
|
||||
map.set(`${item.key}.${child.key}`, child as unknown as MessageActionItem);
|
||||
map.set(`${String(item.key)}.${String(child.key)}`, child as unknown as MessageActionItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const buildActionsMap = (items: MessageActionItemOrDivider[]): Map<string, Messa
|
||||
if ('children' in item && item.children) {
|
||||
for (const child of item.children) {
|
||||
if (child.key) {
|
||||
map.set(`${item.key}.${child.key}`, child as unknown as MessageActionItem);
|
||||
map.set(`${String(item.key)}.${String(child.key)}`, child as unknown as MessageActionItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const buildActionsMap = (items: MessageActionItemOrDivider[]): Map<string, Messa
|
||||
if ('children' in item && item.children) {
|
||||
for (const child of item.children) {
|
||||
if (child.key) {
|
||||
map.set(`${item.key}.${child.key}`, child as unknown as MessageActionItem);
|
||||
map.set(`${String(item.key)}.${String(child.key)}`, child as unknown as MessageActionItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ const buildActionsMap = (items: MessageActionItemOrDivider[]): Map<string, Messa
|
||||
if ('children' in item && item.children) {
|
||||
for (const child of item.children) {
|
||||
if (child.key) {
|
||||
map.set(`${item.key}.${child.key}`, child as unknown as MessageActionItem);
|
||||
map.set(`${String(item.key)}.${String(child.key)}`, child as unknown as MessageActionItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useGoogleDataProtection } from '@/hooks/useGoogleDataProtection';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
|
||||
interface UsePanelHandlersProps {
|
||||
@@ -13,8 +14,15 @@ export const usePanelHandlers = ({
|
||||
}: UsePanelHandlersProps) => {
|
||||
const updateAgentConfig = useAgentStore((s) => s.updateAgentConfig);
|
||||
|
||||
// Google Data Protection check
|
||||
const { checkModelSwitch } = useGoogleDataProtection();
|
||||
|
||||
const handleModelChange = useCallback(
|
||||
async (modelId: string, providerId: string) => {
|
||||
// Google Data Protection: Block switch to restricted provider/model if Google tools enabled
|
||||
const isBlocked = await checkModelSwitch(providerId, modelId);
|
||||
if (isBlocked) return;
|
||||
|
||||
const params = { model: modelId, provider: providerId };
|
||||
if (onModelChangeProp) {
|
||||
onModelChangeProp(params);
|
||||
@@ -22,7 +30,7 @@ export const usePanelHandlers = ({
|
||||
updateAgentConfig(params);
|
||||
}
|
||||
},
|
||||
[onModelChangeProp, updateAgentConfig],
|
||||
[onModelChangeProp, updateAgentConfig, checkModelSwitch],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
|
||||
@@ -204,7 +204,7 @@ export const useSlashItems = (editor: IEditor | undefined): SlashOptions['items'
|
||||
...item,
|
||||
extra: (
|
||||
<Text code fontSize={12} type={'secondary'}>
|
||||
{item.key}
|
||||
{String(item.key)}
|
||||
</Text>
|
||||
),
|
||||
style: {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import type { UIChatMessage } from '@lobechat/types';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { type ConversationHooks } from '@/features/Conversation';
|
||||
|
||||
import { useGoogleDataProtection } from './useGoogleDataProtection';
|
||||
|
||||
interface UseAgentGoogleProtectionHooksParams {
|
||||
messages?: UIChatMessage[];
|
||||
}
|
||||
|
||||
export function useAgentGoogleProtectionHooks(
|
||||
params?: UseAgentGoogleProtectionHooksParams,
|
||||
): ConversationHooks {
|
||||
const { checkMessageHistoryForGoogleTools, checkMessageSend } = useGoogleDataProtection();
|
||||
|
||||
return useMemo(
|
||||
(): ConversationHooks => ({
|
||||
onBeforeSendMessage: async () => {
|
||||
// Check 1: Are Google tools currently enabled with restricted provider?
|
||||
const canSendWithEnabledTools = await checkMessageSend();
|
||||
if (!canSendWithEnabledTools) return false;
|
||||
|
||||
// Check 2: Does message history contain Google tool usage with restricted provider?
|
||||
const canSendWithHistory = await checkMessageHistoryForGoogleTools(params?.messages);
|
||||
if (!canSendWithHistory) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
[checkMessageSend, checkMessageHistoryForGoogleTools, params?.messages],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
'use client';
|
||||
|
||||
import type { UIChatMessage } from '@lobechat/types';
|
||||
import { App } from 'antd';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
GOOGLE_TOOL_IDENTIFIERS,
|
||||
isGoogleRestrictedProvider,
|
||||
isGoogleTool,
|
||||
} from '@/const/googleDataProtection';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentSelectors } from '@/store/agent/selectors';
|
||||
import { useToolStore } from '@/store/tool';
|
||||
import { klavisStoreSelectors } from '@/store/tool/selectors';
|
||||
|
||||
/**
|
||||
* Check if messages contain any Google tool usage
|
||||
*/
|
||||
export const findGoogleToolsInMessages = (messages: UIChatMessage[] | undefined): string[] => {
|
||||
if (!messages || messages.length === 0) return [];
|
||||
|
||||
const googleToolIds = new Set<string>();
|
||||
|
||||
for (const message of messages) {
|
||||
// Check plugin field
|
||||
if (message.plugin?.identifier && isGoogleTool(message.plugin.identifier)) {
|
||||
googleToolIds.add(message.plugin.identifier);
|
||||
}
|
||||
|
||||
// Check tools field
|
||||
if (message.tools) {
|
||||
for (const tool of message.tools) {
|
||||
if (isGoogleTool(tool.identifier)) {
|
||||
googleToolIds.add(tool.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check children (grouped tool messages - AssistantContentBlock has tools field)
|
||||
if (message.children) {
|
||||
for (const child of message.children) {
|
||||
if (child.tools) {
|
||||
for (const tool of child.tools) {
|
||||
if (isGoogleTool(tool.identifier)) {
|
||||
googleToolIds.add(tool.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(googleToolIds);
|
||||
};
|
||||
|
||||
export interface GoogleDataProtectionState {
|
||||
enabledGoogleToolIds: string[];
|
||||
hasConflict: boolean;
|
||||
hasEnabledGoogleTools: boolean;
|
||||
isUsingRestrictedProvider: boolean;
|
||||
}
|
||||
|
||||
export const useGoogleDataProtection = () => {
|
||||
const { t } = useTranslation('setting');
|
||||
const { modal } = App.useApp();
|
||||
|
||||
const plugins = useAgentStore(agentSelectors.currentAgentPlugins);
|
||||
const currentProvider = useAgentStore(agentSelectors.currentAgentModelProvider);
|
||||
const currentModel = useAgentStore(agentSelectors.currentAgentModel);
|
||||
const connectedServers = useToolStore(klavisStoreSelectors.getConnectedServers);
|
||||
|
||||
const state = useMemo((): GoogleDataProtectionState => {
|
||||
const connectedGoogleServerIds = new Set(connectedServers
|
||||
.filter((server) => isGoogleTool(server.identifier))
|
||||
.map((server) => server.identifier));
|
||||
|
||||
const enabledGoogleToolIds = plugins.filter((pluginId) =>
|
||||
connectedGoogleServerIds.has(pluginId),
|
||||
);
|
||||
|
||||
const hasEnabledGoogleTools = enabledGoogleToolIds.length > 0;
|
||||
const isUsingRestrictedProvider = isGoogleRestrictedProvider(currentProvider, currentModel);
|
||||
const hasConflict = hasEnabledGoogleTools && isUsingRestrictedProvider;
|
||||
|
||||
return {
|
||||
enabledGoogleToolIds,
|
||||
hasConflict,
|
||||
hasEnabledGoogleTools,
|
||||
isUsingRestrictedProvider,
|
||||
};
|
||||
}, [plugins, currentProvider, currentModel, connectedServers]);
|
||||
|
||||
const checkGoogleToolConnect = useCallback(
|
||||
async (toolIdentifier: string): Promise<boolean> => {
|
||||
if (!isGoogleTool(toolIdentifier)) return false;
|
||||
if (!isGoogleRestrictedProvider(currentProvider, currentModel)) return false;
|
||||
|
||||
modal.warning({
|
||||
centered: true,
|
||||
content: t('googleDataProtection.cannotConnectGoogle.content'),
|
||||
okText: t('googleDataProtection.understood'),
|
||||
title: t('googleDataProtection.cannotConnectGoogle.title'),
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
[currentProvider, currentModel, modal, t],
|
||||
);
|
||||
|
||||
const checkModelSwitch = useCallback(
|
||||
async (newProviderId: string, newModelId?: string): Promise<boolean> => {
|
||||
if (!isGoogleRestrictedProvider(newProviderId, newModelId)) return false;
|
||||
if (!state.hasEnabledGoogleTools) return false;
|
||||
|
||||
const toolNames = state.enabledGoogleToolIds
|
||||
.map((id) => {
|
||||
const toolInfo = GOOGLE_TOOL_IDENTIFIERS.find((t) => t === id);
|
||||
return toolInfo || id;
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
modal.warning({
|
||||
centered: true,
|
||||
content: t('googleDataProtection.cannotSwitchModel.content', { tools: toolNames }),
|
||||
okText: t('googleDataProtection.understood'),
|
||||
title: t('googleDataProtection.cannotSwitchModel.title'),
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
[state.hasEnabledGoogleTools, state.enabledGoogleToolIds, modal, t],
|
||||
);
|
||||
|
||||
const checkMessageSend = useCallback(async (): Promise<boolean> => {
|
||||
if (!isGoogleRestrictedProvider(currentProvider, currentModel)) return true;
|
||||
if (!state.hasEnabledGoogleTools) return true;
|
||||
|
||||
const toolNames = state.enabledGoogleToolIds
|
||||
.map((id) => {
|
||||
const toolInfo = GOOGLE_TOOL_IDENTIFIERS.find((t) => t === id);
|
||||
return toolInfo || id;
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
modal.warning({
|
||||
centered: true,
|
||||
content: t('googleDataProtection.cannotSendMessage.content', { tools: toolNames }),
|
||||
okText: t('googleDataProtection.understood'),
|
||||
title: t('googleDataProtection.cannotSendMessage.title'),
|
||||
});
|
||||
|
||||
return false;
|
||||
}, [currentProvider, currentModel, state.hasEnabledGoogleTools, state.enabledGoogleToolIds, modal, t]);
|
||||
|
||||
/**
|
||||
* Scenario 4: Check if message history contains Google tool usage
|
||||
* Block sending if using restricted provider with Google tools in history
|
||||
* Returns false to block, true to allow
|
||||
*/
|
||||
const checkMessageHistoryForGoogleTools = useCallback(
|
||||
async (messages: UIChatMessage[] | undefined): Promise<boolean> => {
|
||||
if (!isGoogleRestrictedProvider(currentProvider, currentModel)) return true;
|
||||
|
||||
const googleToolsInHistory = findGoogleToolsInMessages(messages);
|
||||
if (googleToolsInHistory.length === 0) return true;
|
||||
|
||||
const toolNames = googleToolsInHistory.join(', ');
|
||||
|
||||
modal.warning({
|
||||
centered: true,
|
||||
content: t('googleDataProtection.cannotSendWithHistory.content', { tools: toolNames }),
|
||||
okText: t('googleDataProtection.understood'),
|
||||
title: t('googleDataProtection.cannotSendWithHistory.title'),
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
[currentProvider, currentModel, modal, t],
|
||||
);
|
||||
|
||||
return {
|
||||
checkGoogleToolConnect,
|
||||
checkMessageHistoryForGoogleTools,
|
||||
checkMessageSend,
|
||||
checkModelSwitch,
|
||||
state,
|
||||
};
|
||||
};
|
||||
@@ -116,6 +116,19 @@ export default {
|
||||
'defaultAgent.model.desc': 'Default model used when creating a new Agent',
|
||||
'defaultAgent.model.title': 'Model',
|
||||
'defaultAgent.title': 'Default Agent Settings',
|
||||
'googleDataProtection.cannotConnectGoogle.content':
|
||||
'You are currently using Grok or DeepSeek as your model provider. Due to Google\'s data protection policy, Google services (Gmail, Calendar, Drive, Sheets, Docs) cannot be used with these providers. Please switch to a different model provider first.',
|
||||
'googleDataProtection.cannotConnectGoogle.title': 'Cannot Connect Google Services',
|
||||
'googleDataProtection.cannotSendMessage.content':
|
||||
'Your current configuration uses Google tools ({{tools}}) with a restricted AI provider. Due to Google\'s data protection policy, this combination is not allowed. Please switch to a different model provider or disable the Google tools.',
|
||||
'googleDataProtection.cannotSendMessage.title': 'Cannot Send Message',
|
||||
'googleDataProtection.cannotSendWithHistory.content':
|
||||
'This conversation contains data from Google tools ({{tools}}). Due to Google\'s data protection policy, you cannot continue this conversation with Grok or DeepSeek. Please switch to a different model provider.',
|
||||
'googleDataProtection.cannotSendWithHistory.title': 'Cannot Continue Conversation',
|
||||
'googleDataProtection.cannotSwitchModel.content':
|
||||
'You have Google tools enabled ({{tools}}). Due to Google\'s data protection policy, you cannot use Grok or DeepSeek with Google services. Please disable the Google tools first.',
|
||||
'googleDataProtection.cannotSwitchModel.title': 'Cannot Switch to This Provider',
|
||||
'googleDataProtection.understood': 'I Understand',
|
||||
'group.aiConfig': 'Agent',
|
||||
'group.common': 'General',
|
||||
'group.profile': 'Account',
|
||||
|
||||
@@ -232,6 +232,7 @@ describe('execAgent', () => {
|
||||
})
|
||||
.returning();
|
||||
|
||||
// @ts-ignore
|
||||
const [thread] = await serverDB
|
||||
.insert(threads)
|
||||
.values({
|
||||
|
||||
@@ -84,7 +84,50 @@ export const userMemoryRouter = router({
|
||||
|
||||
// ============ Persona ============
|
||||
getPersona: userMemoryProcedure.query(async () => {
|
||||
return { content: '', summary: '' };
|
||||
return {
|
||||
content: `Arvin is a product-and-research hybrid founder who consistently turns hands-on engineering reality into measurable, programmatically verifiable evaluation artifacts—then publishes enough tooling and evidence for others to reproduce, audit, and improve the work.
|
||||
|
||||
## Core profile
|
||||
|
||||
**Builder with a design-engineering spine**
|
||||
|
||||
He operates with an interaction-design-engineer mindset: translating ambiguity into usable workflows, implementable interfaces, and ship-ready systems. His training and instincts emphasize usability, clarity, and end-to-end experience rather than purely theoretical "model talk."
|
||||
|
||||
**Empirical evaluator of agentic systems**
|
||||
|
||||
Arvin's defining trait is verification-driven rigor. He prefers realistic tasks with programmatic checks over vibes-based comparisons, and he pushes for artifacts (benchmarks, validation scripts, traces) that make claims testable and disagreements resolvable.
|
||||
|
||||
**Open-source organizer and maintainer**
|
||||
|
||||
He does not just release code; he runs an ecosystem. That includes repository operations, PR review norms, documentation expectations, attribution/branding enforcement, and public-facing communication that keeps collaboration scalable and the project coherent.
|
||||
|
||||
**Operator who handles edge-cases**
|
||||
|
||||
Arvin is willing to do the unglamorous work required for credibility: user support, compliance-style requests, takedowns, payment/refund workflows, and incident handling. The consistent pattern is ownership of the full lifecycle, not only the headline features.
|
||||
|
||||
## Working style
|
||||
- **Instrumentation-minded:** if something cannot be measured, reproduced, or verified, it is not "done" yet.
|
||||
- **Transparency as a strategy:** he publishes methodology and artifacts to invite scrutiny and community compounding.
|
||||
- **High-agency pragmatism:** he would rather run the evaluation, pay the cost, and ship the evidence than debate hypotheticals.
|
||||
- **Comfort across levels:** he moves fluidly from partnership/comms and governance decisions down to repo hygiene and implementation details.
|
||||
|
||||
## Values and motivations
|
||||
- **Truth-seeking through construction:** learning comes from building systems and letting reality push back.
|
||||
- **Community compounding:** work is designed so others can extend it—auditable, modular, PR-friendly.
|
||||
- **Legibility and fairness:** clear rules, clear attribution, clear verification—reducing ambiguity that later becomes conflict.
|
||||
|
||||
## Strengths that stand out
|
||||
- **Systems thinking across product → research → operations**
|
||||
- **Credibility through receipts (reproducible artifacts and evidence)**
|
||||
- **Pragmatic leadership with strong follow-through**
|
||||
|
||||
## Likely pressure points
|
||||
- **Context-switching load:** founder + maintainer + operator + researcher can fragment attention and increase burnout risk.
|
||||
- **Social debt from enforcement:** branding/attribution and takedown decisions are necessary but draining.
|
||||
- **Rigor overhead:** a high verification bar is a competitive advantage, but it must be scoped to avoid slowing iteration unnecessarily.`,
|
||||
summary:
|
||||
'A product-and-research hybrid founder who turns hands-on engineering into verifiable artifacts, then publishes tooling for others to reproduce, audit, and improve.',
|
||||
};
|
||||
}),
|
||||
|
||||
getPreferences: userMemoryProcedure.query(async ({ ctx }) => {
|
||||
|
||||
@@ -65,7 +65,7 @@ export interface SidebarUIAction {
|
||||
/**
|
||||
* Update group sort order
|
||||
*/
|
||||
updateGroupSort: (items: SessionGroupItem[]) => Promise<void>;
|
||||
updateGroupSort: (items: Pick<SessionGroupItem, 'id'>[]) => Promise<void>;
|
||||
|
||||
// ========== UI State Actions ==========
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user