Skip to content

Release v1.12.0#957

Merged
ItzCrazyKns merged 208 commits intomasterfrom
canary
Dec 27, 2025
Merged

Release v1.12.0#957
ItzCrazyKns merged 208 commits intomasterfrom
canary

Conversation

@ItzCrazyKns
Copy link
Owner

@ItzCrazyKns ItzCrazyKns commented Dec 27, 2025

Summary by cubic

Release v1.12.0 rebuilds search and streaming with a new agent-driven pipeline, source-based search, and smart widgets for richer answers and a smoother UI. It includes breaking API changes (focusMode → sources) and a new message/storage schema.

  • New Features

    • Source-based search: pick Web, Academic, or Discussions; “Quality” mode enabled.
    • New search agent: classifier + researcher (web/academic/social/uploads/scrape URL) + writer, with live streaming and a reconnect endpoint.
    • Widgets: Weather, Stock, and Calculation render inline when relevant.
    • Media agents: image and video search migrated to the new agents.
    • UI: Sources picker replaces Focus; AssistantSteps timeline; better code blocks with copy; improved attachments; Library shows sources and files.
    • Settings: shows app version; measurement unit preference.
    • Infra: docker-compose supports local build; Next config adds NEXT_PUBLIC_VERSION and output tracing for canvas.
  • Migration

    • API: /api/search now requires sources: ["web" | "academic" | "discussions"]; optimizationMode supports "quality". Update clients from focusMode to sources (docs updated).
    • Data: run migration 0002; messages now store responseBlocks and statuses (block-based streaming).
    • UI/Config: Focus modes removed—use Sources selector. Measurement unit key supported as measureUnit or measurementUnit.
    • Docker: uploads volume removed from examples; update run commands if you previously mounted perplexica-uploads.
    • Providers: removed Aiml, DeepSeek, and LM Studio. Remove their configs if present.

Written for commit 0987ee4. Summary will update automatically on new commits.

ItzCrazyKns and others added 27 commits December 24, 2025 15:48
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
…ture

feat: improve search architecture, write custom API classes (remove langchain), add deep research & more
…fore media search to allow component refresh
@ItzCrazyKns ItzCrazyKns marked this pull request as ready for review December 27, 2025 15:21
@ItzCrazyKns ItzCrazyKns merged commit b450d0e into master Dec 27, 2025
7 checks passed
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 issues found across 132 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/lib/agents/search/types.ts">

<violation number="1" location="src/lib/agents/search/types.ts:106">
P2: The generic type parameter `TSchema` is defined but not used for the `schema` property. This breaks type safety since `z.infer&lt;TSchema&gt;` in `execute` won&#39;t be connected to the actual `schema` type.</violation>
</file>

<file name="src/components/Navbar.tsx">

<violation number="1" location="src/components/Navbar.tsx:32">
P1: Missing optional chaining could cause runtime error if `sections` is empty. The `exportAsPDF` function correctly uses `sections[0]?.message?.createdAt`, but `exportAsMarkdown` lacks this safety check. If a user clicks export before any messages exist, this will crash.</violation>
</file>

<file name="src/lib/hooks/useChat.tsx">

<violation number="1" location="src/lib/hooks/useChat.tsx:570">
P1: Stale closure: `message.responseBlocks` references the initial empty array, not the updated state. Use `messagesRef.current` to get the current message state with populated responseBlocks.</violation>

<violation number="2" location="src/lib/hooks/useChat.tsx:762">
P1: History slicing mismatch: `rewrite` already slices chatHistory with `index * 2`, but `sendMessage` slices again with just `messageIndex`. Either multiply by 2 here, or remove the slicing since `rewrite` already handled it.</violation>
</file>

<file name="docs/installation/UPDATING.md">

<violation number="1" location="docs/installation/UPDATING.md:13">
P1: Documentation removes uploads volume mount without migration guidance. Existing users with files in the `perplexica-uploads` volume will lose access to their previously uploaded files when following these update instructions. Consider adding a note explaining that uploads are now stored under the data directory, and provide instructions for users to migrate existing uploads (e.g., copy files from old volume to new location).</violation>
</file>

