From c076090ab80b65580dc4f78bc5a5d85c62e4add2 Mon Sep 17 00:00:00 2001 From: Jamie Rasmussen <112953339+jamie-rasmussen@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:16:51 -0600 Subject: [PATCH 1/8] refactor(ui): break import cycle (#2994) --- .../Browse2RootObjectVersionOutputOf.tsx | 4 ++-- .../Home/Browse2/SpanDetails.tsx | 3 ++- .../Home/Browse3/filters/common.ts | 3 +-- .../Browse3/pages/CallPage/CallSummary.tsx | 3 ++- .../Browse3/pages/CallPage/DataTableView.tsx | 7 +++++-- .../Browse3/pages/CallPage/ObjectViewer.tsx | 2 +- .../Home/Browse3/pages/CallPage/ValueView.tsx | 8 ++++++-- .../ExampleCompareSection.tsx | 19 +++++++++++++------ .../ScorecardSection/ScorecardSection.tsx | 15 +++++++++------ .../leaderboardConfigEditorHooks.ts | 3 +-- .../Browse3/pages/common/KeyValueTable.tsx | 3 ++- .../views/Leaderboard/LeaderboardGrid.tsx | 3 ++- .../Leaderboard/query/leaderboardQuery.ts | 3 +-- weave-js/src/react.tsx | 8 ++++++++ 14 files changed, 55 insertions(+), 29 deletions(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse2/Browse2RootObjectVersionOutputOf.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse2/Browse2RootObjectVersionOutputOf.tsx index ab2d35db341c..9ab172a5657c 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse2/Browse2RootObjectVersionOutputOf.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse2/Browse2RootObjectVersionOutputOf.tsx @@ -1,11 +1,11 @@ import Typography from '@mui/material/Typography'; -import {useNodeValue} from '@wandb/weave/react'; +import {parseRefMaybe, useNodeValue} from '@wandb/weave/react'; import React, {FC, useMemo} from 'react'; import {useParams} from 'react-router-dom'; import {callsTableFilter, callsTableNode, callsTableOpCounts} from './callTree'; import {Browse2RootObjectVersionItemParams} from './CommonLib'; -import {parseRefMaybe, SmallRef} from './SmallRef'; +import {SmallRef} from './SmallRef'; export const Browse2RootObjectVersionOutputOf: FC<{uri: string}> = ({uri}) => { const params = useParams(); diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse2/SpanDetails.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse2/SpanDetails.tsx index fbb0baa83d88..28f71fff4079 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse2/SpanDetails.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse2/SpanDetails.tsx @@ -4,6 +4,7 @@ import * as globals from '@wandb/weave/common/css/globals.styles'; import * as _ from 'lodash'; import React, {FC} from 'react'; +import {parseRefMaybe} from '../../../../react'; import {StatusChip} from '../Browse3/pages/common/StatusChip'; import {Call} from './callTree'; import {DisplayControlChars} from './CommonLib'; @@ -13,7 +14,7 @@ import { OpenAIChatInputView, OpenAIChatOutputView, } from './openai'; -import {parseRefMaybe, SmallRef} from './SmallRef'; +import {SmallRef} from './SmallRef'; const ObjectView: FC<{obj: any}> = ({obj}) => { if (_.isPlainObject(obj)) { diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/filters/common.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/filters/common.ts index 2e93cfb158f8..a6d92bf3041f 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/filters/common.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/filters/common.ts @@ -8,10 +8,9 @@ import { GridFilterItem, GridFilterModel, } from '@mui/x-data-grid-pro'; -import {isWeaveObjectRef} from '@wandb/weave/react'; +import {isWeaveObjectRef, parseRefMaybe} from '@wandb/weave/react'; import _ from 'lodash'; -import {parseRefMaybe} from '../../Browse2/SmallRef'; import {WEAVE_REF_PREFIX} from '../pages/wfReactInterface/constants'; import {TraceCallSchema} from '../pages/wfReactInterface/traceServerClientTypes'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallSummary.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallSummary.tsx index 123ce79cc47c..8dd7b818670b 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallSummary.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallSummary.tsx @@ -1,9 +1,10 @@ import _ from 'lodash'; import React from 'react'; +import {parseRefMaybe} from '../../../../../../react'; import {Timestamp} from '../../../../../Timestamp'; import {UserLink} from '../../../../../UserLink'; -import {parseRefMaybe, SmallRef} from '../../../Browse2/SmallRef'; +import {SmallRef} from '../../../Browse2/SmallRef'; import {SimpleKeyValueTable} from '../common/SimplePageLayout'; import {CallSchema} from '../wfReactInterface/wfDataModelHooksInterface'; import {CostTable} from './cost'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/DataTableView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/DataTableView.tsx index aa8fad868f91..070d8832e3b8 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/DataTableView.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/DataTableView.tsx @@ -28,10 +28,13 @@ import React, { } from 'react'; import {useHistory} from 'react-router-dom'; -import {isWeaveObjectRef, parseRef} from '../../../../../../react'; +import { + isWeaveObjectRef, + parseRef, + parseRefMaybe, +} from '../../../../../../react'; import {flattenObjectPreservingWeaveTypes} from '../../../Browse2/browse2Util'; import {CellValue} from '../../../Browse2/CellValue'; -import {parseRefMaybe} from '../../../Browse2/SmallRef'; import { useWeaveflowCurrentRouteContext, WeaveflowPeekContext, diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewer.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewer.tsx index 63e08185eb62..0341688d4c9a 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewer.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewer.tsx @@ -18,9 +18,9 @@ import React, { useState, } from 'react'; +import {parseRefMaybe} from '../../../../../../react'; import {LoadingDots} from '../../../../../LoadingDots'; import {Browse2OpDefCode} from '../../../Browse2/Browse2OpDefCode'; -import {parseRefMaybe} from '../../../Browse2/SmallRef'; import {isWeaveRef} from '../../filters/common'; import {StyledDataGrid} from '../../StyledDataGrid'; import {isCustomWeaveTypePayload} from '../../typeViews/customWeaveType.types'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ValueView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ValueView.tsx index ffcbbcd872a5..458b7a791c62 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ValueView.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ValueView.tsx @@ -1,7 +1,11 @@ import React, {useMemo} from 'react'; -import {isWeaveObjectRef, parseRef} from '../../../../../../react'; -import {parseRefMaybe, SmallRef} from '../../../Browse2/SmallRef'; +import { + isWeaveObjectRef, + parseRef, + parseRefMaybe, +} from '../../../../../../react'; +import {SmallRef} from '../../../Browse2/SmallRef'; import {isWeaveRef} from '../../filters/common'; import {isCustomWeaveTypePayload} from '../../typeViews/customWeaveType.types'; import {CustomWeaveTypeDispatcher} from '../../typeViews/CustomWeaveTypeDispatcher'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CompareEvaluationsPage/sections/ExampleCompareSection/ExampleCompareSection.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CompareEvaluationsPage/sections/ExampleCompareSection/ExampleCompareSection.tsx index e72cf4d6f965..6041492b5c57 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CompareEvaluationsPage/sections/ExampleCompareSection/ExampleCompareSection.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CompareEvaluationsPage/sections/ExampleCompareSection/ExampleCompareSection.tsx @@ -10,11 +10,15 @@ import { MOON_300, MOON_800, } from '../../../../../../../../common/css/color.styles'; -import {parseRef, WeaveObjectRef} from '../../../../../../../../react'; +import { + parseRef, + parseRefMaybe, + WeaveObjectRef, +} from '../../../../../../../../react'; import {Button} from '../../../../../../../Button'; import {CellValue} from '../../../../../Browse2/CellValue'; import {NotApplicable} from '../../../../../Browse2/NotApplicable'; -import {parseRefMaybe, SmallRef} from '../../../../../Browse2/SmallRef'; +import {SmallRef} from '../../../../../Browse2/SmallRef'; import {isWeaveRef} from '../../../../filters/common'; import {isCustomWeaveTypePayload} from '../../../../typeViews/customWeaveType.types'; import {CustomWeaveTypeDispatcher} from '../../../../typeViews/CustomWeaveTypeDispatcher'; @@ -24,15 +28,18 @@ import {useCompareEvaluationsState} from '../../compareEvaluationsContext'; import { buildCompositeMetricsMap, CompositeSummaryMetricGroupForKeyPath, + DERIVED_SCORER_REF_PLACEHOLDER, resolvePeerDimension, } from '../../compositeMetricsUtil'; -import {DERIVED_SCORER_REF_PLACEHOLDER} from '../../compositeMetricsUtil'; import {CIRCLE_SIZE, SIGNIFICANT_DIGITS} from '../../ecpConstants'; import {EvaluationComparisonState} from '../../ecpState'; import {MetricDefinition, MetricValueType} from '../../ecpTypes'; -import {metricDefinitionId} from '../../ecpUtil'; -import {getMetricIds} from '../../ecpUtil'; -import {dimensionUnit, flattenedDimensionPath} from '../../ecpUtil'; +import { + dimensionUnit, + flattenedDimensionPath, + getMetricIds, + metricDefinitionId, +} from '../../ecpUtil'; import {usePeekCall} from '../../hooks'; import {HorizontalBox, VerticalBox} from '../../Layout'; import { diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CompareEvaluationsPage/sections/ScorecardSection/ScorecardSection.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CompareEvaluationsPage/sections/ScorecardSection/ScorecardSection.tsx index 2b39cc1578de..4b319150fa8e 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CompareEvaluationsPage/sections/ScorecardSection/ScorecardSection.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CompareEvaluationsPage/sections/ScorecardSection/ScorecardSection.tsx @@ -9,29 +9,32 @@ import { MOON_300, MOON_600, } from '../../../../../../../../common/css/color.styles'; -import {WeaveObjectRef} from '../../../../../../../../react'; +import {parseRefMaybe, WeaveObjectRef} from '../../../../../../../../react'; import {Checkbox} from '../../../../../../..'; import {Pill, TagColorName} from '../../../../../../../Tag'; import {CellValue} from '../../../../../Browse2/CellValue'; import {CellValueBoolean} from '../../../../../Browse2/CellValueBoolean'; import {NotApplicable} from '../../../../../Browse2/NotApplicable'; -import {parseRefMaybe, SmallRef} from '../../../../../Browse2/SmallRef'; +import {SmallRef} from '../../../../../Browse2/SmallRef'; import {ValueViewNumber} from '../../../CallPage/ValueViewNumber'; import { + buildCompositeMetricsMap, CompositeScoreMetrics, DERIVED_SCORER_REF_PLACEHOLDER, evalCallIdToScorerRefs, resolveDimension, } from '../../compositeMetricsUtil'; -import {buildCompositeMetricsMap} from '../../compositeMetricsUtil'; import { BOX_RADIUS, + SIGNIFICANT_DIGITS, STANDARD_BORDER, STANDARD_PADDING, } from '../../ecpConstants'; -import {SIGNIFICANT_DIGITS} from '../../ecpConstants'; -import {getOrderedCallIds, getOrderedModelRefs} from '../../ecpState'; -import {EvaluationComparisonState} from '../../ecpState'; +import { + EvaluationComparisonState, + getOrderedCallIds, + getOrderedModelRefs, +} from '../../ecpState'; import {resolveSummaryMetricResultForEvaluateCall} from '../../ecpUtil'; import {usePeekCall} from '../../hooks'; import {HorizontalBox} from '../../Layout'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/LeaderboardPage/leaderboardConfigEditorHooks.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/LeaderboardPage/leaderboardConfigEditorHooks.ts index b7e5caabb527..bdcdf2558a34 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/LeaderboardPage/leaderboardConfigEditorHooks.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/LeaderboardPage/leaderboardConfigEditorHooks.ts @@ -1,8 +1,7 @@ -import {refUri} from '@wandb/weave/react'; +import {parseRefMaybe, refUri} from '@wandb/weave/react'; import {useEffect, useState} from 'react'; import {flattenObjectPreservingWeaveTypes} from '../../../Browse2/browse2Util'; -import {parseRefMaybe} from '../../../Browse2/SmallRef'; import {ALL_VALUE} from '../../views/Leaderboard/types/leaderboardConfigType'; import {EVALUATE_OP_NAME_POST_PYDANTIC} from '../common/heuristics'; import {useGetTraceServerClientContext} from '../wfReactInterface/traceServerClientContext'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/KeyValueTable.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/KeyValueTable.tsx index e1abfae3c7cf..a0d3fcd3eb0c 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/KeyValueTable.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/KeyValueTable.tsx @@ -4,7 +4,8 @@ import _ from 'lodash'; import moment from 'moment'; import React, {useContext, useEffect, useState} from 'react'; -import {parseRefMaybe, SmallRef} from '../../../Browse2/SmallRef'; +import {parseRefMaybe} from '../../../../../../react'; +import {SmallRef} from '../../../Browse2/SmallRef'; import {isPrimitive} from './util'; const VALUE_SPACE = 4; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/views/Leaderboard/LeaderboardGrid.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/views/Leaderboard/LeaderboardGrid.tsx index 334b681335fd..8d28d569ff08 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/views/Leaderboard/LeaderboardGrid.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/views/Leaderboard/LeaderboardGrid.tsx @@ -12,8 +12,9 @@ import {Timestamp} from '@wandb/weave/components/Timestamp'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {useHistory} from 'react-router-dom'; +import {parseRefMaybe} from '../../../../../../react'; import {NotApplicable} from '../../../Browse2/NotApplicable'; -import {parseRefMaybe, SmallRef} from '../../../Browse2/SmallRef'; +import {SmallRef} from '../../../Browse2/SmallRef'; import {useWeaveflowRouteContext} from '../../context'; import {PaginationButtons} from '../../pages/CallsPage/CallsTableButtons'; import {Empty} from '../../pages/common/Empty'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/views/Leaderboard/query/leaderboardQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/views/Leaderboard/query/leaderboardQuery.ts index 80a7ce8bd346..68abf27fe899 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/views/Leaderboard/query/leaderboardQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/views/Leaderboard/query/leaderboardQuery.ts @@ -1,8 +1,7 @@ -import {isWeaveObjectRef} from '@wandb/weave/react'; +import {isWeaveObjectRef, parseRefMaybe} from '@wandb/weave/react'; import _ from 'lodash'; import {flattenObjectPreservingWeaveTypes} from '../../../../Browse2/browse2Util'; -import {parseRefMaybe} from '../../../../Browse2/SmallRef'; import {EVALUATE_OP_NAME_POST_PYDANTIC} from '../../../pages/common/heuristics'; import {TraceServerClient} from '../../../pages/wfReactInterface/traceServerClient'; import {TraceObjSchema} from '../../../pages/wfReactInterface/traceServerClientTypes'; diff --git a/weave-js/src/react.tsx b/weave-js/src/react.tsx index 6221f840955d..b9a049747071 100644 --- a/weave-js/src/react.tsx +++ b/weave-js/src/react.tsx @@ -551,6 +551,14 @@ const RE_WEAVE_CALL_REF_PATHNAME = new RegExp( ].join('') ); +export const parseRefMaybe = (s: string): ObjectRef | null => { + try { + return parseRef(s); + } catch (e) { + return null; + } +}; + export const parseRef = (ref: string): ObjectRef => { const url = new URL(ref); let splitLimit: number; From 90b6a699bafffbf8b53925fb9877121d2d750b66 Mon Sep 17 00:00:00 2001 From: Andrew Truong Date: Mon, 18 Nov 2024 12:23:43 -0500 Subject: [PATCH 2/8] chore(weave): Tidy Tables code (#2941) --- weave/trace/refs.py | 4 ++++ weave/trace/vals.py | 28 +++++++++++----------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/weave/trace/refs.py b/weave/trace/refs.py index 9cc4431f68c2..7557a404f9de 100644 --- a/weave/trace/refs.py +++ b/weave/trace/refs.py @@ -74,6 +74,10 @@ def row_digests(self) -> list[str]: return self._row_digests + @property + def project_id(self) -> str: + return f"{self.entity}/{self.project}" + def __post_init__(self) -> None: if isinstance(self._digest, str): refs_internal.validate_no_slashes(self._digest, "digest") diff --git a/weave/trace/vals.py b/weave/trace/vals.py index 94c787ea4c07..d49eaf3e4fb4 100644 --- a/weave/trace/vals.py +++ b/weave/trace/vals.py @@ -363,28 +363,26 @@ def _local_iter_with_remote_fallback(self) -> Generator[dict, None, None]: yield from self._remote_iter() return - for ndx, row in enumerate(self._prefetched_rows): + for i, _ in enumerate(self._prefetched_rows): next_id_future = wc.future_executor.defer( - lambda ndx_closure=ndx: cached_table_ref.row_digests[ndx_closure] + lambda closure=i: cached_table_ref.row_digests[closure] ) new_ref = self.ref.with_item(next_id_future) - val = self._prefetched_rows[ndx] - res = from_json( - val, self.table_ref.entity + "/" + self.table_ref.project, self.server - ) + val = self._prefetched_rows[i] + res = from_json(val, self.table_ref.project_id, self.server) res = make_trace_obj(res, new_ref, self.server, self.root) yield res def _remote_iter(self) -> Generator[dict, None, None]: + if self.table_ref is None: + return + page_index = 0 page_size = 100 while True: - if self.table_ref is None: - break - response = self.server.table_query( TableQueryReq( - project_id=f"{self.table_ref.entity}/{self.table_ref.project}", + project_id=self.table_ref.project_id, digest=self.table_ref.digest, offset=page_index * page_size, limit=page_size, @@ -402,7 +400,7 @@ def _remote_iter(self) -> Generator[dict, None, None]: ) self._prefetched_rows = None - for ndx, item in enumerate(response.rows): + for i, item in enumerate(response.rows): new_ref = self.ref.with_item(item.digest) if self.ref else None # Here, we use the raw rows if they exist, otherwise we use the # rows from the server. This is a temporary trick to ensure @@ -413,13 +411,9 @@ def _remote_iter(self) -> Generator[dict, None, None]: val = ( item.val if self._prefetched_rows is None - else self._prefetched_rows[ndx] - ) - res = from_json( - val, - self.table_ref.entity + "/" + self.table_ref.project, - self.server, + else self._prefetched_rows[i] ) + res = from_json(val, self.table_ref.project_id, self.server) res = make_trace_obj(res, new_ref, self.server, self.root) yield res From 63335da1b9456812f4ea2553023b3f44da84fa85 Mon Sep 17 00:00:00 2001 From: Andrew Truong Date: Mon, 18 Nov 2024 12:25:04 -0500 Subject: [PATCH 3/8] chore(weave): Tidy weave_client #2996 --- weave/trace/weave_client.py | 44 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/weave/trace/weave_client.py b/weave/trace/weave_client.py index 60b2c93f1108..1e3943362461 100644 --- a/weave/trace/weave_client.py +++ b/weave/trace/weave_client.py @@ -90,14 +90,6 @@ class OpNameError(ValueError): """Raised when an op name is invalid.""" -def dataclasses_asdict_one_level(obj: Any) -> dict[str, Any]: - # dataclasses.asdict is recursive. We don't want that when json encoding - return {f.name: getattr(obj, f.name) for f in dataclasses.fields(obj)} - - -# TODO: unused - - def get_obj_name(val: Any) -> str: name = getattr(val, "name", None) if name is None: @@ -233,23 +225,25 @@ def func_name(self) -> str: def feedback(self) -> RefFeedbackQuery: if not self.id: raise ValueError("Can't get feedback for call without ID") + if self._feedback is None: - project_parts = self.project_id.split("/") - if len(project_parts) != 2: + try: + entity, project = self.project_id.split("/") + except ValueError: raise ValueError(f"Invalid project_id: {self.project_id}") - entity, project = project_parts weave_ref = CallRef(entity, project, self.id) self._feedback = RefFeedbackQuery(weave_ref.uri()) return self._feedback @property def ui_url(self) -> str: - project_parts = self.project_id.split("/") - if len(project_parts) != 2: - raise ValueError(f"Invalid project_id: {self.project_id}") - entity, project = project_parts if not self.id: raise ValueError("Can't get URL for call without ID") + + try: + entity, project = self.project_id.split("/") + except ValueError: + raise ValueError(f"Invalid project_id: {self.project_id}") return urls.redirect_call(entity, project, self.id) @property @@ -257,6 +251,7 @@ def ref(self) -> CallRef: entity, project = self.project_id.split("/") if not self.id: raise ValueError("Can't get ref for call without ID") + return CallRef(entity, project, self.id) # These are the children if we're using Call at read-time @@ -264,6 +259,8 @@ def children(self) -> CallsIter: client = weave_client_context.require_weave_client() if not self.id: raise ValueError("Can't get children of call without ID") + + client = weave_client_context.require_weave_client() return CallsIter( client.server, self.project_id, @@ -419,15 +416,17 @@ def __iter__(self) -> Iterator[WeaveObject]: def make_client_call( entity: str, project: str, server_call: CallSchema, server: TraceServerInterface ) -> WeaveObject: - output = server_call.output + if (call_id := server_call.id) is None: + raise ValueError("Call ID is None") + call = Call( _op_name=server_call.op_name, project_id=server_call.project_id, trace_id=server_call.trace_id, parent_id=server_call.parent_id, - id=server_call.id, + id=call_id, inputs=from_json(server_call.inputs, server_call.project_id, server), - output=from_json(output, server_call.project_id, server), + output=from_json(server_call.output, server_call.project_id, server), summary=dict(server_call.summary) if server_call.summary is not None else None, display_name=server_call.display_name, attributes=server_call.attributes, @@ -435,9 +434,8 @@ def make_client_call( ended_at=server_call.ended_at, deleted_at=server_call.deleted_at, ) - if call.id is None: - raise ValueError("Call ID is None") - return WeaveObject(call, CallRef(entity, project, call.id), server, None) + ref = CallRef(entity, project, call_id) + return WeaveObject(call, ref, server, None) def sum_dict_leaves(dicts: list[dict]) -> dict: @@ -588,8 +586,6 @@ def get(self, ref: ObjectRef) -> Any: # here, we just directly assign the digest. ref = dataclasses.replace(ref, _digest=read_res.obj.digest) - data = read_res.obj.val - # If there is a ref-extra, we should resolve it. Rather than walking # the object, it is more efficient to directly query for the data and # let the server resolve it. @@ -605,6 +601,8 @@ def get(self, ref: ObjectRef) -> Any: if not ref_read_res.vals: raise ValueError(f"Unable to find object for ref uri: {ref.uri()}") data = ref_read_res.vals[0] + else: + data = read_res.obj.val val = from_json(data, project_id, self.server) From 0edc246795266affcde408a04cef6fcf4bc9e25d Mon Sep 17 00:00:00 2001 From: Andrew Truong Date: Mon, 18 Nov 2024 12:25:19 -0500 Subject: [PATCH 4/8] chore(weave): Tidyget_call(s) apis #2953 --- weave/trace/weave_client.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/weave/trace/weave_client.py b/weave/trace/weave_client.py index 1e3943362461..54b953c0134f 100644 --- a/weave/trace/weave_client.py +++ b/weave/trace/weave_client.py @@ -614,7 +614,7 @@ def get(self, ref: ObjectRef) -> Any: def get_calls( self, filter: CallsFilter | None = None, - include_costs: bool | None = False, + include_costs: bool = False, ) -> CallsIter: if filter is None: filter = CallsFilter() @@ -627,12 +627,16 @@ def get_calls( def calls( self, filter: CallsFilter | None = None, - include_costs: bool | None = False, + include_costs: bool = False, ) -> CallsIter: return self.get_calls(filter=filter, include_costs=include_costs) @trace_sentry.global_trace_sentry.watch() - def get_call(self, call_id: str, include_costs: bool | None = False) -> WeaveObject: + def get_call( + self, + call_id: str, + include_costs: bool = False, + ) -> WeaveObject: response = self.server.calls_query( CallsQueryReq( project_id=self._project_id(), @@ -646,7 +650,11 @@ def get_call(self, call_id: str, include_costs: bool | None = False) -> WeaveObj return make_client_call(self.entity, self.project, response_call, self.server) @deprecated(new_name="get_call") - def call(self, call_id: str, include_costs: bool | None = False) -> WeaveObject: + def call( + self, + call_id: str, + include_costs: bool = False, + ) -> WeaveObject: return self.get_call(call_id=call_id, include_costs=include_costs) @trace_sentry.global_trace_sentry.watch() @@ -689,7 +697,6 @@ def create_call( self._save_nested_objects(inputs_postprocessed) inputs_with_refs = map_to_refs(inputs_postprocessed) - call_id = generate_id() if parent is None and use_stack: parent = call_context.get_current_call() @@ -714,6 +721,7 @@ def create_call( op_name_future = self.future_executor.defer(lambda: op_def_ref.uri()) + call_id = generate_id() call = Call( _op_name=op_name_future, project_id=self._project_id(), @@ -728,6 +736,7 @@ def create_call( if callable(name_func := display_name): display_name = name_func(call) call.display_name = display_name + if parent is not None: parent._children.append(call) From db037c3a9418b32df3939dee1a00e69d8709fae0 Mon Sep 17 00:00:00 2001 From: Griffin Tarpenning Date: Mon, 18 Nov 2024 09:32:20 -0800 Subject: [PATCH 5/8] chore(ui): add optional right sidebar in preparation for feedback (#2976) --- .../Home/Browse3/pages/CallPage/CallPage.tsx | 4 +- .../Browse3/pages/common/SimplePageLayout.tsx | 156 +++++++----- .../common/SplitPanels/SplitPanelLeft.tsx | 224 ++++++++++++++++++ .../SplitPanelRight.tsx} | 75 +++--- 4 files changed, 363 insertions(+), 96 deletions(-) create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanels/SplitPanelLeft.tsx rename weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/{SplitPanel.tsx => SplitPanels/SplitPanelRight.tsx} (77%) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallPage.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallPage.tsx index 5635cd911d66..2df8628aeb83 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallPage.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallPage.tsx @@ -236,9 +236,9 @@ const CallPageInnerVertical: FC<{ } - isSidebarOpen={showTraceTree} headerContent={} - leftSidebar={ + isLeftSidebarOpen={showTraceTree} + leftSidebarContent={
{loading ? ( diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SimplePageLayout.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SimplePageLayout.tsx index ec166cbf60db..e0c444096a24 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SimplePageLayout.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SimplePageLayout.tsx @@ -1,6 +1,6 @@ import {Box, SxProps, Theme} from '@mui/material'; import {MOON_200} from '@wandb/weave/common/css/color.styles'; -import {Icon, IconName} from '@wandb/weave/components/Icon'; +import {IconName} from '@wandb/weave/components/Icon'; import * as Tabs from '@wandb/weave/components/Tabs'; import _ from 'lodash'; import React, { @@ -15,7 +15,8 @@ import React, { } from 'react'; import {ErrorBoundary} from '../../../../../ErrorBoundary'; -import {SplitPanel} from './SplitPanel'; +import {SplitPanelLeft} from './SplitPanels/SplitPanelLeft'; +import {SplitPanelRight} from './SplitPanels/SplitPanelRight'; import {isPrimitive} from './util'; type SimplePageLayoutContextType = { @@ -32,7 +33,7 @@ export const SimplePageLayout: FC<{ label: string; content: ReactNode; }>; - leftSidebar?: ReactNode; + leftSidebarContent?: ReactNode; hideTabsIfSingle?: boolean; headerExtra?: ReactNode; }> = props => { @@ -134,7 +135,7 @@ export const SimplePageLayout: FC<{ flexDirection: 'row', flex: '1 1 auto', }}> - {props.leftSidebar && ( + {props.leftSidebarContent && ( - {props.leftSidebar} + {props.leftSidebarContent} )} ; headerExtra?: ReactNode; headerContent: ReactNode; - leftSidebar?: ReactNode; hideTabsIfSingle?: boolean; - isSidebarOpen?: boolean; onTabSelectedCallback?: (tab: string) => void; + // Left sidebar + isLeftSidebarOpen?: boolean; + leftSidebarContent?: ReactNode; + // Right sidebar + isRightSidebarOpen?: boolean; + rightSidebarContent?: ReactNode; }> = props => { const {tabs} = props; const simplePageLayoutContextValue = useContext(SimplePageLayoutContext); @@ -255,63 +260,31 @@ export const SimplePageLayoutWithHeader: FC<{ {simplePageLayoutContextValue.headerSuffix}
- - {props.headerContent && ( - - {props.headerContent} - - )} - {(!props.hideTabsIfSingle || tabs.length > 1) && ( - - - {tabs.map(tab => ( - - {tab.icon && } - {tab.label} - - ))} - - - )} - - {tabContent} - - + + } + /> } />
@@ -319,6 +292,69 @@ export const SimplePageLayoutWithHeader: FC<{ ); }; +const SimpleTabView: FC<{ + headerContent: ReactNode; + tabs: Array<{ + label: string; + content: ReactNode; + }>; + tabContent: ReactNode; + tabId: string; + tabValue: number; + hideTabsIfSingle?: boolean; + handleTabChange: (newValue: string) => void; +}> = props => { + return ( + + + {props.headerContent} + + {(!props.hideTabsIfSingle || props.tabs.length > 1) && ( + + + {props.tabs.map(tab => ( + + {tab.label} + + ))} + + + )} + + {props.tabContent} + + + ); +}; + export const ScrollableTabContent: FC<{ sx?: SxProps; }> = props => { diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanels/SplitPanelLeft.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanels/SplitPanelLeft.tsx new file mode 100644 index 000000000000..1ea1c5113dcb --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanels/SplitPanelLeft.tsx @@ -0,0 +1,224 @@ +/** + * A vertically split panel with a draggable divider and the ability + * to collapse the left panel. + */ + +import {hexToRGB, MOON_250} from '@wandb/weave/common/css/globals.styles'; +import {useLocalStorage} from '@wandb/weave/util/useLocalStorage'; +import {throttle} from 'lodash'; +import React, { + ReactNode, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import {AutoSizer} from 'react-virtualized'; +import styled from 'styled-components'; + +type SplitPanelLeftProps = { + drawer?: ReactNode; + main: ReactNode; + isDrawerOpen: boolean; + minWidth?: number | string; + maxWidth?: number | string; + defaultWidth?: number | string; +}; + +const DIVIDER_LINE_WIDTH = 1; +const DIVIDER_BORDER_WIDTH = 4; +const DIVIDER_WIDTH = 2 * DIVIDER_BORDER_WIDTH + DIVIDER_LINE_WIDTH; + +const DividerLeft = styled.span<{left: number}>` + background-color: ${MOON_250}; + border-left: ${DIVIDER_BORDER_WIDTH}px solid transparent; + border-right: ${DIVIDER_BORDER_WIDTH}px solid transparent; + background-clip: padding-box; + cursor: col-resize; + width: ${DIVIDER_WIDTH}px; + box-sizing: border-box; + position: absolute; + top: 0; + bottom: 0; + left: ${props => props.left}px; + transition: border 0.5s ease; + transition-delay: 0.2s; + + &:hover { + border-left-color: ${hexToRGB(MOON_250, 0.5)}; + border-right-color: ${hexToRGB(MOON_250, 0.5)}; + } +`; +DividerLeft.displayName = 'S.DividerLeft'; + +// Handle percent or pixel specification. +const getWidth = (value: number | string, total: number): number => { + if (typeof value === 'number') { + return value; + } + if (value.endsWith('%')) { + return (total * parseFloat(value)) / 100; + } + return parseFloat(value); +}; + +export const SplitPanelLeft = ({ + main, + drawer, + isDrawerOpen, + minWidth, + maxWidth, + defaultWidth = '30%', +}: SplitPanelLeftProps) => { + const ref = useRef(null); + const dragStartXRef = useRef(0); + const dragStartWidthRef = useRef(0); + + const [width, setWidth] = useLocalStorage( + 'weaveflow-tracetree-width-number', + defaultWidth + ); + + const [isDragging, setIsDragging] = useState(false); + + // Throttle the width setting to 16ms (60fps) + const throttledSetWidth = useMemo( + () => + throttle((newWidth: number) => { + setWidth(newWidth); + }, 16), + [setWidth] + ); + useEffect(() => { + return () => { + throttledSetWidth.cancel(); + }; + }, [throttledSetWidth]); + + const onMouseDown = useCallback( + (e: React.MouseEvent) => { + setIsDragging(true); + dragStartXRef.current = e.clientX; + dragStartWidthRef.current = typeof width === 'number' ? width : 0; + e.preventDefault(); + }, + [width] + ); + const onMouseUp = useCallback(() => { + setIsDragging(false); + }, []); + const onMouseLeave = useCallback(() => { + setIsDragging(false); + }, []); + const onMouseMove = useCallback( + (e: React.MouseEvent) => { + if (!isDragging) { + return; + } + + const deltaX = e.clientX - dragStartXRef.current; + const newWidth = dragStartWidthRef.current + deltaX; + + const bounds = ref.current?.getBoundingClientRect(); + if (!bounds) { + return; + } + + const minW = minWidth ? getWidth(minWidth, bounds.width) : 0; + const maxW = maxWidth ? getWidth(maxWidth, bounds.width) : bounds.width; + + if (newWidth < minW || newWidth > maxW) { + return; + } + + throttledSetWidth(newWidth); + }, + [isDragging, minWidth, maxWidth, throttledSetWidth] + ); + + const cursor = isDragging ? 'col-resize' : undefined; + const pointerEvents = isDragging ? 'none' : 'auto'; + const userSelect = isDragging ? 'none' : 'auto'; + + return ( + + {panelDim => { + const panelW = panelDim.width; + let numW = getWidth(width, panelW); + const minW = minWidth ? getWidth(minWidth, panelW) : 0; + let maxW = maxWidth ? getWidth(maxWidth, panelW) : panelW; + + if (maxW < minW) { + maxW = minW; + } + if (numW < minW) { + numW = minW; + } else if (numW > maxW) { + numW = maxW; + } + + return ( +
+
+ {isDrawerOpen && ( +
+ {drawer} +
+ )} +
+ {main} +
+
+ {isDrawerOpen && ( + + )} +
+ ); + }} +
+ ); +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanel.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanels/SplitPanelRight.tsx similarity index 77% rename from weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanel.tsx rename to weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanels/SplitPanelRight.tsx index 99b8ecdb39b7..bb351ee914c9 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanel.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/common/SplitPanels/SplitPanelRight.tsx @@ -1,6 +1,6 @@ /** * A vertically split panel with a draggable divider and the ability - * to collapse the left panel. + * to collapse the right panel. */ import {hexToRGB, MOON_250} from '@wandb/weave/common/css/globals.styles'; @@ -9,7 +9,7 @@ import React, {ReactNode, useCallback, useRef, useState} from 'react'; import {AutoSizer} from 'react-virtualized'; import styled from 'styled-components'; -type SplitPanelProps = { +type SplitPanelRightProps = { drawer?: ReactNode; main: ReactNode; isDrawerOpen: boolean; @@ -22,7 +22,7 @@ const DIVIDER_LINE_WIDTH = 1; const DIVIDER_BORDER_WIDTH = 4; const DIVIDER_WIDTH = 2 * DIVIDER_BORDER_WIDTH + DIVIDER_LINE_WIDTH; -const Divider = styled.span<{left: number}>` +const DividerRight = styled.span<{right: number}>` background-color: ${MOON_250}; border-left: ${DIVIDER_BORDER_WIDTH}px solid transparent; border-right: ${DIVIDER_BORDER_WIDTH}px solid transparent; @@ -33,7 +33,7 @@ const Divider = styled.span<{left: number}>` position: absolute; top: 0; bottom: 0; - left: ${props => props.left}px; + right: ${props => props.right}px; transition: border 0.5s ease; transition-delay: 0.2s; @@ -42,7 +42,7 @@ const Divider = styled.span<{left: number}>` border-right-color: ${hexToRGB(MOON_250, 0.5)}; } `; -Divider.displayName = 'S.Divider'; +DividerRight.displayName = 'S.DividerRight'; // Handle percent or pixel specification. const getWidth = (value: number | string, total: number): number => { @@ -55,18 +55,17 @@ const getWidth = (value: number | string, total: number): number => { return parseFloat(value); }; -export const SplitPanel = ({ +export const SplitPanelRight = ({ main, drawer, isDrawerOpen, minWidth, maxWidth, defaultWidth = '30%', -}: SplitPanelProps) => { +}: SplitPanelRightProps) => { const ref = useRef(null); - // We store the drawer width and height in local storage so that it persists const [width, setWidth] = useLocalStorage( - 'weaveflow-tracetree-width-number', + 'weaveflow-rightpanel-width-number', defaultWidth ); @@ -85,7 +84,7 @@ export const SplitPanel = ({ if (isDragging) { const panel = (e.target as HTMLElement).parentElement!; const bounds = panel.getBoundingClientRect(); - const x = e.clientX - bounds.left; + const x = bounds.right - e.clientX; if (minWidth && x < getWidth(minWidth, bounds.width)) { return; } @@ -96,7 +95,6 @@ export const SplitPanel = ({ } }; - // TODO: Might be nice to change the cursor if user has gone beyond the min/max width const cursor = isDragging ? 'col-resize' : undefined; const pointerEvents = isDragging ? 'none' : 'auto'; const userSelect = isDragging ? 'none' : 'auto'; @@ -107,21 +105,16 @@ export const SplitPanel = ({ let numW = getWidth(width, panelW); const minW = minWidth ? getWidth(minWidth, panelW) : 0; let maxW = maxWidth ? getWidth(maxWidth, panelW) : panelW; - // Max width constraint might be inconsistent with min constraint. - // E.g. a percentage constraint when the panel is resized to extremes. + if (maxW < minW) { maxW = minW; } - // width value in state may violate constraints because of browser size change. if (numW < minW) { numW = minW; } else if (numW > maxW) { numW = maxW; } - const leftPanelR = numW; - const rightPanelL = isDrawerOpen ? numW + DIVIDER_LINE_WIDTH : 0; - return (
-
- {isDrawerOpen && ( -
- {drawer} -
- )} +
{main}
+ {isDrawerOpen && ( +
+ {drawer} +
+ )}
{isDrawerOpen && ( - )}
From b2f087617c949f359e28b3e5cf3c3a78fc88c49d Mon Sep 17 00:00:00 2001 From: Josiah Lee Date: Mon, 18 Nov 2024 10:28:30 -0800 Subject: [PATCH 6/8] style(weave): chatview styling (#2977) * add playground context * clean up * types * style try * style audit * pr comments --- .../Home/Browse3/pages/ChatView/ChatView.tsx | 37 +++++--- .../Browse3/pages/ChatView/ChoiceView.tsx | 6 +- .../ChatView/HorizontalRuleWithLabel.tsx | 19 ---- .../Browse3/pages/ChatView/MessageList.tsx | 75 +++++++++++++-- .../Browse3/pages/ChatView/MessagePanel.tsx | 91 ++++++++++++++++--- .../Home/Browse3/pages/ChatView/ToolCalls.tsx | 79 ++++++++++++++-- .../Home/Browse3/pages/ChatView/hooks.ts | 13 ++- .../Home/Browse3/pages/ChatView/types.ts | 7 +- 8 files changed, 260 insertions(+), 67 deletions(-) delete mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/HorizontalRuleWithLabel.tsx diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChatView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChatView.tsx index 9109a88fc92d..95d36f2a6e20 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChatView.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChatView.tsx @@ -1,8 +1,7 @@ -import React, {useEffect, useRef} from 'react'; +import React, {useEffect, useMemo, useRef} from 'react'; import {useDeepMemo} from '../../../../../../hookUtils'; import {ChoicesView} from './ChoicesView'; -import {HorizontalRuleWithLabel} from './HorizontalRuleWithLabel'; import {MessageList} from './MessageList'; import {Chat} from './types'; @@ -15,6 +14,11 @@ export const ChatView = ({chat}: ChatViewProps) => { const chatResult = useDeepMemo(chat.result); + const scrollLastMessage = useMemo( + () => !(outputRef.current && chatResult && chatResult.choices), + [chatResult] + ); + useEffect(() => { if (outputRef.current && chatResult && chatResult.choices) { outputRef.current.scrollIntoView(); @@ -22,17 +26,26 @@ export const ChatView = ({chat}: ChatViewProps) => { }, [chatResult]); return ( -
- - +
+ + Messages + + {chatResult && chatResult.choices && ( -
- - -
+ <> + + Response + +
+ +
+ )}
); diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChoiceView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChoiceView.tsx index 6f3eb54bd10d..d16a12a12ca8 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChoiceView.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChoiceView.tsx @@ -11,6 +11,10 @@ type ChoiceViewProps = { export const ChoiceView = ({choice, isStructuredOutput}: ChoiceViewProps) => { const {message} = choice; return ( - + ); }; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/HorizontalRuleWithLabel.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/HorizontalRuleWithLabel.tsx deleted file mode 100644 index 3a81a6604fad..000000000000 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/HorizontalRuleWithLabel.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -type HorizontalRuleWithLabelProps = { - label: string; -}; - -export const HorizontalRuleWithLabel = ({ - label, -}: HorizontalRuleWithLabelProps) => { - return ( -
-
- - {label} - -
-
- ); -}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessageList.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessageList.tsx index c0e964f25505..75f189a6a692 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessageList.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessageList.tsx @@ -1,18 +1,81 @@ -import React from 'react'; +import React, {useEffect, useRef} from 'react'; import {MessagePanel} from './MessagePanel'; -import {Messages} from './types'; +import {Message, Messages, ToolCall} from './types'; type MessageListProps = { messages: Messages; + scrollLastMessage?: boolean; }; -export const MessageList = ({messages}: MessageListProps) => { +export const MessageList = ({ + messages, + scrollLastMessage = false, +}: MessageListProps) => { + const lastMessageRef = useRef(null); + const processedMessages = processToolCallMessages(messages); + + useEffect(() => { + if (lastMessageRef.current && scrollLastMessage) { + lastMessageRef.current.scrollIntoView(); + } + }, [messages.length, scrollLastMessage]); + return ( -
- {messages.map((m, i) => ( - +
+ {processedMessages.map((m, i) => ( +
+ +
))}
); }; + +// Associates tool calls with their responses +const processToolCallMessages = (messages: Messages): Messages => { + const processedMessages: Message[] = []; + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + + // If there are no tool calls, just add the message to the processed messages + // and continue to the next iteration. + if (!message.tool_calls) { + processedMessages.push({ + ...message, + // Store the original index of the message in the message object + // so that we can use it to sort the messages later. + original_index: i, + }); + continue; + } + + // Otherwise, we need to associate the tool calls with their responses. + // Get all the next messages where role = tool, these are all the responses + const toolMessages: Message[] = []; + while (i + 1 < messages.length && messages[i + 1].role === 'tool') { + toolMessages.push({ + ...messages[i + 1], + original_index: (messages[i + 1] as any).original_index ?? i + 1, + }); + i++; + } + + const toolCallsWithResponses: ToolCall[] = message.tool_calls.map( + toolCall => ({ + ...toolCall, + response: toolMessages.find( + toolMessage => toolMessage.tool_call_id === toolCall.id + ), + }) + ); + + processedMessages.push({ + ...message, + tool_calls: toolCallsWithResponses, + }); + } + return processedMessages; +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx index 4ee315b88422..54d275d1edcb 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx @@ -1,3 +1,4 @@ +import {Callout} from '@wandb/weave/components/Callout'; import classNames from 'classnames'; import _ from 'lodash'; import React from 'react'; @@ -7,34 +8,96 @@ import {ToolCalls} from './ToolCalls'; import {Message} from './types'; type MessagePanelProps = { + index: number; message: Message; isStructuredOutput?: boolean; + isNested?: boolean; }; export const MessagePanel = ({ message, isStructuredOutput, + isNested, }: MessagePanelProps) => { const isUser = message.role === 'user'; - const bg = isUser ? 'bg-cactus-300/[0.48]' : 'bg-moon-100'; + const isSystemPrompt = message.role === 'system'; + const isTool = message.role === 'tool'; + const hasToolCalls = + message.tool_calls != null && message.tool_calls.length > 0; + const hasContent = message.content != null && message.content.length > 0; + return ( -
-
{message.role}
- {message.content && ( -
- {_.isString(message.content) ? ( - + {!isNested && !isSystemPrompt && ( +
+ {!isUser && !isTool && ( + - ) : ( - message.content.map((p, i) => ( - - )) )}
)} - {message.tool_calls && } + +
+
+ {isSystemPrompt && ( +
+
+ {message.role.charAt(0).toUpperCase() + message.role.slice(1)} +
+
+ )} + + {isTool && ( +
+
+ Response +
+
+ )} + +
+ {hasContent && ( +
+ {_.isString(message.content) ? ( + + ) : ( + message.content!.map((p, i) => ( + + )) + )} +
+ )} + {hasToolCalls && ( +
+ +
+ )} +
+
+
); }; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ToolCalls.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ToolCalls.tsx index 7a624fa77ab9..2a1083976579 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ToolCalls.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ToolCalls.tsx @@ -1,7 +1,9 @@ +import {Button} from '@wandb/weave/components/Button'; import Prism from 'prismjs'; -import React, {useEffect, useRef} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import {Alert} from '../../../../../Alert'; +import {MessagePanel} from './MessagePanel'; import {ToolCall} from './types'; type OneToolCallProps = { @@ -9,6 +11,19 @@ type OneToolCallProps = { }; const OneToolCall = ({toolCall}: OneToolCallProps) => { + const [isCopying, setIsCopying] = useState(false); + + const handleCopyText = (text: string) => { + try { + setIsCopying(true); + navigator.clipboard.writeText(text); + } finally { + setTimeout(() => { + setIsCopying(false); + }, 2000); + } + }; + const ref = useRef(null); useEffect(() => { if (ref.current) { @@ -16,6 +31,11 @@ const OneToolCall = ({toolCall}: OneToolCallProps) => { } }); + if (!toolCall.function) { + // This prevents an error when the LLM returns null + return
Null tool call
; + } + const {function: toolCallFunction} = toolCall; const {name, arguments: args} = toolCallFunction; let parsedArgs: any = null; @@ -27,17 +47,58 @@ const OneToolCall = ({toolCall}: OneToolCallProps) => { } } catch (e) { // The model does not always generate valid JSON - return Invalid JSON: {args}; + return ( +
+ Invalid JSON: {args} +
+ ); } + const copyText = `${name}(${parsedArgs})`; return ( - - {name}( - - {parsedArgs} - - ) - +
+ {/* The tool call header has a copy button */} +
+
+
Function
+
+
+ + {/* The tool call */} +
+ + {name}( + + {parsedArgs} + + ) + +
+ + {/* The tool call response */} + {toolCall.response && ( +
+ +
+ )} +
); }; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/hooks.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/hooks.ts index 5e4429e76116..38b5c820195b 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/hooks.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/hooks.ts @@ -8,7 +8,7 @@ import { TraceCallSchema, } from '../wfReactInterface/traceServerClientTypes'; import {CallSchema} from '../wfReactInterface/wfDataModelHooksInterface'; -import {ChatCompletion, ChatRequest, Choice} from './types'; +import {Chat, ChatCompletion, ChatRequest, Choice} from './types'; export enum ChatFormat { None = 'None', @@ -59,6 +59,12 @@ export const isToolCall = (toolCall: any): boolean => { }; export const isToolCalls = (toolCalls: any): boolean => { + if (toolCalls === null) { + // Some llms return null for tool calls + // before the chat view was hidden for those calls + return true; + } + if (!_.isArray(toolCalls)) { return false; } @@ -361,10 +367,7 @@ export const useCallAsChat = ( call: TraceCallSchema ): { loading: boolean; - isStructuredOutput: boolean; - request: ChatRequest; - result: ChatCompletion | null; -} => { +} & Chat => { // Traverse the data and find all ref URIs. const refs = getRefs(call); const {useRefsData} = useWFHooks(); diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/types.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/types.ts index 3968fdafa2d1..b56960557120 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/types.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/types.ts @@ -32,12 +32,15 @@ export type ToolCall = { // Validate the arguments in your code before calling your function. arguments: string; }; + response?: Message; }; export type Message = { role: string; content?: string | MessagePart[]; tool_calls?: ToolCall[]; + tool_call_id?: string; + original_index?: number; }; export type Messages = Message[]; @@ -105,7 +108,9 @@ export type ChatCompletion = { export type Chat = { // TODO: Maybe optional information linking back to Call? isStructuredOutput: boolean; - request: ChatRequest; + // When the chat is created from a call, this will be the request. + // When the chat is created from an empty playground, this will be null. + request: ChatRequest | null; result: ChatCompletion | null; }; From dd2d5deebb2c04990a0d8b55a2b9b248a531df0e Mon Sep 17 00:00:00 2001 From: Josiah Lee Date: Mon, 18 Nov 2024 10:59:07 -0800 Subject: [PATCH 7/8] style(weave): show more button (#2978) * add playground context * clean up * types * style try * add show more button * resize and fix gradient covering border corners * style audit * pr comments * update gradient colors to match new system color --- .../Browse3/pages/ChatView/MessagePanel.tsx | 28 +++++++++++++-- .../Browse3/pages/ChatView/ShowMoreButton.tsx | 34 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ShowMoreButton.tsx diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx index 54d275d1edcb..817eb159bb0c 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx @@ -1,9 +1,10 @@ import {Callout} from '@wandb/weave/components/Callout'; import classNames from 'classnames'; import _ from 'lodash'; -import React from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import {MessagePanelPart} from './MessagePanelPart'; +import {ShowMoreButton} from './ShowMoreButton'; import {ToolCalls} from './ToolCalls'; import {Message} from './types'; @@ -19,6 +20,16 @@ export const MessagePanel = ({ isStructuredOutput, isNested, }: MessagePanelProps) => { + const [isShowingMore, setIsShowingMore] = useState(false); + const [isOverflowing, setIsOverflowing] = useState(false); + const contentRef = useRef(null); + + useEffect(() => { + if (contentRef.current) { + setIsOverflowing(contentRef.current.scrollHeight > 400); + } + }, [message.content, contentRef?.current?.scrollHeight]); + const isUser = message.role === 'user'; const isSystemPrompt = message.role === 'system'; const isTool = message.role === 'tool'; @@ -43,6 +54,7 @@ export const MessagePanel = ({
)} -
+
{hasContent && (
)}
+ {isOverflowing && ( + + )}
diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ShowMoreButton.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ShowMoreButton.tsx new file mode 100644 index 000000000000..8c1e68567a3d --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ShowMoreButton.tsx @@ -0,0 +1,34 @@ +import {Button} from '@wandb/weave/components/Button'; +import classNames from 'classnames'; +import React, {Dispatch, SetStateAction} from 'react'; + +type ShowMoreButtonProps = { + isShowingMore: boolean; + setIsShowingMore: Dispatch>; + isUser?: boolean; +}; +export const ShowMoreButton = ({ + isShowingMore, + setIsShowingMore, + isUser, +}: ShowMoreButtonProps) => { + return ( +
+ +
+ ); +}; From f6642687edc0edbab341e7b7a7f7941f9fa2c66b Mon Sep 17 00:00:00 2001 From: Josiah Lee Date: Mon, 18 Nov 2024 11:13:43 -0800 Subject: [PATCH 8/8] chore(weave): Add call chat to playground page (#2993) * add playground context * clean up * types * make context nullable, remove all the optionals * add call chat to playground and add functions interacting with chat messages * lint * remove callchat edits, move provider into playgroundchat * pr comments, tighten type defs --- .../PlaygroundChat/LLMDropdown.tsx | 17 +- .../PlaygroundChat/PlaygroundChat.tsx | 59 ++++++- .../PlaygroundChat/PlaygroundChatTopBar.tsx | 10 +- .../PlaygroundChat/useChatFunctions.tsx | 155 ++++++++++++++++++ .../PlaygroundPage/PlaygroundContext.tsx | 44 +++++ .../PlaygroundSettings/PlaygroundSettings.tsx | 11 +- 6 files changed, 266 insertions(+), 30 deletions(-) create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/useChatFunctions.tsx create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundContext.tsx diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/LLMDropdown.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/LLMDropdown.tsx index b7a71e88df05..d14a9db30427 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/LLMDropdown.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/LLMDropdown.tsx @@ -2,18 +2,19 @@ import {Box} from '@mui/material'; import {Select} from '@wandb/weave/components/Form/Select'; import React from 'react'; -import {LLM_MAX_TOKENS} from '../llmMaxTokens'; +import {LLM_MAX_TOKENS, LLMMaxTokensKey} from '../llmMaxTokens'; interface LLMDropdownProps { - value: string; - onChange: (value: string, maxTokens: number) => void; + value: LLMMaxTokensKey; + onChange: (value: LLMMaxTokensKey, maxTokens: number) => void; } export const LLMDropdown: React.FC = ({value, onChange}) => { - const options = Object.keys(LLM_MAX_TOKENS).map(llm => ({ - value: llm, - label: llm, - })); + const options: Array<{value: LLMMaxTokensKey; label: LLMMaxTokensKey}> = + Object.keys(LLM_MAX_TOKENS).map(llm => ({ + value: llm as LLMMaxTokensKey, + label: llm as LLMMaxTokensKey, + })); return ( = ({value, onChange}) => { LLM_MAX_TOKENS[ (option as {value: string}).value as keyof typeof LLM_MAX_TOKENS ]?.max_tokens || 0; - onChange((option as {value: string}).value, maxTokens); + onChange((option as {value: LLMMaxTokensKey}).value, maxTokens); } }} options={options} diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/PlaygroundChat.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/PlaygroundChat.tsx index cdd14a4a5c25..ec6653801978 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/PlaygroundChat.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/PlaygroundChat.tsx @@ -1,24 +1,26 @@ import {Box, CircularProgress, Divider} from '@mui/material'; import {MOON_200} from '@wandb/weave/common/css/color.styles'; import {Tailwind} from '@wandb/weave/components/Tailwind'; -import React, {SetStateAction, useState} from 'react'; +import React, {useState} from 'react'; +import {CallChat} from '../../CallPage/CallChat'; import {TraceCallSchema} from '../../wfReactInterface/traceServerClientTypes'; -import {PlaygroundState, PlaygroundStateKey} from '../types'; +import {PlaygroundContext} from '../PlaygroundContext'; +import {PlaygroundState} from '../types'; import {PlaygroundCallStats} from './PlaygroundCallStats'; import {PlaygroundChatInput} from './PlaygroundChatInput'; import {PlaygroundChatTopBar} from './PlaygroundChatTopBar'; +import { + SetPlaygroundStateFieldFunctionType, + useChatFunctions, +} from './useChatFunctions'; export type PlaygroundChatProps = { entity: string; project: string; setPlaygroundStates: (states: PlaygroundState[]) => void; playgroundStates: PlaygroundState[]; - setPlaygroundStateField: ( - index: number, - field: PlaygroundStateKey, - value: SetStateAction - ) => void; + setPlaygroundStateField: SetPlaygroundStateFieldFunctionType; setSettingsTab: (callIndex: number | null) => void; settingsTab: number | null; }; @@ -37,6 +39,16 @@ export const PlaygroundChat = ({ const [isLoading, setIsLoading] = useState(false); const chatPercentWidth = 100 / playgroundStates.length; + const {deleteMessage, editMessage, deleteChoice, editChoice, addMessage} = + useChatFunctions(setPlaygroundStateField); + + const handleAddMessage = (role: 'assistant' | 'user', text: string) => { + for (let i = 0; i < playgroundStates.length; i++) { + addMessage(i, {role, content: text}); + } + setChatText(''); + }; + return (
- Chat + {state.traceCall && ( + + deleteMessage(idx, messageIndex, responseIndexes), + editMessage: (messageIndex, newMessage) => + editMessage(idx, messageIndex, newMessage), + deleteChoice: choiceIndex => + deleteChoice(idx, choiceIndex), + addMessage: newMessage => addMessage(idx, newMessage), + editChoice: (choiceIndex, newChoice) => + editChoice(idx, choiceIndex, newChoice), + retry: (messageIndex: number, isChoice?: boolean) => + console.log('retry', messageIndex, isChoice), + sendMessage: ( + role: 'assistant' | 'user' | 'tool', + content: string, + toolCallId?: string + ) => + console.log( + 'sendMessage', + role, + content, + toolCallId + ), + }}> + + + )}
@@ -147,7 +188,7 @@ export const PlaygroundChat = ({ setChatText={setChatText} isLoading={isLoading} onSend={() => {}} - onAdd={() => {}} + onAdd={handleAddMessage} />
); diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/PlaygroundChatTopBar.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/PlaygroundChatTopBar.tsx index 397f3a3a15d0..2fcd24f56e3a 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/PlaygroundChatTopBar.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/PlaygroundChatTopBar.tsx @@ -5,19 +5,17 @@ import React from 'react'; import {useHistory} from 'react-router-dom'; import {CopyableId} from '../../common/Id'; +import {LLMMaxTokensKey} from '../llmMaxTokens'; import {OptionalTraceCallSchema, PlaygroundState} from '../types'; import {DEFAULT_SYSTEM_MESSAGE} from '../usePlaygroundState'; import {LLMDropdown} from './LLMDropdown'; +import {SetPlaygroundStateFieldFunctionType} from './useChatFunctions'; type PlaygroundChatTopBarProps = { idx: number; settingsTab: number | null; setSettingsTab: (tab: number | null) => void; - setPlaygroundStateField: ( - index: number, - field: keyof PlaygroundState, - value: any - ) => void; + setPlaygroundStateField: SetPlaygroundStateFieldFunctionType; entity: string; project: string; playgroundStates: PlaygroundState[]; @@ -60,7 +58,7 @@ export const PlaygroundChatTopBar: React.FC = ({ const handleModelChange = ( index: number, - model: string, + model: LLMMaxTokensKey, maxTokens: number ) => { setPlaygroundStateField(index, 'model', model); diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/useChatFunctions.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/useChatFunctions.tsx new file mode 100644 index 000000000000..178752a71da9 --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundChat/useChatFunctions.tsx @@ -0,0 +1,155 @@ +import {cloneDeep} from 'lodash'; +import {SetStateAction} from 'react'; + +import {Choice, Message} from '../../ChatView/types'; +import {OptionalTraceCallSchema, PlaygroundState} from '../types'; +import {DEFAULT_SYSTEM_MESSAGE} from '../usePlaygroundState'; +type TraceCallOutput = { + choices?: any[]; +}; + +export type SetPlaygroundStateFieldFunctionType = ( + index: number, + field: keyof PlaygroundState, + // The value here is a function that returns a PlaygroundState field + value: SetStateAction +) => void; + +export const useChatFunctions = ( + setPlaygroundStateField: SetPlaygroundStateFieldFunctionType +) => { + const deleteMessage = ( + callIndex: number, + messageIndex: number, + responseIndexes?: number[] + ) => { + setPlaygroundStateField(callIndex, 'traceCall', prevTraceCall => { + const newTraceCall = clearTraceCall( + cloneDeep(prevTraceCall as OptionalTraceCallSchema) + ); + if (newTraceCall && newTraceCall.inputs?.messages) { + // Remove the message and all responses to it + newTraceCall.inputs.messages = newTraceCall.inputs.messages.filter( + (_: any, index: number) => + index !== messageIndex && !responseIndexes?.includes(index) + ); + + // If there are no messages left, add a system message + if (newTraceCall.inputs.messages.length === 0) { + newTraceCall.inputs.messages = [DEFAULT_SYSTEM_MESSAGE]; + } + } + return newTraceCall; + }); + }; + + const editMessage = ( + callIndex: number, + messageIndex: number, + newMessage: Message + ) => { + setPlaygroundStateField(callIndex, 'traceCall', prevTraceCall => { + const newTraceCall = clearTraceCall( + cloneDeep(prevTraceCall as OptionalTraceCallSchema) + ); + if (newTraceCall && newTraceCall.inputs?.messages) { + // Replace the message + newTraceCall.inputs.messages[messageIndex] = newMessage; + } + return newTraceCall; + }); + }; + + const addMessage = (callIndex: number, newMessage: Message) => { + setPlaygroundStateField(callIndex, 'traceCall', prevTraceCall => { + const newTraceCall = clearTraceCall( + cloneDeep(prevTraceCall as OptionalTraceCallSchema) + ); + if (newTraceCall && newTraceCall.inputs?.messages) { + if ( + newTraceCall.output && + (newTraceCall.output as TraceCallOutput).choices && + Array.isArray((newTraceCall.output as TraceCallOutput).choices) + ) { + // Add all the choices as messages + (newTraceCall.output as TraceCallOutput).choices!.forEach( + (choice: any) => { + if (choice.message) { + newTraceCall.inputs!.messages.push(choice.message); + } + } + ); + // Set the choices to undefined + (newTraceCall.output as TraceCallOutput).choices = undefined; + } + // Add the new message + newTraceCall.inputs.messages.push(newMessage); + } + return newTraceCall; + }); + }; + + const deleteChoice = (callIndex: number, choiceIndex: number) => { + setPlaygroundStateField(callIndex, 'traceCall', prevTraceCall => { + const newTraceCall = clearTraceCall( + cloneDeep(prevTraceCall as OptionalTraceCallSchema) + ); + const output = newTraceCall?.output as TraceCallOutput; + if (output && Array.isArray(output.choices)) { + // Remove the choice + output.choices.splice(choiceIndex, 1); + if (newTraceCall) { + newTraceCall.output = output; + } + } + return newTraceCall; + }); + }; + + const editChoice = ( + callIndex: number, + choiceIndex: number, + newChoice: Choice + ) => { + setPlaygroundStateField(callIndex, 'traceCall', prevTraceCall => { + const newTraceCall = clearTraceCall( + cloneDeep(prevTraceCall as OptionalTraceCallSchema) + ); + if ( + newTraceCall?.output && + Array.isArray((newTraceCall.output as TraceCallOutput).choices) + ) { + // Delete the old choice + (newTraceCall.output as TraceCallOutput).choices!.splice( + choiceIndex, + 1 + ); + + // Add the new choice as a message + newTraceCall.inputs = newTraceCall.inputs ?? {}; + newTraceCall.inputs.messages = newTraceCall.inputs.messages ?? []; + newTraceCall.inputs.messages.push({ + role: 'assistant', + content: newChoice.message?.content, + }); + } + return newTraceCall; + }); + }; + + return { + deleteMessage, + editMessage, + addMessage, + deleteChoice, + editChoice, + }; +}; + +export const clearTraceCall = (traceCall: OptionalTraceCallSchema) => { + if (traceCall) { + traceCall.id = ''; + traceCall.summary = undefined; + } + return traceCall; +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundContext.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundContext.tsx new file mode 100644 index 000000000000..14485ed2434d --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundContext.tsx @@ -0,0 +1,44 @@ +import {createContext, useContext} from 'react'; + +import {Choice, Message} from '../ChatView/types'; + +export type PlaygroundContextType = { + isPlayground: boolean; + addMessage: (newMessage: Message) => void; + editMessage: (messageIndex: number, newMessage: Message) => void; + deleteMessage: (messageIndex: number, responseIndexes?: number[]) => void; + + editChoice: (choiceIndex: number, newChoice: Choice) => void; + deleteChoice: (choiceIndex: number) => void; + + retry: (messageIndex: number, isChoice?: boolean) => void; + sendMessage: ( + role: 'assistant' | 'user' | 'tool', + content: string, + toolCallId?: string + ) => void; +}; + +const DEFAULT_CONTEXT: PlaygroundContextType = { + isPlayground: false, + addMessage: () => {}, + editMessage: () => {}, + deleteMessage: () => {}, + + editChoice: () => {}, + deleteChoice: () => {}, + + retry: () => {}, + sendMessage: () => {}, +}; + +// Create context that can be undefined +export const PlaygroundContext = createContext< + PlaygroundContextType | undefined +>(DEFAULT_CONTEXT); + +// Custom hook that handles the undefined context +export const usePlaygroundContext = () => { + const context = useContext(PlaygroundContext); + return context ?? DEFAULT_CONTEXT; +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundSettings/PlaygroundSettings.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundSettings/PlaygroundSettings.tsx index f69a6e52a0a9..63d6539f2f1b 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundSettings/PlaygroundSettings.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/PlaygroundPage/PlaygroundSettings/PlaygroundSettings.tsx @@ -3,9 +3,10 @@ import {MOON_250} from '@wandb/weave/common/css/color.styles'; import {Switch} from '@wandb/weave/components'; import * as Tabs from '@wandb/weave/components/Tabs'; import {Tag} from '@wandb/weave/components/Tag'; -import React, {SetStateAction} from 'react'; +import React from 'react'; -import {PlaygroundState, PlaygroundStateKey} from '../types'; +import {SetPlaygroundStateFieldFunctionType} from '../PlaygroundChat/useChatFunctions'; +import {PlaygroundState} from '../types'; import {FunctionEditor} from './FunctionEditor'; import {PlaygroundSlider} from './PlaygroundSlider'; import {ResponseFormatEditor} from './ResponseFormatEditor'; @@ -13,11 +14,7 @@ import {StopSequenceEditor} from './StopSequenceEditor'; export type PlaygroundSettingsProps = { playgroundStates: PlaygroundState[]; - setPlaygroundStateField: ( - index: number, - field: PlaygroundStateKey, - value: SetStateAction - ) => void; + setPlaygroundStateField: SetPlaygroundStateFieldFunctionType; settingsTab: number; setSettingsTab: (tab: number) => void; };