mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 19:47:43 +01:00
fix: Solves the issue of bottom toolbar overlapping content in mobile Safari (#448)
* style: adjust style * fix: close Splash
This commit is contained in:
2
.env
2
.env
@@ -1,3 +1,5 @@
|
|||||||
COCO_SERVER_URL=http://localhost:9000 #https://coco.infini.cloud #http://localhost:9000
|
COCO_SERVER_URL=http://localhost:9000 #https://coco.infini.cloud #http://localhost:9000
|
||||||
|
|
||||||
COCO_WEBSOCKET_URL=ws://localhost:9000/ws #wss://coco.infini.cloud/ws #ws://localhost:9000/ws
|
COCO_WEBSOCKET_URL=ws://localhost:9000/ws #wss://coco.infini.cloud/ws #ws://localhost:9000/ws
|
||||||
|
|
||||||
|
TAURI_DEV_HOST=0.0.0.0
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
import { CircleX, MoveRight } from "lucide-react";
|
import { CircleX, MoveRight } from "lucide-react";
|
||||||
import { useMount } from "ahooks";
|
import { useMount } from "ahooks";
|
||||||
|
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
import { useConnectStore } from "@/stores/connectStore";
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
import { useThemeStore } from "@/stores/themeStore";
|
import { useThemeStore } from "@/stores/themeStore";
|
||||||
|
|||||||
@@ -37,13 +37,21 @@ export const CallTools = ({ Detail, ChunkData, loading }: CallToolsProps) => {
|
|||||||
className="inline-flex items-center gap-2 px-2 py-1 rounded-xl transition-colors border border-[#E6E6E6] dark:border-[#272626]"
|
className="inline-flex items-center gap-2 px-2 py-1 rounded-xl transition-colors border border-[#E6E6E6] dark:border-[#272626]"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader className="w-4 h-4 animate-spin text-[#1990FF]" />
|
<>
|
||||||
|
<Loader className="w-4 h-4 animate-spin text-[#1990FF]" />
|
||||||
|
<span className="text-xs text-[#999999] italic">
|
||||||
|
{t(`assistant.message.steps.${ChunkData?.chunk_type}`)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Hammer className="w-4 h-4 text-[#38C200]" />
|
<>
|
||||||
|
<Hammer className="w-4 h-4 text-[#38C200]" />
|
||||||
|
<span className="text-xs text-[#999999]">
|
||||||
|
{t(`assistant.message.steps.${ChunkData?.chunk_type}`)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<span className="text-xs text-[#999999] italic">
|
|
||||||
{t(`assistant.message.steps.${ChunkData?.chunk_type}`)}
|
|
||||||
</span>
|
|
||||||
{isThinkingExpanded ? (
|
{isThinkingExpanded ? (
|
||||||
<ChevronUp className="w-4 h-4" />
|
<ChevronUp className="w-4 h-4" />
|
||||||
) : (
|
) : (
|
||||||
@@ -53,11 +61,11 @@ export const CallTools = ({ Detail, ChunkData, loading }: CallToolsProps) => {
|
|||||||
{isThinkingExpanded && (
|
{isThinkingExpanded && (
|
||||||
<div className="pl-2 border-l-2 border-[#e5e5e5] dark:border-[#4e4e56]">
|
<div className="pl-2 border-l-2 border-[#e5e5e5] dark:border-[#4e4e56]">
|
||||||
<div className="text-[#8b8b8b] dark:text-[#a6a6a6] space-y-2">
|
<div className="text-[#8b8b8b] dark:text-[#a6a6a6] space-y-2">
|
||||||
<Markdown
|
<Markdown
|
||||||
content={Data || ""}
|
content={Data || ""}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onDoubleClickCapture={() => {}}
|
onDoubleClickCapture={() => {}}
|
||||||
/>
|
/>
|
||||||
{/* {Data?.split("\n").map(
|
{/* {Data?.split("\n").map(
|
||||||
(paragraph, idx) =>
|
(paragraph, idx) =>
|
||||||
paragraph.trim() && (
|
paragraph.trim() && (
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
--color-fg-subtle: #6e7781;
|
--color-fg-subtle: #6e7781;
|
||||||
--color-canvas-default: transparent;
|
--color-canvas-default: transparent;
|
||||||
--color-canvas-subtle: #f6f8fa;
|
--color-canvas-subtle: #f6f8fa;
|
||||||
--color-border-default: #d0d7de;
|
--color-border-default: #8b949e;
|
||||||
--color-border-muted: hsla(210, 18%, 87%, 1);
|
--color-border-muted: hsla(210, 18%, 87%, 1);
|
||||||
--color-neutral-muted: rgba(175, 184, 193, 0.2);
|
--color-neutral-muted: rgba(175, 184, 193, 0.2);
|
||||||
--color-accent-fg: #0969da;
|
--color-accent-fg: #0969da;
|
||||||
@@ -302,9 +302,12 @@
|
|||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
display: block;
|
display: block;
|
||||||
width: max-content;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
margin: 16px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body td,
|
.markdown-body td,
|
||||||
@@ -668,23 +671,44 @@
|
|||||||
|
|
||||||
.markdown-body table th {
|
.markdown-body table th {
|
||||||
font-weight: var(--base-text-weight-semibold, 600);
|
font-weight: var(--base-text-weight-semibold, 600);
|
||||||
|
background-color: var(--color-canvas-subtle);
|
||||||
|
text-align: left;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body table th,
|
.markdown-body table th,
|
||||||
.markdown-body table td {
|
.markdown-body table td {
|
||||||
padding: 6px 13px;
|
padding: 6px 13px;
|
||||||
border: 1px solid var(--color-border-default);
|
border: 1px solid var(--color-border-default);
|
||||||
|
border-width: 1.5px;
|
||||||
|
line-height: 1.5;
|
||||||
|
vertical-align: middle;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body table td br,
|
||||||
|
.markdown-body table th br {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body table tr {
|
.markdown-body table tr {
|
||||||
background-color: var(--color-canvas-default);
|
background-color: var(--color-canvas-default);
|
||||||
border-top: 1px solid var(--color-border-muted);
|
border-top: 1px solid var(--color-border-muted);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body table tr:nth-child(2n) {
|
.markdown-body table tr:nth-child(2n) {
|
||||||
background-color: var(--color-canvas-subtle);
|
background-color: var(--color-canvas-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-body table tr:hover {
|
||||||
|
background-color: rgba(175, 184, 193, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.markdown-body table img {
|
.markdown-body table img {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,9 +142,6 @@ export default function ChatInput({
|
|||||||
const setModifierKeyPressed = useShortcutsStore((state) => {
|
const setModifierKeyPressed = useShortcutsStore((state) => {
|
||||||
return state.setModifierKeyPressed;
|
return state.setModifierKeyPressed;
|
||||||
});
|
});
|
||||||
const setVisibleStartPage = useConnectStore((state) => {
|
|
||||||
return state.setVisibleStartPage;
|
|
||||||
});
|
|
||||||
const setBlurred = useAppStore((state) => state.setBlurred);
|
const setBlurred = useAppStore((state) => state.setBlurred);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -170,8 +167,6 @@ export default function ChatInput({
|
|||||||
}, [isChatMode, textareaRef, inputRef]);
|
}, [isChatMode, textareaRef, inputRef]);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
setVisibleStartPage(false);
|
|
||||||
|
|
||||||
const trimmedValue = inputValue.trim();
|
const trimmedValue = inputValue.trim();
|
||||||
console.log("handleSubmit", trimmedValue, disabled);
|
console.log("handleSubmit", trimmedValue, disabled);
|
||||||
if (trimmedValue && !disabled) {
|
if (trimmedValue && !disabled) {
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export function useChatActions(
|
|||||||
const { connected } = useChatStore();
|
const { connected } = useChatStore();
|
||||||
const sourceDataIds = useSearchStore((state) => state.sourceDataIds);
|
const sourceDataIds = useSearchStore((state) => state.sourceDataIds);
|
||||||
const MCPIds = useSearchStore((state) => state.MCPIds);
|
const MCPIds = useSearchStore((state) => state.MCPIds);
|
||||||
|
const setVisibleStartPage = useConnectStore((state) => {
|
||||||
|
return state.setVisibleStartPage;
|
||||||
|
});
|
||||||
|
|
||||||
const [keyword, setKeyword] = useState("");
|
const [keyword, setKeyword] = useState("");
|
||||||
|
|
||||||
@@ -132,6 +135,7 @@ export function useChatActions(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("chatHistory:", error);
|
console.error("chatHistory:", error);
|
||||||
}
|
}
|
||||||
|
setVisibleStartPage(false);
|
||||||
},
|
},
|
||||||
[currentServiceId, setActiveChat]
|
[currentServiceId, setActiveChat]
|
||||||
);
|
);
|
||||||
@@ -208,6 +212,7 @@ export function useChatActions(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("createNewChat:", error);
|
console.error("createNewChat:", error);
|
||||||
}
|
}
|
||||||
|
setVisibleStartPage(false);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
currentServiceId,
|
currentServiceId,
|
||||||
@@ -291,6 +296,7 @@ export function useChatActions(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("sendMessage:", error);
|
console.error("sendMessage:", error);
|
||||||
}
|
}
|
||||||
|
setVisibleStartPage(false);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
currentServiceId,
|
currentServiceId,
|
||||||
@@ -353,6 +359,7 @@ export function useChatActions(
|
|||||||
console.error("open_session_chat:", error);
|
console.error("open_session_chat:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
setVisibleStartPage(false);
|
||||||
},
|
},
|
||||||
[currentServiceId]
|
[currentServiceId]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ export function useMessageHandler(
|
|||||||
if (chunkData.chunk_type === "query_intent") {
|
if (chunkData.chunk_type === "query_intent") {
|
||||||
handlers.deal_query_intent(chunkData);
|
handlers.deal_query_intent(chunkData);
|
||||||
} else if (chunkData.chunk_type === "tools") {
|
} else if (chunkData.chunk_type === "tools") {
|
||||||
console.log("tools", chunkData);
|
|
||||||
handlers.deal_tools(chunkData);
|
handlers.deal_tools(chunkData);
|
||||||
} else if (chunkData.chunk_type === "fetch_source") {
|
} else if (chunkData.chunk_type === "fetch_source") {
|
||||||
handlers.deal_fetch_source(chunkData);
|
handlers.deal_fetch_source(chunkData);
|
||||||
|
|||||||
32
src/hooks/useViewportHeight.ts
Normal file
32
src/hooks/useViewportHeight.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Hook for monitoring window size changes and setting viewport height variables
|
||||||
|
* Solves the issue of bottom toolbar overlapping content in mobile Safari
|
||||||
|
*/
|
||||||
|
export function useViewportHeight() {
|
||||||
|
useEffect(() => {
|
||||||
|
// Set viewport height variable
|
||||||
|
const setViewportHeight = () => {
|
||||||
|
// Get actual visible viewport height
|
||||||
|
const vh = window.innerHeight * 0.01;
|
||||||
|
// Set CSS variable --vh
|
||||||
|
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
setViewportHeight();
|
||||||
|
|
||||||
|
// Listen for window resize events
|
||||||
|
window.addEventListener('resize', setViewportHeight);
|
||||||
|
|
||||||
|
// Listen for device orientation changes
|
||||||
|
window.addEventListener('orientationchange', setViewportHeight);
|
||||||
|
|
||||||
|
// Cleanup function
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', setViewportHeight);
|
||||||
|
window.removeEventListener('orientationchange', setViewportHeight);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ export default function useWebSocket({
|
|||||||
// web
|
// web
|
||||||
const { readyState, connect, disconnect } = useWebSocketAHook(
|
const { readyState, connect, disconnect } = useWebSocketAHook(
|
||||||
// "wss://coco.infini.cloud/ws",
|
// "wss://coco.infini.cloud/ws",
|
||||||
// "ws://localhost:9000/ws",
|
//"ws://localhost:9000/ws",
|
||||||
isTauri ? "" : endpoint_websocket,
|
isTauri ? "" : endpoint_websocket,
|
||||||
{
|
{
|
||||||
manual: true,
|
manual: true,
|
||||||
@@ -82,7 +82,7 @@ export default function useWebSocket({
|
|||||||
while (messageQueue.current.length > 0) {
|
while (messageQueue.current.length > 0) {
|
||||||
const msg = messageQueue.current.shift();
|
const msg = messageQueue.current.shift();
|
||||||
if (msg) {
|
if (msg) {
|
||||||
console.log("Processing message:", msg.substring(0, 100));
|
// console.log("Processing message:", msg.substring(0, 100));
|
||||||
processMessage(msg);
|
processMessage(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import SearchChat from "@/components/SearchChat";
|
|||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
|
import { useModifierKeyPress } from "@/hooks/useModifierKeyPress";
|
||||||
|
import useEscape from "@/hooks/useEscape";
|
||||||
|
import { useViewportHeight } from "@/hooks/useViewportHeight";
|
||||||
|
|
||||||
import "@/i18n";
|
import "@/i18n";
|
||||||
import "@/web.css";
|
import "@/web.css";
|
||||||
import { useModifierKeyPress } from "@/hooks/useModifierKeyPress";
|
|
||||||
import useEscape from "@/hooks/useEscape";
|
|
||||||
|
|
||||||
interface WebAppProps {
|
interface WebAppProps {
|
||||||
headers?: Record<string, unknown>;
|
headers?: Record<string, unknown>;
|
||||||
@@ -71,8 +72,8 @@ function WebApp({
|
|||||||
const [isChatMode, setIsChatMode] = useState(false);
|
const [isChatMode, setIsChatMode] = useState(false);
|
||||||
|
|
||||||
useEscape();
|
useEscape();
|
||||||
|
|
||||||
useModifierKeyPress();
|
useModifierKeyPress();
|
||||||
|
useViewportHeight();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -82,7 +83,7 @@ function WebApp({
|
|||||||
style={{
|
style={{
|
||||||
maxWidth: `${width}px`,
|
maxWidth: `${width}px`,
|
||||||
width: `100vw`,
|
width: `100vw`,
|
||||||
height: isMobile ? "100vh" : `${height}px`,
|
height: isMobile ? "calc(var(--vh, 1vh) * 100)" : `${height}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
|
|||||||
11
src/web.css
11
src/web.css
@@ -398,11 +398,6 @@ body,
|
|||||||
sup {
|
sup {
|
||||||
top: -0.5em;
|
top: -0.5em;
|
||||||
}
|
}
|
||||||
table {
|
|
||||||
text-indent: 0;
|
|
||||||
border-color: inherit;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
optgroup,
|
optgroup,
|
||||||
@@ -471,7 +466,7 @@ body,
|
|||||||
figure,
|
figure,
|
||||||
p,
|
p,
|
||||||
pre {
|
pre {
|
||||||
margin: 0;
|
margin: inherit;
|
||||||
}
|
}
|
||||||
fieldset {
|
fieldset {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -484,8 +479,8 @@ body,
|
|||||||
ul,
|
ul,
|
||||||
menu {
|
menu {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: inherit;
|
||||||
padding: 0;
|
padding: inherit;
|
||||||
}
|
}
|
||||||
dialog {
|
dialog {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
const packageJson = {
|
const packageJson = {
|
||||||
name: "@infinilabs/search-chat",
|
name: "@infinilabs/search-chat",
|
||||||
version: "1.1.10",
|
version: "1.1.12",
|
||||||
main: "index.js",
|
main: "index.js",
|
||||||
module: "index.js",
|
module: "index.js",
|
||||||
type: "module",
|
type: "module",
|
||||||
|
|||||||
Reference in New Issue
Block a user