<file name="src/lib/agents/search/researcher/actions/webSearch.ts">

<violation number="1" location="src/lib/agents/search/researcher/actions/webSearch.ts:9">
P2: Schema allows unlimited queries but code silently truncates to 3. Consider adding `.max(3)` to provide proper validation and avoid silent truncation that could surprise API consumers.</violation>
</file>

<file name="src/lib/agents/search/classifier.ts">

<violation number="1" location="src/lib/agents/search/classifier.ts:37">
P2: The `enabledSources` parameter from `ClassifierInput` is received but never used. The classifier might recommend searches for disabled sources (e.g., academic search when it&#39;s not in the enabled sources list). Consider either passing `enabledSources` to the prompt so the LLM can make informed decisions, or removing it from the type if filtering is intentionally handled downstream.</violation>
</file>

<file name="src/components/MessageBox.tsx">

<violation number="1" location="src/components/MessageBox.tsx:80">
P2: This returns a string with literal backtick characters, which will render the backticks as visible text instead of styling the code. Consider returning a JSX element like `&lt;code key={state.key}&gt;{node.text}&lt;/code&gt;` for proper inline code rendering.</violation>
</file>

<file name="src/components/AssistantSteps.tsx">

<violation number="1" location="src/components/AssistantSteps.tsx:74">
P1: Missing `isLast` in useEffect dependency array. The effect references `isLast` in both conditions but doesn&#39;t include it as a dependency, which can cause stale closure bugs.</violation>

<violation number="2" location="src/components/AssistantSteps.tsx:183">
P2: `new URL(url)` can throw if the URL is malformed. Consider wrapping in try-catch to prevent runtime errors.</violation>

<violation number="3" location="src/components/AssistantSteps.tsx:192">
P2: Add `rel=&quot;noopener noreferrer&quot;` when using `target=&quot;_blank&quot;` to prevent potential security vulnerabilities from reverse tabnabbing.</violation>
</file>

<file name="README.md">

<violation number="1" location="README.md:86">
P3: Documentation inconsistency: The note below this command still references &quot;The `-v` flags&quot; (plural) and &quot;your data and uploaded files&quot;, but the uploads volume was removed. If uploads are now stored in the data directory, consider updating the note to say &quot;The `-v` flag creates a persistent volume for your data&quot; for consistency.</violation>
</file>

<file name="src/lib/agents/search/researcher/actions/registry.ts">

<violation number="1" location="src/lib/agents/search/researcher/actions/registry.ts:88">
P1: Race condition: Results are pushed in resolution order, not input order. Since `Promise.all` runs concurrently, faster-completing actions will push first, causing non-deterministic result ordering. Use `Promise.all`&#39;s return value directly to preserve order.</violation>
</file>

<file name="src/components/MessageRenderer/CodeBlock/index.tsx">

<violation number="1" location="src/components/MessageRenderer/CodeBlock/index.tsx:36">
P2: `navigator.clipboard.writeText()` returns a Promise that isn&#39;t awaited. The success state is set before the copy actually completes, and clipboard failures (e.g., permission denied) aren&#39;t handled.</violation>
</file>

<file name="src/components/MessageActions/Copy.tsx">

<violation number="1" location="src/components/MessageActions/Copy.tsx:30">
P1: Calling `.startsWith()` on `s.metadata.url` without checking if `url` is defined may cause a TypeError at runtime. The `Chunk.metadata` type is `Record&lt;string, any&gt;`, so `url` could be undefined. Consider adding a null check before accessing this property.</violation>
</file>

<file name="src/components/Widgets/Weather.tsx">

<violation number="1" location="src/components/Widgets/Weather.tsx:230">
P1: `getMeasurementUnit()` accesses `localStorage` which is not available during SSR. This will cause a `ReferenceError` during server-side rendering. Move this call inside a `useEffect` or `useState` with lazy initialization to ensure it only runs on the client.</violation>
</file>

<file name="src/app/api/chat/route.ts">

<violation number="1" location="src/app/api/chat/route.ts:39">
P2: Sources are validated as generic strings but cast to `SearchSources[]`. Consider validating with `z.enum([&#39;web&#39;, &#39;discussions&#39;, &#39;academic&#39;])` to ensure only valid sources are accepted.</violation>

<violation number="2" location="src/app/api/chat/route.ts:235">
P1: Potential double `writer.close()` call will throw if the stream ends before abort. The abort handler should guard against calling close on an already-closed writer.</violation>
</file>

Reply to cubic to teach it or ask questions. Tag @cubic-dev-ai to re-run a review.

TSchema extends z.ZodObject<any> = z.ZodObject<any>,
> {
name: string;
schema: z.ZodObject<any>;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The generic type parameter TSchema is defined but not used for the schema property. This breaks type safety since z.infer<TSchema> in execute won't be connected to the actual schema type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/agents/search/types.ts, line 106:

<comment>The generic type parameter `TSchema` is defined but not used for the `schema` property. This breaks type safety since `z.infer&lt;TSchema&gt;` in `execute` won&#39;t be connected to the actual `schema` type.</comment>

<file context>
@@ -0,0 +1,122 @@
+  TSchema extends z.ZodObject&lt;any&gt; = z.ZodObject&lt;any&gt;,
+&gt; {
+  name: string;
+  schema: z.ZodObject&lt;any&gt;;
+  getToolDescription: (config: { mode: SearchAgentConfig[&#39;mode&#39;] }) =&gt; string;
+  getDescription: (config: { mode: SearchAgentConfig[&#39;mode&#39;] }) =&gt; string;
</file context>
Fix with Cubic

const exportAsMarkdown = (sections: Section[], title: string) => {
const date = new Date(
sections[0]?.userMessage?.createdAt || Date.now(),
sections[0].message.createdAt || Date.now(),
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing optional chaining could cause runtime error if sections is empty. The exportAsPDF function correctly uses sections[0]?.message?.createdAt, but exportAsMarkdown lacks this safety check. If a user clicks export before any messages exist, this will crash.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/Navbar.tsx, line 32:

<comment>Missing optional chaining could cause runtime error if `sections` is empty. The `exportAsPDF` function correctly uses `sections[0]?.message?.createdAt`, but `exportAsMarkdown` lacks this safety check. If a user clicks export before any messages exist, this will crash.</comment>

<file context>
@@ -28,35 +29,41 @@ const downloadFile = (filename: string, content: string, type: string) =&gt; {
 const exportAsMarkdown = (sections: Section[], title: string) =&gt; {
   const date = new Date(
-    sections[0]?.userMessage?.createdAt || Date.now(),
+    sections[0].message.createdAt || Date.now(),
   ).toLocaleString();
   let md = `# 💬 Chat Export: ${title}\n\n`;
</file context>
Suggested change
sections[0].message.createdAt || Date.now(),
sections[0]?.message?.createdAt || Date.now(),
Fix with Cubic

history: rewrite
? chatHistory.slice(0, messageIndex === -1 ? undefined : messageIndex)
: chatHistory,
? chatHistory.current.slice(
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: History slicing mismatch: rewrite already slices chatHistory with index * 2, but sendMessage slices again with just messageIndex. Either multiply by 2 here, or remove the slicing since rewrite already handled it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/hooks/useChat.tsx, line 762:

<comment>History slicing mismatch: `rewrite` already slices chatHistory with `index * 2`, but `sendMessage` slices again with just `messageIndex`. Either multiply by 2 here, or remove the slicing since `rewrite` already handled it.</comment>

<file context>
@@ -678,11 +756,14 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) =&gt; {
         history: rewrite
-          ? chatHistory.slice(0, messageIndex === -1 ? undefined : messageIndex)
-          : chatHistory,
+          ? chatHistory.current.slice(
+              0,
+              messageIndex === -1 ? undefined : messageIndex,
</file context>
Fix with Cubic

if (data.type === 'researchComplete') {
setResearchEnded(true);
if (
message.responseBlocks.find(
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Stale closure: message.responseBlocks references the initial empty array, not the updated state. Use messagesRef.current to get the current message state with populated responseBlocks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/hooks/useChat.tsx, line 570:

<comment>Stale closure: `message.responseBlocks` references the initial empty array, not the updated state. Use `messagesRef.current` to get the current message state with populated responseBlocks.</comment>

<file context>
@@ -520,95 +547,118 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) =&gt; {
+      if (data.type === &#39;researchComplete&#39;) {
+        setResearchEnded(true);
+        if (
+          message.responseBlocks.find(
+            (b) =&gt; b.type === &#39;source&#39; &amp;&amp; b.data.length &gt; 0,
+          )
</file context>
Suggested change
message.responseBlocks.find(
messagesRef.current.find((m) => m.messageId === messageId)?.responseBlocks.find(
Fix with Cubic

docker stop perplexica
docker rm perplexica
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:latest
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data --name perplexica itzcrazykns1337/perplexica:latest
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Documentation removes uploads volume mount without migration guidance. Existing users with files in the perplexica-uploads volume will lose access to their previously uploaded files when following these update instructions. Consider adding a note explaining that uploads are now stored under the data directory, and provide instructions for users to migrate existing uploads (e.g., copy files from old volume to new location).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/installation/UPDATING.md, line 13:

<comment>Documentation removes uploads volume mount without migration guidance. Existing users with files in the `perplexica-uploads` volume will lose access to their previously uploaded files when following these update instructions. Consider adding a note explaining that uploads are now stored under the data directory, and provide instructions for users to migrate existing uploads (e.g., copy files from old volume to new location).</comment>

<file context>
@@ -10,7 +10,7 @@ Simply pull the latest image and restart your container:
 docker stop perplexica
 docker rm perplexica
-docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:latest
+docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data --name perplexica itzcrazykns1337/perplexica:latest

</file context>


</details>

<a href="https://www.cubic.dev/action/fix/violation/42015f61-1c26-4787-a19f-da595ba3b019" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://cubic.dev/buttons/fix-with-cubic-dark.svg">
    <source media="(prefers-color-scheme: light)" srcset="https://cubic.dev/buttons/fix-with-cubic-light.svg">
    <img alt="Fix with Cubic" src="https://cubic.dev/buttons/fix-with-cubic-dark.svg">
  </picture>
</a>

<button
className="absolute top-2 right-2 p-1"
onClick={() => {
navigator.clipboard.writeText(children as string);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: navigator.clipboard.writeText() returns a Promise that isn't awaited. The success state is set before the copy actually completes, and clipboard failures (e.g., permission denied) aren't handled.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/MessageRenderer/CodeBlock/index.tsx, line 36:

<comment>`navigator.clipboard.writeText()` returns a Promise that isn&#39;t awaited. The success state is set before the copy actually completes, and clipboard failures (e.g., permission denied) aren&#39;t handled.</comment>

<file context>
@@ -0,0 +1,64 @@
+      &lt;button
+        className=&quot;absolute top-2 right-2 p-1&quot;
+        onClick={() =&gt; {
+          navigator.clipboard.writeText(children as string);
+          setCopied(true);
+          setTimeout(() =&gt; setCopied(false), 2000);
</file context>
Fix with Cubic

.flat()
.map(
(s, i) =>
`[${i + 1}] ${s.metadata.url.startsWith('file_id://') ? s.metadata.fileName || 'Uploaded File' : s.metadata.url}`,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Calling .startsWith() on s.metadata.url without checking if url is defined may cause a TypeError at runtime. The Chunk.metadata type is Record<string, any>, so url could be undefined. Consider adding a null check before accessing this property.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/MessageActions/Copy.tsx, line 30:

<comment>Calling `.startsWith()` on `s.metadata.url` without checking if `url` is defined may cause a TypeError at runtime. The `Chunk.metadata` type is `Record&lt;string, any&gt;`, so `url` could be undefined. Consider adding a null check before accessing this property.</comment>

<file context>
@@ -15,14 +16,31 @@ const Copy = ({
+                .flat()
+                .map(
+                  (s, i) =&gt;
+                    `[${i + 1}] ${s.metadata.url.startsWith(&#39;file_id://&#39;) ? s.metadata.fileName || &#39;Uploaded File&#39; : s.metadata.url}`,
+                )
+                .join(`\n`)}`
</file context>
Suggested change
`[${i + 1}] ${s.metadata.url.startsWith('file_id://') ? s.metadata.fileName || 'Uploaded File' : s.metadata.url}`,
`[${i + 1}] ${s.metadata.url?.startsWith('file_id://') ? s.metadata.fileName || 'Uploaded File' : s.metadata.url || 'Unknown Source'}`,
Fix with Cubic

timezone,
}: WeatherWidgetProps) => {
const [isDarkMode, setIsDarkMode] = useState(false);
const unit = getMeasurementUnit();
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: getMeasurementUnit() accesses localStorage which is not available during SSR. This will cause a ReferenceError during server-side rendering. Move this call inside a useEffect or useState with lazy initialization to ensure it only runs on the client.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/Widgets/Weather.tsx, line 230:

<comment>`getMeasurementUnit()` accesses `localStorage` which is not available during SSR. This will cause a `ReferenceError` during server-side rendering. Move this call inside a `useEffect` or `useState` with lazy initialization to ensure it only runs on the client.</comment>

<file context>
@@ -0,0 +1,422 @@
+  timezone,
+}: WeatherWidgetProps) =&gt; {
+  const [isDarkMode, setIsDarkMode] = useState(false);
+  const unit = getMeasurementUnit();
+  const isImperial = unit === &#39;imperial&#39;;
+  const tempUnitLabel = isImperial ? &#39;°F&#39; : &#39;°C&#39;;
</file context>
Fix with Cubic

message: 'Optimization mode must be one of: speed, balanced, quality',
}),
focusMode: z.string().min(1, 'Focus mode is required'),
sources: z.array(z.string()).optional().default([]),
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Sources are validated as generic strings but cast to SearchSources[]. Consider validating with z.enum(['web', 'discussions', 'academic']) to ensure only valid sources are accepted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/api/chat/route.ts, line 39:

<comment>Sources are validated as generic strings but cast to `SearchSources[]`. Consider validating with `z.enum([&#39;web&#39;, &#39;discussions&#39;, &#39;academic&#39;])` to ensure only valid sources are accepted.</comment>

<file context>
@@ -20,47 +20,25 @@ const messageSchema = z.object({
+    message: &#39;Optimization mode must be one of: speed, balanced, quality&#39;,
   }),
-  focusMode: z.string().min(1, &#39;Focus mode is required&#39;),
+  sources: z.array(z.string()).optional().default([]),
   history: z
-    .array(
</file context>
Fix with Cubic

Comment on lines +235 to +238
req.signal.addEventListener('abort', () => {
disconnect();
writer.close();
});
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Potential double writer.close() call will throw if the stream ends before abort. The abort handler should guard against calling close on an already-closed writer.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/api/chat/route.ts, line 235:

<comment>Potential double `writer.close()` call will throw if the stream ends before abort. The abort handler should guard against calling close on an already-closed writer.</comment>

<file context>
@@ -265,48 +135,107 @@ export const POST = async (req: Request) =&gt; {
+      query: body.message.content,
+    });
+
+    req.signal.addEventListener(&#39;abort&#39;, () =&gt; {
+      disconnect();
+      writer.close();
</file context>
Suggested change
req.signal.addEventListener('abort', () => {
disconnect();
writer.close();
});
req.signal.addEventListener('abort', () => {
disconnect();
writer.close().catch(() => {});
});
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant